/*
 * Applet : Pavages par quadrilatères
 */

/*
 * Remarque : il faudrait optimiser la partie Magnet qui fait des
 * calculs en trop
 */

// Bibilothèques utilisées 

/*
 * Gestion de l'interface utlisateur : Swing ou AWT ?
 * 1. Je déconseille Swing pour l'instant.
 * 2. Du coup, comme les boutons, menus et autres dépendent de l'OS
 *    je préfère faire mes propres composants : en fait je ne dérive
 *    rien de la classe Component, je fais tout à la main.
 */

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/*
 * Classe principale (tout est dedans)
 */

public class Quadri 
    extends Base
       // J'ai écrit la classe Base qui regroupe des fonctionnalités
       // communes à mes applets, voir description.txt
    implements 
	// ActionListener,
	MouseListener,
	MouseMotionListener,
	KeyListener
       // signifie qu'il réagit aux actions suivantes 
       // MouseListener       : ... -> clicks souris et entrée/sortie
       //                              du curseur dans la zone d'un Component
       // MouseMotionListener : ... -> mouvements de la souris dans la
       //                              zone d'un component
       // KeyListener      : clavier
{
    /*
     *                             Champs
     */

    /*
     * Je mets ici les constantes utilisées dans le placement des 
     * zones de l'applet
     */
    public static int
	XOFFSET=20, // position de l'aire de jeu (Upper Left corner)
	YOFFSET=20, // idem
	// Ces deux offsets servent aussi à déterminer la distance au
	// bord de certains objets
	PIECES_AREA_WIDTH=100, // width (hors bord) de la zone du
                               // panier et des deux boutons
	BUTTON_SIZE=40, // Taille des boutons (carrés)
	SM_BUT_SIZE=16;

    // Taille des pièces dans le panier, et également taille par
    //  défaut des pièces posées
    public static double 
	PIECES_AREA_SCALE=40;

    public Rectangle bounds; // mémorise la taille de la zone d'affichage

    public int x1,x2,y1,y2;  // mémorise les coords de la zone de pose
                             // des pièces

    public int mouseX,mouseY; // mémorise les coords du curseur :
                              // utilisé dans keyListener

    // Doit-on dessiner les décorations ?
    public boolean decorations;

    /*
     * Magnet
     */

    // Dit si le mode magnet est en route
    public boolean magnet;
    // Dit si on tient compte des décorations dans la magnet
    //    public boolean mustFitDecorations;
    // Dit si on ne permet la pose que des pièces magnétisées
    public boolean dropOnlyStuck;
    // Dit si on ne permet la pose que des pièces dont tous les bords
    //     sont bons
    public boolean checkAll;
    // DIRTY TRICK : pour améliorer un problème de daltonisme
    public boolean doubleArcSize;



    // Dit si l'éventuelle pièce portée est collée
    public boolean stuck;
    // Dit s'il y a un overlap
    public boolean noOverlap; // PAS IMPLEMENTE
    // Dit si tout a été vérifié
    public boolean allChecked;


    // Seuil pour la magnet
    public static double
	SIDE_DIST_TRESH=0.2;

    // Mémorise des coordonnées MATH pour la dépose d'une pièce collée
    public double memX,memY;

    public Color myBlue,COL1,COL2;

    //    public Image magnetPic;
    public Image decorPic;

    /*
     * Zone de pose
     */

    // Données quand on fait glisser la zone de pose
    public boolean draggingDropZone;
    public int clickedX,clickedY;
    public double savedoffx,savedoffy;

    // Décalage et échelle mathématiques de la zone de pose
    public double
	offx,
	offy,
	scale;
    public int scaleIndex;
    public static int
	SCALE_INDEX_MIN=-8,
	SCALE_INDEX_MAX=8;
    public static double
	SCALE_LOG_STEP=0.4;

    // Phase 1 : conception du quadrilatère
    // Phase 2 : pavage avec icelui
    public int phase;
    public int putVertices;

    // Sert à corriger un problème : quand on clique avec un léger
    // déplacement, le systeme considère qu'on fait un "glissé".
    // On pêut peut-être mieux faire ?
    public boolean dragging;

    /*
    public static double[][]
	POL={ {-1,-1},{2,-1},{3,2},{-1.5,1}
             };
    */
    // Le polygône : 4 sommets, chacun étant une liste de 2 coords
    public static double[][] POL; //={{0,0},{0,0},{0,0},{0,0}};

    // Symboles combinatoires sur les côtés
    // pour "matcher" deux côtés doivent suivre les règles suivantes :
    // - pièces d'angles opposés
    // - même côté

    /*
     * Pièce tenue
     */
    public int holdedX;
    public int holdedY;
    public int holdedAngle;

    /*
     * Le polygône du panier (coordonnées écran)
     */
    public Polygon P0;

    /*
     * Table des cos et sin des multiples de 2*Pi/2
     * (hérité d'une version où c'était la table des multiples de 2*Pi/10)
     */
    public double[] COS;
    public double[] SIN;

    /*
     * Géométrie des boutons
     */
    Rectangle but1,but2,but3,but4,but5;

    // ...

    public boolean holdingPiece;

    public Vector droppedPieces;

    /*
     *                          Sous-classes
     */

    /*
     * Pièces posées
     */
    class Tile 
    {
	double x,y;
	int angle;
    }

    /* 
     * Côté (orienté)
     */
    class Side
    {
	double x1,x2,y1,y2;
    }



    /*
     *                           Méthodes
     */

    /*
     * Crée un polygône à coords entière pour affichage à l'écran
     * on donne l'offset, l'échelle, la rotation en tour/2 et le
     * numéro du polygone dans la liste POLS
     */
    public Polygon rotateAndScale(double xoff_, double yoff_, double scale_,
				  int angle)
    {
	Polygon p=new Polygon();
	double u,v;
	for(int i=0;i<4;i++) {
	    u=COS[angle]*POL[i][0] - SIN[angle]*POL[i][1];
	    v=SIN[angle]*POL[i][0] + COS[angle]*POL[i][1];
	    // Remarquer le signe '-' dans la formule suivante
	    p.addPoint((int)(xoff_+scale_*u),
		       (int)(yoff_-scale_*v));
	}
	return p;
    }

    /*
     * Version où les transfo sont faites dans un autre ordre et on
     * ajoute l'offset de la zone de pose
     */
    public Polygon scaleAndPlace(double xoff_, double yoff_, double scale_,
				 int angle)
    {
	Polygon p=new Polygon();
	double u,v;
	for(int i=0;i<4;i++) {
	    u=COS[angle]*POL[i][0] - SIN[angle]*POL[i][1];
	    v=SIN[angle]*POL[i][0] + COS[angle]*POL[i][1];
	    // Remarquer le signe '-' dans la formule suivante
	    p.addPoint(XOFFSET+(int)(scale_*(xoff_+u)),
		       YOFFSET-(int)(scale_*(yoff_+v)));
	}
	return p;
    }

    /*
     * Création du polygône du panier
     * Appelé à chaque Paint()
     */
    public void createBasketPolygons() {
	storeBounds();
	P0=rotateAndScale(x2+(XOFFSET+PIECES_AREA_WIDTH)/2,80,PIECES_AREA_SCALE,0);
    }

    /*
     * Mémorise la taille de la zone d'affichage et calcule quelques constantes
     */
    public void storeBounds() {
	bounds=getBounds(); // prendre la taille de la zone
                            // d'affichage de la fenêtre

	// Zone de pose
	x1=XOFFSET;
	x2=bounds.width-XOFFSET-PIECES_AREA_WIDTH;
	y1=YOFFSET;
	y2=bounds.height-YOFFSET;

	// Boutons
	int w=(bounds.width-x2-2*BUTTON_SIZE)/3;
	but1=new Rectangle(x2+w,y2-BUTTON_SIZE,BUTTON_SIZE,BUTTON_SIZE);
	but2=new Rectangle(x2+2*w+BUTTON_SIZE,y2-BUTTON_SIZE,BUTTON_SIZE,BUTTON_SIZE);
	but3=new Rectangle(x2+w,y2-BUTTON_SIZE-30-SM_BUT_SIZE,SM_BUT_SIZE,SM_BUT_SIZE);
	but4=new Rectangle(x2+w,y2-BUTTON_SIZE,BUTTON_SIZE,BUTTON_SIZE);
	but5=new Rectangle(x2+w,y2-BUTTON_SIZE*2-20,BUTTON_SIZE,BUTTON_SIZE);
    }

    /*
     *
     */
    public void setScale() {
	scale=((double)PIECES_AREA_SCALE)
	    *Math.exp(((double)scaleIndex)*SCALE_LOG_STEP);
    }

    /*
     * Distance entre les centres de deux côtés 
     */
    public double sideDistance(Side s1, Side s2) {
	double dx=(s1.x1+s1.x2)-(s2.x1+s2.x2);
	double dy=(s1.y1+s1.y2)-(s2.y1+s2.y2);
	return Math.max(Math.abs(dx),Math.abs(dy))/2.0;
    }

    /*
     * Parties posées
     */
    public Side getSide(Tile tile, int n) {       
	// Retourne les coords math du côté 'n' de la tuile 'tile'
	Side side=new Side();

	int angle=tile.angle;
	int i;

	i=n;
	side.x1=tile.x + COS[angle]*POL[i][0]
	               - SIN[angle]*POL[i][1];
	side.y1=tile.y + SIN[angle]*POL[i][0]
	               + COS[angle]*POL[i][1];
	i=(n+1) % 4;
	side.x2=tile.x + COS[angle]*POL[i][0]
	               - SIN[angle]*POL[i][1];
	side.y2=tile.y + SIN[angle]*POL[i][0]
	               + COS[angle]*POL[i][1];

	return(side);
    }

    /*
     * Partie portée
     */
    public Side getSide2(int x, int y, int n) {
	Side side=new Side();

	double u,v;
	u=x-XOFFSET;
	u=u/scale-offx;
	v=YOFFSET-y;
	v=v/scale-offy;
	int angle=holdedAngle;
	int i;

	i=n;
	side.x1=u + COS[angle]*POL[i][0] 
                  - SIN[angle]*POL[i][1];
	side.y1=v + SIN[angle]*POL[i][0]
                  + COS[angle]*POL[i][1];
	i=(n+1) %4;
	side.x2=u + COS[angle]*POL[i][0] 
                  - SIN[angle]*POL[i][1];
	side.y2=v + SIN[angle]*POL[i][0]
                  + COS[angle]*POL[i][1];

	return(side);
    }

    public void zoomOut() {
	if(scaleIndex>SCALE_INDEX_MIN) {
	    double xs,ys;
	    xs=-offx+((double)(x2-x1))/(2.0*scale);
	    ys=-offy+((double)(y1-y2))/(2.0*scale);
	    scaleIndex-=1;
	    offx=-xs+(offx+xs)*Math.exp(SCALE_LOG_STEP);
	    offy=-ys+(offy+ys)*Math.exp(SCALE_LOG_STEP);
	    setScale();
	    repaint();
	}
    }

    public void zoomIn() {
	if(scaleIndex<SCALE_INDEX_MAX) {
	    double xs,ys;
	    xs=-offx+((double)(x2-x1))/(2.0*scale);
	    ys=-offy+((double)(y1-y2))/(2.0*scale);
	    scaleIndex+=1;
	    offx=-xs+(offx+xs)*Math.exp(-SCALE_LOG_STEP);
	    offy=-ys+(offy+ys)*Math.exp(-SCALE_LOG_STEP);
	    setScale();
	    repaint();
	}
    }

    /*
     *
     */
    public void myDrawArc(Graphics g,
	  int cx, int cy, int r, int a0, int al) {
	g.drawArc(cx-r,cy-r,2*r,2*r,a0,al);
    }

    /*
     *
     */
    public void drawPiece(Graphics g, Polygon p, int angle,
			  double scale_) {
	// Remplissage
	g.setColor(angle==0 ? Color.green : Color.cyan);
	g.fillPolygon(p);
	/*
	if(decorations) {
	    int a=(int)(0.2*scale_);
	    // Décorations : Aucune
	}
	*/
	// Tracé du bord en noir
	g.setColor(Color.black);
	g.drawPolygon(p);
    }

    /*
     * Constructeur de la classe principale
     */
    public void init() {

	// Toujours appeler le constructeur de la classe mère

	super.init();

	// Message d'aide

        StringBuffer auxString=new StringBuffer(
  "Mode d'emploi de l'applet\n"
+ "\n" 
+ "Affichage :\n"
+ "\n"
+ "  -  Jeu de pavages d'ordre 5, \n"
+ "     type \"Puzzle\".\n"
+ "\n"
);
	auxString.append(
  "\n"
+ "Commandes :\n"
+ "\n"
+ "  -  Cliquer sur une pièce dans la réserve pour la prendre.\n"
+ "  -  La faire éventuellement tourner avec le clavier : a,z ou q,w.\n"
+ "  -  La déposer dans la zone blanche avec un nouveau click.\n"
+ "  -  Shift-click permet de faire glisser la zone blanche.\n"
+ "  -  Les boutons + et - permettent de (dé)zoomer.\n"
+ "  -  La touche '?' ou un click droit ouvrent cette fenêtre d'information.\n"
+ "\n" 
+ "Puzzle, ©2003 Arnaud Chéritat\n"
);

	// MyHelpApplet prendra en charge la gestion du message
	// d'aide, dont le contenu est défini par setHelpText("contenu");

        setHelpText(auxString.toString());

	// Admin stuff : sans ces appels, les évènements ne sont pas
	// redirigés sur le programme

	addKeyListener(this);
	addMouseListener(this);
	addMouseMotionListener(this);

	/*
	 * Chargement de l'image
	 */
	/*
	URL imgURL = ClassLoader.getSystemResource("aimant.png");
	magnetPic = getImage(imgURL);
	*/
	/*
	Image temp = getImage(getCodeBase(),"magnet.png");
	MediaTracker tracker = new MediaTracker(this);
	tracker.addImage(temp,0);
	try {
	    tracker.waitForAll();
	} catch (InterruptedException e) {}

	magnetPic=createImage(temp.getWidth(this),temp.getHeight(this));
	Graphics gr=magnetPic.getGraphics();
	gr.drawImage(temp,0,0,this);
	*/
	/*
	magnetPic = getImage(getCodeBase(),"magnet.png");
	MediaTracker tracker = new MediaTracker(this);
	tracker.addImage(magnetPic,0);
	try {
	    tracker.waitForAll();
	} catch (InterruptedException e) {}
	*/
	decorPic = getImage(getCodeBase(),"decor.png");
	MediaTracker tracker = new MediaTracker(this);
	tracker.addImage(decorPic,0);
	try {
	    tracker.waitForAll();
	} catch (InterruptedException e) {}

	/*
	 * Initialisation des variables utilisateurs
	 */

	String param; // utilisé pour lire les paramètres passés dans l'appel à l'applet

	// Au début, on ne tient aucune pièce
	holdingPiece=false;

	// 
	dragging=false;

	//
	magnet=true;
	stuck=false;
	noOverlap=true;
	allChecked=true;
	decorations=true;
	//	mustFitDecorations=true;

	param=getParameter("dropOnlyStuck");
	if(param!=null) { dropOnlyStuck=param.equals("yes"); }
	else { dropOnlyStuck=false; }

	param=getParameter("checkAll");
	if(param!=null) { checkAll=param.equals("yes"); }
	else { checkAll=false; }

	doubleArcSize=false; // TROP MOCHE, je mets false

	mouseX=0;
	mouseY=0;

	myBlue=new Color(0,64,255);
	COL1=new Color(128,128,128);
	COL2=new Color(255,128,0);

	/*
	 * Initialisation mathématique
	 */


	// Remplissage du tableau des cos, sin de 2*k*Pi/2
	COS=new double[2];
	SIN=new double[2];
	for(int k=0;k<2;k++) {
	    COS[k]=Math.cos(2.0*((double)k)*Math.PI/2.0);
	    SIN[k]=Math.sin(2.0*((double)k)*Math.PI/2.0);
	}

	POL=new double[4][2];

	// Passons en phase 1
	initPhaseOne();
    }

    /*
     * Appelé à chaque fois qu'on veut repasser en phase 1
     */
    public void initPhaseOne() {
	offx=0;
	offy=0;
	scaleIndex=0;
	setScale();
	droppedPieces=new Vector();
	phase=1;
	putVertices=0;
    }

    /*
     * Appelé à chaque fois qu'on veut repasser en phase 2
     */
    public void initPhaseTwo() {
	phase=2;	
	// Il faut calculer le centre du polygône
	double centerx,centery;
	centerx=(POL[0][0]+POL[1][0]+POL[2][0]+POL[3][0])/4.0;
	centery=(POL[0][1]+POL[1][1]+POL[2][1]+POL[3][1])/4.0;
	double max=0;
	// On recentre et on calcule la taille
	for(int i=0; i<4; i++) {
	    POL[i][0]-=centerx;
	    max=Math.max(max,Math.abs(POL[i][0]));
	    POL[i][1]-=centery;
	    max=Math.max(max,Math.abs(POL[i][1]));
	}
	// On "retaille"
	for(int i=0; i<4; i++) {
	    POL[i][0]=POL[i][0]/max;
	    POL[i][1]=POL[i][1]/max;
	}
    }

    /*
     * Pour gérer l'affichage de la class Base,
     * il faut modifier la méthode offPaint
     */
    public void offPaint(Graphics g) {
  
	// Rectangle mathématique en blanc

	storeBounds();

	// Remplissage en blanc
	g.setColor(Color.white);
	g.fillRect(x1, y1, x2-x1, y2-y1);
	// Tracé du bord en noir : remarquer les "-1" en plus
	g.setColor(Color.black);
	g.drawRect(x1, y1, x2-x1-1, y2-y1-1);


	if(phase==1) {
	    // En phase 1 :
	    g.setColor(Color.blue);
	    int x0,y0,ax0,ay0,fx0,fy0;
	    fx0=0; fy0=0; ax0=0; ay0=0;
	    for(int i=0; i<putVertices; i++) {
		x0=XOFFSET+(int)(scale*(offx+POL[i][0]));
		y0=YOFFSET-(int)(scale*(offy+POL[i][1]));
		g.fillRect(x0-1,y0-1,3,3);
		if(i==0 && putVertices==4) {
		    // Mémoriser le premier point
		    fx0=x0;
		    fy0=y0;
		}
		if(i>0) {
		    // Rejoindre au point précédent
		    g.drawLine(x0,y0,ax0,ay0);
		}
		if(i==3) {
		    // Rejoindre au premier point
		    g.drawLine(x0,y0,fx0,fy0);
		}
		ax0=x0;
		ay0=y0;
	    }
	    if(putVertices<4) {
		g.setColor(Color.black);
		g.drawString("Dessin du",x2+16,y2-50);
		g.drawString("quadrilatère",x2+10,y2-30);
	    }
	    else {
		g.setColor(Color.pink);
		g.fillRect(but4.x,but4.y,but4.width,but4.height);
		g.fillRect(but5.x,but5.y,but5.width,but5.height);
		g.setColor(Color.black);
		g.drawRect(but4.x,but4.y,but4.width-1,but4.height-1);
		g.drawRect(but5.x,but5.y,but5.width-1,but5.height-1);
		g.drawString("Go",but4.x+but4.width+4,but4.y+but4.height-15);
		g.drawString("Redo",but5.x+but5.width+4,but5.y+but5.height-15);
	    }
	}
	else {
	    // En phase 2 :

	    // Les deux pièces
	    
	    createBasketPolygons();
	    
	    drawPiece(g,P0,0,PIECES_AREA_SCALE);
	    
	    // Les boutons
	    
	    // Bouton moins
	    g.setColor(Color.pink);
	    g.fillRect(but1.x,but1.y,but1.width,but1.height);
	    g.setColor(Color.black);
	    g.drawRect(but1.x,but1.y,but1.width-1,but1.height-1);
	    // Tracé du signe moins
	    g.fillRect(but1.x+5,but1.y+but1.height/2-3,but1.width-10,6);
	
	    // Bouton plus
	    g.setColor(Color.pink);
	    g.fillRect(but2.x,but2.y,but2.width,but2.height);
	    g.setColor(Color.black);
	    g.drawRect(but2.x,but2.y,but2.width-1,but2.height-1);
	    // Tracé du signe plus
	    g.fillRect(but2.x+5,but2.y+but2.height/2-3,but2.width-10,6);
	    g.fillRect(but2.x+but2.width/2-3,but2.y+5,6,but2.height-10);
	    
	    // Bouton Redo
	    g.setColor(Color.pink);
	    g.fillRect(but5.x,but5.y,but5.width,but5.height);
	    g.setColor(Color.black);
	    g.drawRect(but5.x,but5.y,but5.width-1,but5.height-1);
	    g.drawString("Redo",but5.x+but5.width+4,but5.y+but5.height-15);
	    
	    /*
	    // Bouton Toggle
	    int xm,xM,ym,yM;
	xm=but3.x;
	xM=but3.x+but3.width-1;
	ym=but3.y;
	yM=but3.y+but3.height-1;
	g.setColor(Color.white);
	g.fillRect(xm,ym,but3.width,but3.height);
	g.setColor(Color.gray);
	g.drawLine(xm,ym,xM,ym);
	g.drawLine(xm,ym,xm,yM);
	g.setColor(new Color(220,220,220));
	g.drawLine(xM,yM,xM,ym);
	g.drawLine(xM,yM,xm,yM);
	g.setColor(Color.black);
	if(decorations) {
	    g.drawLine(but3.x,but3.y,but3.x+but3.width-1,but3.y+but3.height-1);
	    g.drawLine(but3.x,but3.y+but3.height-1,but3.x+but3.width-1,but3.y);
	}
	// L'image texte "Décorations"
	g.drawImage(decorPic,but3.x+but3.width+12,but3.y+3,this);
	*/

	    // Les pièces posées
	    
	    Shape savedClip=g.getClip();
	    g.setClip(x1+1,y1+1,x2-x1-2,y2-y1-2);
	    Tile tile;
	    Polygon tp;
	    int s=droppedPieces.size();
	    for(int i=0 ; i<s ; i++) {
		tile=(Tile) droppedPieces.elementAt(i);
		tp=scaleAndPlace(offx+tile.x,offy+tile.y,scale,tile.angle);
		drawPiece(g, tp, tile.angle,scale);
	    }
	    g.setClip(savedClip);
	    
	    // L'éventuelle pièce portée
	    
	    if(holdingPiece) {
		Polygon hp=rotateAndScale(holdedX,holdedY,
					  scale,holdedAngle);
		drawPiece(g, hp, holdedAngle,scale);
	    }
	}
    }

    // 

    public void magnetize(int x, int y) {
	// x,y : coords du curseur

	int n=droppedPieces.size(); // nb de pièces posées
	double px1,py1,px2,py2;
	Side s1,s2;
	double record,sd;
	int ri,rj,rk;
	int j; 
	ri=-1;
	rj=-1;
	rk=-1;
	record=SIDE_DIST_TRESH;
	Tile tl;
	noOverlap=true; // IMPLEMENTER
	stuck=false;
	allChecked=true;
	for(int k=0; k<4; k++) {
	    // Pour chaque côté "s1" de la pièce portée
	    s1=getSide2(x,y,k);
	    for(int i=0; i<n; i++) {
		// Pour chaque pièce "tile"
		tl=(Tile) droppedPieces.elementAt(i);
		// Pourvu que les angles soient opposés
		if(holdedAngle==((tl.angle+1) % 2)) {
		    j=k;
		    
		    s2=getSide(tl,j);
		    // mesurons la distance entre les deux côtés
		    sd=sideDistance(s1,s2);
		    if(sd<record) {
			// Si on est assez proche
			record=sd;
			ri=i;
			rk=k;
			rj=j;
			stuck=true;
		    }
		}
	    }
	}
	// Au cas où on a détecté une pièce :
	if(stuck) {
	    Tile tile=(Tile) droppedPieces.elementAt(ri);
	    Tile t1=new Tile();
	    t1.x=0;
	    t1.y=0;
	    t1.angle=holdedAngle;
	    s1=getSide(t1,rk);
	    s2=getSide(tile,rj);
	    memX=s2.x2-s1.x1;
	    memY=s2.y2-s1.y1;
	    Polygon p1=scaleAndPlace(offx+tile.x,offy+tile.y,
				     scale,tile.angle);
	    Polygon p2=rotateAndScale(x,y,
				      scale,holdedAngle);
	    // On translate pour caler
	    holdedX=x+p1.xpoints[(rj+1) % 4]-p2.xpoints[rk];
	    holdedY=y+p1.ypoints[(rj+1) % 4]-p2.ypoints[rk];
	    
	}
    }

    // Interface MouseListener

    public void mouseClicked(MouseEvent e) {
	if(phase==1) {
	    int x=e.getX();
	    int y=e.getY();
	    if(putVertices<4) {
		if(x>x1 && x<x2 && y>y1 && y<y2) {
		    // A condition d'avoir cliqué dans la zone blanche :
		    double x0,y0;
		    x0=((double)(x-XOFFSET))/scale-offx;
		    y0=((double)(YOFFSET-y))/scale-offy;
		    POL[putVertices][0]=x0;
		    POL[putVertices][1]=y0;
		    putVertices+=1;
		    repaint();
		}	
	    }
	    else {
		if(x>but4.x && x<but4.x+but4.width 
		   && y>but4.y && y<but4.y+but4.height) {
		    // Click dans le bouton 4
		    initPhaseTwo();
		    repaint();
		}
		else if(x>but5.x && x<but5.x+but5.width 
			&& y>but5.y && y<but5.y+but5.height) {
		    initPhaseOne();
		    repaint();
		}
	    }
	}
	else {
	// Testons s'il s'agit du bouton gauche
	if((e.getModifiers() & InputEvent.BUTTON1_MASK)!=0) {
	    // Si oui
	    // Coordonnées du click :
	    int x=e.getX();
	    int y=e.getY();
	    if(holdingPiece) {
		// Si l'on est en train de porter une pièce
		if(x>x1 && x<x2 && y>y1 && y<y2) {
		    boolean cont=true;
		    if(dropOnlyStuck) {
			if(checkAll) {
			    cont = (allChecked) && noOverlap;
			}
			else {
			    cont = stuck || (droppedPieces.size()==0);
			}
		    }   
		    if(cont) {
			holdingPiece=false;
			Tile tile = new Tile();
			if(stuck) {
			    // Si la pièce est magnétiquement collée :
			    tile.x = memX ;
			    tile.y = memY ;
			    stuck=false;
			}
			else {
			    tile.x=((double)(x-XOFFSET))/scale-offx;
			    tile.y=((double)(YOFFSET-y))/scale-offy;
			}
			tile.angle=holdedAngle;
			droppedPieces.addElement(tile);
		    }
		}
		else {
		    holdingPiece=false;
		}
		repaint();
	    }
	    else {
		// quand on ne tient pas de piece
		if(x>x1 && x<x2 && y>y1 && y<y2) {
		    // Cas d'un click dans la zone de pose
		    // Cherchons si on a cliqué sur une pièce posée
		    int n=droppedPieces.size();
		    int k=-1;
		    Polygon p;
		    Tile tile;
		    for(int i=0; i<n; i++) {
			tile=(Tile) droppedPieces.elementAt(i);
			p=scaleAndPlace(offx+tile.x,offy+tile.y,
					 scale,tile.angle);
			if(p.contains(x,y)) {
			    k=i;
			}
		    }
		    // Si oui
		    if(k>-1) {
			tile=(Tile) droppedPieces.elementAt(k);
			holdingPiece=true;
			holdedX=x;
			holdedY=y;
			holdedAngle=tile.angle;
			// Retirons la piece de la liste
			droppedPieces.removeElementAt(k);
			repaint();
		    }
		} 
		else if(x>but1.x && x<but1.x+but1.width 
	             && y>but1.y && y<but1.y+but1.height) {
		    // Click dans le bouton 1
		    zoomOut();
		}
		else if(x>but2.x && x<but2.x+but2.width 
	             && y>but2.y && y<but2.y+but2.height) {
		    // Click dans le bouton 2
		    zoomIn();
		}
		else if(x>but3.x && x<but3.x+but3.width 
	             && y>but3.y && y<but3.y+but3.height) {
		    // Click dans le bouton 3
		    decorations=!decorations;
		    //		    mustFitDecorations=decorations;
		    repaint();
		}
		else if(x>but5.x && x<but5.x+but5.width 
			&& y>but5.y && y<but5.y+but5.height) {
		    initPhaseOne();
		    repaint();
		}
		else {
		    if(P0.contains(x,y)) {
			holdingPiece=true;
		    };
		    if(holdingPiece) {
			holdedAngle=0;
			holdedX=x;
			holdedY=y;
			repaint();
		    }
		}
	    }
	}
	}
    }
    public void mouseEntered(MouseEvent e) {
	// Do nothing
    }
    public void mouseExited(MouseEvent e) {
	// Do nothing
    }
    public void mousePressed(MouseEvent e) {
	if(phase==1) {
	}
	else {
	    if((e.getModifiers() & InputEvent.SHIFT_MASK )!=0) {
		// En présence de Shift
		int x=e.getX();
		int y=e.getY();
		if(x>x1 && x<x2 && y>y1 && y<y2) {
		    draggingDropZone=true;
		    clickedX=x;
		    clickedY=y;
		    savedoffx=offx;
		    savedoffy=offy;
		}
	    }
	}
    }
    public void mouseReleased(MouseEvent e) {
	if(phase==1) {
	}
	else {
	    if(dragging) {
		// Ignorons la différence Dragged/Clicked
		mouseClicked(e);
		dragging=false;
	    }
	    if(draggingDropZone) {
		draggingDropZone=false;
	    }
	}
    }

    // Interface MouseMotionListener

    public void mouseDragged(MouseEvent e) {
	if(phase==1) {
	}
	else {
	int x=e.getX();
	int y=e.getY();
	mouseX=x;
	mouseY=y;
	if(draggingDropZone) {
	    offx=savedoffx+((double)(x-clickedX))/scale;
	    offy=savedoffy-((double)(y-clickedY))/scale;
	    repaint();
	}
	else {
	    // En l'absence de Shift
	    dragging=true;
	}
	// Ignorons la différence Dragged/Clicked
	if(holdingPiece) {
	    mouseMoved(e);
	}
	}
    }
    public void mouseMoved(MouseEvent e) {
	if(phase==1) {
	}
	else {
	int x=e.getX();
	int y=e.getY();
	mouseX=x;
	mouseY=y;
	if(holdingPiece) {
	    holdedX=x;
	    holdedY=y;
	    if(magnet) {
		// Si le mode magnet est actif :
		magnetize(x,y);
	    }
	    // Ordonnons le redessin de la fenêtre
	    repaint();
	}
	}
    }

    // Interface KeyListener

    public void keyPressed(KeyEvent e) {
	char c=e.getKeyChar();

	if(phase==1) {
	    // En phase 1 :
	}
	else {
	    // En phase 2 :
	    switch(c) {
	    case '+' : zoomIn(); break;
	    case '-' : zoomOut(); break;
	    }
	    if(holdingPiece) {
		int left=-1;
		int center=0;
		int right=1;
		
		int turn=center;
		
		switch(c) {
		case 'a' : case 'A' : case 'q' : case 'Q' : 
		case 'g' : case 'G' : turn=left; break;
		case 'z' : case 'Z' : case 'w' : case 'W' :
		case 'd' : case 'D' : turn=right; break;
		}
		if(e.getKeyCode()==KeyEvent.VK_LEFT) { turn=left; }
		if(e.getKeyCode()==KeyEvent.VK_RIGHT) { turn=right; }
		
		if(turn!=center) {
		    holdedAngle += (turn==left) ? 1 : -1;
		    // Rque : les pièces marquées n'ont pas la symétrie centrale
		    holdedAngle= (2+holdedAngle) % 2;
		    holdedX=mouseX;
		    holdedY=mouseY;
		    magnetize(mouseX,mouseY);
		    repaint();
		}
	    }
	}
    }
    public void keyReleased(KeyEvent e) {
	// Do nothing
    }
    public void keyTyped(KeyEvent e) {
	// Do nothing
    }
 


    // Autres

    /*
     * Méthode "abstract" de la classe Base dont dérive cet applet.
     * Gère le Placement des composants en "absolute positioning"
     * Y mettre le code les positionnant --au premier affichage--
     */
    public void position() {
	// Rien pour l'instant
    }
    
    /* Cette fonction parle d'elle-même
     * elle n'est pas utilisée par les navigateurs actuels
     */
    public String getAppletInfo() {
         return "Puzzle, ©2003 Arnaud Chéritat";
    }
}
