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

/**This class lets you manipulate the monochrome
   pieces of a face coloring solution*/

public class PuzzlePiece {
    double area;
    boolean REFLECT;
    int[] angle; //the geometry around the boundary
    int[] sharp; //the edges which have nonzero curvature
    int[] cone;  //the vertices which are cone points
    double[] side; //the side lengths;
    Complex[] position=new Complex[2]; //position of the piece
    PolygonWrapper P; //the realization
    int sol; //the color


    public PuzzlePiece() {}


    /**Here m is the output from the routine ShapeRecognizer.monochromePiece*/
    
    public PuzzlePiece(int[][] m) {
	area=m[0].length;
	REFLECT=false;

	cone=new int[m[3].length];
	for(int i=0;i<cone.length;++i) cone[i]=m[3][i];

	sol=m[4][0];
	position[0]=new Complex(0,0);
	position[1]=new Complex(1,0);

	sharp=new int[m[1].length];
	angle=new int[m[1].length];
	Complex[] H=new Complex[m[1].length];
	int count=0;
	for(int i=0;i<m[1].length;++i) {
	    if(m[2][i]!=3) {
		sharp[count]=m[1][i];
		angle[count]=m[2][i];
		++count;
	    }
	}
	sharp=ListHelp.trim(sharp,count);
	angle=ListHelp.trim(angle,count);

	int[] pos=new int[sharp.length];
	for(int i=0;i<sharp.length;++i) {
	    pos[i]=ListHelp.position(sharp[i],m[1]);
	}
	
	side =new double[sharp.length];
	for(int i=0;i<sharp.length;++i) {
	    int j=(i+1)%sharp.length;
	    side[i]=pos[j]-pos[i];
	    if(side[i]<0) side[i]=side[i]+m[1].length;
	}

	P=PuzzleGeometry.realize(this);
    }



    public PuzzlePiece(PuzzlePiece Y) {

	REFLECT=Y.REFLECT;
	area=Y.area;

       	angle=new int[Y.angle.length];
       	for(int i=0;i<angle.length;++i) angle[i]=Y.angle[i];

	sharp=new int[Y.sharp.length];
	for(int i=0;i<sharp.length;++i) sharp[i]=Y.sharp[i];

	cone=new int[Y.cone.length];
	for(int i=0;i<cone.length;++i) cone[i]=Y.cone[i];

	side=new double[Y.side.length];
	for(int i=0;i<side.length;++i) side[i]=Y.side[i];

	sol=Y.sol;

	P=new PolygonWrapper(Y.P);
	position[0]=new Complex(Y.position[0]);
	position[1]=new Complex(Y.position[1]);

    }

    
    /**This is the basic drawing routing*/
    public void render(ScaleCanvas X,Graphics2D g,Color[] COL,Color COL2) {
	PolygonWrapper Q=getTranslate();
	Path2D.Double p=Q.toPath();
	p=X.transform(p);
	g.setColor(COL[sol]);
	g.fill(p);
	g.setColor(COL2);
	g.draw(p);
    }

    /**This draws the cone points**/
    public void renderCone(ScaleCanvas X,Graphics2D g,Color COL) {
	for(int i=0;i<cone.length;++i) {
	    Complex z=getValue(cone[i]);
	    if(z!=null) {
	       X.fillPoint(g,z,.25,Color.black,32);
	       X.fillPoint(g,z,.20,COL,32);
	    }
	}
    }

    /**This draws the special indices*/
    
    public void render(ScaleCanvas X,Graphics2D g,int[] ii,Color COL) {
    	Complex[] Z=this.triad(ii,0);
	Path2D.Double p=new Path2D.Double();
	if(Z==null) return;
  	p.moveTo(Z[0].x,Z[0].y);
	p.lineTo(Z[1].x,Z[1].y);
	p=X.transform(p);
	g.setColor(COL);
	g.setStroke(new BasicStroke(5));
	g.draw(p);
	g.setStroke(new BasicStroke(1));
    }

