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



public class PuzzleGeometry {

    /**This makes the polygon from the angle and side data*/
    
    public static PolygonWrapper realize(PuzzlePiece P) {
	Complex[] Z=new Complex[P.sharp.length];
	Z[0]=new Complex(0,0);
	Z[1]=new Complex(P.side[0],0);

	for(int i=2;i<Z.length;++i) {
	    Complex w1=Complex.minus(Z[i-1],Z[i-2]);
	    Complex w2=Complex.alpha(P.angle[i-1]-3);
	    Complex w3=Complex.times(w1,w2);
	    w3=w3.unit(); //direction to move
	    w3=w3.scale(P.side[i-1]);
	    Z[i]=Complex.plus(Z[i-1],w3);
	}

	PolygonWrapper X=new PolygonWrapper(Z.length, Z);
	return X;
    }


    /**This works with an assignment of sides that might not lead
        to a closed polygon.  The idea is to compute the failure of the
        closing condition*/
    

    public static Complex holonomy(PuzzlePiece P) {
	Complex[] Z=new Complex[P.sharp.length+1];
	Z[0]=new Complex(0,0);
	Z[1]=new Complex(P.side[0],0);

	for(int i=2;i<Z.length;++i) {
	    int j=(i-1)%P.sharp.length;
	    Complex w1=Complex.minus(Z[j],Z[i-2]);
	    Complex w2=Complex.alpha(P.angle[j]-3);
	    Complex w3=Complex.times(w1,w2);
	    w3=w3.unit(); //direction to move
	    w3=w3.scale(P.side[j]);
	    Z[i]=Complex.plus(Z[j],w3);
	}
	return Complex.minus(Z[Z.length-1],Z[0]);
    }






    

    public static PuzzlePiece vary(PuzzlePiece P,int i1,double t) {
	PuzzlePiece Q=new PuzzlePiece(P);
	double[] v=variation(P,i1);
	for(int i=0;i<v.length;++i) v[i]=t*v[i];
	for(int i=0;i<Q.side.length;++i) Q.side[i]=Q.side[i]+v[i];
	Q.P=realize(Q);
	return Q;
    }
    
    public static double[] variation(PuzzlePiece P,int i1) {
	int n=P.side.length;
	int i0=(i1+n-1)%n;
	int i2=(i1+1)%n;
	int a1=P.angle[i1];
	int a2=P.angle[i2];
	double[] d=variation(a1,a2);
	double[] v=new double[P.side.length];
	for(int i=0;i<P.side.length;++i) v[i]=0;
	v[i0]=d[0];
	v[i1]=d[1];
	v[i2]=d[2];
	return v;
    }
	



    public static double[] variation(int a1,int a2) {
	double[] d=new double[3];
	d[0]=1;
	if(a1>3) d[0]=-1;
	d[2]=1;
	if(a2>3) d[2]=-1;
	d[1]=-c(3-a1)*d[0]-c(3-a2)*d[2];
	return d;
    }

    public static double c(int k) {
	return Math.cos(2*Math.PI*k/6);
    }

    public static PuzzlePiece forceLength(PuzzlePiece P,int s,double goal) {
	for(int i=0;i<P.side.length;++i) {
	    int j=(i+s)%P.side.length;
	    PuzzlePiece Q=forceLength(P,s,goal,j);
	    if(Math.abs(Q.side[s]-goal)<.000001) return Q;
	}
	return P;
    }
    
    public static PuzzlePiece forceLength(PuzzlePiece P,int s,double goal,int choice) {
	PuzzlePiece Q=new PuzzlePiece(P);
	double[] v=variation(P,choice);
	double a=v[s];
	double t=0;
	if(Math.abs(a)<.1) return P;
	t=(goal-P.side[s])/a;
	for(int i=0;i<P.side.length;++i) Q.side[i]=P.side[i]+t*v[i];
	Q.P=realize(Q);
	return Q;
    }


    public static int getSideNumber(PuzzlePiece P,int[] test) {
	for(int i=0;i<P.side.length;++i) {
	    int j=(i+1)%P.side.length;
	    int[] list={P.sharp[i],P.sharp[j]};
	    if(ListHelp.matchUnordered(list,test)==true) return i;
	}
	return -1;
    }

