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


public class ControlCanvas extends ScaleCanvas implements MouseListener, MouseMotionListener {
    int[][] TREE=new int[20][2];
    int[][] TRI=new int[20][3];
    int[][] F=new int[50][50];   
    SelectInteger SIDE,MOD;
    ControlPanel OPTION;
    SelectInteger[] SELECT=new SelectInteger[2];
    Color[] COL=new Color[15];
    Documentation DOC;


     public ControlCanvas() {
	 addMouseListener(this);
	 addMouseMotionListener(this);
	 setScales(200,170);	 	 
                   setControls();
	 initTree();
	 SELECT[0].val=2;
	 SELECT[1].val=5;	 
	 setColors();
	 DOC=new Documentation(405,100);
     }


    public void setColors() {
	for(int i=0;i<15;++i) {
	    if(i%5==0) COL[i]=new Color(0,220,0);
	    if(i%5==1) COL[i]=new Color(90,170,255);
	    if(i%5==2) COL[i]=new Color(200,0,200);
	    if(i%5==3) COL[i]=new Color(255,150,0);
	    if(i%5==4) COL[i]=new Color(255,0,0);
	}
	COL[0]=new Color(0,100,0);
    }

    public void initTree() {
	int side=6;
	try{side=SIDE.val;}
	catch(Exception e) {}
	SELECT[0].max=side;
	SELECT[1].max=side;
	for(int i=0;i<side-3;++i) {
	    TREE[i][0]=0;
	    TREE[i][1]=i+2;
	}

	for(int i=0;i<side-2;++i) {
	    TRI[i][0]=0;
	    TRI[i][1]=i+1;
	    TRI[i][2]=i+2;
	}

	SELECT[0].val=0;
	SELECT[1].val=1;
    }

    public void setControls() {	 
         Color[] C0={new Color(50,100,255),Color.white,Color.white,Color.black,Color.red};
	 String[] OptionString={"basic","mod","ratio1","ratio2","options"};
	 int[] OptionState={1,0,0,0};
                  OPTION=new ControlPanel(C0,OptionString,OptionState,4);   
                  SIDE=new SelectInteger(8,5,40,20,8,4,15,1);
                  MOD=new SelectInteger(520,10,40,20,2,2,20,1);
	  SELECT[0]=new SelectInteger(15,370,40,20,4,0,6,1);
	  SELECT[1]=new SelectInteger(340,370,40,20,1,0,6,1);
    }


   public void paint(Graphics g2) {
      Graphics2D g=(Graphics2D) g2;
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
      drawBG(g);
      drawTree(g);
      drawFrieze(g);
      drawControls(g);    
      drawHyperbolic(g);    
      DOC.render(g);
   }


    public void drawControls(Graphics2D g) { 
         g.setFont(new Font("Helvetica",Font.PLAIN,12));
         g.setColor(Color.white);
          g.drawString("sides",10,42);  
          g.drawString("select point",10,360);  
          g.drawString("select point",330,360);   
          g.drawString("click on diagonals to flip them",125,15);

         SIDE.render(g,new Color(200,0,200),Color.white,Color.white);
         SELECT[0].render(g,new Color(0,200,0),Color.white,new Color(0,0,0,0));
         SELECT[1].render(g,new Color(255,100,0),Color.white,new Color(0,0,0,0));
    }

    public void drawBG(Graphics2D g) {
	g.setColor(Color.black);
	g.fillRect(0,0,this.getWidth(),this.getHeight());
	g.setColor(new Color(0,0,80));
	g.fillRect(2,0,400,400);	
                 g.fillRect(578,0,400,400);
                 g.setColor(new Color(0,0,180));
                 g.fillRect(400,0,178,400);    
                 g.setFont(new Font("Helvetica",Font.PLAIN,54));
                 g.setColor(Color.white);
                 g.drawString("Frieze!",405,50);
                 g.setFont(new Font("Helvetica",Font.PLAIN,18));
                 g.setColor(Color.white);
                 g.drawString("Rich Schwartz",435,80);

               g.setFont(new Font("Helvetica",Font.PLAIN,18));
		 g.setColor(Color.white);

		 g.drawString("B",480,290);
		 g.drawString("A",462,320);
		 g.drawString("D",498,320);
		 g.drawString("C",480,350);

		 g.drawString("AD-BC=1",450,390);
    }

