import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.applet.*;
import java.util.Random;

/* Triangle.java

   In order to change                            Edit
  

   The look of the board                         Board.drawMySelf()
                                                 margins, pieceMargins

   The rules for valid moves                     isValid()

*/

public class TriCirc extends Applet{

    int bottomstrip = 100;

    int pieceSize = 5;    
    int xorig;
    int yorig;

    int cur;
    int level;
    int curLev;
    int pickedup;

    static int RED = 0;
    static int BLUE = 1;
    static int GREEN = 2;

    static int MAXLEV = 8;
    static int NUMCOLORS=6;

    Color colorarray[];
    int pointsSet;
    boolean havenotputupmessage= true;
    DNode points[][];

    private Button clearButton;
    private TextArea adviceField;
    private Label mePlayerLabel;
    private TextField meFirst;
    private Choice mePlayer;
    private Random generator;

    private Button zoomInButton;
    private Button zoomOutButton;
    private Button newLevelButton;

    boolean changed=true;

    GameCanvas gameboard;
    Dimension offDimension;
    Image offImage;
    Graphics offGraphics;

    DNode gamepieces[][];
    
    class  Node {
	// *logical* location of these
	// the board contains something to map logical locations to real locations
	// and real locations to logical
	int x;
	int y;

	Node(int x, int y) {
	    this.x = x;
	    this.y = y;
	}
	Node( Node n ){
	    this.x = n.x ;
	    this.y = n.y ;
	}

	void Copy(Node n) {
	    this.x=n.x;
	    this.y=n.y;
	}

    }

    class  DNode {
	// *logical* location of these
	// the board contains something to map logical locations to real locations
	// and real locations to logical
	double x;
	double y;

	DNode(double x, double y) {
	    this.x = x;
	    this.y = y;
	}
	DNode( DNode n ){
	    this.x = n.x ;
	    this.y = n.y ;
	}

	void Copy(DNode n1) {
	    x=n1.x;
	    y=n1.y;
	}

	Node DNodeToNode() {
	    Node node = new Node((int)x,(int)y);
	    return node;
	}

	void drawMySelf(Graphics g) {
	    gameboard.drawAPiece(this,g);
	}

    }



    /*********************************************************
     * Now we are into the rules of the game.  these functions check
     * which moves are valid under the rules of the game, when someone
     * has won under the rules, etc.  These are what should be changed
     * if the actual game changes
     */ 

    // tests to see whether this is a valid move or not
    // In the game at present a player wins when he gets 3 pieces in a
    // row, column or diagonal.

		    

    // Called by the init function and when the game reinitialises.

    private void setValues() {
	cur = 0;
	level = 0;
	curLev = 0;

	pointsSet=0;
	pickedup=-1;
    }

    public void init() {
	setValues();


	points = new DNode[MAXLEV][3];
	for(int i=0;i<MAXLEV;i++) {
	    for(int j=0;j<3;j++) {
		points[i][j]=new DNode(0,0);
	    }
	}


	Graphics g = getGraphics();
	Dimension d = getSize() ;
	 

	colorarray= new Color[6];
	colorarray[0]=Color.black;
	colorarray[1]=Color.red;
	colorarray[2]=Color.green;
	colorarray[3]=Color.yellow;
	colorarray[4]=Color.blue;
	colorarray[5]=Color.orange;

	gameboard = new GameCanvas(this, size().width, size().height-bottomstrip);

	GridBagLayout gridbag = new GridBagLayout();
	setLayout(gridbag);
	GridBagConstraints c = new GridBagConstraints();
	
	c.fill = GridBagConstraints.BOTH;
	c.gridwidth = GridBagConstraints.REMAINDER;
	c.anchor = GridBagConstraints.NORTH;
	c.weighty = 0.0;
	gridbag.setConstraints(gameboard, c);
	add(gameboard);


	c.gridwidth = 1;

	c.gridy = GridBagConstraints.RELATIVE;
	c.gridx = 0;
	c.gridheight = 4;
	c.weightx=1.0;
	adviceField = new TextArea(5,20);
	adviceField.setEditable(false);
	adviceField.setText("Click above to place 3 points.");
	gridbag.setConstraints(adviceField, c);
	add(adviceField);


	c.gridx = 2;
	c.gridy = 1;
	c.weightx=0.0;
	c.anchor = GridBagConstraints.NORTHWEST;
	c.gridwidth = 1;
	c.gridheight = 1;
	c.fill = GridBagConstraints.NONE;
	clearButton = new Button("Clear");
	gridbag.setConstraints(clearButton, c);
	add(clearButton);

	c.gridy = 2;
	c.anchor = GridBagConstraints.NORTHWEST;
	c.gridwidth = 1;
	c.gridheight = 1;
	zoomInButton = new Button("Zoom In");
	gridbag.setConstraints(zoomInButton, c);
	add(zoomInButton);

	c.gridy = 3;
	c.anchor = GridBagConstraints.NORTHWEST;
	c.gridwidth = 1;
	c.gridheight = 1;
	zoomOutButton = new Button("Zoom Out");
	gridbag.setConstraints(zoomOutButton, c);
	add(zoomOutButton);


	c.gridy = 4;
	c.anchor = GridBagConstraints.NORTHWEST;
	c.gridwidth = 1;
	c.gridheight = 1;
	newLevelButton = new Button("New Level");
	gridbag.setConstraints(newLevelButton, c);
	add(newLevelButton);


	generator = new Random(System.currentTimeMillis());

	// draw the grid

	update(g);

    }

