/*
 * Applet : Pavages d'ordre 5
 */

/*
 * 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 Puzzle 
    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=50;

    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;

    public boolean allowReadWrite;

    /*
     * Magnet
     */

    // Dit si le mode magnet est en route
    public boolean magnet;
    // Dit si l'éventuelle pièce portée est collée
    public boolean stuck;
    // 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;

    // 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;

    // 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;

    /*
     * les deux pièces de base
     */
    public static double[][][]
	POLS={{{-.6545084971874738,-.4755282581475768}
	      ,{0.3454915028125262,-.4755282581475768}
              ,{0.6545084971874738,.4755282581475768}
              ,{-.3454915028125262,.4755282581475768}
              }
	     ,{{-.9045084971874738,-.2938926261462365}
	      ,{0.0954915028125262,-.2938926261462365}
              ,{0.9045084971874738,.2938926261462365}
              ,{-.0954915028125262,.2938926261462365}
              }
	     };

    // Angles orientés des cotés des pièces de base,
    // en 1/10 de tours, par rapport à l'horizontale
    public static int[][] ANGLES={{0,2,5,7},{0,1,5,6}};

    // Symboles combinatoires sur les côtés
    // pour "matcher" deux côtés doivent suivre les 3 règles suivantes :
    // - orientations opposées
    // - charges opposées
    // - même camp
    public static int[][] CHARGE={{0,0,1,1},{0,1,0,1}};
    public static int[][] CAMP={{0,1,1,0},{0,0,1,1}};

    /*
     * Pièce tenue
     */
    public int holdedX;
    public int holdedY;
    public int holdedType;
    public int holdedAngle;

    /*
     * Les deux polygônes du panier (coordonnées écran)
     */
    public Polygon P0, P1;

    /*
     * Table des cos et sin des multiples de 2*Pi/10
     */
    public double[] COS;
    public double[] SIN;

    /*
     * Géométrie des boutons
     */
    Rectangle but1,but2,but3;

    // ...

    public boolean holdingPiece;

    public Vector droppedPieces;

    /*
     *                          Sous-classes
     */

    /*
     * Pièces posées
     */
    class Tile 
    {
	double x,y;
	int angle;
	int type;
    }

    /* 
     * Côté (orienté)
     */
    class Side
    {
	double x1,x2,y1,y2;
    }

    /*
     * Dialogue : il est dommage que ce soit si compliqué
     */
    class SimpleDialog extends Dialog
	implements ActionListener,
		   WindowListener
    {

	SimpleDialog(Frame dw, String title, String message) {
	    super(dw, title, true);

	    //Create middle section.
	    TextArea textArea = new TextArea(message,0,0,TextArea.SCROLLBARS_NONE);
	    textArea.setEditable(false);
	    add("Center", textArea);
	    
	    //Create bottom row.
	    Panel p2 = new Panel();
	    p2.setLayout(new FlowLayout(FlowLayout.CENTER));
	    Button b = new Button("Cancel");
	    b.addActionListener(this);
	    p2.add(b);
	    add("South", p2);
	    
	    addWindowListener(this);

	    //Initialize this dialog to its preferred size.
	    pack();
	}
	
	public void actionPerformed(ActionEvent event) {
	    setVisible(false);
	}

	public void windowActivated(WindowEvent e) {
	}
	public void windowClosed(WindowEvent e) {
	}
	public void windowClosing(WindowEvent e) {
	    setVisible(false);
	}
	public void windowDeactivated(WindowEvent e) {
	}
	public void windowDeiconified(WindowEvent e) {
	}
	public void windowIconified(WindowEvent e) {
	}
	public void windowOpened(WindowEvent e) {
	}
 
    }


    /*
     *                           Méthodes
     */

    /*
     * Crée un polygône à coords entière pour affichage à l'écran
     * on donne l'offset, l'échelle, la rotation en tour/10 et le
     * numéro du polygone dans la liste POLS
     */
    public Polygon rotateAndScale(double xoff_, double yoff_, double scale_,
				  int angle, int index)
    {
	Polygon p=new Polygon();
	double u,v;
	for(int i=0;i<4;i++) {
	    u=COS[angle]*POLS[index][i][0] - SIN[angle]*POLS[index][i][1];
	    v=SIN[angle]*POLS[index][i][0] + COS[angle]*POLS[index][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, int index)
    {
	Polygon p=new Polygon();
	double u,v;
	for(int i=0;i<4;i++) {
	    u=COS[angle]*POLS[index][i][0] - SIN[angle]*POLS[index][i][1];
	    v=SIN[angle]*POLS[index][i][0] + COS[angle]*POLS[index][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 des deux polygônes du panier
     * Appelé à chaque Paint()
     */
    public void createBasketPolygons() {
	storeBounds();
	P0=rotateAndScale(x2+(XOFFSET+PIECES_AREA_WIDTH)/2,80,PIECES_AREA_SCALE,0,0);
	P1=rotateAndScale(x2+(XOFFSET+PIECES_AREA_WIDTH)/2,150,PIECES_AREA_SCALE,0,1);
    }

    /*
     * 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);
    }

    /*
     *
     */
    public void setScale() {
	scale=((double)PIECES_AREA_SCALE)
	    *Math.exp(((double)scaleIndex)*SCALE_LOG_STEP);
    }

    /*
     * Utilisés pout le mode Magnet
     */

    public int getSideAngle(Tile tile, int n) {
	return (tile.angle+ANGLES[tile.type][n]) % 10;
    }

    public int getSideAngle2(int n) {
	return (holdedAngle+ANGLES[holdedType][n]) % 10;
    }

    /*
     * 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 index=tile.type;
	int i;

	i=n;
	side.x1=tile.x + COS[angle]*POLS[index][i][0]
	               - SIN[angle]*POLS[index][i][1];
	side.y1=tile.y + SIN[angle]*POLS[index][i][0]
	               + COS[angle]*POLS[index][i][1];
	i=(n+1) % 4;
	side.x2=tile.x + COS[angle]*POLS[index][i][0]
	               - SIN[angle]*POLS[index][i][1];
	side.y2=tile.y + SIN[angle]*POLS[index][i][0]
	               + COS[angle]*POLS[index][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 index=holdedType;
	int i;

	i=n;
	side.x1=u + COS[angle]*POLS[index][i][0] 
                  - SIN[angle]*POLS[index][i][1];
	side.y1=v + SIN[angle]*POLS[index][i][0]
                  + COS[angle]*POLS[index][i][1];
	i=(n+1) %4;
	side.x2=u + COS[angle]*POLS[index][i][0] 
                  - SIN[angle]*POLS[index][i][1];
	side.y2=v + SIN[angle]*POLS[index][i][0]
                  + COS[angle]*POLS[index][i][1];

	return(side);
    }

    /*
     * Sauvegarde :
     *  Impossible pour un Applet Java, il faut passer à une
     *  Application Java.
     */
    public void saveDroppedTiles() {
	Frame fr=new Frame();
	try {
	    FileDialog fd=new FileDialog(fr, "Sauvegarde du pavage en cours", FileDialog.SAVE);
	    fd.show();
	    String fileName=fd.getFile();
	    String fileDir=fd.getDirectory();

	    if(fileName!=null) {
		FileOutputStream ostream = new FileOutputStream(fileDir+fileName);
		ObjectOutputStream p = new ObjectOutputStream(ostream);

		int s=droppedPieces.size();
		Tile t;
		for(int i=0; i<s; i++) {
		    t=(Tile) droppedPieces.elementAt(i);
		    p.writeDouble(t.x);
		    p.writeDouble(t.y);
		    p.writeInt(t.angle);
		    p.writeInt(t.type);
		}
		
		p.flush();
		ostream.close();
	    }
	}
	catch(Exception e) {
	    Dialog dial=new SimpleDialog(fr,"Problème",
		 "Problème durant la sauvegarde : "+e.getMessage());
	    dial.show();
	    dial.dispose();
	}
	fr.dispose();
    }
    public void loadDroppedTiles() {
	Frame fr=new Frame();
	try {
	    FileDialog fd=new FileDialog(fr, "Charger un pavage", FileDialog.LOAD);
	    fd.show();
	    String fileName=fd.getFile();
	    String fileDir=fd.getDirectory();
	    
	    if(fileName!=null) {
		FileInputStream istream = new FileInputStream(fileDir+fileName);
		ObjectInputStream p = new ObjectInputStream(istream);

		Vector tempPieces=new Vector();
		Tile t;
		for(; p.available()>0;) {
		    t=new Tile();
		    t.x=p.readDouble();
		    t.y=p.readDouble();
		    t.angle=p.readInt();
		    t.type=p.readInt();
		    tempPieces.addElement(t);
		}
		
		istream.close();

		droppedPieces=tempPieces;
		repaint();
	    }
	}
	catch(Exception e) {
	    Dialog dial=new SimpleDialog(fr,"Problème",
		 "Problème durant la sauvegarde : "+e.getMessage());
	    dial.show();
	    dial.dispose();
	}
	fr.dispose();
    }

    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, int type, Polygon p, int angle,
			  double scale_) {
	// Remplissage
	g.setColor(type==0 ? Color.green : myBlue);
	g.fillPolygon(p);
	if(decorations) {
	    int a=(int)(0.2*scale_);
	    // Décorations : très spécifiques aux pièces
	    if(type==0) {
		// Le truc empitique suivant tente (sans grand succès)
		// de résoudre le problème d'alignement des arcs de
		// cercles les plus grands
		int b=(int) (scale_-(double)a);
		g.setColor(COL1);
		myDrawArc(g,p.xpoints[0],p.ypoints[0],
			  b,36*angle,72);
		g.setColor(COL2);
		myDrawArc(g,p.xpoints[2],p.ypoints[2],
			  a,36*angle+180,72);
	    }
	    else {
		g.setColor(COL1);
		myDrawArc(g,p.xpoints[1],p.ypoints[1],
			  a,36*angle+36,144);
		g.setColor(COL2);
		myDrawArc(g,p.xpoints[3],p.ypoints[3],
			  a,36*angle+180+36,144);
	    }
	}
	// 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"
+ "  -  Décocher la case pour enlever les décorations.\n"
+ "  -  Sauvegarder son travail avec la touche s et charger avec l.\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;

	// Aucune pièce posée
	droppedPieces=new Vector();
	//	droppedPieces=new MyVector();

	// 
	dragging=false;

	//
	magnet=true;
	stuck=false;
	decorations=true;
	mustFitDecorations=true;
	param=getParameter("dropOnlyStuck");
	if(param!=null) { dropOnlyStuck=param.equals("yes"); }
	else { dropOnlyStuck=false; }

	//
	param=getParameter("enableReadWrite");
	if(param!=null) { allowReadWrite=param.equals("yes"); }
	else { allowReadWrite=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
	 */

	// ...
	offx=0;
	offy=0;
	scaleIndex=0;
	setScale();

	// Remplissage du tableau des cos, sin de 2*k*Pi/10
	COS=new double[10];
	SIN=new double[10];
	for(int k=0;k<10;k++) {
	    COS[k]=Math.cos(2.0*((double)k)*Math.PI/10.0);
	    SIN[k]=Math.sin(2.0*((double)k)*Math.PI/10.0);
	}

	// APRES remplissage des cos,sin et ... : créér les polygônes du panier
	createBasketPolygons(); // Bien placé ???
    }

    /*
     * 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);

	// Les deux pièces

	createBasketPolygons();

	drawPiece(g,0,P0,0,PIECES_AREA_SCALE);

	drawPiece(g,1,P1,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 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,tile.type);
	    drawPiece(g, tile.type, tp, tile.angle,scale);
	}
	g.setClip(savedClip);

	// L'éventuelle pièce portée

	if(holdingPiece) {
	    Polygon hp=rotateAndScale(holdedX,holdedY,
		      scale,holdedAngle,holdedType);
	    drawPiece(g, holdedType, 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; 
	ri=-1;
	rj=-1;
	rk=-1;
	record=SIDE_DIST_TRESH;
	Tile tl;
	int angle1,angle2,charge1,charge2,camp1,camp2;
	for(int k=0; k<4; k++) {
	    // Pour chaque côté "s1" de la pièce portée
	    s1=getSide2(x,y,k);
	    angle1=getSideAngle2(k);
	    charge1=CHARGE[holdedType][k];
	    camp1=CAMP[holdedType][k];
	    for(int i=0; i<n; i++) {
		// Pour chaque pièce "tile"
		tl=(Tile) droppedPieces.elementAt(i);
		for(int j=0; j<4; j++) {
		    // Pour chaque côté "s2" de la pièce
		    angle2=getSideAngle(tl,j);
		    charge2=CHARGE[tl.type][j];
		    camp2=CAMP[tl.type][j];
 
		    boolean cont=false;
		    // testons d'abord l'angle :
		    // Les angles sont-ils opposés ? 
		    cont = angle1 == ((angle2 + 5) % 10);
		    if(cont && mustFitDecorations) {
			// puis éventuellement les décorations :
			cont = (charge1 != charge2) && (camp1 == camp2);
		    }

		    if(cont) {
			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;
			}
		    }
		}
	    }
	}
	// Au cas où on a détecté une pièce :
	if(ri>-1) {
	    stuck=true;
	    Tile tile=(Tile) droppedPieces.elementAt(ri);
	    Tile t1=new Tile();
	    t1.x=0;
	    t1.y=0;
	    t1.angle=holdedAngle;
	    t1.type=holdedType;
	    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,tile.type);
	    Polygon p2=rotateAndScale(x,y,
				      scale,holdedAngle,holdedType);
	    // On translate pour caler
	    holdedX=x+p1.xpoints[(rj+1) % 4]-p2.xpoints[rk];
	    holdedY=y+p1.ypoints[(rj+1) % 4]-p2.ypoints[rk];
	}
	else {
	    stuck=false;
	}
    }

    // Interface MouseListener

    public void mouseClicked(MouseEvent e) {
	// 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) {
		    if((!dropOnlyStuck) || stuck || (droppedPieces.size()==0)) {
			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.type=holdedType;
			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,tile.type);
			if(p.contains(x,y)) {
			    k=i;
			}
		    }
		    // Si oui
		    if(k>-1) {
			tile=(Tile) droppedPieces.elementAt(k);
			holdingPiece=true;
			holdedX=x;
			holdedY=y;
			holdedType=tile.type;
			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(P0.contains(x,y)) {
			holdingPiece=true;
			holdedType=0;
		    };
		    if(P1.contains(x,y)) {
			holdingPiece=true;
			holdedType=1;
		    };
		    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) {
	// Do nothing
	    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(dragging) {
	    // Ignorons la différence Dragged/Clicked
	    mouseClicked(e);
	    dragging=false;
	}
	if(draggingDropZone) {
	    draggingDropZone=false;
	}
    }

    // Interface MouseMotionListener

    public void mouseDragged(MouseEvent e) {
	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) {
	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();

	switch(c) {
	case '+' : zoomIn(); break;
	case '-' : zoomOut(); break;
	}
	if(allowReadWrite) {
	    switch(c) {
	    case 's' : case 'S' : saveDroppedTiles(); break;
	    case 'l' : case 'L' : loadDroppedTiles(); 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= (10+holdedAngle) % 10;
		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";
    }
}