    public void setFrieze() {
	F=Frieze.setFrieze(SIDE.val,TREE);
    }

    public void drawFrieze(Graphics2D g) {         
                  g.setFont(new Font("Helvetica",Font.PLAIN,18));
	setFrieze();
	g.setColor(Color.white);
	int n=SIDE.val;

	g.translate(0,150-10*n);


	int[] test={0,0};
	for(int ii=-n;ii<10*n;++ii) {
	    for(int j=0;j<n-1;++j) {
	        int i=(ii+n)%n;	
		test[0]=0;
		test[1]=0;
		Color C=new Color(150,180,255);
		if((i+j)%n==(0+SELECT[0].val%n)) test[0]=1;
		if((i)%n==(1+SELECT[1].val)%n) test[1]=1;
		if((i)%n==(1+SELECT[0].val)%n) test[0]=1;
		if((i+j)%n==(0+SELECT[1].val%n)) test[1]=1;
		if(SELECT[0].val==SELECT[1].val) {
		    test[0]=0;
		    test[1]=0;
		}
		if((test[0]==1)&&(test[1])==0) C=new Color(0,200,0);
		if((test[0]==0)&&(test[1])==1) C=new Color(255,100,0);
		if((test[0]==1)&&(test[1])==1) C=Color.white;
	                  int q=F[j][(2*i+j)%(2*n)];
	                  Integer Q=Integer.valueOf(q);
		g.setColor(C);    
                                    g.setFont(new Font("Helvetica",Font.PLAIN,18));	
                                    if((test[0]==1)&&(test[1])==1)  
                                    g.setFont(new Font("Helvetica",Font.PLAIN,18));	
		if(q<10)   g.drawString(Q.toString(),(int)(15+25*ii+12.5*j),435+20*j);
		if(q>9)   g.drawString(Q.toString(),(int)(12+25*ii+12.5*j),435+20*j);
	    }
	}

	for(int ii=-n;ii<10*n;++ii) {
	                  int j=-1;
	                  int i=(ii+n)%n;	
		test[0]=0;
		test[1]=0;
		Color C1=Color.black;
		if((i+j+2)%n==((2+SELECT[0].val)%n)) test[0]=1;
		if((i+2)%n==(3+SELECT[1].val)%n) test[1]=1;
		if((test[0]==1)&&(test[1])==0) C1=new Color(0,200,0);
		if((test[0]==0)&&(test[1])==1) C1=new Color(255,100,0);
		if((test[0]==1)&&(test[1])==1) C1=Color.white;
		Complex z=new Complex(19+25*ii+12.5*j,430+20*j);
		GeneralPath gp=Disk.disk(z,5,16);
		g.setColor(C1);
		g.fill(gp);

		z=new Complex(18+25*(ii)+12.5*(j+n),430+20*(j+n));
		gp=Disk.disk(z,5,16);
		g.setColor(C1);
		g.fill(gp);

		z=new Complex(18+25*(ii)+12.5*(j+n),390+20*(j+n));
		gp=Disk.disk(z,10,16);
		g.draw(gp);


	                  z=new Complex(18+25*ii+12.5*j,470+20*j);
		gp=Disk.disk(z,10,16);
		g.setColor(C1);
		g.draw(gp);
	}

	g.translate(0,-150+10*n);

    }

    public Complex pointC(int i,int n) {
	double t=2.0*i*Math.PI/n;
	Complex z=new Complex();
	z.x=Math.cos(t);
	z.y=Math.sin(t);
	return(z);
    }

    public float[] point(int i,int n) {
	Complex z=pointC(i,n);
	float[] X={(float)(z.x),(float)(z.y)};
	return(X);
    }

