import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.math.*;



public class SelectCanvas extends ScaleCanvas implements MouseListener, MouseMotionListener {
    Manager M;
    EnhancedPolygon E;
    ListenSquare MOTIONBOX,CONTROL,EXPORT,STOP,GO,RESET,AUTOFIT;
    PolygonWrapper MOTION,STILL;
    ControlPanel ACTION,ORDER,STRATUM;
    ControlPanelColor DISPLAY;
    Complex DILATE;
    int FUDGE_VAL;
    Output OUT;
    Solver SOL;

     public SelectCanvas() {
	 addMouseListener(this);
	 addMouseMotionListener(this);
	 setScales(200,550,30);
	 E=PolygonData.stratum(1,2,1);
	 CONTROL=new ListenSquare(0,0,120,0);
	 MOTIONBOX=new ListenSquare(0,0,110,100);
	 DILATE=Complex.eis(1,2);
	 makeMotion();
	 setPanels();
	 EXPORT=new ListenSquare(0,120,45,20);
	 AUTOFIT=new ListenSquare(0,160,45,20);
	 AUTOFIT.on=1;
	 int[] f=E.buildGraph(ORDER.mode);
	 GO=new ListenSquare(0,100,45,20);
	 RESET=new ListenSquare(0,140,45,20);
	 STOP=new ListenSquare(45,100,45,20);
	 SOL=new Solver();
     }
    
    public void makeMotion() {
	MOTION=new PolygonWrapper();
	MOTION.count=6;
	STILL=new PolygonWrapper();
	STILL.count=6;
	
	for(int i=0;i<6;++i) {
    	   Complex w=Complex.alpha(i);
	   w=w.scale(50);
	   w=Complex.plus(w,new Complex(55,50));
	   MOTION.z[i]=new Complex(w);
	}
	
	for(int i=0;i<6;++i) {
	   Complex w=Complex.alpha(i);
	   w=w.scale(20);
	   w=Complex.plus(w,new Complex(55,50));
	   STILL.z[i]=new Complex(w);
	}
    }


    public void setPanels() {

       Color[] C0={new Color(100,150,255),
                   Color.white,
                   Color.white,
                   Color.black,
                   Color.white};
       
       Color[] C1={new Color(150,0,150),
                   Color.white,
                   Color.white,
                   Color.black,
                   Color.white};

       String[] DisplayString={"background","grid","polygon","poly vertices","axes","node ordering","sol color1","sol color2","edge color","solution","puzzle solid","puzzle outline","display"};
       int[] DisplayState={0,1,1,0,0,0,0,0,0,1,0,0,0};
       
       Color[] DisplayColor={new Color(180,180,180),
			     new Color(0,0,50,200),
			     new Color(50,100,255),
			      new Color(255,255,255),
			     new Color(0,0,200),
			     new Color(0,80,200),
			      Color.white,
			      Color.black,
			      Color.red,
			      Color.white,
			     new Color(255,0,255,100),
			     Color.orange,
			      new Color(255,0,150,180)};

       
        DISPLAY=new ControlPanelColor(C0,DisplayString,DisplayState,12,DisplayColor);
	DISPLAY.mode=0;



	
	String[] ActionString={"nothing","dilation","control vertex","node","mouse click selects:"};
	int[] ActionState={1,0,0,0};
        ACTION=new ControlPanel(C0,ActionString,ActionState,4);
	ACTION.mode=0;


	String[] OrderString={"lex","rand","order"};
      	int[] OrderState={1,0};
        ORDER=new ControlPanel(C0,OrderString,OrderState,2);
	ORDER.mode=0;


       String[] StratumString={"(12)5","(6)4","(4)3","stratum"};
       int[] StratumState={0,1,0};
       STRATUM=new ControlPanel(C0,StratumString,StratumState,3);
	STRATUM.mode=1;

    }

    


