//
//  Need to have window of size 840x502 for program
//

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

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

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

	Point notDraggingPoint = new Point(500,300);
	Point lastPushed = new Point(500,300);

	boolean inTriangle=true;
	boolean isDragging=false;
	boolean notDraggingInside=false;
	boolean canDrag;
	
	Image backbuffer;
	Graphics backg;

	public void init() {
		backbuffer = createImage( 2*BUFFER+T_WIDTH, 2*BUFFER+T_HEIGHT );
		backg = backbuffer.getGraphics();
		newCoat();
		
		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(testInTriangle(test)==false){
			if(test.x>BUFFER && test.x<BUFFER+15){  //in the x-strip with the buttons
				if(test.y>220 && test.y<235){  //pushed up button for method
					if(method==5){ method=1; } else { method++; }
					newCoat();
				}
				else if(test.y>235 && test.y<250){  //pushed down button for method
					if(method==1){ method=5; } else { method--; }
					newCoat();
				}
				else if(test.y>270 && test.y<285){  //pushed up button for depth
					if(depth<6){ depth++; }
					if(depth>3){ canDrag=false; }
					newCoat();
				}
				else if(test.y>285 && test.y<300){  //pushed down button for depth
					if(depth>1){ depth--; }
					if(depth<=3){ canDrag=true; }
					newCoat();
				}
			}
		}
		e.consume();
	}
	public void mousePressed( MouseEvent e ) {
		Point test = new Point (e.getX(),e.getY());

		if(testInTriangle(test)==true){
			inTriangle=true; isDragging=true;
			lastPushed = test;
		}
		else{ inTriangle=false; }
		newCoat();
		e.consume();
	}
	public void mouseReleased( MouseEvent e ) {
		isDragging = false;
		e.consume();
	}
	public void mouseMoved( MouseEvent e ) { 
		Point test = new Point (e.getX(),e.getY());
		backg.setColor(Color.black);
		backg.fillRect(BUFFER+200,0,220,BUFFER+180);
		if(testInTriangle(test)==true)
		{
			notDraggingInside=true;
			notDraggingPoint=test;
			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+200,BUFFER+350,BUFFER+200+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+200,BUFFER+175);

		}
		else
		{
			notDraggingInside=false;
		}
		repaint();
//		newCoat();
		e.consume();
	}
	public void mouseDragged( MouseEvent e ) {
		Point test = new Point(e.getX(),e.getY());
		if(testInTriangle(test)==true && isDragging==true){
			lastPushed = test;
			newCoat();
			e.consume();
		}
		else if(testInTriangle(test)==false && isDragging==true){
			lastPushed = nearestInTriangle(test);
			newCoat();
			e.consume();
		}
	}

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

	public void paint( Graphics g ) {
		update( g );
	}
	
	public void newCoat(){
		backg.setColor(Color.black);
		backg.fillRect(0,0,2*BUFFER+T_WIDTH,2*BUFFER+T_HEIGHT);
		
		DecimalFormat df = new DecimalFormat("##0.0");
// draw the large white triangle
		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);
//draw "gui"
		backg.setColor( Color.white );
		backg.drawRect( BUFFER,220,15,15);
		int[] UpTriangleX1 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] UpTriangleY1 = {220+12,220+12,220+3};
		backg.fillPolygon( UpTriangleX1, UpTriangleY1, 3 );
		backg.drawRect( BUFFER,235,15,15);
		int[] DownTriangleX1 = {BUFFER+3,BUFFER+11,BUFFER+7};
		int[] DownTriangleY1 = {235+3,235+3,235+12};
		backg.fillPolygon( DownTriangleX1, DownTriangleY1, 3 );
		backg.drawRect(BUFFER+15,220,150,30);
		
		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,40,30);
				
		backg.drawString(centerTypes[method], BUFFER+30, 240);
		backg.drawString(""+depth, BUFFER+30, 290);

		
		triangle lastPushedTriangle = findTriangle(lastPushed);
// draw the triangle that corresponds to the recursion
		Point currentShape=findVertex(lastPushedTriangle);
		int[] XArrayCurrent={BUFFER,BUFFER+150,BUFFER+currentShape.x};
		int[] YArrayCurrent={BUFFER+150,BUFFER+150,BUFFER+150-currentShape.y};
		backg.setColor( Color.yellow );
		backg.fillPolygon(XArrayCurrent,YArrayCurrent,3);
		double a=lastPushedTriangle.X(), b=lastPushedTriangle.Y(), c=lastPushedTriangle.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; }
		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);
