import java.text.DecimalFormat;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class Gergonne extends Applet implements MouseListener, MouseMotionListener {
	int BUFFER=20, T_WIDTH=800, T_HEIGHT=462;  // constants used for size of triangle
	int MAX_DEPTH=20;

	String[] centerTypes = { "", "bisectors", "medians", "Lemoine point", "Gergonne point", "Nagel point", "Spieker center"};
	public int depth=2;  //used to keep track of type of center used in the calculations

	Point lastPushed = new Point(500,300);

	Image backbuffer;
	Graphics backg;

	public void init() {
		backbuffer = createImage( 2*BUFFER+T_WIDTH, 2*BUFFER+T_HEIGHT );
		backg = backbuffer.getGraphics();
		backg.setColor(Color.black);
		backg.fillRect(0,0,2*BUFFER+T_WIDTH,2*BUFFER+T_HEIGHT);
		
		int[] XArray={BUFFER,T_WIDTH+BUFFER,T_WIDTH+BUFFER};
		int[] YArray={BUFFER+T_HEIGHT,BUFFER+T_HEIGHT,BUFFER};
		backg.setColor( Color.white );
		backg.fillPolygon(XArray,YArray,3);
		backg.setColor(Color.white);
		backg.drawString("Add more points", BUFFER+20, 240);
		backg.drawRect(BUFFER,215,140,40);

		backg.drawRect( BUFFER,270,15,15);
		int[] UpTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] UpTriangleY2 = {270+12,270+12,270+3};
		backg.fillPolygon( UpTriangleX2, UpTriangleY2, 3 );
		backg.drawRect( BUFFER,285,15,15);
		int[] DownTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] DownTriangleY2 = {285+3,285+3,285+12};
		backg.fillPolygon( DownTriangleX2, DownTriangleY2, 3 );
		backg.drawRect(BUFFER+15,270,45,30);
				
		backg.drawString(""+depth, BUFFER+30, 290);


		repaint();
		
		addMouseListener( this );
		addMouseMotionListener( this );
	}

	public void mouseEntered( MouseEvent e ) { }
	public void mouseExited( MouseEvent e ) { }
	public void mouseClicked( MouseEvent e ) { 
		Point test = new Point (e.getX(),e.getY());
		if(test.x>BUFFER && test.x<BUFFER+140 && test.y>215 &&test.y<255){
			triangle T;
			Point P;
			backg.setColor(Color.red);
			for(int i=1; i<=(75000/depth); i++){
				T= new triangle(Math.random(),Math.random(),Math.random());
				for(int j=1; j<=depth; j++){
					int n = (int)(6.0*Math.random());
					if(n==0){ T=Gergonne(pA(T));	}
					else if(n==1){ T=Gergonne(pB(T));	}
					else if(n==2){ T=Gergonne(pC(T));	}
					else if(n==3){ T=Gergonne(pD(T));	}
					else if(n==4){ T=Gergonne(pE(T));	}
					else { T=Gergonne(pF(T));	}
				}
				P=findPoint(T);
				backg.drawOval(P.x,P.y,1,1);
			}
			repaint();
		}
		if(test.x>BUFFER && test.x<BUFFER+15 && test.y>270 && test.y<285){
			if(depth<MAX_DEPTH)
			{
				depth++;
				backg.setColor(Color.black);
				backg.fillRect(0,0,2*BUFFER+T_WIDTH,2*BUFFER+T_HEIGHT);
				
				int[] XArray={BUFFER,T_WIDTH+BUFFER,T_WIDTH+BUFFER};
				int[] YArray={BUFFER+T_HEIGHT,BUFFER+T_HEIGHT,BUFFER};
				backg.setColor( Color.white );
				backg.fillPolygon(XArray,YArray,3);
				backg.setColor(Color.white);
				backg.drawString("Add more points", BUFFER+20, 240);
				backg.drawRect(BUFFER,215,140,40);

				backg.drawRect( BUFFER,270,15,15);
				int[] UpTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
				int[] UpTriangleY2 = {270+12,270+12,270+3};
				backg.fillPolygon( UpTriangleX2, UpTriangleY2, 3 );
				backg.drawRect( BUFFER,285,15,15);
				int[] DownTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
				int[] DownTriangleY2 = {285+3,285+3,285+12};
				backg.fillPolygon( DownTriangleX2, DownTriangleY2, 3 );
				backg.drawRect(BUFFER+15,270,45,30);
						
				backg.drawString(""+depth, BUFFER+30, 290);

				repaint();
			}
		}
		if(test.x>BUFFER && test.x<BUFFER+15 && test.y>285 && test.y<300){
			if(depth>1){
				depth--;
				backg.setColor(Color.black);
				backg.fillRect(0,0,2*BUFFER+T_WIDTH,2*BUFFER+T_HEIGHT);
				
				int[] XArray={BUFFER,T_WIDTH+BUFFER,T_WIDTH+BUFFER};
				int[] YArray={BUFFER+T_HEIGHT,BUFFER+T_HEIGHT,BUFFER};
				backg.setColor( Color.white );
				backg.fillPolygon(XArray,YArray,3);
				backg.setColor(Color.white);
				backg.drawString("Add more points", BUFFER+20, 240);
				backg.drawRect(BUFFER,215,140,40);

				backg.drawRect( BUFFER,270,15,15);
				int[] UpTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
				int[] UpTriangleY2 = {270+12,270+12,270+3};
				backg.fillPolygon( UpTriangleX2, UpTriangleY2, 3 );
				backg.drawRect( BUFFER,285,15,15);
				int[] DownTriangleX2 = {BUFFER+3,BUFFER+11,BUFFER+7};
				int[] DownTriangleY2 = {285+3,285+3,285+12};
				backg.fillPolygon( DownTriangleX2, DownTriangleY2, 3 );
				backg.drawRect(BUFFER+15,270,45,30);
						
				backg.drawString(""+depth, BUFFER+30, 290);

				repaint();
			}
		}
		e.consume();
	}
	public void mousePressed( MouseEvent e ) {	}
	public void mouseReleased( MouseEvent e ) { }
	public void mouseMoved( MouseEvent e ) {
		Point test = new Point (e.getX(),e.getY());
		backg.setColor(Color.black);
		backg.fillRect(0,0,BUFFER+220,BUFFER+180);
		if(testInTriangle(test)==true)
		{
			Point currentShape;
			double a,b,c,min,mid,max;
			DecimalFormat df = new DecimalFormat("##0.0");
			backg.setColor(Color.orange);
			currentShape=findVertex(findTriangle(test));
			int[] XArrayDrag={BUFFER,BUFFER+150,BUFFER+currentShape.x};
			int[] YArrayDrag={BUFFER+150,BUFFER+150,BUFFER+150-currentShape.y};
			backg.fillPolygon(XArrayDrag,YArrayDrag,3);
			a=findTriangle(test).X();  b=findTriangle(test).Y();  c=findTriangle(test).Z();
			if		(a<=b && b<=c)	{ min=a; mid=b; max=c; }
			else if	(a<=c && c<=b)	{ min=a; mid=c; max=b; }
			else if	(b<=a && a<=c)	{ min=b; mid=a; max=c; }
			else if	(b<=c && c<=a)	{ min=b; mid=c; max=a; }
			else if	(c<=a && a<=b)	{ min=c; mid=a; max=b; }
			else					{ min=c; mid=b; max=a; }
			backg.drawString(df.format(min*180.0/Math.PI)+" -- "+df.format(mid*180.0/Math.PI)+" -- "+df.format(max*180.0/Math.PI), BUFFER,BUFFER+175);
		}
		repaint();
		e.consume();
	}
	public void mouseDragged( MouseEvent e ) { }

	public void update( Graphics g ) {
		g.drawImage( backbuffer, 0, 0, this );
	}

	public void paint( Graphics g ) {
		update( g );
	}

