package awt;

import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import math.Maths;
import math.matrix.Matrix;
import math.matrix.Vector;
import physics.Nyquist;
import stdlib.Color;
import stdlib.StdDraw;

/**
 * @author Daniel Weschke
 */
public class Draw extends StdDraw{
	static Thread plot;
	static JLabel infoLabel;

	public static double scaleLeft = -10;
	public static double scaleRight = 10;
	public static double scaleBottom = -10;
	public static double scaleTop = 10;
	
	// TODO: combine X,Y,XM,YM ???
	// see in animate
	static double[] X;
	static double[] Y;
	static Matrix XM;
	static Matrix YM;
	
	static Matrix M;
	static Matrix dM;
	static Vector m; // for FEM
	static double fac; // for FEM
	
	boolean animationSet = false;
	
	private static double process;
	
	/**
	 * plot
	 */
	public Draw(){
		setupGUI();
	}
	
	/**
	 * plot
	 */
	public Draw(String title){
		setupGUI(title);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(int[] X, int[] Y){
		setupGUI();
		allocation(X, Y);
		setScale(Maths.min(X)*1.1, Maths.max(X)*1.1, Maths.min(Y)*1.1, Maths.max(Y)*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(int[] X, int[] Y, String title){
		setupGUI(title);
		allocation(X, Y);
		setScale(Maths.min(X)*1.1, Maths.max(X)*1.1, Maths.min(Y)*1.1, Maths.max(Y)*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(double[] X, double[] Y){
		setupGUI();
		allocation(X, Y);
		setScale(Maths.min(X)*1.1, Maths.max(X)*1.1, Maths.min(Y)*1.1, Maths.max(Y)*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(double[] X, double[] Y, String title){
		setupGUI(title);
		allocation(X, Y);
		setScale(Maths.min(X)*1.1, Maths.max(X)*1.1, Maths.min(Y)*1.1, Maths.max(Y)*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(Vector X, Vector Y){
		setupGUI();
		allocation(X, Y);
		setScale(X.min()*1.1, X.max()*1.1, Y.min()*1.1, Y.max()*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(Vector X, Vector Y, String title){
		setupGUI(title);
		allocation(X, Y);
		setScale(X.min()*1.1, X.max()*1.1, Y.min()*1.1, Y.max()*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(Matrix X, Matrix Y){
		setupGUI();
		allocation(X, Y);
		setScale(X.min()*1.1, X.max()*1.1, Y.min()*1.1, Y.max()*1.1);
	}
	
	/**
	 * xy-plot
	 * @param X
	 * @param Y
	 */
	public Draw(Matrix X, Matrix Y, String title){
		setupGUI(title);
		allocation(X, Y);
		setScale(X.min()*1.1, X.max()*1.1, Y.min()*1.1, Y.max()*1.1);
	}

	
// GUI
	
	private void setupGUI(){
		frame.setLocation(100, 50);
	}
	
	private void setupGUI(String title){
		frame.setLocation(100, 50);
		frame.setTitle(title);
	}
	
	public void setBGC(Color bgColor){
		clear(bgColor);
	}
	
	public void setFGC(Color Color){
		setGraphColor(Color);
	}
	
	
// Graph
	
	private void allocation(int[] X, int[] Y){
		int imax = X.length;
		Draw.XM = new Matrix(imax,1);
		Draw.YM = new Matrix(imax,1);
		int i;
		for(i=0; i<imax; i++){
			Draw.XM.set(i, 1, X[i]);
			Draw.YM.set(i, 1, Y[i]);
		}
	}
	
	public void allocation(double[] X, double[] Y){
		Draw.XM = new Matrix(X).transpose();  // datas in rows
		Draw.YM = new Matrix(Y).transpose();
	}
	
	public void allocation(Vector X, Vector Y){
		Draw.XM = new Matrix(X).transpose(); // datas in rows
		Draw.YM = new Matrix(Y).transpose();
	}
	
	public void allocation(Matrix X, Matrix Y){
		Draw.XM = X;
		Draw.YM = Y;
	}
	
	public void setScale(double x1, double x2, double y1, double y2){
		scaleLeft = x1;
		scaleRight = x2;
		scaleBottom = y1;
		scaleTop = y2;
        
        setXscale(scaleLeft, scaleRight);
        setYscale(scaleBottom, scaleTop);
	}

	public static double getProcess() {
		return process;
	}

	public static void setProcess(double process) {
		Draw.process = process;
		infoLabel.setText(String.format("%.0f %%",process));
	}
	
	public void plot(){
		contents();
		int i;
        double x = 0.0, y = 0.0;
        if(X!=null){
            setPenColor(Color.LIGHT_GRAY); // BLUE
        	int imax = X.length; 
	        for(i=0; i<imax; i++){
	    		x = X[i];
	    		y = Y[i];
	            point(x, y);
//	            System.out.println(y);
	        }
        }
	}
	
	public void plotLines(double[][] A, double[][] dA){
		M = new Matrix(A);
		dM = new Matrix(dA);
		contents();
		if(M!=null){
			int i;
        	int imax = M.getM();
        	double f = 50;
//        	System.out.println(f);
    		clear();
    		for(i=0; i<imax; i++){
    			setPenColor(Color.GRAY);
        		line(M.get(i,0), M.get(i,1), M.get(i,2), M.get(i,3));
    			setPenColor(Color.RED);
        		line(M.get(i,0)+dM.get(i,0)*f, M.get(i,1)+dM.get(i,1)*f,
        				M.get(i,2)+dM.get(i,2)*f, M.get(i,3)+dM.get(i,3)*f);
        	}
		}
	}
	
	@SuppressWarnings("deprecation")
	public void animate(){
		if(!animationSet){
			animateContents();
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					new Animate();
				}
			});
			animationSet = true;
		} else contents();
        if(plot!=null) plot.stop();
		plot = new Thread(new Animate());
		plot.start();
	}
	
	public void animate(double[] X, double[] Y){
		allocation(X, Y);
		double dx = 0, dy = 0;
		if(Maths.min(X) == Maths.max(X)) dx = 0.5;
		if(Maths.min(Y) == Maths.max(Y)) dy = 0.5;
		setScale(Maths.min(X)*1.1-dx, Maths.max(X)*1.1+dx,
				 Maths.min(Y)*1.1-dy, Maths.max(Y)*1.1+dy);
		animate();
	}
	
	public void animate(Vector X, Vector Y){
		allocation(X, Y);
		double[] x = X.getArray();
		double[] y = Y.getArray();
		double dx = 0, dy = 0;
		if(Maths.min(x) == Maths.max(x)) dx = 0.5;
		if(Maths.min(y) == Maths.max(y)) dy = 0.5;
		setScale(Maths.min(x)*1.1-dx, Maths.max(x)*1.1+dx,
				 Maths.min(y)*1.1-dy, Maths.max(y)*1.1+dy);
		animate();
	}
	
	public void animate(Matrix X, Matrix Y){
		allocation(X,Y);
		setScale(X.min()*1.1, X.max()*1.1,
				 Y.min()*1.1, Y.max()*1.1);
		animate();
	}

	@SuppressWarnings("deprecation")
	public void animateLines(double[][] A, double[][] dA) {
		M = new Matrix(A);
		dM = new Matrix(dA);
		if(!animationSet){
			animateContents();
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
		            setPenColor(Color.LIGHT_GRAY);
					new AnimateLines();
				}
			});
			animationSet = true;
		} else contents();
        setPenColor(Color.LIGHT_GRAY);
        if(plot!=null) plot.stop();
		plot = new Thread(new AnimateLines());
		plot.start();
	}

	// TODO: merge with animateLines -> class: DrawFEM?
	@SuppressWarnings("deprecation")
	public void animateFEM(double[][] A, double[][] dA, double[] a) {
		M = new Matrix(A);
		dM = new Matrix(dA);
		m = new Vector(a);
		// Display size
		double xMin = M.getN(2).min();
		double xMax = M.getN(2).max();
		double yMin = M.getN(3).min();
		double yMax = M.getN(3).max();
		if(M.getN(0).min() < xMin) xMin = M.getN(0).min();
		if(M.getN(0).max() > xMax) xMax = M.getN(0).max();
		if(M.getN(1).min() < yMin) yMin = M.getN(1).min();
		if(M.getN(1).max() > yMax) yMax = M.getN(1).max();
//		System.out.println((xMax-xMin)+" "+(yMax-yMin));
//		System.out.println(((xMax-xMin) - (yMax-yMin))/2);
//		System.out.println(yMin+" "+yMax);
		double xMM  = xMax - xMin;
		double yMM  = yMax - yMin;
		if((xMM) > (yMM)){
			double dif = (xMM - yMM)/2;
			yMin -= dif*1.05; // kleine Verschiebung nach oben
			yMax += dif*0.95;
		} else {
			double dif = (yMM - xMM)/2;
			xMin -= dif*1.05;
			xMax += dif*0.95;
		}
//		System.out.println(yMin+" "+yMax);
//		System.out.println((xMax-xMin)+" "+(yMax-yMin));
		setScale(xMin, xMax, yMin, yMax);
		// Displacement factor
		double xdMM = dM.getN(2).max() - dM.getN(2).min();
		double ydMM = dM.getN(3).max() - dM.getN(3).min();
		double dMM = Math.sqrt(xdMM*xdMM+ydMM*ydMM);
		double MM = Math.sqrt(xMM*xMM+yMM*yMM);
		fac = MM/dMM/100*2;
		if(fac<1)fac=1;
//		System.out.println(dMM);
//		System.out.println(MM);
		System.out.printf("Scalierung %.4g%n",fac);
		if(!animationSet){
			animateContents();
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
		            setPenColor(Color.LIGHT_GRAY);
					new AnimateFEM();
				}
			});
			animationSet = true;
		} else contents();
        setPenColor(Color.LIGHT_GRAY);
        if(plot!=null) plot.stop();
		plot = new Thread(new AnimateFEM());
		plot.start();
	}
	
	private void contents(){
		frame.setLocation(100, 50);
        
        setXscale(scaleLeft, scaleRight);
        setYscale(scaleBottom, scaleTop);
	}
	
	public void cross(){
        setPenColor(Color.GRAY);
        line(scaleLeft, 0, scaleRight, 0);
        line(0, scaleBottom, 0, scaleTop);
        setPenColor(getGraphColor());
	}
	
	private void animateContents(){
		contents();
		infoLabel = new JLabel("0 %");
		infoLabel.setHorizontalAlignment(SwingConstants.RIGHT);
		infoLabel.setBounds(DEFAULT_SIZE+left-107, top+5, 100, 20);
        frame.add(infoLabel);
	}


	/**
	 * Initialize Nyquist data
	 */
	private void nyquist(){
		frame.setTitle("Nyquist");
		Nyquist nyq = new Nyquist();
		Draw.X = nyq.getNyquist().getN(0).getArray();
		Draw.Y = nyq.getNyquist().getN(1).getArray();

		scaleLeft = -1.5;
		scaleRight = 2.5;
		scaleBottom = -2.5;
		scaleTop = 1.5;
	}


	/**
	 * Plot Nyquist
	 */
	public void plotNyquist() {
		nyquist();
		plot();
	}


	/**
	 * Animate Nyquist
	 */
	public void animateNyquist() {
		nyquist();
		animate();
		cross();
		//TODO:mark?
		markX(-1);
	}
	
	/**
	 * 
	 */
	public class Animate implements Runnable{
		@Override
		public void run(){
			if(XM != null && YM != null){
				// TODO: different Color
				Color[] color = new Color[]{
						Color.LIGHT_BLUE,
						Color.PURPLE,
						Color.YELLOW,
						Color.GREEN,
						Color.RED};
				int i, j;
	        	int imax = XM.getM();
	        	int jmax = XM.getN();
	        	int max = imax > jmax ? imax : jmax;
	        	int sec = (int) 1000./max*10;
	        	sec = sec > 10 ? 10 : sec;
//	            System.out.println(max);
//	            System.out.println(sec);
		        for(j=0; j<jmax; j++){
		        	for(i=0; i<imax; i++){
//		        		setPenColor(color[((int) (Math.pow(-1, i)))<0?0:1]);
		        		if(i>=color.length) setPenColor(color[0]);
		        		else setPenColor(color[i]);
			            point(XM.get(i,j), YM.get(i,j));
			            show(sec);
			            setProcess((double)(i+j+1)/(imax+jmax)*100);
			        }
		        }
			} else if(X!=null){
				int i;
	        	int imax = X.length;
	        	int sec = (int) 1000./imax*10;
	        	sec = sec > 10 ? 10 : sec;
//	            System.out.println(imax);
//	            System.out.println(sec);
		        for(i=0; i<imax; i++){
		            point(X[i], Y[i]);
		            show(sec);
//		            System.out.println(X[i]);
		            setProcess((double)(i+1)/imax*100);
//		        	System.out.println(process);
		        }
	        }
		}
	}
	
	/**
	 * 
	 */
	public class AnimateLines implements Runnable{
		@Override
		public void run(){
			if(M!=null){
				int i, j;
	        	int imax = M.getM();
	        	int jmax = 200;
	        	double f;
	        	for(j=0; j<jmax; j++){
	        		f = (double)(j+1)/jmax * 50;
//	        		System.out.println(f);
	        		clear();
	        		for(i=0; i<imax; i++){
	        			setPenColor(Color.GRAY);
		        		line(M.get(i,0), M.get(i,1), M.get(i,2), M.get(i,3));
		    			setPenColor(Color.RED);
		        		line(M.get(i,0)+dM.get(i,0)*f, M.get(i,1)+dM.get(i,1)*f,
		        				M.get(i,2)+dM.get(i,2)*f, M.get(i,3)+dM.get(i,3)*f);
		        	}
		            show(10);
	        		setProcess((double)(i+j+1)/(imax+jmax)*100);
		        }
			}
		}
	}
	
	/**
	 * 
	 */
	// TODO: merge with animateLines -> class: DrawFEM?
	public class AnimateFEM implements Runnable{
		@Override
		public void run(){
			if(M!=null){
				int i, j;
	        	int imax = M.getM();
	        	int jmax = 200;
	        	int mMax = m.abs().indexOfMax();
	        	double max = m.max();
	        	double min = m.min();
	        	double f;
	        	for(j=0; j<jmax; j++){
	        		f = (double)(j+1)/jmax * fac;
//	        		System.out.println(f);
	        		clear();
	        		for(i=0; i<imax; i++){
	        			setPenColor(Color.GRAY);
		        		line(M.get(i,0), M.get(i,1), M.get(i,2), M.get(i,3));
		    			if(i == mMax) setPenColor(Color.RED);
		    			else if(m.get(i) >= 0.92*max) setPenColor(Color.ORANGE);
		    			else if(m.get(i) >= 0.81*max) setPenColor(Color.YELLOW);
		    			else if(m.get(i) >= 0.67*max) setPenColor(Color.GREEN);
		    			else if(m.get(i) >= 0.50*max) setPenColor(Color.GREEN);
		    			else if(m.get(i) >= 0.30*max) setPenColor(Color.GREEN);
		    			else if(m.get(i) >  0.30*min) setPenColor(Color.GREEN);
		    			else if(m.get(i) >  0.50*min) setPenColor(Color.GREEN);
		    			else if(m.get(i) >  0.67*min) setPenColor(Color.GREEN);
		    			else if(m.get(i) >  0.81*min) setPenColor(Color.GREEN);
		    			else if(m.get(i) >  0.92*min) setPenColor(Color.CYAN);
		    			else if(m.get(i) >  1.00*min) setPenColor(Color.BLUE);
		    			else setPenColor(Color.PURPLE);
		        		line(M.get(i,0)+dM.get(i,0)*f, M.get(i,1)+dM.get(i,1)*f,
		        				M.get(i,2)+dM.get(i,2)*f, M.get(i,3)+dM.get(i,3)*f);
		        	}
		            show(10);
	        		setProcess((double)(i+j+1)/(imax+jmax)*100);
		        }
			}
		}
	}
	
	@SuppressWarnings("deprecation")
	public void stop(){
		plot.stop();
//		plot.interrupt();
//      frame.repaint(); // bringt nichts
//		frame.removeAll();
	}
	
	public void markX(double x){
		double size = (scaleTop-scaleBottom)*.01;
		line(x, -size, x, size);
		if(x!=0)
			if(scaleLeft==0) text(x, 3*size, String.format("%.1f", x));
			else text(x, -4*size, String.format("%.1f", x));
	}
	
	public void markY(double y){
		double size = (scaleRight-scaleLeft)*.01;
		line(-size, y, size, y);
		if(y!=0)
			if(scaleBottom==0) textLeft(2*size, y, String.format("%.1f", y));  
			else textRight(-2.5*size, y, String.format("%.1f", y));
	}
	
	public void mark(double x, double y){
		double sizeX = (scaleTop-scaleBottom)*.01;
		double sizeY = (scaleRight-scaleLeft)*.01;
		filledCircle(x, y, sizeX<sizeY?sizeX:sizeY);
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
//		new Draw();
//		new Draw().plotNyquist();
		new Draw().animateNyquist();
//		new Draw().animate();
	}
}