   public void paint(Graphics g2) {
      Graphics2D g=(Graphics2D) g2;
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
      try{scaleNicely();}
      catch(Exception e) {}
      drawBG(g);
      drawPolySolid(g);
      drawGrid(g);
      drawPoly(g);
      if(DISPLAY.L[9].on==1) drawSolution(g);
      if(DISPLAY.L[10].on==1) {
	  drawMonochrome(g);
      }
      if(DISPLAY.L[11].on==1) {
          drawPuzzle(g);
      }
      if(DISPLAY.L[4].on==1) drawAxes(g);
      if(DISPLAY.L[3].on==1) drawPolygonVertices(g);
      if(ACTION.mode==2) drawControlVertices(g);
      if(ACTION.mode==1) {
	  drawDilateSelector(g);
	  drawAxes(g);
      }
      if(DISPLAY.L[5].on==1) drawNodes(g);
      if(ACTION.mode==3) drawSelectedNode(g);
      drawControls(g);
   }



    /**This scales nicely*/
    public boolean scaleNicely() {
	if(AUTOFIT.on==0) return false;
	Path2D.Double GP=E.toPath();
   	AffineTransform[] AA=preFit(GP,140,20,getWidth()-160,getHeight()-40);
	A[0]=AA[0];
	A[1]=AA[1];
	return(true);
    }

    public static AffineTransform[] preFit(Path2D.Double gp,double x,double y,double w,double h) {
	AffineTransform[] AFF=new AffineTransform[2];
	Rectangle2D R0=gp.getBounds2D();
	Rectangle2D.Double R1=(Rectangle2D.Double)(R0);
	double[] t={R1.x,R1.y,R1.x+R1.getWidth(),R1.y+R1.getHeight()};
	 double x1=1.0*(w)/(t[2]-t[0]);
	 double x2=1.0*(h)/(t[3]-t[1]);
	 double sc=x1;
	 if(sc>x2) sc=x2;	 
         AFF[1]=AffineTransform.getScaleInstance(sc,sc);
	 double cenx=(t[0]+t[2])/2.0;
	 double ceny=(t[1]+t[3])/2.0;
	 double targetx=x+1.0*w/2;
	 double targety=y+1.0*h/2;
	 double xx=targetx-cenx*sc;
	 double yy=targety-ceny*sc;
	 AFF[0]=AffineTransform.getTranslateInstance(xx,yy);
	 return(AFF);
    }

    /**done scaling*/




    
    public void drawBG(Graphics2D g) {
	g.setColor(DISPLAY.M[0].C);
        g.fillRect(0,0,getWidth(),getHeight()); 
    }
    
    public void drawGrid(Graphics2D g) {
	if(DISPLAY.L[1].on==0) return;
	Color C1=DISPLAY.M[1].C;
	double h=Math.sqrt(3)/2;
	for(int q=0;q<3;++q) {
	    Complex w=Complex.alpha(q);
	    for(int i=-100;i<100;++i) {
	       Complex z1=new Complex(-100,i*h);
	       Complex z2=new Complex(100,i*h);
	       z1=Complex.times(z1,w);
	       z2=Complex.times(z2,w);
	       drawLine(g,z1,z2,C1);

	    }
	}
    }

    public void drawLine(Graphics2D g,Complex z1,Complex z2,Color C) {
	Path2D.Double p=new Path2D.Double();
	p.moveTo(z1.x,z1.y);
	p.lineTo(z2.x,z2.y);
	p=transform(p);
	g.setColor(C);
	g.draw(p);
    }
    
    public void drawAxes(Graphics2D g) {
	Color C2=DISPLAY.M[4].C;
	double h=Math.sqrt(3)/2;
	for(int q=0;q<3;++q) {
	    Complex w=Complex.alpha(q);
	       g.setStroke(new BasicStroke((float)(1.5)));
	       Complex z1=new Complex(-100,0);
	       Complex z2=new Complex(100,0);
	       z1=Complex.times(z1,w);
	       z2=Complex.times(z2,w);
	       drawLine(g,z1,z2,C2);
	}
       g.setStroke(new BasicStroke(1));
    }


    public void drawDilateSelector(Graphics2D g) {
	fillPoint(g,DILATE,.35,new Color(0,0,0),32);
	fillPoint(g,DILATE,.3,Color.orange,32);
	fillPoint(g,new Complex(0,0),5,new Color(255,0,255,50),128);
    }


    public void drawPolySolid(Graphics2D g) {
	if(E==null) return;
	if(E.READY==false) return;
     	Path2D.Double p=E.toPath();
	p=transform(p);
	g.setColor(DISPLAY.M[2].C);
	boolean test=E.isEmbedded();
	if(test==false) g.setColor(new Color(255,0,0,200));
	g.fill(p);
    }
    
