package fplot;

import java.util.*;
/**
 * Die Knoten des Syntaxbaumes.
 * @author Christian Semrau
 * <a href="mailto:Christian.Semrau@student.uni-magdeburg.de">
 * Christian.Semrau@student.uni-magdeburg.de</a>
 * <br><a href="http://chsemrau.de">Homepage</a>
 */
class FuncNode extends NodeBase {

	// Operator
	private int op;
	// Funktion
	private int func;
	// Zahlenwert
	private double val;
	// Unterknotenliste: {left, ..., right}
	private FuncNode[] nodes;
	// Laenge der Unterknotenliste
	private int nlen;

	// Unterknoten klammern:
	// [WENIG,MITTEL,ALLES][left,...,right][oeffnen,schliessen]
	private String[][][] par;

	// Klammerungsstufe
	private int klammerung = KLAMMERUNG_MITTEL;

	/** Zufallszahlengenerator */
	private static Random rnd = new Random();

/** Konstruiert einen neuen Knoten ohne Unterknoten */
FuncNode(int oper, int func, double value) {
	this(oper, func, value, null, null);
}

/** Konstruiert einen neuen Knoten */
FuncNode(int oper, int func, double value, FuncNode l, FuncNode r) {
	this.op = oper;
	this.func = func;
	this.val = value;
	this.nodes = new FuncNode[]{l, r};
	this.nlen = 2;
	calcPara();
}

/** Konstruiert einen neuen Knoten */
FuncNode(int oper, int func, double value, FuncNode[] subnodes) {
	this.op = oper;
	this.func = func;
	this.val = value;
	if (subnodes == null || subnodes.length == 0)
		this.nodes = new FuncNode[]{null, null};
	else
	if (subnodes.length == 1)
		this.nodes = new FuncNode[]{subnodes[0], null};
	else
		this.nodes = (FuncNode[])subnodes.clone();

	this.nlen = nodes.length;
	calcPara();
}

/** Liefert den linken Unterknoten */
public FuncNode left() {
	return nodes[0];
}

/** Liefert den rechten Unterknoten */
public FuncNode right() {
	return nodes[nlen-1];
}

/**
 * 
 */
protected void calcPara() {
   if(op==FUNC0 || op==FUNC1 || op==FUNC2 || op==FUNC3) {

	final int W = 0, M = 1, A = 2;
	// [W,M,A][links,...,rechts]
	boolean[][] kl = new boolean[3][nlen];

	for (int n=0; n<nlen; n++) {
		kl[A][n] = true;
		kl[M][n] = false;
		kl[W][n] = false;
	}

	// [W,M,A][links,...,rechts][oeffnen,schliessen]
	par = new String[3][nlen][2];

	for(int stufe=0; stufe<3; stufe++)
	for(int n=0; n<nlen; n++){
		if (kl[stufe][n]){
			par[stufe][n][0] = "(";
			par[stufe][n][1] = ")";
		}else{
			par[stufe][n][0] = "";
			par[stufe][n][1] = "";
		}
	}

   }else {

	final int W = 0, M = 1, A = 2, L = 0, R = 1;
	// [W,M,A][links,rechts]
	boolean[][] kl = new boolean[3][2];
	int lop, rop;

	if (left()==null || isConst(lop = left().op)
	|| (lop==ZAHL && left().val>=0)){
		kl[A][L] = false; kl[M][L] = false; kl[W][L] = false;
	}else{
		kl[A][L] = true;

		if (lop==FUNC0 || lop==FUNC1 || lop==FUNC2 || lop==FUNC3
		|| (isOp(op) && prioritaet(op)==prioritaet(lop))
		|| op==ADD){
			kl[M][L] = false;
			kl[W][L] = false;
		}else{
			kl[M][L] = true;
			kl[W][L] = prioritaet(op)>=prioritaet(lop);
		}
	}

	if (right()==null || isConst(rop = right().op)
	|| (rop==ZAHL && right().val>=0)){
		kl[A][R] = false; kl[M][R] = false; kl[W][R] = false;
	}else{
		kl[A][R] = true;

		if (rop==FUNC0 || rop==FUNC1 || rop==FUNC2 || rop==FUNC3
		|| (isOp(op) && prioritaet(op)==prioritaet(rop) && assoziativ(op))
		|| op==ADD){
			kl[M][R] = false;
			kl[W][R] = false;
		}else{
			kl[M][R] = true;
			kl[W][R] = prioritaet(op)>=prioritaet(rop);
		}
	}
	
	// [W,M,A][links,rechts][oeffnen,schliessen]
	par = new String[3][2][2];

	for(int stufe=0; stufe<3; stufe++)
	for(int lr=0; lr<2; lr++){
		if (kl[stufe][lr]){
			par[stufe][lr][0] = "(";
			par[stufe][lr][1] = ")";
		}else{
			par[stufe][lr][0] = "";
			par[stufe][lr][1] = "";
		}
	}
	
    }
}

/** Wertet die Funktion des Knoten aus.
 * Wird von evalFunc(double[]) aufgerufen. */
protected double evalFunc(double v1, double v2) {
	switch(func){
		// FUNC0
		case RND: return Math.random();
		case RNDN: return rnd.nextGaussian();

		// FUNC1
		case SIN: return Math.sin(v1);
		case COS: return Math.cos(v1);
		case TAN: return Math.tan(v1);
		case COT: return Math.tan(Math.PI/2-v1);
		case ASIN: return Math.asin(v1);
		case ACOS: return Math.acos(v1);
		case ATAN: return Math.atan(v1);
		case ACOT: return Math.PI/2-Math.atan(v1);
		case SINH: return (Math.exp(v1)-Math.exp(-v1))/2;
		case COSH: return (Math.exp(v1)+Math.exp(-v1))/2;
		case TANH: return (Math.exp(v1)-Math.exp(-v1))/(Math.exp(v1)+Math.exp(-v1));
		case COTH: return (Math.exp(v1)+Math.exp(-v1))/(Math.exp(v1)-Math.exp(-v1));
		case ARSINH: return Math.log(v1+Math.sqrt(v1*v1+1));
		case ARCOSH: return Math.log(v1+Math.sqrt(v1*v1-1));
		case ARTANH: return Math.log(Math.sqrt((1+v1)/(1-v1)));
		case ARCOTH: return Math.log(Math.sqrt((v1+1)/(v1-1)));
		case ABS: return Math.abs(v1);
		case SGN: return (v1>0)?+1:(v1<0)?-1:0;
		case INV: return 1/v1;
		case INT: return Math.floor(v1);
		case FRAC: return v1-Math.floor(v1);
		case SQR: return v1*v1;
		case SQRT: return Math.sqrt(v1);
		case FAC: return fakd((int)v1);
		case GAMMA: return gamma(v1);
		case EXP: return Math.exp(v1);
		case LN: return Math.log(v1);
		case LG: return Math.log(v1)/Math.log(10);
		case LD: return Math.log(v1)/Math.log(2);

		// FUNC2
		case LOG: return Math.log(v2)/Math.log(v1);
		case POW: return Math.pow(v1,v2);
		case ROOT:
			if (v2==(int)v2 && ((int)v2%2==1) && v1<0)
			// ganzzahlige ungerade Wurzel und negativer Radikand
				return -Math.pow(-v1,1/v2);
			else
				return Math.pow(v1,1/v2);
		case MPOW: {
			int index = (int)v2;
			if (index==0) return 1;
			else if (index<0) return 0;
			double ret = v1;
			for (int i=1; i<index; i++)
				ret = Math.pow(v1, ret);
			return ret;
		}
		case MIN: return Math.min(v1,v2);
		case MAX: return Math.max(v1,v2);

		default: return 0;  // NOFUNC
	} // switch
}

/** Wertet die Funktion des Knoten aus.
 * Wird von evaluate(double, double, double, int) aufgerufen. */
protected double evalFunc(double[] v) {
	double v1 = v.length>=1 ? v[0] : 0;
	double v2 = v.length>=2 ? v[1] : 0;
	double v3 = v.length>=3 ? v[2] : 0;

	switch(func){
		case SUM: return 0;
		// wird bereits von evaluate(double,double,double,int) ausgewertet
		case SUM3: return 0;
		// wird bereits von evaluate(double,double,double,int) ausgewertet
		case IF: return 0;
		// wird bereits von evaluate(double,double,double,int) ausgewertet

		default: return evalFunc(v1, v2);
	}
}

/** Wertet den Knoten aus mit dem gegebenen Wert fuer x */
public double evaluate(double x) {
	return evaluate(x, 0, 0, 0);
}

/** Wertet den Knoten aus mit den gegebenen Werten fuer x,p,q und i */
public double evaluate(double x, double p, double q, int ci) {
	double s;

	// Sonderbehandlung fuer sum(grenze, ausdruck)
	if (op==FUNC2 && func==SUM){
		if (right()==null) return 0;
		double le = left()!=null ? left().evaluate(x, p, q, ci) : 0;
		s = 0;
		int index = (int)le;
		for(int i=1; i<=index; i++)
			s+= right().evaluate(x, p, q, i);
		return s;
	}

	// Sonderbehandlung fuer sum3(anfang, ende, ausdruck)
	if (op==FUNC3 && func==SUM3){
		if (right()==null) return 0;
		double anf = nodes[0]!=null ? nodes[0].evaluate(x, p, q, ci) : 0;
		double ende = nodes[1]!=null ? nodes[1].evaluate(x, p, q, ci) : 0;
		s = 0;
		int Anf = (int)anf;
		int Ende = (int)ende;
		for(int i=Anf; i<=Ende; i++)
			s+= right().evaluate(x, p, q, i);
		return s;
	}

	// Sonderbehandlung fuer IF(bedingung, wert1, wert2)
	if (op==FUNC3 && func==IF){
		double bedingung, wert;
		bedingung = nodes[0]!=null ? nodes[0].evaluate(x, p, q, ci) : 0;
		if (bedingung>0)
			wert = nodes[1]!=null ? nodes[1].evaluate(x, p, q, ci) : 0;
		else
			wert = nodes[2]!=null ? nodes[2].evaluate(x, p, q, ci) : 0;
		return wert;
	}

	double[] v = new double[nodes.length];
	double le;
	double ri;

	for (int i=0; i<v.length; i++) {
		v[i] = nodes[i]!=null ? nodes[i].evaluate(x, p, q, ci) : 0;
	}

	le = v[0];
	ri = v[1];

	switch(op){
		case ADD: s=le+ri; break;
		case SUB: s=le-ri; break;
		case MUL: s=le*ri; break;
		case DIV: s=le/ri; break;
		case IDIV: s=(int)(le/ri); break;
		case MOD: s=(le/ri)-(int)(le/ri); break;
		case OPOW: s=Math.pow(le,ri); break;
		case NEG: s=-le; break;
		case ZAHL: s=val; break;

		case CVARX: s=x; break;
		case CVARP: s=p; break;
		case CVARQ: s=q; break;
		case CVARI: s=ci; break;
		case CE : s=Math.E; break;
		case CPI: s=Math.PI; break;

		case FUNC0:
		case FUNC1:
		case FUNC2:
		case FUNC3: s=evalFunc(v); break;

		default: s=Double.NaN;
	}
	return s;
}

/** Liefert den Namen der Funktion */
protected String funcName() {
	return funcName(func);
}
/**
 * 
 * @return java.lang.String
 */
String internal() {

	String s = "";
	s+="["+opString(op)+", "+func+", "+val+", "+klammerung;

	// [WENIG,MITTEL,ALLES][links,rechts][oeffnen,schliessen]
	// par

	for (int i=0; i<nodes.length; i++)
	if (nodes[i]!=null)
		s+=", n["+i+"]="+nodes[i].internal();

	s+="]";
	return s;
}
/**
 * 
 * @param kl boolean
 */
void setKlammerung(int kl) {
	if (klammerung==kl) return;
	klammerung = kl;
	for (int i=0; i<nodes.length; i++)
	if (nodes[i]!=null)
		nodes[i].setKlammerung(kl);
}

/** Liefert den Ausdruck des Baumes in Infix-Notation */
public String toString() {
	String s;
	int kl = klammerung;
	String le = left() !=null ? par[kl][0][0]+left() .toString()+par[kl][0][1] : "";
	String ri = right()!=null ? par[kl][1][0]+right().toString()+par[kl][1][1] : "";
	String[] mid = new String[nlen-2];
	for (int i=1; i<nlen-1; i++)
		mid[i-1] = nodes[i]!=null ? par[kl][i][0]+nodes[i].toString()+par[kl][i][1] : "";

	if (isOp(op))
		s=le+opName(op)+ri;
	else
	if (isConst(op))
		s=constName(op);
	else
	switch(op){
		case NEG: s="-"+le; break;
		case ZAHL: s=( (val==(int)val) ? ""+(int)val : ""+val); break;

		case FUNC0: s=funcName(); break;
		case FUNC1: s=funcName()+"("+le+")"; break;
		case FUNC2: s=funcName()+"("+le+","+ri+")"; break;
		case FUNC3:
			s=funcName()+"("+le+",";
			for (int i=0; i<mid.length; i++)
				s += mid[i]+",";
			s += ri+")";
			break;
		default: s=le+"["+op+"]"+ri;
	}
	return s;
}

/** Liefert den Ausdruck des Baumes in Postfix-Notation */
public String toStringPostfix() {
	String s;
	String le = left()!=null ? left().toStringPostfix() : "";
	String ri = right()!=null ? right().toStringPostfix() : "";
	String[] mid = new String[nlen-2];
	for (int i=1; i<nlen-1; i++)
		mid[i-1] = nodes[i]!=null ? nodes[i].toStringPostfix() : "";

	if (isOp(op))
		s = le+" "+ri+" "+opName(op);
	else
	if (isConst(op))
		s = constName(op);
	else
	switch(op){
		case NEG: s=le+" _"; break;
		case ZAHL: s=( (val==(int)val) ? (int)val+"" : val+""); break;

		case FUNC0: s=funcName(); break;
		case FUNC1: s=le+" "+funcName(); break;
		case FUNC2: s=le+" "+ri+" "+funcName(); break;
		case FUNC3:
			s=le+" ";
			for (int i=0; i<mid.length; i++)
				s += mid[i]+" ";
			s+=ri+" "+funcName();
			break;
		default: s=le+" "+ri+" ["+op+"]";
	}
	return s;
}
} // FuncNode
