import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

/**
 * This class abstracts a scalar and vector and all the arithmetic
 * operations defined on these values.
 */
public class Value
{
  public static final Value ZERO = new Value( 0 );

  private double x = 0.0d;
  private double y = 0.0d;
  private double z = 0.0d;

  public static final int typeScalar = 1;
  public static final int typeVector = 2;

  private int type = typeScalar;

  public Value(double x, double y, double z)
  {
    this.x = x;
    this.y = y;
    this.z = z;
    this.type = typeVector;
  }

  public Value(double x)
  {
    this.x = x;
    this.type = typeScalar; // this line is reduntant, since
                            // type is initialized with typeScalar;
  }

  /**
   * @return <tt>this + rhs</tt>
   */
  public Value add(Value rhs) throws InvalidParameterException
  {
    if (type != rhs.type)
      throw new
              InvalidParameterException("rhs"
                      , "incompatible data types, neighter the operation vector + scalar nor scalar + vector are defined"
              );

    if (type == typeScalar)
      return new Value(x + rhs.x);
    else
      return new Value(x + rhs.x, y + rhs.y, z + rhs.z);
  }

  public boolean equals( Value rhs, double epsilon )
  {
    return type == rhs.type &&
             (  ( type == typeScalar
                  && Math.abs(x-rhs.x)<epsilon
                )
             || ( type == typeVector
                  && Math.abs(x-rhs.x)<epsilon
                  && Math.abs(y-rhs.y)<epsilon
                  && Math.abs(z-rhs.z)<epsilon
                )
             );
  }

  public boolean equals( double x, double epsilon )
  {
    return ( type == typeScalar && Math.abs(this.x-x)<epsilon );
  }

  public boolean equals( double x, double y, double z, double epsilon )
  {
    return ( type == typeVector
             && Math.abs(this.x-x)<epsilon
             && Math.abs(this.y-y)<epsilon
             && Math.abs(this.z-z)<=epsilon
           );
  }

  // some getter and setter functions, it's not worth to document them :)
  public int getType()
  {
    return type;
  }

  public double getX()
  {
    return x;
  }

  public double getY()
  {
    return y;
  }

  public double getZ()
  {
    return z;
  }

  public void setValue( double x )
  {
    this.type = typeScalar;
    this.x = x;
    this.y = 0.0f;
    this.z = 0.0f;
  }

  public void setValue( double x, double y, double z )
  {
    this.type = typeVector;
    this.x = x;
    this.y = y;
    this.z = z;
  }

  /**
   * @return <tt>-this</tt>
   */
  public Value negate()
  {
    if (type == typeScalar)
      return new Value(-x);
    else
      return new Value(-x, -y, -z);
  }

  /**
   * Returns a <tt>Value</tt> whos valus <tt>this * rhs</tt>
   *
   * The calculation is performed on depanding the Value types.
   * scalar * scalar -> scalar, vector * scalar -> vector,
   * scalar * vector -> vector, vector * vector -> vector.<br>
   * vector * vector -> vector, performes the vector ex-product
   *
   * @return <tt>this * rhs</tt>
   */
  public Value multiply(Value rhs)
  {
    if (type == typeScalar)
    {
      if (rhs.type == typeScalar)
        return new Value(x * rhs.x);
      else
        return new Value(x * rhs.x, x * rhs.y, x * rhs.z);
    }
    else
    {
      if (rhs.type == typeScalar)
        return new Value(x * rhs.x, y * rhs.x, z * rhs.x);
      else
        // vector ex-product
        return new Value( y * rhs.z - z * rhs.y
                        , z * rhs.x - x * rhs.z
                        , x * rhs.y - y * rhs.x
                        );
    }
  }

  /**
   * Returns a <tt>Value</tt> whos value is <tt>this - rhs</tt>
   * @param rhs value to be substracted from this Value
   * @return <tt>this - rhs</tt>
   */
  public Value subtract(Value rhs) throws InvalidParameterException
  {
    if (type != rhs.type)
      throw new
              InvalidParameterException("rhs"
                      , "incompatible data types, neighter the operation vector - scalar nor scalar - vector are defined"
              );

    if (type == typeScalar)
      return new Value(x - rhs.x);
    else
      return new Value(x - rhs.x, y - rhs.y, z - rhs.z);
  }

  /**
   * @return the string representation of the value
   */
  public String toString()
  {
    DecimalFormat df = new DecimalFormat("#0.000");
    DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
    dfs.setDecimalSeparator('.');
    df.setDecimalFormatSymbols(dfs);

    if ( type == typeScalar )
      return df.format(x);
    else
      return "[ " + df.format(x) + " , " + df.format(y) + " , " + df.format(z) + " ]";
  }
}