    public static int[] getSideVertices(PuzzlePiece P,int i) {
       int j=(i+1)%P.side.length;
       int[] list={P.sharp[i],P.sharp[j]};
       return list;
    }



    public static int[][] orderSides(PuzzlePiece[] P,int COUNT) {
	int count=0;
	int[][] L=new int[COUNT*6][2];
	for(int i=0;i<COUNT;++i) {
	    if(P[i].sol==0) {
	       int s=P[i].side.length-2;
	       for(int j=0;j<s;++j) {
		  L[count][0]=i;
		  L[count][1]=j;
	  	  ++count;
	       }
	    }
	}
	L=ListHelp.trim(L,count);
	return L;
    }

    public static int[] sides(PuzzlePiece[] P,int COUNT,int choice) {
	int count=0;
	int[] L=new int[COUNT];
	for(int i=0;i<COUNT;++i) {
	    if(P[i].sol==choice) {
		  L[count]=i;
	  	  ++count;
	    }
	}
	L=ListHelp.trim(L,count);
	return L;
    }

    public static double[] extractVariation(int[][] list,int[] white,int choice,double[] guess) {
	int m=white[choice];
	double[] d=new double[6];
	int count=0;
	for(int i=0;i<list.length;++i) {
	    if(list[i][0]==m) {
		d[count]=guess[i];
		++count;
	    }
	}
	d=ListHelp.trim(d,count);
	return d;
    }


    public static int[] getMatch(PuzzlePiece[] Y,int COUNT,int[] k) {
	int[] test=getSideVertices(Y[k[0]],k[1]);
	for(int i=0;i<COUNT;++i) {
	    for(int j=0;j<Y[i].side.length;++j) {
		int[] test2=getSideVertices(Y[i],j);
		if(i!=k[0]) {
		    if(ListHelp.matchUnordered(test,test2)==true) {
			int[] I={i,j};
			return I;
		    }
		}
	    }
	}
	return null;
    }
	

    public static PuzzlePiece changeGeometry(PuzzlePiece P,double[] t) {
	PuzzlePiece Q=new PuzzlePiece(P);
	int n=Q.side.length;
	for(int i=0;i<n-2;++i) {
	   double[] v=variation(P,i);
           for(int j=0;j<n;++j) {
	   Q.side[j]=Q.side[j]+t[i]*v[j];
	   }
	}
	Q.P=realize(Q);
	return Q;
    }


    public static PuzzlePiece transportGeometry(PuzzlePiece[] P,int COUNT,int i) {
	PuzzlePiece B=new PuzzlePiece(P[i]);
	for(int j=0;j<B.side.length;++j) {
	   int[] A={i,j};
	   int[] I=PuzzleGeometry.getMatch(P,COUNT,A);
	   double d=P[I[0]].side[I[1]];
	   B.side[j]=d;
	}
	B.P=realize(B);
	return B;
    }


    /**This routine makes the deformation based on a random guess*/

    
    public static PuzzlePiece[] deformation(PuzzlePiece[] P,int COUNT,double[] guess) {
	PuzzlePiece[] Q=new PuzzlePiece[COUNT];
	for(int i=0;i<COUNT;++i) Q[i]=new PuzzlePiece(P[i]);

	int[][] list=orderSides(P,COUNT);
	int[] wlist=sides(P,COUNT,0);
	int[] blist=sides(P,COUNT,1);

	//assigns the variation to the white pieces
	for(int i=0;i<wlist.length;++i) {
	    double[] var = extractVariation(list,wlist,i,guess);
	    int j=wlist[i];
	    Q[j]=changeGeometry(P[j],var);
	}

	//transfers it to the black pieces
	for(int i=0;i<blist.length;++i) {
	    int j=blist[i];
	    Q[j]=transportGeometry(Q,COUNT,j);
	}
	return Q;
    }

    public static double quality(PuzzlePiece[] P,int COUNT) {
	double sum=0;
        for(int i=0;i<COUNT;++i) {
  	   Complex z=holonomy(P[i]);
	   sum=sum+z.norm();
	}
	return sum;
    }



}

