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


public class PuzzleCanvas extends ScaleCanvas implements MouseListener, MouseMotionListener {
    Manager M;
    PuzzlePiece[] P=new PuzzlePiece[100];
    PuzzlePiece[] Q=new PuzzlePiece[100];
    int COUNT;
    ListenSquare CONTROL,GET,AUTOFIT,GO,STOP;
    ControlPanelColor DISPLAY;
    SelectInteger TRANS;
    ControlPanel AUTO,ACTION;
    int[][] INDEX=new int[2][3];  //gets the indices of the pieces selected and paired
    int[] GLOM={0};
    Puzzler PUZZLE;
    int[][][] RECORD;
  

     public PuzzleCanvas() {
	 addMouseListener(this);
   	 addMouseMotionListener(this);
	 CONTROL=new ListenSquare(0,0,120,0);
	 setScales(300,400,40,40);
	 GET=new ListenSquare(0,0,120,35);
	 AUTOFIT=new ListenSquare(0,35,45,20);
	 GO=new ListenSquare(0,55,45,20);
	 STOP=new ListenSquare(45,35,40,20);

	 
	 TRANS=new SelectInteger(10,315,40,20,0,0,3,1);
	 COUNT=0;
	 for(int i=0;i<2;++i) {
	     for(int j=0;j<3;++j) INDEX[i][j]=-1;
	 }
	 PUZZLE=new Puzzler();
	 RECORD=null;
	 setPanels();
     }


    