    public GeneralPath pointG(int i,int n,double r) {
	Complex z=pointC(i,n);
	return(Disk.disk(z,r,16));
    }

    public int orient(int a1,int a2,int a3) {
	int n=SIDE.val;
	Complex z1=pointC(a1,n);
	Complex z2=pointC(a2,n);
	Complex z3=pointC(a3,n);
	double t=Complex.area(z1,z2,z3);
	if(t<0) return(-1);
	if(t>0) return(1);
	return(0);
    }

    public int cross(int a,int b) {
	int c=SELECT[0].val;
	int d=SELECT[1].val;
	int n1=orient(a,b,c);
	int n2=orient(a,b,d);
	if(n1*n2==-1) return(1);
	return(0);
    }

    public int involve(int[] a) {
	int test=0;
	if(cross(a[0],a[1])==1) return(1);
	if(cross(a[1],a[2])==1) return(1);
	if(cross(a[2],a[0])==1) return(1);
	return(0);
    }

    public void drawHyperbolic(Graphics2D g) {
	int[] good=involveRaw();

	if(good.length>0) {
	    GeneralPath gp=new GeneralPath();
	good=getOrder(good);
	double[][] LABELS=allLabelsX(good);
	int[] s=finalLabel(good);
	
                  AffineTransform A1=AffineTransform.getScaleInstance(2,2);
	AffineTransform A2=AffineTransform.getTranslateInstance(207,-50);

                         gp=Disk.box();
	       gp=transform(gp);
	       g.setColor(Color.black);
	       g.setStroke(new BasicStroke(1));
	       gp.transform(A1);
	       gp.transform(A2);
  	       g.fill(gp);

	   gp=Disk.ideal0(20);
	    gp=transform(gp);
	    gp.transform(A1);
	    gp.transform(A2);   
                      g.setColor(COL[0]);
	    g.fill(gp);
	    g.setColor(Color.white);
	   g.draw(gp);

                         gp=Disk.horizontal();
	       gp=transform(gp);
	       g.setColor(Color.blue);
	       gp.transform(A1);
	       gp.transform(A2);
  	       g.draw(gp);



	   for(int i=1;i<good.length;++i) {
	       gp=Disk.ideal(LABELS[i][0],LABELS[i][1],LABELS[i][2],40);
	       gp=transform(gp);
	       g.setColor(COL[i]);
	       gp.transform(A1);
	       gp.transform(A2);
  	       g.fill(gp);
	      g.setColor(Color.white);
	      g.draw(gp);
	   }   

                        double r=1.0*s[0]/s[1];
	      gp=Disk.vertical(r);
	       gp=transform(gp);
	       g.setColor(new Color(0,0,0,100));
	       g.setStroke(new BasicStroke(1));
	       gp.transform(A1);
	       gp.transform(A2);
  	       g.draw(gp);  


	   //final value
                     g.setFont(new Font("Helvetica",Font.PLAIN,18));
	   Integer[] S=new Integer[2];
	   for(int i=0;i<2;++i) S[i]=Integer.valueOf(s[i]);	   
	   g.setColor(new Color(50,100,255));
	   g.drawString(S[0].toString(),(int)(604+340*r),370);
	   g.setColor(Color.white);
	   if(s[1]<10) g.drawString(S[1].toString(),(int)(604+340*r),390);
	   if(s[1]>9) g.drawString(S[1].toString(),(int)(601+340*r),390);
	   g.setColor(Color.orange);
	   g.drawLine((int)(602+340*r),375,(int)(612+340*r),375);
	   g.setColor(new Color(50,100,255));
	   g.drawString("0",593,350);
	   g.drawString("1",952,350);


	}
    }