    /**In the ideal case, one of these points is inside and
       the other one is outside.  They are listed in the
       order (inside,outside)*/
    
    public Complex[] adjacentCenters(int v1,int v2) {
  	PolygonWrapper Q=getTranslate();
  	Complex z0=adjacentCenter(v1,v2,0);
	Complex z1=adjacentCenter(v1,v2,1);
	boolean test1=Q.contains(z0);
	boolean test2=Q.contains(z1);
	if((test1==false)&&(test2==false)) return null;
	if((test1==true)&&(test2==true)) return null;
	if(test1==true) {
	    Complex[] Z={z0,z1};
	    return Z;
	}
	Complex[] Z={z1,z0};
	return Z;
    }
    

    public Complex adjacentCenter(int v1,int v2,int k) {
	int[] choice={1,5};
  	Complex z1=getValue(v1);
	Complex z2=getValue(v2);
	Complex z12=Complex.plus(z1,z2);
	z12=z12.scale(.5);
      	Complex z3=Complex.minus(z2,z1);
	Complex z4=Complex.times(z3,Complex.alpha(choice[k]));
	Complex z5=Complex.plus(z4,z1);
	Complex z6=Complex.plus(z5.scale(.2),z12.scale(.8));
	return z6;
    }

    public Complex getValue(int v) {
	PolygonWrapper Q2=getTranslate();
	Complex z=new Complex(0,0);
	
	for(int i=0;i<sharp.length;++i) {
	    if(sharp[i]==v) return Q2.z[i];
	}

	return null;
    }
    
    public PolygonWrapper getTranslate() {
	PolygonWrapper Q=new PolygonWrapper(P);
	if(REFLECT==true) {
	   for(int i=0;i<Q.count;++i) Q.z[i]=Q.z[i].conjugate();
	}
	
	for(int i=0;i<Q.count;++i) {
	    Q.z[i]=Complex.times(Q.z[i],position[1]);
	    Q.z[i]=Complex.plus(Q.z[i],position[0]);
	}
	return Q;
    }

    

    /**checks if the translated puzzle polygon contains the point*/

    public boolean engulfs(Complex z) {
	PolygonWrapper Q=getTranslate();
	return Q.contains(z);
    }



    public Complex[] triad(int[] v,int choice) {
	Complex z1=getValue(v[1]);
  	Complex z2=getValue(v[2]);
	Complex[] Z=adjacentCenters(v[1],v[2]);
	if(Z==null) return null;
	Complex[] W={z1,z2,Z[choice]};
	return W;
    }

    
    
    public int[] recognizeSide(Complex w) {
	if(engulfs(w)==false) return null;
	PolygonWrapper Q=getTranslate();
	double min=100;
	int index1=0;
	int index2=0;

	for(int i=0;i<sharp.length;++i) {
	    int j=(i+1)%sharp.length;
	    Complex z1=getValue(sharp[i]);
	    Complex z2=getValue(sharp[j]);
	    
	    double t=Complex.signedArea(z1,z2,w);
	    t=t/Complex.dist(z1,z2);
	    t=Math.abs(t);
	    if(min>t) {
		min=t;
		index1=sharp[i];
		index2=sharp[j];
	    }
	}
	int[] index={index1,index2};
	return index;
    }

    /**This reflects the second piece, if needed, so that the two pieces
       have the same orientation.*/
    
    public static void align(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
   	alignReflect(P1,P,INDEX);
	alignRotate(P1,P,INDEX);
	alignTranslate(P1,P,INDEX);
    }

    public static void alignReverse(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
   	alignReflectReverse(P1,P,INDEX);
	alignRotate(P1,P,INDEX);
	alignTranslate(P1,P,INDEX);
    }

    
    
    public static void alignReflect(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
	Complex[] Z1=P1.triad(INDEX[0],0);
	Complex[] Z2=P.triad(INDEX[1],1);
	if(Z1==null) return;
	if(Z2==null) return;
	boolean test1=Complex.isPositivelyOriented(Z1);
	boolean test2=Complex.isPositivelyOriented(Z2);
	if(test1==test2) return;
	P.reflect();
    }