    public void setPanels() {

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

	String[] DisplayString={"background",
				"grid",
				"gluings",
				"cone pointss",
                                "multipoint color1",
				"multipoint color2",
				"multipoint color3",
				"multipoint color4",
                                "display"};
	Color[] DisplayColor={new Color(180,180,180),
			      new Color(0,0,255,50),
                              new Color(255,0,0),
   			      new Color(50,100,255),
			      new Color(255,255,0),
			      new Color(255,150,0),
			      new Color(0,255,0),
			      new Color(0,255,255),
			      new Color(255,0,0,50)};
	int[] DisplayState={1,1,1,1,1,1,1,1};
        DISPLAY=new ControlPanelColor(C0,DisplayString,DisplayState,8,DisplayColor);

	
	String[] AutoString={"random","optimize","assembly options"};
	int[] AutoState={1,0};
        AUTO=new ControlPanel(C0,AutoString,AutoState,2);
	AUTO.mode=0;
    }

    
    /**drawing routines*/
   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);
      drawPieces(g);
      if(DISPLAY.L[2].on==1) drawGluings(g);
      if(DISPLAY.L[1].on==1) drawGrid(g);
      if(DISPLAY.L[3].on==1) drawConePoints(g);
      if(DISPLAY.L[4].on==1) drawMultiPoints(g);
      drawControls(g);
   }

    /**This scales nicely*/
    public boolean scaleNicely() {
  	if(AUTOFIT.on==0) return(false);
	if(COUNT==0) return false;
	PolygonWrapper Y=getBoundingBox();
	Path2D.Double GP=Y.toPath();
   	AffineTransform[] AA=preFit(GP,150,40,getWidth()-180,getHeight()-80);
	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);
    }
    

    public PolygonWrapper getBoundingBox() {
	double minx=10000;
	double miny=10000;
	double maxx=-10000;
	double maxy=-10000;
	
	for(int i=0;i<COUNT;++i) {
  	    PolygonWrapper Q=P[i].getTranslate();
	    for(int j=0;j<Q.count;++j) {
		Complex w=Q.z[j];
		if(minx>w.x) minx=w.x;
		if(maxx<w.x) maxx=w.x;
		if(miny>w.y) miny=w.y;
		if(maxy<w.y) maxy=w.y;
	    }
	}
	Complex[] Z={new Complex(minx,miny),new Complex(minx,maxy),new Complex(maxx,maxy),new Complex(maxx,miny)};
	PolygonWrapper Y=new PolygonWrapper(4,Z);
	return Y;
    }
    /**end scaling routines*/
    
    
    public void drawBG(Graphics2D g) {
	g.setColor(DISPLAY.M[0].C);
        g.fillRect(0,0,getWidth(),getHeight()); 
    }

    public void drawControls(Graphics2D g) {
	CONTROL.h=this.getHeight();
	CONTROL.render(g,Color.black);
	DISPLAY.render(g,0,135,120);
	AUTO.render(g,0,80,120);
        GET.render(g,"get puzzle",20,4,new Color(0,170,0));
	TRANS.render(g,Color.red,Color.white,Color.white);
	g.setFont(new Font("Helvetica",Font.PLAIN,12));
        if(AUTOFIT.on==1)   AUTOFIT.render(g,"fit: yes",12,4,new Color(200,0,200));
        if(AUTOFIT.on==0)   AUTOFIT.render(g,"fit: no",12,4,new Color(200,0,200));

	if(PUZZLE.halt==false) STOP.render(g,"stop",12,4,Color.red);
	g.setColor(Color.white);
	g.drawString("transparency",5,307);
	drawInstructions(g);
    }

    public void drawInstructions(Graphics2D g) {
	g.setColor(Color.white);
	g.setFont(new Font("Helvetica",Font.PLAIN,16));
	g.drawString("mouse action:",5,368);
	g.setFont(new Font("Helvetica",Font.PLAIN,13));
	g.drawString("left: summon",5,385);
	g.drawString("middle: drag",5,400);
	g.drawString("left: chase",5,415);
    }

    
    public void drawPieces(Graphics2D g) {
   	Color[] COL={M.S.DISPLAY.M[6].C,M.S.DISPLAY.M[7].C};
	Color COL2=M.S.DISPLAY.M[8].C;
	COL[0]=transify(COL[0]); //make semitransparent
	COL[1]=transify(COL[1]);
	if(COUNT==0) return;
	for(int i=0;i<COUNT;++i) P[i].render(this,g,COL,COL2);
    }

    public void drawGluings(Graphics2D g) {
	for(int q=0;q<2;++q) {
  	    int i=INDEX[q][0];
	    if(i==-1) return;
   	    P[i].render(this,g,INDEX[q],DISPLAY.M[2].C);
	}
    }


    public void drawConePoints(Graphics2D g) {
	Color C=DISPLAY.M[3].C;
	for(int i=0;i<COUNT;++i) {
  	    P[i].renderCone(this,g,C);
	}
    }

    public void drawMultiPoints(Graphics2D g) {
	Color[] C=new Color[4];
	for(int i=0;i<4;++i) C[i]=DISPLAY.M[i+4].C;
	int[] t=PuzzlePiece.multiPoints(P,COUNT);
	double[] d={.25,.15,.05,.04,.02,.01};
	
	for(int i=0;i<COUNT;++i) {
	    for(int j=0;j<t.length;++j) {
		Complex z=P[i].getValue(t[j]);
   		if(z!=null) {
  		    fillPoint(g,z,d[j/4],Color.black,32);
		    fillPoint(g,z,.8*d[j/4],C[j%4],32);
		}
	    }
	}
    }



    

    public Color transify(Color C) {
	int[] A={255,160,80,0};
	int r=C.getRed();
	int g=C.getGreen();
	int b=C.getBlue();
	int a=A[TRANS.val];
	return new Color(r,g,b,a);
    }
    
    
	
    public void drawGrid(Graphics2D g) {
	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);
    }
    /**end drawing routines*/

    
    public void resetIndex() {
	for(int i=0;i<2;++i) {
	    for(int j=0;j<3;++j) {
		INDEX[i][j]=-1;
	    }
	}
    }
    
    


    public void recognize() {
	resetIndex();
	for(int i=0;i<COUNT;++i) {
 	    if(P[i].engulfs(SOURCE)==true) {
		INDEX[0][0]=i;
	    }
	}
    }

    public void getGlue() {
	recognize();
	if(INDEX[0][0]==-1) return;
	int[] test=P[INDEX[0][0]].recognizeSide(SOURCE);
	if(test==null) return;
  	INDEX[0][1]=test[0];
	INDEX[0][2]=test[1];

	for(int i=0;i<COUNT;++i) {
	    boolean match=P[i].hasSide(test);
  	    if((match==true)&&(i!=INDEX[0][0])) {
		INDEX[1][0]=i;
	        INDEX[1][1]=test[0];
	        INDEX[1][2]=test[1];
	    }
	}
    }

    /**This routine pulls the second piece to the first*/
    
    public void attract() {
	getGlue();
	if(indexDefined()==false) return;
	PuzzlePiece.align(P[INDEX[0][0]],P[INDEX[1][0]],INDEX);
    }

    /**This routine pushes the first piece to the second*/
    public void chase() {
	getGlue();
	if(indexDefined()==false) return;
	int[][] INDEX2={INDEX[1],INDEX[0]};
	PuzzlePiece.align(P[INDEX2[0][0]],P[INDEX2[1][0]],INDEX2);
    }


    public boolean indexDefined() {
	for(int i=0;i<2;++i) {
	    for(int j=0;j<3;++j) {
		if(INDEX[i][j]==-1) return false;
	    }
	}
	return true;
    }

    /**snaps to the Eisenstein lattice*/
    
    public void snap() {
	for(int i=0;i<COUNT;++i) P[i].toEis();
    }

    
    public void doControls(Point X) {
	if(GET.inside(X)==1) {
	    if(M.S.E.SOLVED==false) return;
    	    if(AUTO.mode==0) {
		getPuzzle();
		RECORD=Puzzler.assembleRandomly(P,COUNT);
	    }
	    
	    else{
		getPuzzle();
		if(PUZZLE.halt==true) {
		   PUZZLE=new Puzzler(this);
		   new Thread(PUZZLE).start();
		}
	    }
	}

	if(STOP.inside(X)==1) PUZZLE.halt=true;

	
	if(AUTOFIT.inside(X)==1) AUTOFIT.on=1-AUTOFIT.on;
	TRANS.modify(X);
	DISPLAY.process(X,M.C.CS.C);
	AUTO.switchMode(X);
    }



    /**This gets the puzzle*/
    
    public void getPuzzle() {
	resetIndex();
	int[][][] m=ShapeRecognizer.monochromePieceList(M.S.E);
	for(int i=0;i<m.length;++i) {
	    P[i]=new PuzzlePiece(m[i]);
	}
	COUNT=m.length;
    }

    
    public void mouseClicked(MouseEvent e) {
        MouseData J=MouseData.process(e);
	if(CONTROL.inside(J.X)==1) {
	    doControls(J.X);
	    repaint();
	    return;
	}

	recognize();
	if(INDEX[0][0]==-1) {
	  if(J.mode==1)  scaleUp(J.X,0);
          if(J.mode==3)  scaleUp(J.X,1);
	}
	
	if(INDEX[0][0]!=-1) {
	    if(J.mode==2) getGlue();
	    if(J.mode==1) attract();
	    if(J.mode==3) chase();
	}
	
	if(J.mode==2) {
	    SOURCE=unTransform(J.X);
	}
	
	repaint();
    }


    
    
    public void mousePressed(MouseEvent e) {
    }


     public void mouseReleased(MouseEvent e) {
	 MouseData J=MouseData.process(e);
	 snap();
	 repaint();
     }

     public void mouseEntered(MouseEvent e) {
	 requestFocus();
     }
    
     public void mouseExited(MouseEvent e) {}   
    
    public void mouseMoved(MouseEvent e) {
	MouseData J=MouseData.process(e);
	if(CONTROL.inside(J.X)==1) return;
	 SOURCE=unTransform(J.X);
	 getGlue();
 	 repaint();
    }

    


    public void mouseDragged(MouseEvent e) {
	MouseData J=MouseData.process(e);
	if(CONTROL.inside(J.X)==1) return;
   	if(INDEX[0][0]==-1) return;
	
  	if((J.mode==2)&&(INDEX[0][0]!=-1)) {
	  double x0=SOURCE.x;
	  double y0=SOURCE.y;
      	  double x=P[INDEX[0][0]].position[0].x;
	  double y=P[INDEX[0][0]].position[0].y;
	  SOURCE=unTransform(J.X);
	  double x1=SOURCE.x;
	  double y1=SOURCE.y;
	  x=x+x1-x0;
	  y=y+y1-y0;
	  P[INDEX[0][0]].position[0]=new Complex(x,y);
	  repaint();
	}
    }


}

    