//
//  The following function is to test if a point is in the triangle
//
	public boolean testInTriangle (Point P) {
		if(P.y>BUFFER+T_HEIGHT){ return false; }
		else if(P.x>T_WIDTH+BUFFER){ return false; }
		else if(T_WIDTH*P.y<-T_HEIGHT*P.x+(BUFFER+T_HEIGHT)*(T_WIDTH+BUFFER)-BUFFER*BUFFER){ return false; }
		else{ return true; }
	}

//
//  The following function returns the vertex of the triangle with base at (0,0)---(150,0)
//  (the largest vertex is put on the top)
//
	public Point findVertex(triangle T) {
		double a=T.X(), b=T.Y(), c=T.Z(), min, mid, max;
		if		(a<=b && b<=c)	{ min=a; mid=b; max=c; }
		else if	(a<=c && c<=b)	{ min=a; mid=c; max=b; }
		else if	(b<=a && a<=c)	{ min=b; mid=a; max=c; }
		else if	(b<=c && c<=a)	{ min=b; mid=c; max=a; }
		else if	(c<=a && a<=b)	{ min=c; mid=a; max=b; }
		else					{ min=c; mid=b; max=a; }
		int xSlot=(int)(150.0*Math.cos(min)*Math.sin(mid)/Math.sin(max));
		int ySlot=(int)(150.0*Math.sin(min)*Math.sin(mid)/Math.sin(max));
		return new Point(xSlot,ySlot);
	}

