/*
 *
 * J4.1 version
 * A RAJOUTER : bouton de choix entre haute et basse qualité
 *              bouton de choix entre Klein et Poincaré
 *
 */

import java.awt.*;
import java.util.*;
import java.applet.Applet;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;

public class Hyp
    extends Applet
    implements MouseListener,
	       MouseMotionListener,
	       Runnable,
	       ActionListener,
	       FocusListener,
	       LayoutManager,
	       ItemListener
{

    public static final int WORD_LENGTH_MAX=10;

    public final static int
	XOFF=20,
	YOFF=20,
	SIZE=300,
	XOFF2=40,
	YOFF2=250,
	BSIZE=50,
	YOFF3=110,
	XOFF4=25,
	YOFF4=290,
	XOFF5=10,
	YOFF5=10,
	XOFF6=10,
	YOFF6=40,
	XOFF7=-20,
	YOFF7=310,
	BBSIZE=28,
	CSIZE=3;

    public int TIME;
    public static final int
	DEFAULT_TIME=0;

    public Color myGray=new Color(0.7f,0.7f,0.7f);

    public int possible; // Is the covering possible ?
    public static final int
	POSSIBLE=0,
	IMPOSSIBLE=1,
	OVERFLOW=2;
    public int
	pValue, // nb of sides of the regular polygon
	sValue; // nb of polygons per vertex
    public static final int
	DEFAULT_P=5,
	DEFAULT_S=4;
    public static final int
	pMAX=50,
	sMAX=50;

    public double side; // Length of the sides of the polygons
    public double radius; // outer radius of the polygons
    //    public double diam; // not the diameter, but twice the radius
    public Complex[] director, director2;

    public boolean klein; // Do we use the Klein or the Poincaré Model ?

    public int debug_nb_pts, debug_nb_segs, debug_nb_aff;

    public boolean orientation; // true if positive, false if negative

    public final static int
	NOTHING=0,
	FORWARD=1,
	TURNLEFT=2,
	TURNRIGHT=3,
	CLICK=4;

    public final static double
	FACTOR=10.0;

    public final static double
	VS=0.4, // size of vector
	STEP=0.02, // mathematical step when moving forward
	STEP2=0.03, // mathematical step factor when click-moving
	ASTEP=0.04, // angular step
	STP=0.1, // line draw step
	DEFAULT_HFOG=2.0,
	HFOG_MAX=2.64,
	KHFOG_MAX=2.0;


    public double
	x,y,   // position of character in the map
	cs,sn, // direction of the tangent in the map
	hFog, //
	eFog, // euclidean radius at which the fog hides everything,
	      // in the Poincaré model
	clickx,	clicky; // where did the user last click ?

    public static final double
	tcs=Math.cos(ASTEP),
	tsn=Math.sin(ASTEP),
	t2cs=Math.cos(2.0*Math.PI/3.0),
	t2sn=Math.sin(2.0*Math.PI/3.0),
	seuil=0.151,
	t3cs=Math.cos(Math.PI),
	t3sn=Math.sin(Math.PI);


    public Thread thread;

    public int
	type; // used in thread

    public long lastMovedTime;

    public BufferedImage bufferedImage,mask;
    public Graphics2D g;

    public Random rand;

    public TextField fogText,delayText,pText,sText;
    public Checkbox kleinBox;

    public boolean bascule; // needed to avoid loops in TextField updates

    public Font font1=new Font("Times New Roman",Font.PLAIN,14);
    public Font font2=new Font("Times New Roman",Font.PLAIN,24);

    public Complex[] point; // list of the points
    public Complex[] pts;   // used when drawn = points transformed
                             // by the point of view
    public int[][] segment; // list of the segments

    // Interface Runnable

    public void run() {
	Thread curThread = Thread.currentThread();
	while(thread==curThread) {
	    doStep();
	    repaint();
	    try {
		long newMovedTime=new Date().getTime();
		thread.sleep(Math.max(0,Math.min(TIME,
			 TIME-(newMovedTime-lastMovedTime))));
		lastMovedTime=newMovedTime;
	    }
	    catch(InterruptedException e) {
	    }
	}
    }

    // Interface MouseListener    

    public void mouseClicked(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	int clX,clY;
	clX=e.getX();
	clY=e.getY();
	type=NOTHING;
	clickx=((double) (e.getX()-XOFF-SIZE/2))/(double)(SIZE/2);
	clicky=-((double) (e.getY()-YOFF-SIZE/2))/(double)(SIZE/2);
	if(clickx*clickx+clicky*clicky<1.0) {
	    type=CLICK;
	}
	if(XOFF+SIZE+XOFF2+BSIZE/2<clX &&
	   clX<XOFF+SIZE+XOFF2+BSIZE+BSIZE/2 &&
	   YOFF2-BSIZE>clY && clY>YOFF2-2*BSIZE) {
	    type=FORWARD;
	}
	if(XOFF+SIZE+XOFF2<clX &&
	   clX<XOFF+SIZE+XOFF2+BSIZE &&
	   YOFF2>clY && clY>YOFF2-BSIZE) {
	    type=TURNLEFT;
	}
	if(XOFF+SIZE+XOFF2+BSIZE<clX &&
	   clX<XOFF+SIZE+XOFF2+2*BSIZE &&
	   YOFF2>clY && clY>YOFF2-BSIZE) {
	    type=TURNRIGHT;
	}
	if(type!=NOTHING) {
	    if((e.getModifiers() & InputEvent.SHIFT_MASK) ==
	       InputEvent.SHIFT_MASK) {
		doStep();
		repaint();
	    }
	    else {
		thread=new Thread(this, "HypThread");
		    thread.start();
	    }
	}
    }
    public void mouseReleased(MouseEvent e) {
	thread=null;
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }

    // Interface MouseMotionListener

    public void mouseMoved(MouseEvent e) {
    }
    public void mouseDragged(MouseEvent e) {
	clickx=((double) (e.getX()-XOFF-SIZE/2))/(double)(SIZE/2);
	clicky=-((double) (e.getY()-YOFF-SIZE/2))/(double)(SIZE/2);
    }

    // Interface ItemListener

    public void itemStateChanged(ItemEvent e) {
	updateKlein();
    }

    // Interface ActionListener

    public void actionPerformed(ActionEvent e) {
	if(!bascule) {
	    Object sc=e.getSource();
	    if(sc.equals(fogText)) {
		updateFog();
	    }
	    else if(sc.equals(delayText)) {
		updateDelay();
	    }
	    else if(sc.equals(pText)) {
		updateP();
	    }
	    else if(sc.equals(sText)) {
		updateS();
	    }
	}
	else {
	    bascule=false;
	}
    }

    // Interface FocusListener

    public void focusGained(FocusEvent e) {
    }
    public void focusLost(FocusEvent e) {
	Component sc=e.getComponent();
	if(sc.equals(fogText)) {
	    updateFog();
	}
	else if(sc.equals(delayText)) {
	    updateDelay();
	}
	else if(sc.equals(pText)) {
	    updateP();
	}
	else if(sc.equals(sText)) {
	    updateS();
	}
    }

    // Interface LayoutManager

    public void addLayoutComponent(String name, Component comp) {
	// Do nothing
    }
    public void removeLayoutComponent(Component comp) {
	// Do nothing
    }
    public Dimension minimumLayoutSize(Container parent) {
	return getSize();
    }
    public Dimension preferredLayoutSize(Container parent) {
	return getSize();
    }
    public void layoutContainer(Container parent) {
	position();
    }

    // Custom subclasses

    /* Custom complex number class */
    public class Complex {
	double re,im;
	public Complex(double x, double y) {
	    re=x;
	    im=y;
	}
	public Complex() {
	    re=0;
	    im=0;
	}
	public Complex(Complex z) {
	    re=z.re;
	    im=z.im;
	}
	public Complex conj() {
	    return new Complex(re,-im);
	}
	public Complex oconj() {
	    return new Complex(-re,im);
	}
	public Complex mul(Complex a) {
	    return new Complex(a.re*re-a.im*im,a.re*im+a.im*re);
	}
	public Complex mul(double a) {
	    return new Complex(a*re,a*im);
	}
	public Complex add(Complex a) {
	    return new Complex(a.re+re,a.im+im);
	}
	public Complex add(double a) {
	    return new Complex(re+a,im);
	}
	public double norm() {
	    return re*re+im*im;
	}
	public Complex opposite() {
	    return new Complex(-re,-im);
	}
    }

    /*
     */
    public class MyPoint {
	Complex z; // coordinate in the unit disk
	int n; // number of polygons having this vertex
	int index; // place in the list
	MyPoint(Complex zz, int nn, int ii) {
	    z=zz;
	    n=nn;
	    index=ii;
	}
    }

    /*
     */
    public class MySegment {
	MyPoint a,b;
	MySegment prev,next;
	boolean active;
	MySegment(MyPoint aa, MyPoint bb, boolean ac, MySegment pr,
		  MySegment nx) {
	    a=aa;
	    b=bb;
	    active=ac;
	    prev=pr;
	    next=nx;
	}
    }

    // Custom methods

    /* Absolute positioning */
    public void position() {
	fogText.setBounds(XOFF+SIZE+XOFF2+20,YOFF3,80,20);
	delayText.setBounds(XOFF+SIZE+XOFF4+20,YOFF4,80,20);
	pText.setBounds(XOFF+SIZE+XOFF5,YOFF5,40,20);
	sText.setBounds(XOFF+SIZE+XOFF6,YOFF6,40,20);
	kleinBox.setBounds(XOFF+SIZE+XOFF7,YOFF7,80,20);
    }

    /* move (mathematically) */
    public synchronized void doStep() {
	double numx,numy,vx,vy,d,denx,deny,nx,ny,rox,roy,ncs,nsn,wx,wy;
	switch(type) {
	case FORWARD :
	    vx=STEP*cs;
	    vy=STEP*sn;
	    numx=1+x*vx+y*vy;
	    numy=x*vy-y*vx;
	    d=numx*numx+numy*numy;
	    denx=x+vx;
	    deny=y+vy;
	    nx=(denx*numx+deny*numy)/d;
	    ny=(deny*numx-denx*numy)/d;
	    x=nx;
	    y=ny;
	    
	    rox=(numx*numx-numy*numy)/d;
	    roy=-2*numx*numy/d;
	    ncs=cs*rox-sn*roy;
	    nsn=sn*rox+cs*roy;
	    cs=ncs;
	    sn=nsn;
	    replace();
	    replace2();
	    break;
	case TURNLEFT : case TURNRIGHT :
	    if(orientation ^ type==TURNLEFT) {
		ncs=cs*tcs+sn*tsn;
		nsn=sn*tcs-cs*tsn;
	    }
	    else {
		ncs=cs*tcs-sn*tsn;
		nsn=sn*tcs+cs*tsn;
	    }
	    cs=ncs;
	    sn=nsn;
	    replace2();
	    break;
	case CLICK :
	    if(orientation) {
		vx=STEP2*(cs*clicky+sn*clickx);
		vy=STEP2*(sn*clicky-cs*clickx);
	    }
	    else {
		vx=STEP2*(cs*clicky-sn*clickx);
		vy=STEP2*(sn*clicky+cs*clickx);
	    }
	    numx=1+x*vx+y*vy;
	    numy=x*vy-y*vx;
	    d=numx*numx+numy*numy;
	    denx=x+vx;
	    deny=y+vy;
	    nx=(denx*numx+deny*numy)/d;
	    ny=(deny*numx-denx*numy)/d;
	    x=nx;
	    y=ny;
	    
	    rox=(numx*numx-numy*numy)/d;
	    roy=-2*numx*numy/d;
	    ncs=cs*rox-sn*roy;
	    nsn=sn*rox+cs*roy;
	    cs=ncs;
	    sn=nsn;
	    replace();   
	    replace2();
	    break;
	}
    }

    /*
     * if out of some domain, apply one element of the group to come
     * back in
     */
    public void replace() {
	boolean finished,ok2,changed,ok;
	Complex z=new Complex(x,y);
	Complex dir=new Complex(cs,sn);
	changed=false;
	int count=0;
	do {
	    count++;
	    double ax,ay,aux,ncs,nsn,nx,ny;
	    Complex nz,a,centre,b,ndir;
	    // D'abord, corrigeons l'angle par rapport à 0, pour
	    // qu'il soit entre -pi/p et pi/p
	    ok=true;
	    double alpha=Math.atan2(z.im,z.re);
	    double k=Math.floor(pValue*alpha/(2.0*Math.PI)+0.5);
	    if(k!=0) {
		ok=false;
		aux=-k*2.0*Math.PI/pValue;
		a=new Complex(Math.cos(aux),Math.sin(aux));
		z=z.mul(a);
		dir=dir.mul(a);
	    }
	    // Puis faisons une éventuelle réflexion
	    if(z.im<0) {
		ok=false;
		z=z.conj();
		dir=dir.conj();
		orientation=!orientation;
	    }
	    // Puis, faisons un "phi"
	    centre=new Complex(radius,0);
	    b=centre.mul(z.conj());
	    b.re=1.0-b.re;
	    b.im=-b.im;
	    b=b.mul(b);
	    ndir=dir.mul(b);
	    nz=phi(centre.opposite(),z);
	    // Tournons à nouveau dans ces nouvelle coords
	    ok2=true;
	    alpha=Math.atan2(nz.im,nz.re);
	    k=Math.floor(sValue*(alpha/(2.0*Math.PI)-0.5+0.5/sValue));
	    if(k!=0) {
		ok2=false;
		aux=-k*2.0*Math.PI/sValue;
		a=new Complex(Math.cos(aux),Math.sin(aux));
		nz=nz.mul(a);
		ndir=ndir.mul(a);
	    }
	    if(nz.im<0) {
		ok2=false;
		nz=nz.conj();
		ndir=ndir.conj();
		orientation=!orientation;
	    }
	    if(!ok2) {
		// puis, "phi" encore
		centre.re=-centre.re;
		centre.im=-centre.im;
		b=centre.mul(nz.conj());
		b.re=1.0-b.re;
		b.im=-b.im;
		b=b.mul(b);
		dir=ndir.mul(b);
		z=phi(centre.opposite(),nz);
	    }
	    ok=ok && ok2;
	    changed=changed || !ok;
	} 
	while((!ok) && (count<10));

	if(changed) {
	    x=z.re;
	    y=z.im;
	    double aux=Math.sqrt(dir.norm());
	    cs=dir.re/aux;
	    sn=dir.im/aux;
	}
    }

    /* normalise direction vector (when norm too far from 1)*/
    public void replace2() {
	double nrm=cs*cs+sn*sn;
	if(nrm>1.01 || nrm<0.99) {
	    double sqt=Math.sqrt(nrm);
	    cs=cs/sqt;
	    sn=sn/sqt;
	}
    }

    public void drawCross(Graphics gg, int x, int y, int s) {
	gg.drawLine(x-s,y-s,x+s,y+s);
	gg.drawLine(x-s,y+s,x+s,y-s);
    }

    public int xToX(double x) {
	return((int)(FACTOR*(XOFF+((1+x)/2)*(double)SIZE)));
    }

    public int yToY(double y) {
	return((int)(FACTOR*(YOFF+((1-y)/2)*(double)SIZE)));
    }

    /*
     * phi(a,z) = (a+z)/(1+conj(a).z)
     */
    public Complex phi(Complex z0, Complex z) {
	double ax,ay,bx,by,d;
	ax=z0.re+z.re;
	ay=z0.im+z.im;
	bx=1+z0.re*z.re+z0.im*z.im;
	by=z0.re*z.im-z0.im*z.re;
	d=bx*bx+by*by;
	return(new Complex((ax*bx+ay*by)/d,
			   (ay*bx-ax*by)/d));
    }

    public void segment(Graphics gg, Complex z1, Complex z2)
    {
	if(klein) {
	    kleinSegment(gg,z1,z2);
	}
	else {
	    poincareSegment(gg,z1,z2);
	}
    }

    /* Draw segment in Klein's model (which turns out to be a
     * segment on the screen)
     */
    public void kleinSegment(Graphics gg, Complex z1, Complex z2)
    {
	double d1,d2,xx1,xx2,yy1,yy2;
	d1=2.0/(1+z1.norm());
	xx1=z1.re*d1;
	yy1=z1.im*d1;
	d2=2.0/(1+z2.norm());
	xx2=z2.re*d2;
	yy2=z2.im*d2;
	gg.drawLine(xToX(xx1),yToY(yy1),
		    xToX(xx2),yToY(yy2));	
    }

    /* Draw segment in Poincaré's model (which turns out to be an arc
     * of circle on the screen)
     */
    public void poincareSegment(Graphics gg, Complex z1, Complex z2)
    {
	Complex mz1=z1.opposite();
	Complex z3=phi(mz1,z2);
	Complex z4,nz4;
	z4=new Complex(z1);
	double t=0;
	double d=z3.re*z3.re+z3.im*z3.im;
	if(d>STP*STP) {
	    d=Math.sqrt(d);
	    Complex z5=new Complex(z3.re/d,z3.im/d);
	    while(t<d) {
		t=(t+STP)/(1.0+STP*t);
		if(t>d) {t=d;}
		nz4=phi(z1,new Complex(t*z5.re,t*z5.im));
		gg.drawLine(xToX(z4.re),yToY(z4.im),
			   xToX(nz4.re),yToY(nz4.im));
		z4=new Complex(nz4);
	    }
	}
	else {
	    gg.drawLine(xToX(z1.re),yToY(z1.im),
		       xToX(z2.re),yToY(z2.im));
	}
    }

    public void updateFogText() {
	    bascule=true;
	fogText.setText(String.valueOf(hFog));
    }

    public void updateDelayText() {
	    bascule=true;
	delayText.setText(String.valueOf(TIME));
    }

    public void updatePText() {
	    bascule=true;
	pText.setText(String.valueOf(pValue));
    }

    public void updateSText() {
	    bascule=true;
	sText.setText(String.valueOf(sValue));
    }

    public void updateFog() {
	double newValue;
	try { 
	    newValue=Double.valueOf(fogText.getText()).doubleValue();
	    if(newValue<0.01) { newValue=0.01; }
	    if(klein) {
		if(newValue>KHFOG_MAX) { newValue=KHFOG_MAX; }
	    }
	    else {
		if(newValue>HFOG_MAX) { newValue=HFOG_MAX; }
	    }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_HFOG;
	}
	if(newValue!=hFog) {
	    hFog=newValue;
	    updateFogText();
	    double aux=Math.exp(2*hFog);
	    eFog=(aux-1)/(aux+1);
	    setMaskAccordingToFog();
	    psComputations();
	    repaint();
	}
    }

    public void updateKlein() {
	klein=kleinBox.getState();
	kleinBox.setLabel(klein ? "Klein" : "Poincaré");
	setMaskAccordingToFog();
	repaint();
	//	updateFog();
    }

    public void updateDelay() {
	try {
	    TIME=Integer.valueOf(delayText.getText()).intValue();
	    if(TIME<0) { TIME=0; updateDelayText(); }
	    if(TIME>10000) { TIME=10000; updateDelayText(); }
	}
	catch(NumberFormatException e) {
	    TIME=DEFAULT_TIME; updateDelayText();
	}
	repaint();
    }

    public void updateP() {
	int newValue;
	try {
	    newValue=Integer.valueOf(pText.getText()).intValue();
	    if(newValue<3) { newValue=3; }
	    if(newValue>pMAX) { newValue=pMAX; }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_P; 
	}
	if(newValue!=pValue) {
	    pValue=newValue;
	    updatePText();
	    psComputations();
	    replace();
	    repaint();
	}
    }

    public void updateS() {
	int newValue;
	try {
	    newValue=Integer.valueOf(sText.getText()).intValue();
	    if(newValue<3) { newValue=3; }
	    if(newValue>sMAX) { newValue=sMAX; }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_S;
	}
	if(newValue!=sValue) {
	    sValue=newValue;
	    updateSText();
	    psComputations();
	    replace();
	    repaint();
	}
    }

    /* first compute a few geometric constants depending on 'p' and 's'
     * then creates the tiling
     */
    public void psComputations() {

	possible=sValue*pValue-2*(pValue+sValue)>0 
    //	    ? (pValue*sValue<=75 ? POSSIBLE : OVERFLOW) 
	    ? (pValue<=pMAX && sValue<=sMAX ? POSSIBLE : OVERFLOW) 
	    : IMPOSSIBLE;
	if(possible==POSSIBLE) {
	    double alpha;

	    // 1

	    double r;
	    r=Math.sqrt(
	        Math.cos(Math.PI*(1.0/pValue+1.0/sValue))
               /Math.cos(Math.PI*(1.0/pValue-1.0/sValue))
			    );
	    radius=r;
	    //	    diam=2.0*r/(1.0+r*r);
	    side=2.0*Math.sin(Math.PI/pValue)
		/Math.sqrt(r*r+1.0/(r*r)-2.0*Math.cos(2*Math.PI/pValue));
	    director=new Complex[sValue-1];
	    director2=new Complex[sValue];
	    for(int i=0; i<sValue-1; i++) {
		alpha=2.0*Math.PI*(0.5+(i+1.0)/sValue);
		director[i]=new Complex(Math.cos(alpha),Math.sin(alpha));
	    }
	    for(int i=0; i<sValue; i++) {
		alpha=2.0*Math.PI*(i+1.0)/sValue;
		director2[i]=new Complex(Math.cos(alpha),Math.sin(alpha));
	    }

	    // 2

	    Vector pt=new Vector(); // The list of points
	    Vector seg=new Vector(); // The list of segments
	    MySegment l1;
	    //	LinkedList active=new LinkedList();

	    // Base polygon :
	    // points
	    alpha=Math.PI*2.0/(double) pValue;
	    for(int i=0; i<pValue; i++) {
		pt.add(new MyPoint(new Complex(radius*Math.cos(i*alpha),
					       radius*Math.sin(i*alpha)),
				   1,-1));
	    }
	    // segments
	    MySegment s;
	    for(int i=0; i<pValue; i++) {
		s=new MySegment((MyPoint) pt.get(i),
				(MyPoint) pt.get((i+1) % pValue),
				true,null,null);
		seg.add(s);
	    }
	    // links
	    for(int i=0; i<pValue; i++) {
((MySegment) seg.get(i)).next=(MySegment) seg.get((i+1) % pValue);
((MySegment) seg.get(i)).prev=(MySegment) seg.get((i+pValue-1) % pValue);
	    }
	    l1=(MySegment) seg.get(0);
	    
	    // Create the "based" polygon
	    Complex[] based=new Complex[pValue];
	    Complex z0=((MyPoint) pt.get(0)).z.opposite();
	    Complex rho=phi(z0,((MyPoint) pt.get(pValue-1)).z);
	    rho=rho.conj().mul(1/Math.sqrt(rho.norm()));
	    for(int i=0; i<pValue; i++) {
		based[i]=phi(z0,((MyPoint) pt.get(i)).z).mul(rho);
	    }

	    Complex zz2=new Complex(radius,0);
	    double r2=radius*(Math.cos(Math.PI/sValue)
			  -Math.sin(Math.PI/pValue))
	    /Math.cos(Math.PI/pValue+Math.PI/sValue);
	/*
	    double r2=Math.sin(Math.PI/sValue)
		/Math.sqrt(radius*radius+1.0/(radius*radius)
			   -2.0*Math.cos(2*Math.PI/sValue));
	*/
	    Complex zz3=new Complex(r2*Math.cos(Math.PI/pValue),
			    r2*Math.sin(Math.PI/pValue));
	    
	    Vector activeList;
	    MySegment ms,sgp,sgn,sg;
	    //	    double e2=(eFog+diam)/(1+eFog*diam);
	    for(int count=0;l1!=null; count++) {
		
		// create the vector
		activeList=new Vector();
		activeList.add(l1);
		int act=0;
		if(segDistToZero(l1.a.z,l1.b.z)<eFog || 
		   segDistToZ(l1.a.z,l1.b.z,zz2)<eFog ||
		   segDistToZ(l1.a.z,l1.b.z,zz3)<eFog) { act++; }
		else { l1.active=false; }
		sg=l1.next;
		for(;sg!=l1;) {
		    if(segDistToZero(sg.a.z,sg.b.z)<eFog || 
		   segDistToZ(sg.a.z,sg.b.z,zz2)<eFog ||
		   segDistToZ(sg.a.z,sg.b.z,zz3)<eFog)  {act++; }
		    else { sg.active=false; }
		    activeList.add(sg);
		    sg=sg.next;		
		}

		if(act==0) { l1=null; }
		else {

//		System.out.println("l1 length : "+activeList.size());
		
		// scan it
		
		// for each element of the vector
		Iterator it=activeList.iterator();
		int ja,jb;
		for(;it.hasNext();) {
		    ms=(MySegment) it.next();
		    // if this element is active
		    if(ms.active) {
			
			ms.active=false;
			
			// then add polygon :
			
			// first, follow the "s-1"'s
			sgn=ms;
			for(jb=0; jb<pValue && sgn.b.n==sValue-1; jb++) {
			    sgn=sgn.next;
			    sgn.active=false;
			}
			if(jb==pValue) {
			    // Finish
			    // This very particular case should not happen
			    // for our hyperbolic tilings
			    System.out.println("Error during constuction");
			    System.exit(1);
			}
			else {
			    sgp=ms;
			    for(ja=0; ja<pValue && sgp.a.n==sValue-1; ja++) {
				sgp=sgp.prev;
				sgp.active=false;
			    }
//			    System.out.print("ja="+ja+" jb="+jb+" ");
			    // add 1 to point's polygon count
			    sgp.a.n++;
			    sgn.b.n++;
			    // test
			    if(ja+jb >= pValue) {
				// This should not happen at all !
				System.out.println("Error during constuction");
				System.exit(1);
			    }
			    // create new points if any
			    int k=pt.size();
			    Complex z=phi(ms.a.z.opposite(),ms.b.z);
			    z=z.mul(1/Math.sqrt(z.norm()));
			    for(int i=0;i<pValue-ja-jb-2;i++) {
		pt.add(new MyPoint(phi(ms.a.z,z.mul(based[ja+1+i])),1,-1));
			    }			
			    // create new segments if any
			    int kk=seg.size();
			    for(int i=0;i<pValue-ja-jb-1;i++) {
				seg.add(new MySegment(
	      i==0 ? sgp.a : (MyPoint) pt.get(k+i-1), 
	      i==pValue-ja-jb-2 ? sgn.b : (MyPoint) pt.get(k+i),
	      true,null,null));
			    }
			    if(pValue-ja-jb-1>0) {l1=(MySegment) seg.get(kk);}
			    // modify the chain
			    MySegment a,b;
			    for(int i=0;i<pValue-ja-jb;i++) {
	      a = i==0 ? sgp.prev : (MySegment) seg.get(kk+i-1);
	      b = i==pValue-ja-jb-1 ? sgn.next : (MySegment) seg.get(kk+i);
				a.next=b;
				b.prev=a;
//	if(!(a.active && b.active)) { System.out.print(" BUG ! ");} // DEBUG
			    }			
			}
		    }
		}
		}
	    }

	    // Read the points and segments

	    Vector pt2=new Vector();
	    Vector seg2=new Vector();
	    int l=seg.size();
	    MyPoint aa,bb;
	    MySegment ss;
	    Complex aa2,bb2,aa3,bb3;
	    int ind=0;
	    for(int i=0; i<l; i++) {
		ss=((MySegment) seg.get(i));
		aa=ss.a;
		bb=ss.b;
//		if(segDistToZero(aa.z,bb.z)<e2) {
//		if(segDistToZero(aa.z,bb.z)<eFog) {
		if(segDistToZero(aa.z,bb.z)<eFog || 
		   segDistToZ(aa.z,bb.z,zz2)<eFog ||
		   segDistToZ(aa.z,bb.z,zz3)<eFog) {
    // INDEXOF IS TOO TIME CONSUMING : SOLVE THIS PROBLEM
		    if(aa.index==-1) {
		//if(pt2.indexOf(aa)==-1) {
			aa.index=ind++;
			pt2.add(aa);
		    }
		    if(bb.index==-1) {
		//if(pt2.indexOf(bb)==-1) {
			bb.index=ind++;
			pt2.add(bb);
		    }
		    seg2.add(new MySegment(aa,bb,false,null,null));
		}
	    }


	    l=pt2.size();
//	    System.out.println(l+" points");
	    point=new Complex[l];
	    for(int i=0; i<l; i++) {
		point[i]=(((MyPoint) pt2.get(i)).z);
//	System.out.print(String.valueOf(point[i].re)+"+I*"+String.valueOf(point[i].im)+" ");
	    }
	    pts=new Complex[l];
//	    System.out.println();

	    /*
		activeList=new Vector();
		activeList.add(l1);
		sg=l1.next;
		for(;sg!=l1;) {
		    activeList.add(sg);
		    sg=sg.next;		
		}
		l=activeList.size();
		System.out.println(l);
		segment=new int[l][2];
		for(int i=0; i<l; i++) {
		    segment[i][0]=pt.indexOf(((MySegment) activeList.get(i)).a);
		    segment[i][1]=pt.indexOf(((MySegment) activeList.get(i)).b);
		}
	    */

	    l=seg2.size();
//	    System.out.println(l+" segments");
	    segment=new int[l][2];
	    for(int i=0; i<l; i++) {
		aa=((MySegment) seg2.get(i)).a;
		bb=((MySegment) seg2.get(i)).b;
		segment[i][0]=pt2.indexOf(aa);
		segment[i][1]=pt2.indexOf(bb);
// System.out.print(String.valueOf(segment[i][0])+","+String.valueOf(segment[i][1])+" ");	
	    }
//	    System.out.println();

	    debug_nb_pts=point.length;	    
	    debug_nb_segs=segment.length;	    

	    //	System.exit(2);
	}
    }

    /*
     * euclideanized hyperbolic distance from hyp segment [z0,z1] to z
     */
    public double segDistToZ(Complex z0, Complex z1, Complex z) {
	Complex zz=z.opposite();
	return(segDistToZero(phi(zz,z0),phi(zz,z1)));
    }

    /*
     * euclideanized hyperbolic distance from hyp segment [z0,z1] to 0
     * NON TESTE !!!
     */
    public double segDistToZero(Complex z0, Complex z1) {
	Complex a,b;
	a=phi(z0.opposite(),z1);
	if(a.norm()<0.0001) {
	    return(Math.sqrt(z0.norm()));
	}

	b=z0.opposite().mul(a.conj()).mul(1/Math.sqrt(a.norm()));
	if(b.re<0) {
	    return(Math.sqrt(z0.norm()));
	}

	a=phi(z1.opposite(),z0);		
	b=z1.opposite().mul(a.conj()).mul(1/Math.sqrt(a.norm()));
	if(b.re<0) {
	    return(Math.sqrt(z1.norm()));
	}

	// A VERIFIER !!!
	Complex u,v;
	u=b.add(-1.0).opposite();
	u=u.conj().mul(1.0/u.norm());
	u=u.mul(b.add(1.0));
	u=u.mul(1.0/Math.sqrt(u.norm()));
	v=u.add(1.0);
	v=v.conj().mul(1.0/v.norm());
	v=v.mul(u.add(-1.0));
	return(Math.abs(v.im));

    }

    //

    public void setMaskAccordingToFog() {
	double tr;
	int c=SIZE/2;
	WritableRaster wr=mask.getRaster();
	int[] a=new int[4];
	a[0]=255;
	a[1]=255;
	a[2]=255;
	for(int i=0; i<SIZE; i++) {
	    for(int j=0; j<SIZE; j++) {
		
		tr=(i-c)*(i-c)+(j-c)*(j-c);
		tr=4.0*tr/(SIZE*SIZE);
		tr=Math.sqrt(tr);
		// tr contient maintenant la valeur absolue de z
		/*
		if(tr>0.99) {
		    if(tr>1) {
			tr=1;
		    }
		    else {
			tr=0;
		    }
		}
		*/
		if(tr>1) {
		    tr=1;
		}
		else {
		    if(klein) {
			tr=tr/(1+Math.sqrt(1-tr*tr));
		    }
		    tr=Math.log((1+tr)/(1-tr))/2;		    
		    tr=1-tr/hFog;
		    tr=Math.max(0,tr);
//		    tr=Math.sqrt(tr);

		}
		a[3]=(int)(255.0*(1-tr));
		wr.setPixel(i,j,a);		
		/*
		// DrawRect is SO SLOW !
		maskGraphics.setColor(new Color(1.0f,1.0f,1.0f,(float)tr));
		maskGraphics.drawRect(i,j,0,0);
		*/
	    }
	}
    }

    // Overrid methods

    public synchronized void update(Graphics gg) {

	// Absolute Positioning

	// Painting

	Dimension dim=getSize();

	// fills with a custom background color
	g.setColor(myGray);
	g.fillRect(0,0,dim.width-1,dim.height-1);
	// fills the buttons rectangle in bluish gray
	g.setColor(new Color(0.7f,0.7f,0.8f));
	g.fillRect(XOFF+SIZE+XOFF2,YOFF2-BSIZE,BSIZE,BSIZE);
	g.fillRect(XOFF+SIZE+XOFF2+BSIZE,YOFF2-BSIZE,BSIZE,BSIZE);
	g.fillRect(XOFF+SIZE+XOFF2+BSIZE/2,YOFF2-2*BSIZE,BSIZE,BSIZE);
	// draw various black lines
	g.setColor(Color.black);
	// a box surrounding the applet
	g.drawRect(0,0,dim.width-1,dim.height-1);
	// some text
	g.setFont(font1);
	g.drawString("fog (between 0 and "+String.valueOf(
		      klein ? KHFOG_MAX : HFOG_MAX)+")",
		     XOFF+SIZE+XOFF2-15,YOFF3-15);
	g.drawString("animation delay (milliseconds)",XOFF+SIZE+XOFF4-30,YOFF4-15);
	g.drawString("polygon sides",XOFF+SIZE+XOFF5+50,YOFF5+14);
	g.drawString("polygons per vertex",XOFF+SIZE+XOFF6+50,YOFF6+14);
	/*
	int m=(pValue-1)/2;
	int n=pValue-2-m;
	g.drawString(String.valueOf(m),0,20);
	g.drawString(String.valueOf(n),0,40);
	*/
	// the buttons boundaries
	g.drawRect(XOFF+SIZE+XOFF2,YOFF2-BSIZE,BSIZE,BSIZE);
	g.drawRect(XOFF+SIZE+XOFF2+BSIZE,YOFF2-BSIZE,BSIZE,BSIZE);
	g.drawRect(XOFF+SIZE+XOFF2+BSIZE/2,YOFF2-2*BSIZE,BSIZE,BSIZE);
	// draw the arrows in the buttons
	int N=7;
	double xa[]={0.35,0.65,0.65,0.8,0.5,0.2,0.35};
	double ya[]={0.15,0.15,0.55,0.55,0.85,0.55,0.55};
	int[] xa2=new int[N];
	int[] ya2=new int[N];
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+BSIZE/2+(int)(xa[i]*(double)BSIZE);
	    ya2[i]=YOFF2-BSIZE-(int)(ya[i]*(double)BSIZE);
	}
	g.setColor(Color.blue);
	g.fillPolygon(xa2,ya2,7);
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+(int)((1-ya[i])*(double)BSIZE);
	    ya2[i]=YOFF2-(int)(xa[i]*(double)BSIZE);
	}
	g.fillPolygon(xa2,ya2,7);
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+BSIZE+(int)(ya[i]*(double)BSIZE);
	    ya2[i]=YOFF2-(int)((1-xa[i])*(double)BSIZE);
	}
	g.fillPolygon(xa2,ya2,7);

	// fill the circle with white
	g.setColor(Color.white);
	g.fillOval(XOFF,YOFF,1+SIZE,1+SIZE);

	if(possible==POSSIBLE) {
	// Let's set the pixel positioning precision to higher (FACTOR)
	AffineTransform savedTransform=g.getTransform();
	Stroke savedStroke=g.getStroke();

	AffineTransform af=new AffineTransform();
	af.scale(1/FACTOR,1/FACTOR);
	g.transform(af);

	g.setStroke(new BasicStroke((float) FACTOR));
	g.setColor(Color.red);

	Complex mz0=new Complex(-x,-y);
	Complex rho=new Complex(sn,cs);
	//	Complex rho=new Complex(-sn,cs);
	Complex z1,z2;
	if(orientation) {
	    for(int i=0; i<point.length; i++) {
		pts[i]=phi(mz0,point[i]).mul(rho);
	    }
	}
	else {
	    for(int i=0; i<point.length; i++) {
		pts[i]=phi(mz0,point[i]).mul(rho).oconj();
	    }
	}
	debug_nb_aff=0;
	for(int i=0; i<segment.length; i++) {
	    z1=pts[segment[i][0]];
	    z2=pts[segment[i][1]];
	    if(segDistToZero(z1,z2)<eFog) {

		debug_nb_aff++;
		segment(g,z1,z2);
	    }
	}

	// TEMPORAIRE
	g.setColor(Color.blue);
	z1=phi(mz0,new Complex(0,0)).mul(rho);
	z2=phi(mz0,new Complex(radius,0)).mul(rho);
	double r2=radius*(Math.cos(Math.PI/sValue)
			  -Math.sin(Math.PI/pValue))
	    /Math.cos(Math.PI/pValue+Math.PI/sValue);
	Complex z3=new Complex(r2*Math.cos(Math.PI/pValue),
			       r2*Math.sin(Math.PI/pValue));
	z3=phi(mz0,z3).mul(rho);
	/*
	z1=phi(mz0,point[0]).mul(rho);
	z2=phi(mz0,point[1]).mul(rho);
	z3=phi(mz0,point[2]).mul(rho);*/
	if(!orientation) { z1=z1.oconj(); z2=z2.oconj(); z3=z3.oconj(); }
	segment(g,z1,z2);
	segment(g,z2,z3);
	segment(g,z1,z3);
	/*
	z1=pts[0];
	z2=pts[1];
	z3=pts[2];
	segment(g,z1,z2);
	segment(g,z2,z3);
	segment(g,z1,z3);
	*/

	g.setTransform(savedTransform);
	g.setStroke(savedStroke);

	g.drawImage(mask,XOFF,YOFF,this);

	// draw a cross at the character's position
	g.setColor(Color.red);
	drawCross(g,XOFF+SIZE/2,YOFF+SIZE/2,CSIZE);
	}
	else {
	    g.setColor(Color.black);
	    g.setFont(font2);
	    g.drawString(possible==IMPOSSIBLE ? "Impossible" :
	"Overflow" ,XOFF + SIZE/4 + 20,YOFF+SIZE/2-10);
	}

	// draw the circle
	Stroke savedStroke=g.getStroke();
	g.setStroke(new BasicStroke(2.0f));
	g.setColor(Color.black);
	g.drawOval(XOFF,YOFF,1+SIZE,1+SIZE);
	g.setStroke(savedStroke);

	if(possible==POSSIBLE) {
	    g.drawString(""+debug_nb_pts,5,20);
	    g.drawString(""+debug_nb_segs,5,40);
	    g.drawString(""+debug_nb_aff,5,60);
	}

	gg.drawImage(bufferedImage,0,0,this);
    }

    public void paint(Graphics gg) {
	update(gg);
    }

    public void init() {
	setLayout(this);

	//	setBackground(Color.lightGray);
	debug_nb_pts=0;
	debug_nb_segs=0;
	debug_nb_aff=0;
	klein=true;
        x=0;
	y=0;
	orientation=true;
	double angle=Math.PI/2;
	cs=Math.cos(angle);
	sn=Math.sin(angle);
	clickx=0;
	clicky=0;
	type=NOTHING;
	thread=null;
	addMouseListener(this);
	addMouseMotionListener(this);
	Dimension dim=getSize();
	bufferedImage=new BufferedImage(dim.width,dim.height,BufferedImage.TYPE_INT_RGB);
	g=bufferedImage.createGraphics();
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE);
	rand= new Random();
	hFog=DEFAULT_HFOG;
	double aux=Math.exp(2*hFog);
	eFog=(aux-1)/(aux+1);
	TIME=DEFAULT_TIME;
	pValue=DEFAULT_P;
	sValue=DEFAULT_S;
	psComputations();
	fogText=new TextField("fog",5);
	delayText=new TextField("delay",5);
	pText=new TextField("p",2);
	sText=new TextField("s",2);
	add(fogText);
	add(delayText);
	add(pText);
	add(sText);
	updateFogText();
	updateDelayText();
	updatePText();
	updateSText();
	bascule=false;
	fogText.addActionListener(this);
	fogText.addFocusListener(this);
	delayText.addActionListener(this);
	delayText.addFocusListener(this);
	pText.addActionListener(this);
	pText.addFocusListener(this);
	sText.addActionListener(this);
	sText.addFocusListener(this);
	/* MARCHE PAS...
	fogText.setFont(font1);
	delayText.setFont(font1);
	pText.setFont(font1);
	sText.setFont(font1);
	*/
	kleinBox=new Checkbox("");
	kleinBox.setState(klein);
	kleinBox.setBackground(myGray);
	kleinBox.addItemListener(this);
	add(kleinBox);

	lastMovedTime=new Date().getTime();

	mask=new BufferedImage(SIZE,SIZE,BufferedImage.TYPE_INT_ARGB);
	updateKlein(); // Will call setMaskAccordingToFog
    }

    public void stop() {
        thread=null;
    }

}
