package gve.calc.formula;

public class GoniomFunctions implements Function {
	public GoniomFunctions() {}

	public Part call(String funcname,Part args,Evaluator eval) {
		if (funcname.equals("sin")) return computeSin(eval,args.evaluate(eval));
		else if (funcname.equals("cos")) {
			Part a = args.evaluate(eval);
			if (a instanceof Real)
				return new Real(Math.cos( ((Real)a).doubleValue ));
			else if (a instanceof Interval) {
				Interval intv = (Interval)a;
				if (intv.high - intv.low > Math.PI*2) return new Interval(-1,1,false,false);
				return intervalSin(intv.low+Math.PI/2,intv.high+Math.PI/2,intv.openLow,intv.openHigh);
			}
		}
		return null;
	}

	private static Part computeSin(Evaluator ev,Object a) {
		if (a instanceof Real)
			return new Real(Math.sin( ((Real)a).doubleValue ));
		else if (a instanceof Interval) {
			Interval intv = (Interval)a;
			if (intv.high - intv.low > Math.PI*2) return new Interval(-1,1,false,false);
			return intervalSin(intv.low,intv.high,intv.openLow,intv.openHigh);
		} else if (a instanceof OperatorUnion) {
			OperatorUnion un = (OperatorUnion)a;
			// sin(un.left  u  un.right) == sin(un.left)  u  sin(un.right)
			return new OperatorUnion((Part)computeSin(ev,un.left),(Part)computeSin(ev,un.right));
		} else return null;
	}

	private static Interval intervalSin(double low,double high,boolean openLow,boolean openHigh) {
		if (Double.isInfinite(low) || Double.isInfinite(high))
			return new Interval(-1,1,false,false);
		{	// herleiden zodat low in [0,2pi[
			double shift = low - (low % (Math.PI*2));
			high = high - shift;
			low = low - shift;
			while (low < 0) {
				high += Math.PI*2;
				low += Math.PI*2;
			}
		}
		if (low < Math.PI/2) {
			if (high < Math.PI/2) {
				return new Interval(Math.sin(low),Math.sin(high),openLow,openHigh);
			} else if (high < Math.PI) {
				double sinlow = Math.sin(low);
				double sinhigh = Math.sin(high);
				if (sinlow < sinhigh)
					return new Interval(sinlow,1,openLow,false);
				else return new Interval(sinhigh,1,openHigh,openLow);
			} else if (high < Math.PI*3/2) {
				return new Interval(Math.sin(high),1,openHigh,false);
			} else return new Interval(-1,1,false,false);
		} else if (low < Math.PI) {
			if (high < Math.PI*3/2) return new Interval(Math.sin(high),Math.sin(low),openHigh,openLow);
			else if (high < Math.PI*2) return new Interval(-1,Math.sin(low),false,openLow);
			else if (high < Math.PI*5/2) {
				double sinlow = Math.sin(low);
				double sinhigh = Math.sin(high);
				if (sinlow < sinhigh) return new Interval(-1,sinhigh,false,openHigh);
				else return new Interval(-1,sinlow,false,openLow);
			} else return new Interval(-1,1,false,false);
		} else if (low < Math.PI*3/2) {
			if (high < Math.PI*3/2) return new Interval(Math.sin(high),Math.sin(low),openHigh,openLow);
			else if (high < Math.PI*2) {
				double sinlow = Math.sin(low);
				double sinhigh = Math.sin(high);
				if (sinlow < sinhigh) return new Interval(-1,sinhigh,false,openHigh);
				else return new Interval(-1,sinlow,false,openLow);
			} else if (high < Math.PI*3.5) {
				return new Interval(-1,Math.sin(high),false,openHigh);
			} else return new Interval(-1,1,false,false);
		} else {	// low in ]3pi/2,2pi[
			if (high < Math.PI*2.5) return new Interval(Math.sin(low),Math.sin(high),openLow,openHigh);
			else if (high < Math.PI*3) return new Interval(Math.sin(low),1,openLow,false);
			else if (high < Math.PI*3.5) {
				double sinlow = Math.sin(low);
				double sinhigh = Math.sin(high);
				if (sinlow < sinhigh) return new Interval(sinlow,1,openLow,false);
				else return new Interval(sinhigh,1,openHigh,false);
			} else return new Interval(-1,1,false,false);
		}
	}
}