//
//  The following two functions translate back and forth between points on the screen and triangles
//
	public Point findPoint(triangle T) { 
		double a=T.X(), b=T.Y(), c=T.Z(), min, mid, max;
		if		(a<=b && b<=c)	{ min=a; mid=b; max=c; }
		else if	(a<=c && c<=b)	{ min=a; mid=c; max=b; }
		else if	(b<=a && a<=c)	{ min=b; mid=a; max=c; }
		else if	(b<=c && c<=a)	{ min=b; mid=c; max=a; }
		else if	(c<=a && a<=b)	{ min=c; mid=a; max=b; }
		else					{ min=c; mid=b; max=a; }
		int x=BUFFER+(int)((min+2.0*mid)*(double)(T_WIDTH)/Math.PI);
		int y=BUFFER+(int)((-2.0*min+mid+max)*(double)(T_HEIGHT)/Math.PI);
		Point P = new Point(x,y);
		return P;
	}

	public triangle findTriangle(Point P) { 
		double a=1.0-((double)(P.x-BUFFER)/(double)(T_WIDTH));
		double b=((double)(P.y-BUFFER)/(double)(T_HEIGHT))-a;
		double angleX=Math.PI*(1.0-a-b)/3.0;
		double angleY=Math.PI*(2.0-2.0*a+b)/6.0;
		double angleZ=Math.PI*(2.0+4.0*a+b)/6.0;
//  if "close" to the edge push back a little bit
		if( angleX < 0.005 ) { angleX = 0.005; }
		if( angleY < 0.005 ) { angleY = 0.005; }
		triangle S = new triangle(angleX,angleY,angleZ);
		return S;
	}


	public static triangle Gergonne(triangle T) {
		double p,q,r,s,t,u,v;
		p=Math.sin(T.Y())/Math.sin(T.Z());
		q=Math.cos(T.X())*p;
		r=Math.sin(T.X())*p;
		s=Math.cos(0.5*T.X())*Math.sin(0.5*T.Y())/Math.sin(0.5*(T.X()+T.Y()));
		t=(q-s)*(q-s)+r*r;
		u=Math.acos((p*p+t-s*s)/(2.0*p*Math.sqrt(t)));

		p=Math.sin(T.Z())/Math.sin(T.X());
		q=Math.cos(T.Y())*p;
		r=Math.sin(T.Y())*p;
		s=Math.cos(0.5*T.Y())*Math.sin(0.5*T.Z())/Math.sin(0.5*(T.Y()+T.Z()));
		t=(q-s)*(q-s)+r*r;
		v=Math.acos((p*p+t-s*s)/(2.0*p*Math.sqrt(t)));

		return new triangle (v,Math.PI-T.X()-u,T.X()-v+u);
	}

//
//  The following functions permute the triangle and allows us to easily set up recursions
//
	public static  triangle pA(triangle T) {
		return new triangle(T.X(),T.Y(),T.Z());
	}

	public static  triangle pB(triangle T) {
		return new triangle(T.X(),T.Z(),T.Y());
	}

	public static  triangle pC(triangle T) {
		return new triangle(T.Y(),T.X(),T.Z());
	}

	public static  triangle pD(triangle T) {
		return new triangle(T.Y(),T.Z(),T.X());
	}

	public static  triangle pE(triangle T) {
		return new triangle(T.Z(),T.X(),T.Y());
	}

	public static  triangle pF(triangle T) {
		return new triangle(T.Z(),T.Y(),T.X());
	}
}