    /****************************************************************
     * The interface:  what happens when mouse events occur
     */

    // Handle the other actions

    public boolean action(Event e, Object arg) {
	Object target = e.target;
                  
	if (e.target == clearButton) { 
	    setValues();
	    points = new DNode[MAXLEV][3];
	    for(int i=0;i<MAXLEV;i++) {
		for(int j=0;j<3;j++) {
		    points[i][j]=new DNode(0,0);
		}
	    }
	    changed = true;
	    Graphics g = gameboard.getGraphics();
	    gameboard.update(g);
	}
	if (e.target == zoomInButton) { 
	    // hm. this doesn't work at the moment (wrong algorithm, not
	    // need to find min x, min y, max x, max y
	    if(curLev==MAXLEV-1) {
		adviceField.appendText("\nCannot zoom further. Try zooming out or starting over.");
	    }
	    else if(level==curLev) {
		adviceField.appendText("\nNeed to add a level before you can zoom in!");
	    }
	    else {
		curLev++;
		gameboard.dopoints();
		Graphics g = gameboard.getGraphics();
		changed = true;
		gameboard.update(g);
	    }
	}
	if (e.target == zoomOutButton) { 
	    if(curLev>0) {
		curLev--;}
	    else {
		adviceField.append("\nCannot zoom out any further.");}
		Graphics g = gameboard.getGraphics();
		changed = true;
		gameboard.update(g);
	}
	if (e.target == newLevelButton) { 
	    if(havenotputupmessage) {
		adviceField.append("\nYou can zoom in to get a closer look.\nThe picture will focus on the biggest \ninscribed triangle");
		havenotputupmessage=false;
	    }
	    level++;
	    Graphics g = gameboard.getGraphics();
	    changed = true;
	    gameboard.update(g);
	}
	
	return false;
    }


    /********************************************************************
     * Into the painting methods.....
     *
     */

    public void paint(Graphics g) {
	//	meFirstLabel.reshape(10, size().height-65, 120, 25);
	//	meFirst.reshape(130, size().height-65,80, 25); 
	//	againButton.reshape(235, size().height-65, 50, 30);

    }

