/*
 * Amount.java
 *
 * Status: Functional
 */

package finance;

import java.math.BigDecimal;

/**
 * An amount, which is either a debit or a credit.  {@code Amount}s are
 * immutable.
 */
public final class Amount
 {
  public static final Amount ZERO = new Amount(BigDecimal.ZERO, BalanceType.DEBIT);
  public static final Amount ZERO_DEBIT = ZERO;
  public static final Amount ZERO_CREDIT = new Amount(BigDecimal.ZERO, BalanceType.CREDIT);


  /**
   * Converts an {@code Amount} object into an array of bytes suitable for
   * writing to a file.
   *
   * @param obj the {@code Amount} object to convert.
   *
   * @return an array of bytes representing the given {@code Amount} object.
   */
  public static byte[] convertToBytes(Amount obj)
   {
    int size = 1;

    byte[] valueBytes = String.format("%.2f", obj.value).getBytes();
    size += valueBytes.length;

    byte[] bytes = new byte[size];
    System.arraycopy(valueBytes, 0, bytes, 0, valueBytes.length);
    if(obj.type == BalanceType.DEBIT)
      bytes[size-1] = 1;
    else if(obj.type == BalanceType.CREDIT)
      bytes[size-1] = 2;

    return bytes;
   }


  /**
   * Converts an array of bytes into an {@code Amount} object.
   *
   * @param bytes the array of bytes to convert.
   *
   * @return an {@code Amount} object constructed from the given array of
   * bytes.
   */
  public static Amount convertFromBytes(byte[] bytes)
   {
    byte[] valueBytes = new byte[bytes.length-1];
    System.arraycopy(bytes, 0, valueBytes, 0, valueBytes.length);

    BalanceType type = null;
    if(bytes[bytes.length-1] == 1)
      type = BalanceType.DEBIT;
    else if(bytes[bytes.length-1] == 2)
      type = BalanceType.CREDIT;

    return new Amount(new String(valueBytes), type);
   }


  /**
   * Adds {@code a} to {@code b} and returns the result.
   */
  public static Amount add(Amount a, Amount b)
   {
    if(a != null && b != null)
     {
      if(a.type == b.type)
        return new Amount(a.value.add(b.value), a.type);
      else if(a.value.compareTo(b.value) < 0)
        return new Amount(b.value.subtract(a.value), b.type);
      else if(a.value.compareTo(b.value) > 0)
        return new Amount(a.value.subtract(b.value), a.type);
      else
        return ZERO;
     }
    else if(a == null && b == null)
      return ZERO;
    else if(a == null)
      return add(ZERO, b);
    else if(b == null)
      return add(ZERO, a);
    return ZERO;
   }


  /**
   * Subtracts {@code b} from {@code a} and returns the result.
   */
  public static Amount subtract(Amount a, Amount b)
   {
    if(a != null && b != null)
     {
      if(a.type != b.type)
        return new Amount(a.value.add(b.value), a.type);
      else if(a.value.compareTo(b.value) > 0)
        return new Amount(a.value.subtract(b.value), a.type);
      else if(a.value.compareTo(b.value) < 0)
        return new Amount(b.value.subtract(a.value), switchType(a.type));
      else
        return ZERO;
     }
    else if(a == null && b == null)
      return ZERO;
    else if(a == null)
      return subtract(ZERO, b);
    else if(b == null)
      return subtract(a, ZERO);
    return ZERO;
   }


  private final BigDecimal value;
  private final BalanceType type;


  public Amount(BigDecimal value, BalanceType type)
   {
    if(value == null)
      throw new IllegalArgumentException("value == null");
    if(type == null)
      throw new IllegalArgumentException("type == null");
    if(value.signum() == -1)
      throw new IllegalArgumentException("value < 0");

    this.value = value;
    this.type = type;
   }

  public Amount(String value, BalanceType type)
   {
    this(new BigDecimal(value), type);
   }

  public Amount add(Amount a)
   { return add(this, a); }

  public Amount subtract(Amount a)
   { return subtract(this, a); }

  public BigDecimal getValue()
   { return value; }

  public BalanceType getType()
   { return type; }

  public boolean equals(Object o)
   {
    if(o != null && o instanceof Amount)
     {
      Amount a = (Amount)o;
      return type == a.type && value.compareTo(a.value) == 0;
     }
    return false;
   }

  private static BalanceType switchType(BalanceType type)
   {
    if(type == BalanceType.DEBIT)
      return BalanceType.CREDIT;
    else
      return BalanceType.DEBIT;
   }
 }