//draw the triangle that corresponds to the current location of the cursor (if applicable)
		if(notDraggingInside==true)// && isDragging==false)
		{
			backg.setColor(Color.orange);
			if(isDragging==false){
				currentShape=findVertex(findTriangle(notDraggingPoint));
				a=findTriangle(notDraggingPoint).X();  b=findTriangle(notDraggingPoint).Y();  c=findTriangle(notDraggingPoint).Z();
			}
			else{
				currentShape=findVertex(lastPushedTriangle);
				a=lastPushedTriangle.X();  b=lastPushedTriangle.Y();  c=lastPushedTriangle.Z();
			}
			int[] XArrayDrag={BUFFER+200,BUFFER+350,BUFFER+200+currentShape.x};
			int[] YArrayDrag={BUFFER+150,BUFFER+150,BUFFER+150-currentShape.y};
			backg.fillPolygon(XArrayDrag,YArrayDrag,3);
//			a=findTriangle(notDraggingPoint).X();  b=findTriangle(notDraggingPoint).Y();  c=findTriangle(notDraggingPoint).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+200,BUFFER+175);
		}

//mark the points used by method A
		backg.setColor( Color.red );
		drawDotsRecursively ( lastPushedTriangle , 0 );
		
//draw blue cross indicating current location of starting triangle
		backg.setColor( Color.blue );
		backg.drawLine( lastPushed.x-5, lastPushed.y+5,  lastPushed.x+5, lastPushed.y-5);
		backg.drawLine( lastPushed.x+5, lastPushed.y+5,  lastPushed.x-5, lastPushed.y-5);
		backg.drawLine( lastPushed.x-5, lastPushed.y+4,  lastPushed.x+4, lastPushed.y-5);
		backg.drawLine( lastPushed.x+5, lastPushed.y+4,  lastPushed.x-4, lastPushed.y-5);
		backg.drawLine( lastPushed.x-4, lastPushed.y+5,  lastPushed.x+5, lastPushed.y-4);
		backg.drawLine( lastPushed.x+4, lastPushed.y+5,  lastPushed.x-5, lastPushed.y-4);


//output the picture
		repaint();
	}
	

	public void drawDotsRecursively (triangle T, int level) {
		if(level==depth) { //output the dots
			Point P=findPoint(T);
			int radius=4-(depth/2);
			backg.setColor( Color.red );
			backg.fillOval(P.x-radius,P.y-radius,2*radius+1,2*radius+1);
//			backg.setColor( Color.black );
//			backg.drawOval(P.x-radius,P.y-radius,2*radius+1,2*radius+1);
		}
		else { //recurse to the next level
			if(method==1) {
				drawDotsRecursively(bisector(pA(T)),level+1);
				drawDotsRecursively(bisector(pB(T)),level+1);
				drawDotsRecursively(bisector(pC(T)),level+1);
				drawDotsRecursively(bisector(pD(T)),level+1);
				drawDotsRecursively(bisector(pE(T)),level+1);
				drawDotsRecursively(bisector(pF(T)),level+1);
			}
			else if(method==2 ){
				drawDotsRecursively(median(pA(T)),level+1);
				drawDotsRecursively(median(pB(T)),level+1);
				drawDotsRecursively(median(pC(T)),level+1);
				drawDotsRecursively(median(pD(T)),level+1);
				drawDotsRecursively(median(pE(T)),level+1);
				drawDotsRecursively(median(pF(T)),level+1);
			}
			else if(method==3) {
				drawDotsRecursively(Lemoine(pA(T)),level+1);
				drawDotsRecursively(Lemoine(pB(T)),level+1);
				drawDotsRecursively(Lemoine(pC(T)),level+1);
				drawDotsRecursively(Lemoine(pD(T)),level+1);
				drawDotsRecursively(Lemoine(pE(T)),level+1);
				drawDotsRecursively(Lemoine(pF(T)),level+1);
			}
			else if(method==4) {
				drawDotsRecursively(Gergonne(pA(T)),level+1);
				drawDotsRecursively(Gergonne(pB(T)),level+1);
				drawDotsRecursively(Gergonne(pC(T)),level+1);
				drawDotsRecursively(Gergonne(pD(T)),level+1);
				drawDotsRecursively(Gergonne(pE(T)),level+1);
				drawDotsRecursively(Gergonne(pF(T)),level+1);
			}
			else {
				drawDotsRecursively(Spieker(pA(T)),level+1);
				drawDotsRecursively(Spieker(pB(T)),level+1);
				drawDotsRecursively(Spieker(pC(T)),level+1);
				drawDotsRecursively(Spieker(pD(T)),level+1);
				drawDotsRecursively(Spieker(pE(T)),level+1);
				drawDotsRecursively(Spieker(pF(T)),level+1);
			}
		}
	}

