package martin;

public class Num {
	private Num(){}

	private static abstract class Functor extends Number implements Comparable<Number>{
		protected Functor(){}
		/*implements Comparable*/
		public int compareTo(Number o) {
			double tmp = this.doubleValue() - o.doubleValue();
			if (tmp < 1 && tmp > -1)
				if (tmp > 0)
					return 1;
				else if (tmp < 0)
					return -1;
				else
					return 0;
			return (int)tmp;
		}
	};

	public static Number add(final Number a, final Number b){
		return new Functor(){
			//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 boolean equals(Object o){ return a.equals(o) && b.equals(o);}
			public int hashCode(){ return 17 + 37 * a.hashCode() + b.hashCode();}
			public String toString(){ return "add " + a.toString() + " " + b.toString();}
		};
	}

	public static Number sub(final Number a, final Number b){
		return new Functor(){
			//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 boolean equals(Object o){ return a.equals(o) && b.equals(o);}
			public int hashCode(){ return 18 + 33 * a.hashCode() + b.hashCode();}
			public String toString(){ return "sub " + a.toString() + " " + b.toString();}
		};
	}

	public static Number mul(final Number a, final Number b){
		return new Functor(){
			//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 boolean equals(Object o){ return a.equals(o) && b.equals(o);}
			public int hashCode(){ return 19 + 31 * a.hashCode() + b.hashCode();}
			public String toString(){ return "mul " + a.toString() + " " + b.toString();}
		};
	}

	public static Number div(final Number a, final Number b){
		return new Functor(){
			//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 boolean equals(Object o){ return a.equals(o) && b.equals(o);}
			public int hashCode(){ return 20 + 29 * a.hashCode() + b.hashCode();}
			public String toString(){ return "div " + a.toString() + " " + b.toString();}
		};
	}

	public static Number mod(final Number a, final Number b){
		return new Functor(){
			//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 boolean equals(Object o){ return a.equals(o) && b.equals(o);}
			public int hashCode(){ return 21 + 23 * a.hashCode() + b.hashCode();}
			public String toString(){ return "mod " + a.toString() + " " + b.toString();}
		};
	}

	public static Number sin(final double rad){
		return new Double(Math.sin(rad));
	}
	public static Number sin(final Number rad){
		return sin(rad.doubleValue());
	}

	public static Number cos(final double rad){
		return new Double(Math.cos(rad));
	}
	public static Number cos(final Number rad){
		return cos(rad.doubleValue());
	}

	public static Number sqrt(final double pow){
		return new Double(Math.sqrt(pow));
	}
	public static Number sqrt(final Number pow){
		return sqrt(pow.doubleValue());
	}

	public static Number random(){
		return new Double(Math.random());
	}

	private static Number _pi = new Double(Math.PI);
	public static Number pi(){
		return _pi;
	}
}
