/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ahat.heapdump;

import java.util.Objects;

/**
 * A Java instance or primitive value from a parsed heap dump.
 * Note: To save memory, a null Value is used to represent a null Java
 * instance from the heap dump.
 */
public abstract class Value {
  Value() { }
  /**
   * Constructs a Value for an AhatInstance.
   * Note: returns null for null <code>value</code>.
   *
   * @param value the AhatInstance to make into a value
   * @return the constructed value.
   */
  public static Value pack(AhatInstance value) {
    return value == null ? null : new InstanceValue(value);
  }

  /**
   * Constructs a Value for a boolean.
   *
   * @param value the boolean to make into a value
   * @return the constructed value.
   */
  public static Value pack(boolean value) {
    return new BooleanValue(value);
  }

  /**
   * Constructs a Value for a char.
   *
   * @param value the char to make into a value
   * @return the constructed value.
   */
  public static Value pack(char value) {
    return new CharValue(value);
  }

  /**
   * Constructs a Value for a float.
   *
   * @param value the float to make into a value
   * @return the constructed value.
   */
  public static Value pack(float value) {
    return new FloatValue(value);
  }

  /**
   * Constructs a Value for a double.
   *
   * @param value the double to make into a value
   * @return the constructed value.
   */
  public static Value pack(double value) {
    return new DoubleValue(value);
  }

  /**
   * Constructs a Value for a byte.
   *
   * @param value the byte to make into a value
   * @return the constructed value.
   */
  public static Value pack(byte value) {
    return new ByteValue(value);
  }

  /**
   * Constructs a Value for a short.
   *
   * @param value the short to make into a value
   * @return the constructed value.
   */
  public static Value pack(short value) {
    return new ShortValue(value);
  }

  /**
   * Constructs a Value for a int.
   *
   * @param value the int to make into a value
   * @return the constructed value.
   */
  public static Value pack(int value) {
    return new IntValue(value);
  }

  /**
   * Constructs a Value for a long.
   *
   * @param value the long to make into a value
   * @return the constructed value.
   */
  public static Value pack(long value) {
    return new LongValue(value);
  }

  /**
   * Returns the type of the given value.
   *
   * @param value the value to get the type of
   * @return the value's type
   */
  public static Type getType(Value value) {
    return value == null ? Type.OBJECT : value.getType();
  }

  /**
   * Return the type of the given value.
   */
  abstract Type getType();

  /**
   * Returns true if the Value is an AhatInstance rather than a primitive
   * value.
   *
   * @return true if the value is an AhatInstance
   */
  public boolean isAhatInstance() {
    return false;
  }

  /**
   * Returns the Value as an AhatInstance if it is one.
   * Returns null if the Value represents a Java primitive value.
   *
   * @return the AhatInstance packed into this value
   */
  public AhatInstance asAhatInstance() {
    return null;
  }

  /**
   * Returns true if the Value is an int.
   *
   * @return true if the value is an int.
   */
  public boolean isInteger() {
    return false;
  }

  /**
   * Returns the Value as an int if it is one.
   * Returns null if the Value does not represent an int.
   *
   * @return the int packed into this value
   */
  public Integer asInteger() {
    return null;
  }

  /**
   * Returns true if the Value is an long.
   *
   * @return true if the value is an long.
   */
  public boolean isLong() {
    return false;
  }

  /**
   * Returns the Value as an long if it is one.
   * Returns null if the Value does not represent an long.
   *
   * @return the long packed into this value
   */
  public Long asLong() {
    return null;
  }

  /**
   * Returns the Value as an byte if it is one.
   * Returns null if the Value does not represent an byte.
   *
   * @return the byte packed into this value
   */
  public Byte asByte() {
    return null;
  }

  /**
   * Returns the Value as an char if it is one.
   * Returns null if the Value does not represent an char.
   *
   * @return the char packed into this value
   */
  public Character asChar() {
    return null;
  }

  @Override
  public abstract String toString();

  Value getBaseline() {
    return this;
  }

  /**
   * Returns the baseline of the given value for the purposes of diff.
   * This method can be used to handle the case when the Value is null.
   *
   * @param value the value to get the baseline of
   * @return the baseline of the value
   * @see Diffable#getBaseline
   */
  public static Value getBaseline(Value value) {
    return value == null ? null : value.getBaseline();
  }

  @Override
  public abstract int hashCode();

  @Override
  public abstract boolean equals(Object other);

  private static class BooleanValue extends Value {
    private boolean mBool;

    BooleanValue(boolean bool) {
      mBool = bool;
    }

    @Override
    Type getType() {
      return Type.BOOLEAN;
    }

    @Override
    public String toString() {
      return Boolean.toString(mBool);
    }

