/*
 *
 *
 */

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 final static int
	XOFF=20,
	YOFF=20,
	SIZE=500,
	XOFF2=60,
	YOFF2=250,
	BSIZE=50,
	YOFF3=110,
	XOFF4=45,
	YOFF4=290,
	XOFF5=30,
	YOFF5=10,
	XOFF6=30,
	YOFF6=40,
	XOFF7=20,
	YOFF7=310,
	XOFF8=110,
	YOFF8=310,
	XOFF9=30,
	YOFF9=350,
	BBSIZE=28,
	CSIZE=3;

    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
	depth;  // nb of layers in the onion
    public static final int
	DEFAULT_P=7,
	DEFAULT_S=3,
	DEFAULT_DEPTH=3;
    public static final int
	pMAX=50,
	sMAX=50,
	depthMAX=20;

    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 boolean quality; // High quality ?

    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 Onion.Face
	currentCell; // which room is the character in

    public double
	x,y,   // position of character in the canonic map
	cs,sn, // direction of the tangent in the canonic 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,pText,sText,depthText;
    public Checkbox kleinBox;
    public Checkbox qualityBox;

    //    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);

    // Maze data

    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
    public boolean[] isOpen;    // is the door open on this segment ?

    // Interface Runnable

    public void run() {
	Thread curThread = Thread.currentThread();
	while(thread==curThread) {
	    doStep();
	    repaint();
	    try {
		long newMovedTime=new Date().getTime();
		thread.sleep(0);
		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) {
	Object sc=e.getSource();
	if(sc.equals(kleinBox)) {
	    updateKlein();
	}
	else if (sc.equals(qualityBox)) {
	    updateQuality();
	}
    }

    // Interface ActionListener

    public void actionPerformed(ActionEvent e) {
	//	if(!bascule) {
	    Object sc=e.getSource();
	    if(sc.equals(fogText)) {
		updateFog();
	    }
	    else if(sc.equals(pText)) {
		updateP();
	    }
	    else if(sc.equals(sText)) {
		updateS();
	    }
	    else if(sc.equals(depthText)) {
		updateDepth();
	    }
    }

    // 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(pText)) {
	    updateP();
	}
	else if(sc.equals(sText)) {
	    updateS();
	}
	else if(sc.equals(depthText)) {
	    updateDepth();
	}
    }

    // 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


    /*
     */
    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);
	pText.setBounds(XOFF+SIZE+XOFF5,YOFF5,40,20);
	sText.setBounds(XOFF+SIZE+XOFF6,YOFF6,40,20);
	kleinBox.setBounds(XOFF+SIZE+XOFF7,YOFF7,80,20);
	qualityBox.setBounds(XOFF+SIZE+XOFF8,YOFF8,90,20);
	depthText.setBounds(XOFF+SIZE+XOFF9,YOFF9,40,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;
	    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;
	    replace2();
	    break;
	}
    }

    /* 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 updatePText() {
	//	    bascule=true;
	pText.setText(String.valueOf(pValue));
    }

    public void updateSText() {
	//	    bascule=true;
	sText.setText(String.valueOf(sValue));
    }

    public void updateDepthText() {
	//	    bascule=true;
	depthText.setText(String.valueOf(depth));
    }

    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 updateQuality() {
	quality=qualityBox.getState();
	qualityBox.setLabel(quality ? "Quality : high" : "Quality : low");
	if(quality) {
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE);
	}
	else {
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_DEFAULT);
	}
	repaint();
    }

    public void updateP() {
	int newValue;
	try {
	    newValue=Integer.valueOf(pText.getText()).intValue();
	    if(newValue<3) { newValue=3; pText.setText("3"); }
	    if(newValue>pMAX) { newValue=pMAX; }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_P; 
	}
	if(newValue!=pValue) {
	    pValue=newValue;
	    updatePText();
	    createMaze();
	    repaint();
	}
    }

    public void updateS() {
	int newValue;
	try {
	    newValue=Integer.valueOf(sText.getText()).intValue();
	    if(newValue<3) { newValue=3; sText.setText("3"); }
	    if(newValue>sMAX) { newValue=sMAX; }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_S;
	}
	if(newValue!=sValue) {
	    sValue=newValue;
	    updateSText();
	    createMaze();
	    repaint();
	}
    }

    public void updateDepth() {
	int newValue;
	try {
	    newValue=Integer.valueOf(depthText.getText()).intValue();
	    if(newValue<0) { newValue=0; depthText.setText("0"); }
	    if(newValue>depthMAX) { newValue=depthMAX; }
	}
	catch(NumberFormatException e) {
	    newValue=DEFAULT_DEPTH; 
	}
	if(newValue!=depth) {
	    depth=newValue;
	    updateDepthText();
	    createMaze();
	    repaint();
	}
    }

    public void psComputations() {
    }

    public void createMaze() {

	possible=sValue*pValue-2*(pValue+sValue)>0 
	    ? (pValue<=pMAX && sValue<=sMAX ? POSSIBLE : OVERFLOW) 
	    : IMPOSSIBLE;
	if(possible==POSSIBLE) {
	    Onion o=new Onion();
	    o.doAll(pValue,sValue,depth);

	    // Read the points and segments

	    int l=o.vertices.size();
	    pts=new Complex[l];
	    point=new Complex[l];
	    for(int i=0; i<l; i++) {
		point[i]=((Onion.Vertex) o.vertices.elementAt(i)).z;
	    }

	    l=o.segments.size();
	    /*
	    int m=0;
	    for(int i=0; i<l; i++) {
		if(!((Onion.Segment) o.segments.elementAt(i)).isOpen) m++;
	    }
	    */
	    int m=l;

	    segment=new int[m][2];
	    isOpen=new boolean[m];
	    int k=0;
	    Onion.Segment s;
	    for(int i=0; i<l; i++) {
		s=(Onion.Segment) o.segments.elementAt(i);
//		if(!s.isOpen) {
		    isOpen[k]=s.isOpen;
		    segment[k][0]=o.vertices.indexOf(s.v1);
		    segment[k][1]=o.vertices.indexOf(s.v2);
    //	    System.out.println(s.v1.z.re+ " " + s.v1.z.im);
    //    	    System.out.println(segment[k][0]+ " " + segment[k][1]);
    //    	    System.out.println(point[segment[k][0]].re+ " " + point[segment[k][1]].re);
		    k++;
//		}
	    }

	    //	    System.out.println(point.length+" points\n");
	    // CE QUI PRECEDE EST INEFFICACE : A CHANGER

	    /*
	    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;
	    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;

	    // 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);
	    Complex zz3=new Complex(r2*Math.cos(Math.PI/pValue),
			    r2*Math.sin(Math.PI/pValue));

	    Vector activeList;
	    MySegment ms,sgp,sgn,sg;
//	    for(int count=0;l1!=null; count++) {
	    for(int count=0;l1!=null && count<4; 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 {

		
		// 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;
			    }
			    // 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;
			    }			
			}
		    }
		}
		}
	    }
*/
	    /*
	    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)<eFog
//		   || segDistToZ(aa.z,bb.z,zz2)<eFog
//		   || segDistToZ(aa.z,bb.z,zz3)<eFog
                //		   ) {
		    if(aa.index==-1) {
			aa.index=ind++;
			pt2.add(aa);
		    }
		    if(bb.index==-1) {
			bb.index=ind++;
			pt2.add(bb);
		    }
		    seg2.add(new MySegment(aa,bb,false,null,null));
		    //		}
	    }


	    l=pt2.size();
	    point=new Complex[l];
	    for(int i=0; i<l; i++) {
		point[i]=(((MyPoint) pt2.get(i)).z);
	    }
	    pts=new Complex[l];


	    l=seg2.size();
	    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);
	    }
	    */
	}
    }

    /*
     * 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];
	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>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);

		}
		a[0]=(int)(127+128*tr);
		a[1]=(int)(127+128*tr);
		a[2]=(int)(127+128*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);
		*/
	    }
	}
    }

    // Overridden 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("polygon sides",XOFF+SIZE+XOFF5+50,YOFF5+14);
	g.drawString("polygons per vertex",XOFF+SIZE+XOFF6+50,YOFF6+14);
	g.drawString("layers",XOFF+SIZE+XOFF9+50,YOFF9+14);
	// 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.blue);

	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();
	    }
	}
	for(int i=0; i<segment.length; i++) {
	    z1=pts[segment[i][0]];
	    z2=pts[segment[i][1]];
	    g.setColor(isOpen[i] ? Color.yellow : Color.blue);
	    segment(g,z1,z2);
	}

	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 + 25,YOFF+SIZE/2+5);
	}

	// 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);

	gg.drawImage(bufferedImage,0,0,this);
    }

    public void paint(Graphics gg) {
	update(gg);
    }

    public Hyp() {
    }

    public void init() {

	klein=false;
	quality=false;
        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();
	rand= new Random();
	hFog=DEFAULT_HFOG;
	double aux=Math.exp(2*hFog);
	eFog=(aux-1)/(aux+1);
	pValue=DEFAULT_P;
	sValue=DEFAULT_S;
	depth=DEFAULT_DEPTH;
	psComputations();
	createMaze();
	fogText=new TextField("fog",5);
	pText=new TextField("p",2);
	sText=new TextField("s",2);
	depthText=new TextField("depth",2);
	add(fogText);
	add(pText);
	add(sText);
	add(depthText);
	updateFogText();
	updatePText();
	updateSText();
	updateDepthText();
	//	bascule=false;
	fogText.addActionListener(this);
	fogText.addFocusListener(this);
	pText.addActionListener(this);
	pText.addFocusListener(this);
	sText.addActionListener(this);
	sText.addFocusListener(this);
	depthText.addActionListener(this);
	depthText.addFocusListener(this);
	/* MARCHE PAS...
	fogText.setFont(font1);
	pText.setFont(font1);
	sText.setFont(font1);
	depthText.setFont(font1);
	*/
	kleinBox=new Checkbox("");
	kleinBox.setState(klein);
	kleinBox.setBackground(myGray);
	kleinBox.addItemListener(this);
	add(kleinBox);
	qualityBox=new Checkbox("");
	qualityBox.setState(quality);
	qualityBox.setBackground(myGray);
	qualityBox.addItemListener(this);
	add(qualityBox);

	lastMovedTime=new Date().getTime();

	mask=new BufferedImage(SIZE,SIZE,BufferedImage.TYPE_INT_ARGB);
	updateKlein(); // Will call setMaskAccordingToFog

	updateQuality(); 

	setLayout(this);
    }

    public void stop() {
        thread=null;
    }

}
