// NeSto, 24 juin 2006
// Deformation d'image :
// Calcul des coordonnees dans un repere orthogonal 
// a partir de la position dans un quadrilatere quelconque
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class DeformApplet extends Applet
implements MouseListener, MouseMotionListener {
	
	/** Size of the applet window */
	int appletWidth, appletHeight;
	
	/** Mouse coordinates */
	int mx, my; 
	
	/** State of the mouse button */
	boolean isButtonPressed = false;
	
	/** Corner identifiers */
	final static int A = 1, B = 2,  C = 3, D = 4, P = 5;

	/** Coordinates of the corners */
	int xa, ya, // top left
		xb, yb, // top right
		xc, yc, // bottom right
		xd, yd, // bottom left
		xp, yp; // point being drawn
	
	/** Currently nearest corner */
	int currentCorner;
	
	/** Size of the zone the user can play with */
	int mapWidth, mapHeight;
	
	/** Position in display */
	int computedX, computedY;
	
	/** Size of the zone the display will be showed */
	final int dispWidth = 200, dispHeight = 200;
	final int spacing = 10;
	
	public void init() {
		appletWidth = getSize().width;
		appletHeight = getSize().height;

		mapWidth = appletWidth - dispWidth - spacing;
		mapHeight = appletHeight;
		
		addMouseListener( this );
		addMouseMotionListener( this );
		
		initializeBorder();

		//showStatus("Approchez-vous d'un coin pour le deplacer");
	}
	
	/** draws initial border */
	private void initializeBorder() {
		int margin = 20;
		xa = margin;
		ya = margin;
		xb = mapWidth - margin - 20;
		yb = margin + 10;
		xd = margin;
		yd = mapHeight - margin;
		xc = mapWidth - margin - 25;
		yc = mapHeight - margin - 12;
		xp = mapWidth/2;
		yp = mapWidth/2;
		currentCorner = P;
		mx = mapWidth/2;
		my = mapHeight/2;
	}
	
	/** called when the pointer enters the applet's rectangular area */
	public void mouseEntered( MouseEvent e ) {}
	
	/** called when the pointer leaves the applet's rectangular area */
	public void mouseExited( MouseEvent e ) {}
	
	/**	 called after a press and release of a mouse button
		* with no motion in between
		* (If the user presses, drags, and then releases, there will be
		   * no click event generated.)
		*/
	public void mouseClicked( MouseEvent e ) {
		mousePressed(e);
		mouseDragged(e);
		mouseReleased(e);
		e.consume();
	}
	
	/**  called after a button is pressed down */
	public void mousePressed( MouseEvent e ) {
		isButtonPressed = true;
		repaint();
		e.consume();
	}
	
	/** called after a button is released */
	public void mouseReleased( MouseEvent e ) {
		isButtonPressed = false;
		repaint();
		e.consume();
	}
	
	/** called during motion when no buttons are down */
	public void mouseMoved( MouseEvent e ) {
		mx = e.getX();
		my = e.getY();
		if (!isButtonPressed) {
			updateCurrentCorner();
		}
		repaint();
		e.consume();
	}
	
	/** Computes which corner is nearest */
	private void updateCurrentCorner() {
		double distanceA = Math.sqrt((mx-xa)*(mx-xa) + (my-ya)*(my-ya));
		double distanceB = Math.sqrt((mx-xb)*(mx-xb) + (my-yb)*(my-yb));
		double distanceC = Math.sqrt((mx-xc)*(mx-xc) + (my-yc)*(my-yc));
		double distanceD = Math.sqrt((mx-xd)*(mx-xd) + (my-yd)*(my-yd));
		double distanceP = Math.sqrt((mx-xp)*(mx-xp) + (my-yp)*(my-yp));
		
		if (distanceA <= distanceB 
				&& distanceA <= distanceC 
				&& distanceA <= distanceD
				&& distanceA <= distanceP) {
			currentCorner = A;
		} else if (distanceB <= distanceA 
				&& distanceB <= distanceC 
				&& distanceB <= distanceD
				&& distanceB <= distanceP) {
			currentCorner = B;
		} else if (distanceC <= distanceA
				&& distanceC <= distanceB 
				&& distanceC <= distanceD
				&& distanceC <= distanceP) {
			currentCorner = C;
		} else if (distanceD <= distanceA
				&& distanceD <= distanceB
				&& distanceD <= distanceC
				&& distanceD <= distanceP) {
			currentCorner = D;
		} else {
			currentCorner = P;
		}
	}
	
	/** called during motion with buttons down */
	public void mouseDragged( MouseEvent e ) {
		if (e.getX() > mapWidth) {
			e.consume();
			return;
		}
		mx = Math.min(Math.max(e.getX(), 0), mapWidth);
		my = Math.min(Math.max(e.getY(), 0), mapHeight);
		
		switch (currentCorner) {
			case A:
				xa = mx;
				ya = my;
				break;
			case B:
				xb = mx;
				yb = my;
				break;
			case C:
				xc = mx;
				yc = my;
				break;
			case D:
				xd = mx;
				yd = my;
				break;
			case P:
			default:
				xp = mx;
				yp = my;
				break;
		}
		repaint();
		e.consume();
	}
	
	/** Paints the applet's contents */
	public void paint( Graphics g ) {
		
		if ( isButtonPressed ) {
			g.setColor( Color.green );
		}
		else {
			g.setColor( Color.red );
		}
		
		g.drawLine(xa,ya,xb,yb);
		g.drawLine(xb,yb,xc,yc);
		g.drawLine(xc,yc,xd,yd);
		g.drawLine(xd,yd,xa,ya);
		g.drawLine(2*xa/3+xd/3,2*ya/3+yd/3,2*xb/3+xc/3,2*yb/3+yc/3);
		g.drawLine(xa/3+2*xd/3,ya/3+2*yd/3,xb/3+2*xc/3,yb/3+2*yc/3);
		g.drawLine(2*xa/3+xb/3,2*ya/3+yb/3,2*xd/3+xc/3,2*yd/3+yc/3);
		g.drawLine(xa/3+2*xb/3,ya/3+2*yb/3,xd/3+2*xc/3,yd/3+2*yc/3);
		g.setColor(Color.black);
		g.drawLine(xp - 10, yp, xp + 10, yp);
		g.drawLine(xp, yp - 10, xp, yp + 10);
		
		g.setColor(Color.blue);
		drawLineInDisplay(g, 0, 0, 0, dispHeight);
		drawLineInDisplay(g, 0, 0, dispWidth, 0);
		drawLineInDisplay(g, 0, dispHeight, 
				dispWidth, dispHeight);
		drawLineInDisplay(g, dispWidth, 0, 
				dispWidth, dispHeight);
		drawLineInDisplay(g, 0, dispHeight/3, 
				dispWidth, dispHeight/3);
		drawLineInDisplay(g, dispWidth/3, 0, 
				dispWidth/3, dispHeight);
		drawLineInDisplay(g, 0, 2*dispHeight/3, 
				dispWidth, 2*dispHeight/3);
		drawLineInDisplay(g, 2*dispWidth/3, 0, 
				2*dispWidth/3, dispHeight);
		
		computePosition();
		
		g.setColor(Color.black);
		drawLineInDisplay(g, computedX - 10, computedY,
				computedX + 10, computedY);
		drawLineInDisplay(g, computedX, computedY - 10,
						computedX, computedY + 10);
	}

	private void computePosition() {
		double a = (xc-xb)*(yd-ya) - (xd-xa)*(yc-yb);
		double b = xb*(yd-ya) + (xc-xb)*ya - (xd-xa)*yb - xa*(yc-yb)
		+ (ya-yb+yc-yd)*xp - (xa-xb+xc-xd)*yp;
		double c = xb*ya - xa*yb + (yb-ya)*xp - (xb-xa)*yp;
		double delta = b*b - 4*a*c;
		double mu = (-b + Math.sqrt(delta))/(2*a);
		double lambda = (xp - xa - mu*(xd-xa))/(xb - xa + mu*(xa-xb+xc-xd));
		computedX = (int) (lambda * (double) dispWidth);
		computedY = (int) (mu * (double) dispHeight);
	}

	private void drawLineInDisplay(Graphics g, int i, int j, int k, int l) {
		g.drawLine(mapWidth + spacing - 2 + i, j, mapWidth + spacing - 2 + k, l);
	}
}