    /*    public synchronized void update(Graphics g) {
	Dimension d = getSize() ;
	 if ( (offGraphics == null)
	      || (d.width != offDimension.width)
	      || (d.height != offDimension.height) ) {
	     offDimension = d;
	     offImage = createImage(d.width, d.height);
	     offGraphics = offImage.getGraphics();
	 }

	 //Erase the previous image.
	 //	  offGraphics.setColor(getBackground());
	 offGraphics.setColor(Color.white);
	  offGraphics.fillRect(0, 0, d.width, d.height);
	 offGraphics.setColor(Color.black);
	 
	 //	 gameboard.paint(offGraphics);

	 g.drawImage(offImage, 0, 0, null);
	 repaint();
	 }*/
    
class GameCanvas extends Canvas  implements MouseListener,
					      MouseMotionListener{
    
    int width;
    int height;
    Container pappy;
    boolean trueSizeKnown = false;
    Dimension minSize;
    Graphics offGraphics;
    Graphics g;



    GameCanvas(Container parent, int w, int h) {
	width=w;
	height=h;
	addMouseListener(this);
        addMouseMotionListener(this);

        pappy = parent;

        minSize = new Dimension(width,height);
        setSize(width, height);

        pappy.validate();
    }
    

    public Dimension preferredSize() {
	return minimumSize();
    }
    
    public synchronized Dimension minimumSize() {
	return minSize;
    }
   
    public void paint(Graphics g) {
	Dimension d = getSize();
	g.setColor(Color.white);
	g.fillRect(0, 0, d.width, d.height);
	g.setColor(Color.black);

	//	drawSelf(g);
    }

    public void dopoints() {
	double minx=100000.0;
	double miny = 100000.0;
	double maxx = -1.0;
	double maxy = -1.0;
	
	for(int i = 0;i<3;i++) {
	    if(points[curLev][i]==null) {
		//		System.err.println("freak out "+curLev);
	    }
	    if (minx>=points[curLev][i].x) {
		minx=points[curLev][i].x;
	    }
	    if( maxx<=points[curLev][i].x) {
		maxx=points[curLev][i].x;
	    }
	    if(miny>=points[curLev][i].y) {
		miny=points[curLev][i].y;
	    }
	    if( maxy<=points[curLev][i].y) {
		maxy=points[curLev][i].y;
	    }
	}
	// translate everything so that minx and miny are 0, 0
	for (int i=0;i<3;i++) {
	    points[curLev][i].x-=(minx);
	}
	minx=0;
	maxx-=minx;
	
	for (int i=0;i<3;i++) {
	    points[curLev][i].y-=(miny);
	}
	miny=0;
	maxy-=miny;
	
	DNode center = new DNode(0,0);
	for(int i = 0;i<3;i++) {
	    center.x+=points[curLev][i].x;
	    center.y+=points[curLev][i].y;
	}

	center.x/=3;
	center.y/=3;

	Dimension d = gameboard.getSize();
	double scale;
	
	double dists[] = new double[3];
	
	double maxdist = -1;
	for(int i=0;i<3;i++) {
	    dists[i]=dist(center, points[curLev][i]);
	    if(dists[i]>maxdist) 
		maxdist=dists[i];
	}
	double radius;
	
	if(d.width<d.height) {
	    radius = (d.width-3)/2;
	}
	else {
	    radius = (d.height-3)/2;
	}
	
	scale = radius/maxdist;
	
	for(int j=0;j<3;j++) {
	    points[curLev][j] = alongLine(scale*dists[j], center, points[curLev][j], points[curLev][j]);
	}

	// and translate over
	for(int j=0;j<3;j++) {
	    points[curLev][j].x+=(d.width/2-center.x);
	    points[curLev][j].y+=(d.height/2-center.y);
	}
    }
	

    public void update(Graphics g) {

	System.err.println("update");
	if(changed == true) {
	    Dimension d = getSize() ;
	    
	    if ( (offGraphics == null)
		 || (d.width != offDimension.width)
		 || (d.height != offDimension.height) ) {
		offDimension = d;
		offImage = createImage(d.width, d.height);
		offGraphics = offImage.getGraphics();
	    }
	    
	    //Erase the previous image.
	    //	  offGraphics.setColor(getBackground());
	    offGraphics.setColor(Color.white);
	    offGraphics.fillRect(0, 0, d.width, d.height);
	    offGraphics.setColor(Color.black);
	    
	    drawSelf(offGraphics);
	    
	    if ((pointsSet==3) && (pickedup==-1)) {
		System.err.println("drawing");
		constructStuff(offGraphics, curLev);

	    }
	    if(g==null) {
		g=this.getGraphics();
	    }
	    g.drawImage(offImage, 0, 0, null);
	    changed=false;
	    //	    repaint();
	}
    }

    public boolean isInPoint(int x, int y, DNode n) {

	return((Math.abs(n.x-x)<=pieceSize/2) && (Math.abs(n.y-y)<=pieceSize/2));
    }


    public DNode alongLine(double x, DNode A, DNode B, DNode C) {

	// translate so that start coordinate is 0.  we'll translate back at
	// the end.

	DNode D = new DNode(0.0,0.0);
	DNode E = new DNode(0.0, 0.0);
	B.x-=A.x;
	B.y-=A.y;

	double m = B.y/B.x;

	D.x = Math.sqrt((x*x)/(1+m*m));
	D.y = m*D.x;

	E.x = -Math.sqrt((x*x)/(1+m*m));
	E.y= m*E.x;

	// now we need to check to see whether we want the positive or negative
	// square root.......

	B.x+=A.x;
	B.y+=A.y;
	D.x+=A.x;
	D.y+=A.y;

	E.x+=A.x;
	E.y+=A.y;
	
	if(dist(D,C)<dist(E,C))
	    return D;
	else return E;

    }

    double dist(DNode A, DNode B) {
	return Math.sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
    }

    public DNode bisectAngle(DNode A, DNode B, DNode C) {

	DNode D = alongLine(dist(A,B), B, C, A);
	return alongLine(0.5*dist(A,D), A,D, B);

    }

    public DNode lineIntersection(DNode A1, DNode A2, DNode B1, DNode B2) {

	DNode retnode= new DNode(-1, -1);

	// just need to put into y = mx+b form;
	double m1= (A2.y-A1.y)/(A2.x - A1.x);
	double b1= (A1.y- m1*A1.x);


	double m2= (B2.y-B1.y)/(B2.x - B1.x);
	double b2= (B1.y- m2*B1.x);


	if (m1-m2==0)
	    return retnode;
	else {
	    retnode.x = (b1-b2)/(m2-m1);
	    retnode.y = m1*retnode.x +b1;
	}
	
	return retnode;

    }

    public DNode findPerp(DNode A1, DNode A2, DNode P) {

	double m= (A2.y-A1.y)/(A2.x - A1.x);
	double b= (A1.y- m*A1.x);

	DNode erp = new DNode(-1,-1);

	erp.x=(P.x-b*m+P.y*m)/(1+m*m);
	erp.y = m*erp.x + b;
	return erp;
    }


    public void constructStuff(Graphics grr, int curLev) 
    {
	// first thing is to get some angle bisectors

	// what will be returned is a second point on the angle bisector
	// this will be the point along A, B which is dist away from A
	// and closest to C
	
	// at the moment bisectAngle doesn't work for angles >90degrees.  So
	// we have to make sure that we pick two angles which are less than that.
	// can do this by taking the angle between the longest side and the shortest side
	// and the longest side and the second longest side.

	DNode spoints[] = new DNode[3];

	spoints[0] = points[curLev][0];
	spoints[1] = points[curLev][1];
	spoints[2] = points[curLev][2];

	// this used to be done recursively in recurseConstruct....
	DNode bis1, bis2, inter;
	DNode newPoints[] = new DNode[3];
	int radius;

	double side1, side2, side3;
	int A, B, C, max, min;

	int lev = 0;
	while(lev<=level-curLev) {
	    side1 = dist(spoints[0], spoints[1]);
	    side2 = dist(spoints[0], spoints[2]);
	    side3 = dist(spoints[1], spoints[2]);
	    
	    drawAPiece(spoints[0], grr);
	    drawAPiece(spoints[1], grr);
	    drawAPiece(spoints[2], grr);

	    grr.setColor(colorarray[(curLev+lev)%NUMCOLORS]);
	    grr.drawLine((int)spoints[0].x, (int)spoints[0].y, (int)spoints[1].x, (int)spoints[1].y);
	    grr.drawLine((int)spoints[1].x, (int)spoints[1].y, (int)spoints[2].x, (int)spoints[2].y);
	    grr.drawLine((int)spoints[0].x, (int)spoints[0].y, (int)spoints[2].x, (int)spoints[2].y);

	    
	    if(side1>=side2) {
		if(side2>=side3) {
		    A=0;
		    B=1;
		    C=2;
		    max=1;
		    min=3;
		    
		}
		else if(side1>=side3) {
		    A=1;
		    B=0;
		    C=2;
		    max = 1;
		    min = 2;
		}
		else {
		    A=1;
		    B=2;
		    C=0;
		    max=3;
		    min=2;
		}
	    }
	    else {
		if(side1>=side3) {
		    A=0;
		    B=2;
		    C=1;
		    max = 2;
		    min = 3;
		}
		else if(side2>=side3) {
		A=2;
		B=0;
		C=1;
		max = 2;
		min =1 ;
		}
		else {
		    A=2;
		    B=1;
		    C=0;
		    max = 3;
		    min=1;
		}
	    }
	    
	    // AB will be longest, AC second longest, BC shortest
	    

	bis1=new DNode(0,0);
	bis1.Copy(bisectAngle(spoints[B], spoints[A],spoints[C]));
	//	grr.drawOval((int)bis1.x, (int)bis1.y, pieceSize, pieceSize);
	//	grr.drawLine((int)points[A].x, (int)points[A].y, (int)bis1.x, (int)bis1.y);
	bis2  = new DNode(0,0);
	bis2.Copy(bisectAngle(spoints[A], spoints[B],spoints[C]));
	//	grr.drawOval((int)bis2.x, (int)bis2.y, pieceSize, pieceSize);
	//       	grr.drawLine((int)points[B].x, (int)points[B].y, (int)bis2.x, (int)bis2.y);

	inter = new DNode(0,0); 
	inter.Copy(lineIntersection(spoints[A], bis1, spoints[B], bis2));
	//	System.err.println(inter.x+" "+inter.y);
	//	grr.drawOval((int)(inter.x-pieceSize/2), (int)(inter.y-pieceSize/2), pieceSize, pieceSize);

	newPoints[0] = new DNode(0,0);
	newPoints[0].Copy(findPerp(spoints[B], spoints[A], inter));

	//	grr.drawOval((int)(newPoints[0].x-pieceSize/2), (int)(newPoints[0].y-pieceSize/2), pieceSize, pieceSize);

	newPoints[1] = new DNode(0,0);
	newPoints[1].Copy(findPerp(spoints[B], spoints[C], inter));

	//	grr.drawOval((int)(newPoints[1].x-pieceSize/2), (int)(newPoints[1].y-pieceSize/2), pieceSize, pieceSize);

	newPoints[2] = new DNode(0,0);
	newPoints[2].Copy(findPerp(spoints[C], spoints[A], inter));

	//	grr.drawOval((int)(newPoints[2].x-pieceSize/2), (int)(newPoints[2].y-pieceSize/2), pieceSize, pieceSize);
	//	if (curLev+lev<havedone    (curLev+1==level) && (lev==1)  && 
	//	    (curLev+1<MAXLEV)) {
	    //	    notdoneit = true;
	if((curLev+lev+1<MAXLEV) && (lev>=0)) {
	    for(int j=0;j<3;j++) {
		points[curLev+lev+1][j].Copy(newPoints[j]);
	    }
	}
	radius = (int)(dist(inter, newPoints[1]));
	
	grr.drawOval((int)(inter.x-radius), (int)(inter.y-radius), 2*radius, 2*radius);
	drawAPiece(newPoints[0], grr);
	drawAPiece(newPoints[1], grr);
	drawAPiece(newPoints[2], grr);

	spoints[0]=newPoints[0];
	spoints[1]=newPoints[1];
	spoints[2]=newPoints[2];
	lev++;
	}
    }



    public void drawSelf (Graphics g) {

	

	//	for(int i = 0;i<MAXLEV; i++) {
	    for(int j=0;j<pointsSet;j++) {
		    drawAPiece(points[curLev][j], g);
		}
	    if(pointsSet>1) {
		g.drawLine((int)points[curLev][0].x, (int)points[curLev][0].y, 
			   (int)points[curLev][1].x, (int)points[curLev][1].y);
		if((pointsSet>2)) {
		    g.drawLine((int)points[curLev][0].x, (int)points[curLev][0].y, 
			       (int)points[curLev][2].x, (int)points[curLev][2].y);

		    g.drawLine((int)points[curLev][2].x, (int)points[curLev][2].y, 
			       (int)points[curLev][1].x, (int)points[curLev][1].y);
	
		}
	    }
		    
    }
		


    public void drawAPiece(DNode n, Graphics g) {
	Node newn = n.DNodeToNode();
	g.setColor(Color.red);
        g.fillOval((int)n.x-pieceSize/2, (int)n.y-pieceSize/2, pieceSize, pieceSize);
	g.setColor(Color.black);
        g.drawOval((int)n.x-pieceSize/2, (int)n.y-pieceSize/2, pieceSize, pieceSize);
    }
		  

    public void mouseDown(MouseEvent e) {
		     
    }
    
    public void mouseDragged(MouseEvent e) {
	if((pickedup!=-1) && (0<e.getX()) && (e.getX()<width) && 
	   (0<e.getY()) && (e.getY()<height)) {
	    points[curLev][pickedup].x=e.getX();
	    points[curLev][pickedup].y=e.getY();
	
	    if(g==null) {
		g = this.getGraphics();}
	    changed = true;
	    update(g);
	}
    }


    public void mouseClicked(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();

	if (pointsSet <3) {
	    points[curLev][pointsSet]= new DNode(x,y);
	    pointsSet++;
	    if(pointsSet==3) {
		adviceField.appendText("\nClick New Level to construct the inscribed \n triangle and its inscribed circle.");
	    }
	}
	//	repaint();
	if (g==null) {
	    g= this.getGraphics();
	}
	changed = true;
	update(g);
    }
    
    public void mouseExited(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
	pickedup=-1;
	changed = true;
	if(g==null) {
	    g=this.getGraphics();
	}
	update(g);
    }

    public void mousePressed(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();

	if(pointsSet==3) {
	    if(isInPoint(x,y,points[curLev][0])) {
		pickedup=0;}
	    else if (isInPoint(x,y,points[curLev][1])) {
		pickedup=1;}
	    else if (isInPoint(x,y,points[curLev][2])) {
		pickedup=2;}
	}

    }

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


}

}

