package calc;

/*
 * what wrong with java wrapper
 * first of all, they break the
 * java convention where no
 * operator overloading is allowed.
 * secundo they does not support
 * math operation when used with
 * their interface 'Number'.
 * Then i had to write static
 * function in order to permit
 * using math on 'Number'.
 * I wanted to inherit them all
 * and simply add method to do
 * so but they are final class.
 * anyway it is not recommended
 * to inherit java lib. Then
 * i build functor from 'Number'
 * that compute result only when
 * converting to primitive
 *  (lazy evaluation)
 * \__________________________/
 *      /
 * \ ('<
 * (<=)
 * */

/**
 * @author martin
 *Number wrapper opperation
 */
public class Num {
	/**
	 * no initialisation
	 */
	private Num(){}
	/**
	 * @author martin
	 *number operation
	 */
	private static abstract class NumOp extends Number{
		public String toString(){
			return Double.toString(this.doubleValue());
		}
	};

	/**
	 * compare two Number
	 * @param a as Number
	 * @param b as Number
	 * @return differente as double
	 */
	public static double cmp(final Number a, final Number b){
		return a.doubleValue()-b.doubleValue();
	}
	/**
	 * test if a greater than b
	 * @param a as Number
	 * @param b as Number
	 * @return boolean
	 */
	public static boolean greater(final Number a, final Number b){
		return cmp(a,b)>0;
	}
	/**
	 * test if a is lower than b
	 * @param a as Number
	 * @param b as Number
	 * @return boolean
	 */
	public static boolean lower(final Number a, final Number b){
		return cmp(a,b)<0;
	}

	/**
	 * addition
	 * @param a as Number
	 * @param b as Number
	 * @return Number
	 */
	public static Number add(final Number a, final Number b){
		return new NumOp(){
			//extends Number
			public double doubleValue() { return a.doubleValue() + b.doubleValue();}
			public float floatValue() { return a.floatValue() + b.floatValue();}
			public int intValue() { return a.intValue() + b.intValue();}
			public long longValue() { return a.longValue() + b.longValue();}
			//default
			public int hashCode(){ return 17 + 37 * a.hashCode() + b.hashCode();}
		};
	}

	/**
	 * sub
	 * @param a as Number
	 * @param b as Number
	 * @return Number
	 */
	public static Number sub(final Number a, final Number b){
		return new NumOp(){
			//extends Number
			public double doubleValue() { return a.doubleValue() - b.doubleValue();}
			public float floatValue() { return a.floatValue() - b.floatValue();}
			public int intValue() { return a.intValue() - b.intValue();}
			public long longValue() { return a.longValue() - b.longValue();}
			//default
			public int hashCode(){ return 18 + 33 * a.hashCode() + b.hashCode();}
		};
	}

	/**
	 * mul
	 * @param a as Number
	 * @param b as Number
	 * @return Number
	 */
	public static Number mul(final Number a, final Number b){
		return new NumOp(){
			//extends Number
			public double doubleValue() { return a.doubleValue() * b.doubleValue();}
			public float floatValue() { return a.floatValue() * b.floatValue();}
			public int intValue() { return a.intValue() * b.intValue();}
			public long longValue() { return a.longValue() * b.longValue();}
			//default
			public int hashCode(){ return 19 + 31 * a.hashCode() + b.hashCode();}
		};
	}

	/**
	 * div
	 * @param a as Number
	 * @param b as Number
	 * @return Number
	 */
	public static Number div(final Number a, final Number b){
		return new NumOp(){
			//extends Number
			public double doubleValue() { return a.doubleValue() / b.doubleValue();}
			public float floatValue() { return a.floatValue() / b.floatValue();}
			public int intValue() { return a.intValue() / b.intValue();}
			public long longValue() { return a.longValue() / b.longValue();}
			//default
			public int hashCode(){ return 20 + 29 * a.hashCode() + b.hashCode();}
		};
	}

	/**
	 * mod
	 * @param a as Number
	 * @param b as Number
	 * @return Number
	 */
	public static Number mod(final Number a, final Number b){
		return new NumOp(){
			//extends Number
			public double doubleValue() { return a.doubleValue() % b.doubleValue();}
			public float floatValue() { return a.floatValue() % b.floatValue();}
			public int intValue() { return a.intValue() % b.intValue();}
			public long longValue() { return a.longValue() % b.longValue();}
			//default
			public int hashCode(){ return 21 + 23 * a.hashCode() + b.hashCode();}
		};
	}

	/**
	 * sinus
	 * @param rad as Number radian
	 * @return new Number
	 */
	public static Number sin(final Number rad){
		return new Double(Math.sin(rad.doubleValue()));
	}

	/**
	 * cosinus
	 * @param rad as Number radian
	 * @return new Number
	 */
	public static Number cos(final Number rad){
		return new Double(Math.cos(rad.doubleValue()));
	}

	/**
	 * square root
	 * @param rad as Number square
	 * @return new Number
	 */
	public static Number sqrt(final Number pow){
		return new Double(Math.sqrt(pow.doubleValue()));
	}

	/**
	 * random number generator
	 * @return new Number
	 */
	public static Number random(){
		return new Double(Math.random());
	}

	private static Integer _one = 1;
	/**
	 * factorial of a number
	 * @param i as number of iteration
	 * @return number as factorial of ith
	 */
	public static Number factorial(Number i){
		if (Num.lower(i,2))
			return _one;
		return Num.mul(i,Num.factorial(Num.sub(i,1)));
	}

	private static Number _pi = new Double(Math.PI);
	/**
	 * pi constant
	 * @return pi as Number
	 */
	public static Number pi(){
		return _pi;
	}
}