    public void drawTree(Graphics2D g) {
	int n=SIDE.val;
	GeneralPath gp=new GeneralPath();


	//make big polygon
	GeneralPath gp2=new GeneralPath();
	g.setStroke(new BasicStroke(1));
	for(int i=0;i<n;++i) {
	    float[] X0=point(i+0,n);
	    if(i==0) gp2.moveTo(X0[0],X0[1]);
	    if(i!=0) gp2.lineTo(X0[0],X0[1]);
	}
	gp2.closePath();
	gp2=transform(gp2);


	//draw big polygon
	g.setColor(Color.black);
	g.fill(gp2);


	//draw triangles special	
                   g.setStroke(new BasicStroke(1));
	int[] good=involveRaw();
	if(good.length>0) {
	good=getOrder(good);
	for(int j=0;j<good.length;++j) {
	    gp.reset();
	    int i=good[j];
                      float[] X0=point(TRI[i][0],n);
	    float[] X1=point(TRI[i][1],n);
	    float[] X2=point(TRI[i][2],n);
                     gp.moveTo(X0[0],X0[1]);
	   gp.lineTo(X1[0],X1[1]);
	   gp.lineTo(X2[0],X2[1]);
	   g.setColor(new Color(150,0,150));
	   g.setColor(COL[j]);
	   gp=transform(gp);
	   g.fill(gp);
	}}



	//draw tree edges	
                  g.setStroke(new BasicStroke(2));
	for(int i=0;i<n-3;++i) {
	    gp.reset();
	    float[] X0=point(TREE[i][0],n);
	    float[] X1=point(TREE[i][1],n);
	    gp.moveTo(X0[0],X0[1]);
	    gp.lineTo(X1[0],X1[1]);
	    gp=transform(gp);
	    g.setColor(Color.white);
	    g.draw(gp);
	}

	//draw big polygon
	g.setColor(Color.white);
                  g.setStroke(new BasicStroke(2));
	g.draw(gp2);

	//draw special edge
	g.setStroke(new BasicStroke(1));
	g.setColor(new Color(0,0,0,100));
	if(good.length<2) {
	     g.setStroke(new BasicStroke(6));
	     g.setColor(new Color(50,100,255));
	}
                   float[] x0=point(SELECT[0].val%n,n);
	 float[] x1=point(SELECT[1].val%n,n);   
                    gp.moveTo(x0[0],x0[1]);
	  gp.lineTo(x1[0],x1[1]);	
                  gp=transform(gp);
	g.draw(gp);

	// vertices
	g.setStroke(new BasicStroke(1));
	for(int i=0;i<n;++i) {
	    g.setColor(Color.blue);
	    if(i==SELECT[0].val%n) g.setColor(new Color(0,200,0));
	    if(i==SELECT[1].val%n) g.setColor(new Color(255,100,0));
	    if((i==SELECT[0].val)&&(i==SELECT[1].val)) g.setColor(Color.white);
	    gp=pointG(i,n,.05);
	    gp=transform(gp);
	    g.fill(gp);
	}
    }

    public int findEdge(int a,int b) {
	int n=SIDE.val;
	for(int i=0;i<n-3;++i) {
	    if((TREE[i][0]==a)&&(TREE[i][1]==b)) return(1);
	    if((TREE[i][1]==a)&&(TREE[i][0]==b)) return(1);
	}
	return(0);
    }


    public int[] getOpposites(int i) {

	int n=SIDE.val;
	int a=TREE[i][0];
	int b=TREE[i][1];
	if(a>b) {
	    a=TREE[i][1];
	    b=TREE[i][0];
	}

	int index1=-1;
	for(int j=a+1;j<b;++j) {
	    if((findEdge(a,j)==1)&&(findEdge(j,b)==1)) index1=j;
	    if((j==a+1)&&(findEdge(j,b)==1)) index1=j;
	    if((findEdge(a,j)==1)&&(j==b-1)) index1=j;	    
                      if((j==a+1)&&(j==b-1)) index1=j;
	}

	int index2=-1;
	for(int j=b+1;j<a+n;++j) {
	    if((findEdge(a,j%n)==1)&&(findEdge(j%n,b)==1)) index2=j%n;    
	    if((j%n==(b+1)%n)&&(findEdge(j%n,a)==1)) index2=j%n; 
	    if((j%n==(a+n-1)%n)&&(findEdge(j%n,b)==1)) index2=j%n;
	    if((j%n==(a+n-1)%n)&&(j%n==(b+1)%n)) index2=j%n;

	}

	int[] q={index1,index2};
	return(q);
    }