    public static void alignReflectReverse(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
	Complex[] Z1=P1.triad(INDEX[0],0);
	Complex[] Z2=P.triad(INDEX[1],1);
	if(Z1==null) return;
	if(Z2==null) return;
	boolean test1=Complex.isPositivelyOriented(Z1);
	boolean test2=Complex.isPositivelyOriented(Z2);
	if(test1!=test2) return;
	P.reflect();
    }

    

    public static void alignRotate(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
	Complex[] Z1=P1.triad(INDEX[0],0);
	Complex[] Z2=P.triad(INDEX[1],1);
	if(Z1==null) return;
	if(Z2==null) return;
	Complex z1=Complex.minus(Z1[0],Z1[1]);
	Complex z2=Complex.minus(Z2[0],Z2[1]);
	z1=z1.unit();
	z2=z2.unit();
	Complex z12=Complex.divide(z1,z2);
	P.position[1]=Complex.times(P.position[1],z12);
    }
    
    public static void alignTranslate(PuzzlePiece P1,PuzzlePiece P,int[][] INDEX) {
	Complex[] Z1=P1.triad(INDEX[0],0);
	Complex[] Z2=P.triad(INDEX[1],1);
	if(Z1==null) return;
	if(Z2==null) return;
	Complex z1=Complex.minus(Z1[0],Z2[0]);

	P.position[0]=Complex.plus(P.position[0],z1);
    }
    

    /**This decides if the puzzle piece has a pair of vertices*/


    public boolean hasSide(int[] t) {
	int[] g=ListHelp.intersection(t,sharp);
	if(g.length==2) return true;
	return false;
    }


    

    /**This makes the position vector the nearest Eisenstein integer*/

    public void toEis() {
	this.position[0]=this.position[0].toEisCx();
    }
    
    public void rotate() {
	Complex w=Complex.alpha(1);
	this.position[1]=Complex.times(w,this.position[1]);
    }
    
    public void reflect() {
	boolean test=REFLECT;
	if(test==false) REFLECT=true;
	if(test==true) REFLECT=false;
    }
	


    /**This routine gets the multipoints.  These are indixes of
(pre-identified) vertices which belong to at least 3 pieces and
are pairwise disjoint.*/

	public static int[] multiPoints(PuzzlePiece[] A,int COUNT) {
	int[] sharp=allSharpPoints(A,COUNT);
	int[] list2=new int[sharp.length];
	int count=0;
	
	for(int q=0;q<sharp.length;++q) {
	    for(int i=0;i<COUNT;++i) {
		for(int j=i+1;j<COUNT;++j) {
		    for(int k=j+1;k<COUNT;++k) {
			Complex z1=A[i].getValue(sharp[q]);
			Complex z2=A[j].getValue(sharp[q]);
			Complex z3=A[k].getValue(sharp[q]);
			Complex[] Z={z1,z2,z3};
			if(allDistinct(z1,z2,z3)==true) {
			    if(ListHelp.onList(sharp[q],list2,count)==false) {
			       list2[count]=sharp[q];
			       ++count;
			    }
			}
		    }
		}
	    }
	}
	list2=ListHelp.trim(list2,count);
	return list2;
    }
    
    public static boolean allDistinct(Complex z1,Complex z2,Complex z3) {
	if(z1==null) return false;
	if(z2==null) return false;
	if(z3==null) return false;
	if(Complex.dist(z1,z2)<.1) return false;
	if(Complex.dist(z1,z3)<.1) return false;
	if(Complex.dist(z3,z2)<.1) return false;
	return true;
    }
    
    public static int[] allSharpPoints(PuzzlePiece[] A,int COUNT) {
	int[] list=new int[0];
	for(int i=0;i<COUNT;++i) {
	    list=ListHelp.merge(list,A[i].sharp);
	}
	list=ListHelp.irredundant(list);
	return list;
    }


    




    
}



