//========================================================
// Transforms - a simple applet demonstrating transformations
// Written by Chris DeCoro - cdecoro@cat.nyu.edu
// Sept. 9, 2002
//========================================================

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import Geometry.*;

public class Transforms extends TransformableApplet
{
	public int randChar()
	{
		return (int)(Math.random()*255.0);
	}
	
	public void render(Graphics g) 
	{
		if (damage) 
		{
			setContext(g);

			transform( (new Matrix3x3()) );

			begin(RENDER_POLYGON);
			color(255, 255, 255);
			vertex(0, 0);
			vertex(bounds().width, 0);
			vertex(bounds().width, bounds().height);
			vertex(0, bounds().height);
			end();
	
			double hw=bounds().width/2, hh=bounds().height/2;
			transform( (new Matrix3x3())
					.scale(scaleX,-1*scaleY)
					.rotate(theta)
					.translate(translateX+hw,translateY+hh) );

			//Draw the axis lines
			/*
			begin(RENDER_LINES);
			color(127, 127, 0);
			vertex(0, 0);
			vertex(300, 0);
			
			color(127, 0, 127);
			vertex(0, 0);
			vertex(0, 300);
			end();
			*/

			//Draw the two triangles for the eyes
			begin(RENDER_TRIANGLE);
			color(randChar(), randChar(), randChar());
			vertex(-50, 50);
			vertex(-20, 50);
			vertex(-35, 80);
				
			color(randChar(), randChar(), randChar());
			vertex(50, 50);
			vertex(20, 50);
			vertex(35, 80);
			end();
			
			//Draw the square for the nose
			begin(RENDER_POLYGON);
			color(255, 0, 0);
			vertex(-15,  15);
			vertex(-15, -15);
			vertex( 15, -15);
			vertex( 15,  15);
			end();

			//Draw the line strip for the mouth
			begin(RENDER_LINE_STRIP);
			color(127, 64, 64);
			vertex(-45, -30);
			vertex(-25, -40);
			vertex(+25, -40);
			vertex(+45, -30);
			end();
		
			//Draw the border of the face
			begin(RENDER_OPEN_POLYGON);
			vertex(-50, 100);
			vertex(-60, 90);
			vertex(-60, -50);
			vertex(-50, -60);

			vertex(+50, -60);
			vertex(+60, -50);
			vertex(+60, 90);
			vertex(+50, 100);

			end();
			
			setContext(null);
		}
	}



	public boolean mouseDown(Event e, int x, int y) 
	{		 
		boolean leftButton, centerButton, rightButton;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;
	  
		downX = x;
		downY = y;

		oldTranslateX = translateX;
		oldTranslateY = translateY;

		oldScaleX = scaleX;
		oldScaleY = scaleY;

		oldTheta = theta;

		setDamage();
		return true;
	}

	public boolean mouseDrag(Event e, int x, int y) 
	{ 
		boolean leftButton, centerButton, rightButton;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;

		if( downX == -1 )
			return true;

		if(rightButton)
		{
			System.out.println("Right button");
			scaleX = oldScaleX + (x - downX)/10.0;
			scaleY = oldScaleY + -(y - downY)/10.0;

			//if( Math.abs(scaleX) < 0.1 )			scaleX = 0.1;
				//	scaleX = ( scaleX < 0 ? -1 : 1 );
			//if( Math.abs(scaleY) < 0.1 )			scaleY = 0.1;
				//scaleY = ( scaleY < 0 ? -1 : 1 );
		}
		else if(centerButton)
		{
			theta = oldTheta + ((x - downX))/2;
		}
		else if(leftButton)
		{
			translateX = oldTranslateX + (x - downX);
			translateY = oldTranslateY + (y - downY);
		}
		
		setDamage();
		return true; 
	}

	public boolean mouseUp(Event e, int x, int y) 
	{		 
		boolean leftButton, centerButton, rightButton;
		centerButton = ((e.modifiers & e.ALT_MASK) > 0);
		rightButton = ((e.modifiers & e.META_MASK) > 0);
		leftButton = !centerButton && !rightButton;
	   
		if( downX == -1 )
			return true;

		downX = -1;
		downY = -1;
		setDamage();
		return true;
	}

	int downX=-1, downY=-1;
	double translateX=0, translateY=0;
	double oldTranslateX=0, oldTranslateY=0;

	double scaleX=1, scaleY=1;
	double oldScaleX=1, oldScaleY=1;

	double theta=0, oldTheta=0;
}

class TransformableApplet extends GenericApplet
{
	int x = 100, y = 100;

	void setContext(Graphics g)
	{
		graphics = g;
	}
	
	void begin(int shapeType)
	{
		if( renderState != RENDER_NONE )
		{
			throw new RuntimeException("Cannot call begin() twice without intervening end()");
		}

		if( shapeType < RENDER_NONE || shapeType > RENDER_LAST )
		{
			throw new RuntimeException("Attempted to draw an invalid primitive");
		}

		renderState = shapeType;
		sentVertices = 0;
		polygon = new Polygon();
	}