    public int getLine(Complex z) {
	int n=SIDE.val;
	if(z.x>1) return(-1);
	if(z.y>1) return(-1);

	int index=-1;
	double min=100;

	    for(int i=0;i<n-3;++i) {
		    Complex Z0=pointC(TREE[i][0],n);
		    Complex Z1=pointC(TREE[i][1],n);
		    double test=Complex.nearness(Z0,Z1,z);
		    if(test<min) {
			min=test;
			index =i;
		    }
	    }
	    return(index);
    }


    public int[] involveRaw() {
	int n=SIDE.val;
	int[] k=new int[n-2];
	int count=0;
	for(int i=0;i<n-2;++i) {
	    if(involve(TRI[i])==1) {
		k[count]=i;
		++count;
	    }
	}
	int[] kk=new int[count];
	for(int i=0;i<count;++i) kk[i]=k[i];
	return(kk);
    }


    public int containsVertex(int j,int v) {
	for(int k=0;k<3;++k) {
	    if(TRI[j][k]==v) return(1);
	}
	return(0);
    }

    public int getFirst(int[] list) {
	for(int i=0;i<list.length;++i) {
	    if(containsVertex(list[i],SELECT[0].val)==1) {
		int q=list[i];
		return(q);
	    }
	}
	return(-1);
    }

    public void adjust(int q) {
	if(TRI[q][1]==SELECT[0].val) {
	    int temp=TRI[q][1];
	    TRI[q][1]=TRI[q][0];
	    TRI[q][0]=temp;
	}

	if(TRI[q][2]==SELECT[0].val) {
	    int temp=TRI[q][2];
	    TRI[q][2]=TRI[q][0];
	    TRI[q][0]=temp;
	}
    }

    public int shareEdge(int a,int b) {
	int count=0;
	for(int i=0;i<3;++i) {
	    for(int j=0;j<3;++j) {
		if(TRI[a][i]==TRI[b][j]) ++count;
	    }
	}
	if(count==2) return(1);
	    return(0);
    }

    public int getNext(int a,int b,int[] list) {
	for(int i=0;i<list.length;++i) {
	    if((shareEdge(list[i],b)==1)&&(list[i]!=a)) return(list[i]);
	}
	return(-1);
    }

    public int[] getOrder(int[] list) {
	int[] LIST=new int[list.length];
	LIST[0]=getFirst(list);
	adjust(LIST[0]);
	if(list.length==1) return(LIST);
	LIST[1]=getNext(-1,LIST[0],list);
	if(list.length==1) return(LIST);
	for(int j=2;j<list.length;++j) {
	    LIST[j]=getNext(LIST[j-2],LIST[j-1],list);
	}
	return(LIST);
    }

    int area(int k) {
	int n=SIDE.val;
	Complex z1=pointC(TRI[k][0],n);
	Complex z2=pointC(TRI[k][1],n);
	Complex z3=pointC(TRI[k][2],n);
	double t=Complex.area(z1,z2,z3);
	if(t<0) return(-1);
	return(1);
    }

    public int[] firstLabels(int k) {

	int[] q=new int[6];
	q[0]=1;
	q[1]=0;
	int t=area(k);
	if(t==1) {
	    q[2]=0;
	    q[3]=1;
	    q[4]=1;
	    q[5]=1;
	}
	if(t==-1) {
	    q[2]=1;
	    q[3]=1;
	    q[4]=0;
	    q[5]=1;
	}
	return(q);
    }