    public void drawPoly(Graphics2D g) {
	if(E==null) return;
	if(E.READY==false) return;
     	Path2D.Double p=E.toPath();
	p=transform(p);
	g.setColor(Color.black);
	g.setStroke(new BasicStroke(3));
	g.draw(p);
	g.setStroke(new BasicStroke(1));
    }




    
    public void drawPolygonVertices(Graphics2D g) {
	for(int i=0;i<E.count;++i) {
	    fillPoint(g,E.z[i],.15,Color.black,32);
	    fillPoint(g,E.z[i],.1,DISPLAY.M[3].C,32);
	}
    }

    public void drawControlVertices(Graphics2D g) {
	for(int i=1;i<E.count;++i) {
            	if(E.free[i]==1) {
		fillPoint(g,E.z[i],.25,Color.black,32);
		fillPoint(g,E.z[i],.2,Color.white,32);
   		if(E.control_point==i) {
		    fillPoint(g,E.z[i],.35,Color.black,32);
		    fillPoint(g,E.z[i],.3,Color.yellow,32);
		}
	    }
	}
    }


    
    public void drawNodes(Graphics2D g) {
	boolean used=false;
	int index=0;
	for(int i=0;i<E.V.length;++i) {
	    fillPoint(g,E.V[i],.1,DISPLAY.M[5].C,32);
	    if(used==false) {
		if(E.V[i].a[0]==0) {
		    index =i;
		    used=true;
		}
	    }
	}

	Path2D.Double p=new Path2D.Double();
	used=false;
	for(int i=0;i<E.V.length;++i) {
	    if((used==false)&&(E.V[i].a[0]==0)) {
		p.moveTo(E.V[i].x,E.V[i].y);
		used=true;
	    }

	    if((used==true)&&(E.V[i].a[0]==0)) {
		p.lineTo(E.V[i].x,E.V[i].y);
	    }
	}
	    
	p=transform(p);
	g.setColor(DISPLAY.M[5].C);
	g.draw(p);
    }
    
    public void drawSelectedNode(Graphics2D g) {
	drawVertex(g,E.current_vertex,new Color(255,255,0));
	int[] t=E.graph[E.current_vertex];
	for(int i=0;i<t.length;++i) drawVertex(g,t[i],Color.red);
    }



    public void drawVertex(Graphics2D g,int k,Color C) {
	int[] t=E.equiv[k];
	for(int i0=0;i0<t.length;++i0) {
	    int j0=t[i0];
	    Complex z0=E.V[j0];
	    drawVertex(g,z0,C);
	}
    }
	
    public void drawVertex(Graphics2D g,Complex z0,Color C) {
	    fillPoint(g,z0,.25,new Color(0,0,0),32);
	    fillPoint(g,z0,.2,C,32);
    }



    /**These routines draw the solution on the enhanced polygon*/
    
    public void drawSolution(Graphics2D g) {
	if(E.SOLVED==false) return;
    	TriangleFiller X=TriangleFiller.getFiller(E);
	if(X==null) return;
	for(int i=0;i<X.count;++i) {
	  int t=X.SOL[i];
    	  Complex[] W=X.TRI[i];
  	  PolygonWrapper P1=new PolygonWrapper(3,W);
	  try{drawPoly(g,P1,t);}
	  catch(Exception e) {return;}
	}
	drawReverse(g);
    }
    
    public void drawMonochrome(Graphics2D g) {
	if(E.SOLVED==false) return;
	int f=M.P.TRIANGLE;
	int[][] m0=ShapeRecognizer.monochromePiece(E,f);
	int[] m=m0[0];

	
	TriangleFiller X=TriangleFiller.getFiller(E);
	if(X==null) return;
	for(int i=0;i<X.count;++i) {
	  int t=X.SOL[i];
    	  Complex[] W=X.TRI[i];
  	  PolygonWrapper P1=new PolygonWrapper(3,W);
	  if(ListHelp.onList(X.TAG[i],m)==true)  drawTriangle(g,X.TRI[i],DISPLAY.M[10].C);
	}
	drawReverse(g);
    }


    
     public  void drawReverse(Graphics2D g) {
	PolygonWrapper P = E.reverse();
	Path2D.Double p=P.toPath();
	p=transform(p);
	g.setColor(DISPLAY.M[0].C);
	g.fill(p);
 	drawPolyFancy(g,E,3,6,Color.white);
    }



    
    public void drawPuzzle(Graphics2D g) {
	if(E.SOLVED==false) return;
	TriangleFiller X=TriangleFiller.getFiller(E);
	int[][] m=ShapeRecognizer.monochromePiece(E,M.P.TRIANGLE);
      	PolygonWrapper P=PuzzleFitter.fit(X,E,m,FUDGE_VAL);
	if(P==null) return;
	drawPolyFancy(g,P,3,6,DISPLAY.M[11].C);
    }