    @Override public int hashCode() {
      return Objects.hash(mBool);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof BooleanValue) {
        BooleanValue value = (BooleanValue)other;
        return mBool == value.mBool;
      }
      return false;
    }
  }

  private static class ByteValue extends Value {
    private byte mByte;

    ByteValue(byte b) {
      mByte = b;
    }

    @Override
    public Byte asByte() {
      return mByte;
    }

    @Override
    Type getType() {
      return Type.BYTE;
    }

    @Override
    public String toString() {
      return Byte.toString(mByte);
    }

    @Override public int hashCode() {
      return Objects.hash(mByte);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof ByteValue) {
        ByteValue value = (ByteValue)other;
        return mByte == value.mByte;
      }
      return false;
    }
  }

  private static class CharValue extends Value {
    private char mChar;

    CharValue(char c) {
      mChar = c;
    }

    @Override
    public Character asChar() {
      return mChar;
    }

    @Override
    Type getType() {
      return Type.CHAR;
    }

    @Override
    public String toString() {
      return Character.toString(mChar);
    }

    @Override public int hashCode() {
      return Objects.hash(mChar);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof CharValue) {
        CharValue value = (CharValue)other;
        return mChar == value.mChar;
      }
      return false;
    }
  }

  private static class DoubleValue extends Value {
    private double mDouble;

    DoubleValue(double d) {
      mDouble = d;
    }

    @Override
    Type getType() {
      return Type.DOUBLE;
    }

    @Override
    public String toString() {
      return Double.toString(mDouble);
    }

    @Override public int hashCode() {
      return Objects.hash(mDouble);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof DoubleValue) {
        DoubleValue value = (DoubleValue)other;
        return mDouble == value.mDouble;
      }
      return false;
    }
  }

  private static class FloatValue extends Value {
    private float mFloat;

    FloatValue(float f) {
      mFloat = f;
    }

    @Override
    Type getType() {
      return Type.FLOAT;
    }

    @Override
    public String toString() {
      return Float.toString(mFloat);
    }

    @Override public int hashCode() {
      return Objects.hash(mFloat);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof FloatValue) {
        FloatValue value = (FloatValue)other;
        return mFloat == value.mFloat;
      }
      return false;
    }
  }

  private static class InstanceValue extends Value {
    private AhatInstance mInstance;

    InstanceValue(AhatInstance inst) {
      assert(inst != null);
      mInstance = inst;
    }

    @Override
    public boolean isAhatInstance() {
      return true;
    }

    @Override
    public AhatInstance asAhatInstance() {
      return mInstance;
    }

    @Override
    Type getType() {
      return Type.OBJECT;
    }

    @Override
    public String toString() {
      return mInstance.toString();
    }

    @Override
    Value getBaseline() {
      return InstanceValue.pack(mInstance.getBaseline());
    }

    @Override public int hashCode() {
      return Objects.hash(mInstance);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof InstanceValue) {
        InstanceValue value = (InstanceValue)other;
        return mInstance.equals(value.mInstance);
      }
      return false;
    }
  }

  private static class IntValue extends Value {
    private int mInt;

    IntValue(int i) {
      mInt = i;
    }

    @Override
    public boolean isInteger() {
      return true;
    }

    @Override
    public Integer asInteger() {
      return mInt;
    }

    @Override
    Type getType() {
      return Type.INT;
    }

    @Override
    public String toString() {
      return Integer.toString(mInt);
    }

    @Override public int hashCode() {
      return Objects.hash(mInt);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof IntValue) {
        IntValue value = (IntValue)other;
        return mInt == value.mInt;
      }
      return false;
    }
  }

  private static class LongValue extends Value {
    private long mLong;

    LongValue(long l) {
      mLong = l;
    }

    @Override
    public boolean isLong() {
      return true;
    }

    @Override
    public Long asLong() {
      return mLong;
    }

    @Override
    Type getType() {
      return Type.LONG;
    }

    @Override
    public String toString() {
      return Long.toString(mLong);
    }

    @Override public int hashCode() {
      return Objects.hash(mLong);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof LongValue) {
        LongValue value = (LongValue)other;
        return mLong == value.mLong;
      }
      return false;
    }
  }

  private static class ShortValue extends Value {
    private short mShort;

    ShortValue(short s) {
      mShort = s;
    }

    @Override
    Type getType() {
      return Type.SHORT;
    }

    @Override
    public String toString() {
      return Short.toString(mShort);
    }

    @Override public int hashCode() {
      return Objects.hash(mShort);
    }

    @Override public boolean equals(Object other) {
      if (other instanceof ShortValue) {
        ShortValue value = (ShortValue)other;
        return mShort == value.mShort;
      }
      return false;
    }
  }
}
