package fplot;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
 * Die Anzeigeflaeche.
 * @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 GraphFlaeche extends Canvas {
	// das Applet
	private PlotFrame applet;

	int fmodCount = 0;

	// Einheitskreis anzeigen
	private boolean einheitskreis = false;
	// Wert der Parameter
	private double parameterP = 0;
	private double parameterQ = 0;
	// Nummer der Formel, die die X bzw. Y Koordinate erzeugt
	private int tXindex = -1, tYindex = -1;
	// Bereich den der Parameter t durchlaeuft,
	// der Parameter wird in den beiden Formel als x angesprochen!
	private Parameter T = new Parameter(0, 1, 100);

	static class Parameter{
		double start;
		double stop;
		long numsteps;
		double step;
		Parameter(double astart, double astop, long anumsteps){
			start = astart;
			stop = astop;
			numsteps = anumsteps;
			step = (stop-start)/numsteps;
		}
		double get(long i){
			return start + step*i;
		}
	}

	// Klammerung anzeigen
	private int klammerung;
	// alter modCount
	private int expectedModCount = 0;

	private int numFormeln = 1;
	// aktuelle Funktionen
	private FuncNode[] function = null;
	private boolean[] showfunc = null;

	// Graphen der Funktionen
	private Graph[] graph = null;

	// Breite, Hoehe und Skalierung des Graphen
	private int graph_w, graph_h, graph_scale;

	// Parameterkurve anzeigen
	private boolean showparam = false;

	// Uebernimmt das Zoomen und Scrollen mit der Maus
	GraphZoomer zoomer;

/** Konstruiert eine neue Anzeigeflaeche */
public GraphFlaeche(PlotFrame app) {
	super();
	zoomer = new GraphZoomer(this);

	zoomer.setCoords(-10, +10, -10, +10);
//	zoomer.xmin = zoomer.ymin = -10;
//	zoomer.xmax = zoomer.ymax = +10;

	applet = app;
	setFormula(0, "x",false);
	setShowfunc(0, true);

	setBackground(Color.white);
	addMouseListener(zoomer);
	zoomer.changeMouseCursor();
	graph_w = graph_h = 0; graph_scale = 1;
}

/**
 * Erzeugt den Graph der Funktion.
 * @param sca die Schrittweite der Berechnung, 1 = jede Spalte
 */
void berechne(int sca) {
	expectedModCount = getModCount();
	int h = getBounds().height; int w = getBounds().width;
	if (sca<1) sca=1;
	if (sca>w/3) sca=w/3;

	if (graph==null || graph.length<function.length
	|| graph_h!=h || graph_w!=w || graph_scale!=sca){
		// das graph-Array muss neu konstruiert werden
		graph = new Graph[Math.max(function.length,numFormeln)];
		for (int i=0; i<graph.length; i++)
		if (i<function.length && getShowfunc(i))
			graph[i] = new Graph(w/sca, h, w);
		graph_w = w; graph_h = h;
		graph_scale = sca;
	}

	for(int index=0; index<function.length; index++)
	if (function[index]!=null && getShowfunc(index))
		if (index==tYindex && showparam && function[tXindex]!=null)
			berechne3(tYindex, tXindex);
		else
		if (index==tXindex && showparam);
		else
			berechne2(index);
}
/**
 * Erzeugt den Graph der Funktion index.
 * @param index int
 */
private void berechne2(int index) {

	int h = getBounds().height; int w = getBounds().width;
	if (graph[index]==null){
		graph[index] = new Graph(w/graph_scale, h, w);
	}else
		graph[index].reset();

	for(int px=0; px<w; px+=graph_scale){
		double x = zoomer.scaleI2X(px);
		try{
			double y = f(index, x);
			if (y!=y){ // y ist NAN
				graph[index].setUndefined();
			}else{
				int py = zoomer.scaleY2I(y);
				if (py<0)
					graph[index].setTooLow(px);
				else
				if (py>=h)
					graph[index].setTooHigh(px);
				else
					graph[index].set(px, h-1-py);
			}
		}catch(ArithmeticException exc){
			graph[index].setUndefined();  // -2 bedeutet Fehler
		}
	}
}
/**
 * Erzeugt den Graph der Funktion y = f_Yindex(t), x = f_Xindex(t).
 * @param index int
 */
private void berechne3(int Yindex, int Xindex) {

	double t_min = getTstart(), t_max = getTstop();
	long t_step = getTstep();
	
	int h = getBounds().height; int w = getBounds().width;
	if (graph[Yindex]==null){
		graph[Yindex] = new Graph(w/graph_scale, h, w);
	}else
		graph[Yindex].reset();

	if (graph[Xindex]==null)
		graph[Xindex] = new Graph(w/graph_scale, h, w);
	else
		graph[Xindex].reset();

	double step = (t_max - t_min)/t_step;
	double t; int pt;
	for(pt = 0, t = t_min; pt <= t_step; pt++, t += step){
		try{
			double x = f(Xindex, t);
			double y = f(Yindex, t);
			if (y!=y || x!=x){ // y oder x ist NAN
				graph[Yindex].setUndefined();
			}else{
				int px = zoomer.scaleX2I(x);
				int py = zoomer.scaleY2I(y);
				if (py<0)
					graph[Yindex].setTooLow(px);
				else
				if (py>=h)
					graph[Yindex].setTooHigh(px);
				else
				if (px<0)
					graph[Yindex].setTooLeft(h-1-py);
				else
				if (px>=w)
					graph[Yindex].setTooRight(h-1-py);
				else
					graph[Yindex].set(px, h-1-py);
			}
		}catch(ArithmeticException exc){
			graph[Yindex].setUndefined();  // -2 bedeutet Fehler
		}
	}
}

/** Wertet die Funktion an der Stelle x aus */
private double f(int index, double x) {
	FuncNode r = function[index];
	double d = r.evaluate(x, parameterP, parameterQ, 0);
	return d;
}
boolean getEinheitskreis() { return einheitskreis;}

/** Liefert die Funktion in Infix- oder Postfix-Notation */
String getFormula(int index, boolean postfix) {
	if (function==null) return "";
	if (index<0 || index>=function.length) return "";
	FuncNode r = function[index];
	if (r==null) return "";
	return postfix ? r.toStringPostfix() : r.toString();
}
/**
 * 
 * @return int
 */
int getGraphScale() {
	return graph_scale;
}

int getKlammerung() { return klammerung;}

/** Liefert den Veraenderungs-Zaehler */
int getModCount() { return zoomer.modCount + fmodCount;}

int getNumFormeln() { return numFormeln;}

double getP() { return parameterP;}
/**
 * 
 * @return java.awt.Dimension
 */
public Dimension getPreferredSize() {
	return new Dimension(500,500);
}

double getQ() { return parameterQ;}
/**
 * 
 * @return boolean
 * @param index int
 */
boolean getShowfunc(int index) {
	if (showfunc==null) return false;
	if (index<0 || index>=showfunc.length) return false;
	return showfunc[index];
}

double getTstart() { return T.start;}

long getTstep() { return T.numsteps;}

double getTstop() { return T.stop;}

int getTXi() { return tXindex;}

int getTYi() { return tYindex;}

double getXmax() { return zoomer.getXmax();}

double getXmin() { return zoomer.getXmin();}

double getYmax() { return zoomer.getYmax();}

double getYmin() { return zoomer.getYmin();}
/**
 * 
 * @return boolean
 */
boolean haveToCalc() {
	return (graph==null
	|| graph_w!=getBounds().width || graph_h!=getBounds().height
	|| expectedModCount!=getModCount());
}

/** Stellt die Anzeigeflaeche und die Funktion dar */
public void paint(Graphics g) {

	Image img = createImage(getBounds().width, getBounds().height);
	Graphics bg = img.getGraphics();

	int w = getBounds().width;
	int h = getBounds().height;
	double xdif = getXmax()-getXmin();
	double ydif = getYmax()-getYmin();
	if (xdif<1./zoomer.grenze) return;
	if (ydif<1./zoomer.grenze) return;

	// Horizontale Linien zeichnen
	int xpot = potenzVon(10, xdif*0.4);
	double xp10 = Math.pow(10, xpot);
	double x1 = Math.ceil(getXmin()/xp10)*xp10;
	do{
		if (Math.abs(x1)<xdif/100)
			bg.setColor(Color.blue);
		else
			bg.setColor(Color.lightGray);
		int px1 = zoomer.scaleX2I(x1);
		if (px1>=0 && px1<w)
			bg.drawLine(px1, 0, px1, h-1);
		x1 += xp10;
	}while (x1<=getXmax());

	// Vertikale Linien zeichnen
	int ypot = potenzVon(10, ydif*0.4);
	double yp10 = Math.pow(10, ypot);
	double y1 = Math.ceil(getYmin()/yp10)*yp10;
	do{
		if (Math.abs(y1)<ydif/100)
			bg.setColor(Color.blue);
		else
			bg.setColor(Color.lightGray);
		int py1 = h-1-zoomer.scaleY2I(y1);
		if (py1>=0 && py1<h)
			bg.drawLine(0, py1, w-1, py1);
		y1 += yp10;
	}while (y1<=getYmax());

	// linke untere und rechte obere Ecke mit Koordinaten beschriften
	bg.setColor(Color.black);
	String s; int sl;
	FontMetrics fm = bg.getFontMetrics();
	s = "("+(float)getXmin()+"; "+(float)getYmin()+")";
	bg.drawString(s,10,h-10);
	s = "("+(float)getXmax()+"; "+(float)getYmax()+")";
	sl = fm.stringWidth(s);
	bg.drawString(s, w-sl-10,20);

	if (einheitskreis){
		// Einheitskreis
		bg.setColor(Color.blue);
		// linke obere Ecke des Quadrates um den Einheitskreis
		int m1 = zoomer.scaleX2I(-1);
		int m2 = h-1-zoomer.scaleY2I(1);
		// Ausdehnung des Kreises
		int w1 = (int)(2/zoomer.scaleI2X());
		int w2 = (int)(2/zoomer.scaleI2Y());
		if (m1>-5000 && m2>-5000 && m1+w1<5000 && m2+w2<5000)
			bg.drawOval(m1, m2, w1, w2);
	}

	// Berechnet die Funktion falls noetig
	if (haveToCalc())
		berechne(1);

	bg.setColor(Color.black);
	for (int fi = 0; fi<function.length; fi++)
	if (function[fi]!=null && getShowfunc(fi) && graph[fi]!=null){
		graph[fi].paint(bg);
	} // if
	g.drawImage(img,0,0,null);

//	System.out.println(this);
}

/**
 * Liefert den Exponenten der groessten Potenz von basis,
 * die kleiner ist als wert. Funktioniert nur fuer wert
 * groesser als 0.
 */
public static int potenzVon(int basis, double wert) {
	int expo = 0, power = 1;
	if (wert<=0) return 0;
	if (wert>1){
		while(power*basis<wert){
			power *= basis;
			expo++;
		}
		return expo;
	}else{
		while(wert*power<=1){
			power *= basis;
			expo--;
		}
		return expo;
	}
}

/** Setzt den Darstellungsbereich auf die uebergebenen Werte,
 * passt den Bereich an die Grenzen an */
void setCoords(double xmi, double xma, double ymi, double yma) {
	zoomer.setCoords(xmi, xma, ymi, yma);
}
/**
 * 
 * @param state boolean
 */
void setEinheitskreis(boolean state) {
	if (einheitskreis != state){
		einheitskreis=state;
		//modCount++;
	}
}

/** Konstruiert aus der Funktion einen Syntaxbaum */
boolean setFormula(int index, String f, boolean postfix) {
	if (index<0) return false;

	// den String ein wenig filtern
	while (f.startsWith(" ")) f = f.substring(1);
	while (f.endsWith(" ")) f = f.substring(0, f.length()-1);
	f = f.toLowerCase();

	// keine Formel angegeben
	if (f.length()==0){
		if (function!=null && index<function.length)
			function[index] = null;
		setShowfunc(index, false);
		fmodCount++;
		return true;
	}

	if (function==null){
		function = new FuncNode[Math.max(index+1, numFormeln)];
	}else
	if (index>=function.length){
		FuncNode[] r2 = new FuncNode[Math.max(index+1, numFormeln)];
		for(int i=0; i<function.length; i++){
			r2[i] = function[i];
		}
		function = r2;
	}

	if (!postfix)
		// Wandelt den Infix-Ausdruck in einen Postfix-Ausdruck um
		try{
			f = NodeConstructor.parseInfix(f);
		}catch(EmptyStackException exc){
			return false;
		}catch(IllegalArgumentException exc){
			return false;
		}

	// Wandelt den Postfix-Ausdruck in einen Syntaxbaum um
	try{
		function[index] = NodeConstructor.parseRPN(f);
		function[index].setKlammerung(klammerung);
		fmodCount++;
		return true;
	}catch(StringIndexOutOfBoundsException exc){
		return false;
	}catch(EmptyStackException exc){
		return false;
	}catch(IllegalArgumentException exc){
		return false;
	}
}
/**
 * 
 * @param kl boolean
 */
void setKlammerung(int kl) {
	klammerung = kl;
	for(int i=0; i<function.length; i++)
	if (function[i]!=null)
		function[i].setKlammerung(kl);
}
/**
 * 
 * @param num int
 */
void setNumFormeln(int num) {
	for(int i=num; i<numFormeln; i++)
		setShowfunc(i, false);
	numFormeln = num;
	fmodCount++;
}
/**
 * 
 * @param p double
 */
void setP(double p) {
	parameterP = p;
	fmodCount++;
}
/**
 * 
 * @param q double
 */
void setQ(double q) {
	parameterQ = q;
	fmodCount++;
}
/**
 * 
 * @param index int
 * @param show boolean
 */
void setShowfunc(int index, boolean show) {
	if(!show){
		if (showfunc!=null && index<showfunc.length)
			showfunc[index] = false;
		if (graph!=null && index<graph.length)
			graph[index] = null;
	}else{
		if (showfunc==null){
			showfunc = new boolean[Math.max(index+1, numFormeln)];
		}else
		if (index>=showfunc.length){
			boolean[] s2 = new boolean[Math.max(index+1, numFormeln)];
			for(int i=0; i<showfunc.length; i++){
				s2[i] = showfunc[i];
			}
			showfunc = s2;
		}
		if (!showfunc[index]) fmodCount++;
		showfunc[index] = true;
	} // if
}
/**
 * 
 * @param tstart double
 * @param tstop double
 * @param tstep long
 */
void setT(double tstart, double tstop, long tstep, int Xi, int Yi) {
	T = new Parameter(tstart, tstop, tstep);
	if (Xi>=0 && Xi < function.length && function[Xi]!=null)
		tXindex = Xi;
	else
		tXindex = -1;
	if (Yi>=0 && Yi < function.length && function[Yi]!=null)
		tYindex = Yi;
	else
		tYindex = -1;
	showparam = tXindex>=0 && tYindex>=0;
	fmodCount++;
}
/**
 * 
 * @return java.lang.String
 */
public String toString() {
	String s = "GraphFlaeche[\n"+
	"numformeln:"+numFormeln+"\n"+
	"function:"+(function!=null?""+function.length:"null")+"\n"+
	"showfunc:"+(showfunc!=null?""+showfunc.length:"null")+" : ";
	for(int i = 0; i<showfunc.length; i++)
		s += showfunc[i]?"+":"-";
	s += "\n"+
	"graph:"+(graph!=null?""+graph.length:"null")+"\n";
	for(int i = 0; i<graph.length; i++)
		s += graph[i]!=null?"+":"-";
	s += "\n"+
	"]";
	return s;
}
/**
 * 
 * @param g java.awt.Graphics
 */
public void update(Graphics g) {
	paint(g);
}

/** Schreibt die Funktionskoordinaten des Pixels x/y in das
 * Koordinaten-Textfield des Anzeigefensters */
void writeKoords(int x, int y) {
	int h = getBounds().height;
	applet.writeKoords(zoomer.scaleI2X(x), zoomer.scaleI2Y(h-1-y));
}
}