    /**polygon drawing routines*/
    public void drawPolyFancy(Graphics2D g,PolygonWrapper P,int a,int b,Color C) {
        Path2D.Double p=P.toPath();
	p=transform(p);
	g.setColor(Color.black);
	g.setStroke(new BasicStroke(b));
        g.draw(p);
	g.setColor(C);
	g.setStroke(new BasicStroke(a));
        g.draw(p);
	g.setStroke(new BasicStroke(1));
    }

    public void drawPoly(Graphics2D g,PolygonWrapper X,int t) {
	Color[] COL={DISPLAY.M[6].C,DISPLAY.M[7].C};
	Color COL2=DISPLAY.M[8].C;
	Path2D.Double p=X.toPath();
	p=transform(p);
	g.setColor(COL[t]);
	g.fill(p);
	g.setColor(COL2);
	g.draw(p);
    }

    public void drawTriangle(Graphics2D g,Complex[] Z,Color C) {
	PolygonWrapper X=new PolygonWrapper(3,Z);
	Path2D.Double p=X.toPath();
	p=transform(p);
	g.setColor(C);
	g.fill(p);
	g.setColor(Color.black);
	g.draw(p);
    }

    public void drawCenter(Graphics2D g,Complex[] Z,Color C) {
	Complex w=new Complex(0,0);
	for(int i=0;i<3;++i) w=Complex.plus(w,Z[i]);
	w=w.scale(1.0/3);
	fillPoint(g,w,.2,C,32);
    }
    /**end polygon drawing routines*/
	

    public void drawControls(Graphics2D g) {
	CONTROL.h=this.getHeight();
	CONTROL.render(g,new Color(0,0,200));
	STRATUM.render(g,55,125,60);
	ORDER.render(g,55,195,60);
	ACTION.render(g,0,265,120);
	DISPLAY.render(g,0,350,120);
	MOTIONBOX.render(g,Color.black);
	
   	Path2D.Double p=MOTION.toPath();
	g.setColor(new Color(0,0,255));
	g.fill(p);
	g.setColor(Color.white);
	g.draw(p);

   	p=STILL.toPath();
	g.setColor(new Color(80,140,255));
	g.fill(p);
	g.setColor(Color.white);
	g.draw(p);
	EXPORT.render(g,"export",12,2,new Color(80,100,120));
	RESET.render(g,"reset",12,2,new Color(30,60,255));
	String str="fit: no";
	if(AUTOFIT.on==1) str="fit: yes";
	AUTOFIT.render(g,str,12,2,new Color(200,0,200));

        if(SOL.halt==true) GO.render(g,"go",12,2,new Color(0,160,0));
        if(SOL.halt==false) STOP.render(g,"stop",12,2,new Color(200,0,0));

	
    }

    public void doControls(Point X) {
	if(GO.inside(X)==1) doGo();
	if(STOP.inside(X)==1) doStop();
	DISPLAY.process(X,M.C.CS.C);
	ORDER.switchMode(X);
	STRATUM.switchMode(X);
	if(AUTOFIT.inside(X)==1) AUTOFIT.on=1-AUTOFIT.on;
	if(RESET.inside(X)==1) doReset();
	int t=ACTION.switchMode(X);
	if(MOTIONBOX.inside(X)==1) changePoly(X);
	if(EXPORT.inside(X)==1) doExport();
    }


    public void doGo() {
	if(SOL.halt==false) return;
	SOL=new Solver(this.M);
	new Thread(SOL).start();
    }
    
    public void doStop() {
	SOL.halt=true;
    }
    


    /**This is what adjusts the control points*/

    public void doReset() {
	int t=STRATUM.mode;
 	E=PolygonData.stratum(t,DILATE);
	E.buildGraph(M.S.ORDER.mode);
    }