//
//  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 projects to a point in the triangle
//  assumes you are outside the triangle (!!)
//
	public Point nearestInTriangle(Point P) {
		int xSlot, ySlot;
		if(P.y>BUFFER+T_HEIGHT) { //below the triangle
			ySlot=BUFFER+T_HEIGHT;
			if		(P.x<BUFFER)  xSlot=BUFFER; 
			else if	(P.x>BUFFER+T_WIDTH)  xSlot=BUFFER+T_WIDTH; 
			else	xSlot=P.x;
		}
		else if(P.x>BUFFER+T_WIDTH) { //to the right of the triangle
			xSlot=BUFFER+T_WIDTH;
			if		(P.y<BUFFER)  ySlot=BUFFER; 
			else if	(P.y>BUFFER+T_HEIGHT)  ySlot=BUFFER+T_HEIGHT; 
			else	ySlot=P.y;
		}
		else { // above the slanty part of the triangle
			double m=-(double)(T_HEIGHT)/(double)(T_WIDTH);
			double b=(double)(BUFFER+T_HEIGHT)+(double)(T_HEIGHT*BUFFER)/(double)(T_WIDTH);
			double q=(double)(P.x)+m*(double)(P.y);
			xSlot = (int)((q-b*m)/(m*m+1));
			ySlot = (int)((m*q+b)/(m*m+1));
			if(xSlot<BUFFER) { xSlot=BUFFER; ySlot=BUFFER+T_HEIGHT; }
			else if(xSlot>BUFFER+T_WIDTH) { xSlot=BUFFER+T_WIDTH; ySlot=BUFFER; }
		}
		return new Point(xSlot,ySlot);
	}

//
//  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;
	}

//
//  Given an initial triangle the following functions return one of the six triangles
//  formed under their respective method of splitting up the triangle
//
	public static triangle bisector(triangle T) {
		triangle S = new triangle(0.5*T.X(),0.5*T.X()+0.5*T.Y(),0.5*T.Y()+T.Z());
		return S;
	}

	public static  triangle median(triangle T) {
		double p,q,r,s,t;
		p=Math.sin(T.Y())/(3.0*Math.sin(T.Z()));
		q=(1.0/3.0)+Math.cos(T.X())*p;
		r=p*Math.sin(T.X());
		s=Math.sqrt((q-0.5)*(q-0.5)+r*r);
		t=Math.sqrt(q*q+r*r);
		triangle S = new triangle(Math.acos((s*s+t*t-0.25)/(2.0*s*t)),Math.acos((0.25+s*s-t*t)/s),Math.acos((0.25+t*t-s*s)/t));
		return S;
	}
	
	public static triangle Lemoine(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=(1.0+q)*(1.0+q)+r*r;
		t=(q-0.5)*(q-0.5)+r*r;
		u=Math.acos((1.0+q)/Math.sqrt(s));
		v=Math.acos((p*p+t-0.25)/(2.0*p*Math.sqrt(t)));
		triangle S = new triangle(T.X()-u,Math.PI-T.X()-T.Z()+v,T.Z()+u-v);
		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);
	}

	public static triangle Nagel(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=0.5-(0.5*p)+((0.5*Math.sin(T.X()))/Math.sin(T.Z()));
		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=0.5-(0.5*p)+((0.5*Math.sin(T.Y()))/Math.sin(T.X()));
		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);
	}

	public static triangle Spieker(triangle T) {
		double p,q,r,s,t,u,v,w,x,y,z;
		double SpiekerX,SpiekerY;
		
		p=Math.sin(T.Y())/Math.sin(T.X()+T.Y());
		q=Math.cos(T.X())*p;
		r=Math.sin(T.X())*p;
		s=Math.sin(0.5*T.Y())/Math.sin(0.5*T.X()+0.5*T.Y());
		t=Math.cos(0.5*T.X())*s;
		u=Math.sin(0.5*T.X())*s;
		SpiekerX=0.5*(1.0+q-t);
		SpiekerY=0.5*(r-u);
		v=SpiekerX*SpiekerX+SpiekerY*SpiekerY;
		w=(SpiekerX-q)*(SpiekerX-q)+(SpiekerY-r)*(SpiekerY-r);
		x=(1.0-SpiekerX)*(1.0-SpiekerX)+SpiekerY*SpiekerY;

		y=Math.acos((v+1.0-x)/(2.0*Math.sqrt(v)));
		z=Math.acos((p*p+w-v)/(2.0*p*Math.sqrt(w)));
		
		return new triangle (y,Math.PI-T.X()-z,T.X()-y+z);
	}

//
//  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());
	}
}