    public int[] nextLabels(int[] prev,int k1,int k2) {
	int[] next=new int[6];
	int index=-1;	    
                  int[] farey={0,0};
	for(int j=0;j<3;++j) {
	    int count=0;
	    for(int k=0;k<3;++k) {
		if(TRI[k2][j]==TRI[k1][k]) {
		    next[2*j]=prev[2*k];
		    next[2*j+1]=prev[2*k+1];
		    farey[0]=farey[0]+prev[2*k];
		    farey[1]=farey[1]+prev[2*k+1];
		    ++count;
		}
	    }
	    if(count==0) index=j;
	}
	next[2*index]=farey[0];
	next[2*index+1]=farey[1];
	return(next);
    }

    public int[][] allLabels(int[] good) {
	int[][] Q=new int[good.length][6];
	Q[0]=firstLabels(good[0]);
	if(good.length==1) return(Q);
	for(int i=1;i<good.length;++i) {
	     Q[i]=nextLabels(Q[i-1],good[i-1],good[i]);
	}
	return(Q);
    }

    public double[][] allLabelsX(int[] good) {
	int[][] Q=allLabels(good);
	double[][] R=new double[good.length][3];
	for(int j=1;j<good.length;++j) {
	    R[j][0]=1.0*Q[j][0]/Q[j][1];
	    R[j][1]=1.0*Q[j][2]/Q[j][3];
	    R[j][2]=1.0*Q[j][4]/Q[j][5];
	}
	return(R);
    }

    public int[] finalLabel(int[] good) {
	int[][] Q=allLabels(good);
	int[] R=Q[good.length-1];
	int[] s={0,0};
	int q=good[good.length-1];
	for(int j=0;j<3;++j) {
	  if(TRI[q][j]==SELECT[1].val) {
	    s[0]=R[2*j];
	    s[1]=R[2*j+1];
	  }
	}
	return(s);
    }


    public int recognizeTriangle(int a,int b,int c) {
	int n=SIDE.val;
	for(int i=0;i<n-2;++i) {
	    if((TRI[i][0]==a)&&(TRI[i][1]==b)&&(TRI[i][2]==c)) return(i);
	    if((TRI[i][0]==a)&&(TRI[i][2]==b)&&(TRI[i][1]==c)) return(i);
	    if((TRI[i][1]==a)&&(TRI[i][0]==b)&&(TRI[i][2]==c)) return(i);
	    if((TRI[i][1]==a)&&(TRI[i][2]==b)&&(TRI[i][0]==c)) return(i);
	    if((TRI[i][2]==a)&&(TRI[i][0]==b)&&(TRI[i][1]==c)) return(i);
	    if((TRI[i][2]==a)&&(TRI[i][1]==b)&&(TRI[i][0]==c)) return(i);
	}
	return(-1);
    }

    public void select(Complex z) {
	int index=getLine(z);
	int[] p={0,0};
	if(index!=-1) {	
	    p[0]=TREE[index][0];
	    p[1]=TREE[index][1];
	   int[] q=getOpposites(index);
	   TREE[index][0]=q[0];
	   TREE[index][1]=q[1];
	   int t1=recognizeTriangle(p[0],p[1],q[0]);
	   int t2=recognizeTriangle(p[0],p[1],q[1]);
	   TRI[t1][0]=q[0];
	   TRI[t1][1]=q[1];
	   TRI[t1][2]=p[0];
	   TRI[t2][0]=q[0];
	   TRI[t2][1]=q[1];
	   TRI[t2][2]=p[1];
	}
    }

    public void mousePressed(MouseEvent e) { }
    public void mouseClicked(MouseEvent e) { 
          MouseData J=MouseData.process(e);
	  SOURCE=unTransform(J.X);
	  select(SOURCE);    
                    SIDE.modify(J.X);
                    SELECT[0].modifyCyclic(J.X);
                    SELECT[1].modifyCyclic(J.X);
                    if(SIDE.isModified(J.X)==1) initTree();
		    DOC.process(J.X);
	  repaint();
    }

     public void mouseReleased(MouseEvent e) {	 
     }

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

     public void mouseMoved(MouseEvent e) {}   
     public void mouseDragged(MouseEvent e) {
     }


}