    /**This processes clicks to the motion hexagon and
       changes the polygon by moving the relevant control point.*/
    
    public void changePoly(Point X) {
   	int s=E.control_point;
	Complex zz=E.z[s];
	int[] tt=zz.toEis();
        Complex ww=Complex.eis(tt);
	
	s=E.convert(s);
	Complex w=new Complex(X.x,100-X.y);
	int t=MOTION.recognize(w);
	Path2D.Double p=STILL.toPath();
	if(p.contains(w.x,w.y)==false) E.changeGeometry(s,t);
	//else getArea();
	int[] f=E.buildGraph(ORDER.mode);
	sendGraphData(f);
    }

    /**this is just a test*/
    public void getArea() {
	Complex z=AreaForm.area(E);
	System.out.print("scaled area is ... ");z.print();
	Complex[] Z=AreaForm.trial();
	Complex[] W=AreaForm.trial();
	Complex zz=AreaForm.form(Z,W);
	System.out.print("real? ");  zz.print();
    }


    

    public void setDilation() {
	if(SOURCE.norm()>5) return;
   	int[] k=SOURCE.toEis();
	DILATE=Complex.eis(k);
       int t=STRATUM.mode;
       E=PolygonData.stratum(t,DILATE);
       int[] f=E.buildGraph(ORDER.mode);
       sendGraphData(f);
    }


    public void sendGraphData(int[] f) {
	if(f==null) {
	    M.C.STR[0]="graph failure";
	    return;
	}
	Integer F0=Integer.valueOf(f[0]);
	Integer F1=Integer.valueOf(f[1]);
	M.C.STR[0]="faces "+F0.toString()+"   edges " + F1.toString();


    }


    
    /**export routines*/
    public void doExport() {
	OUT=new Output("Output/graph");
	exportSolution();
    }

    
    public void exportSolution() {
	if(E.SOLVED==false) return;
	Color[] COL={DISPLAY.M[6].C,DISPLAY.M[7].C};
	Color COL2=DISPLAY.M[8].C;
    	TriangleFiller X=TriangleFiller.getFiller(E);
	if(X==null) return;
	for(int i=0;i<X.count;++i) {
	  int t=X.SOL[i];
    	  Complex[] W=X.TRI[i];
  	  PolygonWrapper P1=new PolygonWrapper(3,W);
	  exportPolygon(P1,COL[t],COL2,1);
	}
	PolygonWrapper P = E.reverse();
	exportPolygon(P,new Color(140,140,140),new Color(140,140,140),0);
       	exportPolygonOutline(E,Color.black,6);
        exportPolygonOutline(E,Color.white,3);
    }


    public void exportPolygon(PolygonWrapper X,Color C1,Color C2,double w) {
	Path2D.Double p=X.toPath();
	OUT.polyWrite(p,C1,C2,w);
    }


    public void exportPolygonOutline(PolygonWrapper X,Color C2,double w) {
	Path2D.Double p=X.toPath();
	OUT.polyOutlineWrite(p,C2,w);
    }


    
    /**end export routines*/
    
    
    

    public void mousePressed(MouseEvent e) { }
    
    public void mouseClicked(MouseEvent e) { 
	MouseData J=MouseData.process(e);
	if(CONTROL.inside(J.X)==1) {
	    doControls(J.X);
	    M.repaint();
	    return;
	}
	
        if(J.mode==1)  scaleUp(J.X,0);
        if(J.mode==3)  scaleUp(J.X,1);
	if(J.mode==2) {
	    SOURCE=unTransform(J.X);
 	    if(ACTION.mode==2) E.setControlPoint(SOURCE);
 	    if(ACTION.mode==1) setDilation();
 	    if(ACTION.mode==3) E.setCurrentVertex(SOURCE);
	}
	M.repaint();
    }

    
    public void mouseDragged(MouseEvent e) { 
	MouseData J=MouseData.process(e);
	if(J.mode==2) {
	    SOURCE=unTransform(J.X);
	}
	M.repaint();
    }

    
     public void mouseReleased(MouseEvent e) {	 
     }

     public void mouseEntered(MouseEvent e) {}
     public void mouseExited(MouseEvent e) {}   

     public void mouseMoved(MouseEvent e) {}   



}

  