	void end()
	{
		if( renderState == RENDER_NONE )
		{
			throw new RuntimeException("Cannot call end() without begin()");
		}
		else if( renderState == RENDER_POLYGON )
		{
			graphics.fillPolygon(polygon);
		}
		else if( renderState == RENDER_OPEN_POLYGON )
		{
			graphics.drawPolygon(polygon);
		}

		renderState = RENDER_NONE;
	}

	void color(int r, int g, int b)
	{
		graphics.setColor(new Color(r,g,b));
	}

	void vertex(int x, int y)
	{
		Vector3 worldCoord, screenCoord;
		worldCoord = new Vector3(x, y, 1);
		screenCoord = transformMatrix.mul(worldCoord);
		
		int screenX, screenY;
		screenX = (int)screenCoord.array()[0];
		screenY = (int)screenCoord.array()[1];

		if( renderState == RENDER_NONE )
		{
			throw new RuntimeException("Must call begin() before sending a vertex");
		}
		
		else if( renderState == RENDER_LINES )
		{
			if( sentVertices == 0 )
			{
				lineX = screenX;
				lineY = screenY;
				sentVertices++;
			}	
			else
			{
				System.out.println(lineX + " " + lineY +" " + screenX + " " + screenY);
				graphics.drawLine(lineX, lineY, screenX, screenY);
				sentVertices = 0;
			}
		}
		else if( renderState == RENDER_LINE_STRIP )
		{
			if( sentVertices > 0 )
				graphics.drawLine(lineX, lineY, screenX, screenY);

			lineX = screenX;
			lineY = screenY;
			sentVertices++;			
		}
		else if( renderState == RENDER_POLYGON  ||
		    renderState == RENDER_OPEN_POLYGON ||
		    renderState == RENDER_TRIANGLE ||
		    renderState == RENDER_OPEN_TRIANGLE )
		{
			polygon.addPoint(screenX, screenY);
			sentVertices++;
		}

		if( renderState == RENDER_TRIANGLE && sentVertices == 3 )
		{
			graphics.fillPolygon(polygon);
			polygon = new Polygon();
			sentVertices = 0;
		}
		else if( renderState == RENDER_OPEN_TRIANGLE && sentVertices == 3 )
		{
			graphics.drawPolygon(polygon);
			polygon = new Polygon();
			sentVertices = 0;
		}
	}

	void transform(Matrix3x3 mat)
	{
		transformMatrix = mat;
	}
	
	void setDamage()
	{
		damage = true;
	}

	void printMatrix(Matrix3x3 matr)
	{
		double[][] mat = matr.array();
		
		System.out.println("| " + mat[0][0] + " " + mat[0][1] + " " + mat[0][2] + " |");
		System.out.println("| " + mat[1][0] + " " + mat[1][1] + " " + mat[1][2] + " |");
		System.out.println("| " + mat[2][0] + " " + mat[2][1] + " " + mat[2][2] + " |");
	}
	
	static int RENDER_NONE=0;
	static int RENDER_LINES=1;
	static int RENDER_LINE_STRIP=2;
	static int RENDER_POLYGON=3;
	static int RENDER_OPEN_POLYGON=4;
	static int RENDER_TRIANGLE=5;
	static int RENDER_OPEN_TRIANGLE=6;
	static int RENDER_LAST=RENDER_OPEN_TRIANGLE;
	

	//Current transform matrix, which takes world coord to screen coord
	private Matrix3x3 transformMatrix = new Matrix3x3();

	//Current Render state; which primitive, if any, is being rendered
	private int renderState = RENDER_NONE;

	//Storage for any polygon-based primitive being submitted
	private Polygon polygon = new Polygon();

	//Storage for the last coordinate for a line-based primitive
	private int lineX= 0;
	private int lineY= 0;

	//Counts the number of vertices sent for the current primitive
	private int sentVertices = 0;

	//Current rendering context
	private Graphics graphics = null;

}

class GenericApplet extends java.applet.Applet implements Runnable
{
   public boolean damage = true;        // you can force a render
   public void render(Graphics g) { }   // you can define how to render

   private Image image = null;
   private Graphics buffer = null;
   private Thread t;
   private Rectangle r = new Rectangle(0, 0, 0, 0);

   public void start() { if (t == null) { t = new Thread(this); t.start(); } }
   public void stop()  { if (t != null) { t.stop(); t = null; } }
   public void run()   { try { while (true) { repaint(); t.sleep(30); } }
                             catch(InterruptedException e){}; }

   public void update(Graphics g) {
      if (r.width != bounds().width || r.height != bounds().height) {
         image = createImage(bounds().width, bounds().height);
         buffer = image.getGraphics();
         r = bounds();
         damage = true;
      }
      render(buffer);
      damage = false;
      if (image != null)
         g.drawImage(image,0,0,this);
   }
}



