/*
 * Applet : Zak2
 */

// Bibilothèques utilisées 

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/*
 * Classe principale
 */

public class Zak2
    extends Base
       // J'ai écrit la classe Base qui regroupe des fonctionnalités
       // communes à mes applets, voir description.txt
    implements 
	MouseListener,
	MouseMotionListener,
	KeyListener,
	LayoutManager,
	Runnable
{
    /*
     *                             Champs
     */

    /*
     * Je mets ici les constantes utilisées dans le placement des 
     * zones de l'applet
     */
    public static int
	XOFF=20, // position de l'aire d'affichage (Upper Left corner)
	YOFF=20, // idem
        XSIZE=360,
        YSIZE=360;

    public static int
	STEPS=400,
	COUNTMAX=1000000,
	ITER=500;

    public static double
	scale0=4.0,
	LOG=Math.log(10.0)/3.0;

    public int scaleLevel;

    public Image aire,bkgnd;

    public Thread thread;

    public Rectangle bounds; // mémorise la taille de la zone d'affichage

    public int clickedX,clickedY;
    public double savedoffx,savedoffy;

    public boolean newCFlag,bifRestart,bifFini;

    public boolean back; // background ?

    // 
    public Font zeFont;

    //
    public Color COL1,COL2;

    //
    public Complex a1,a2,a3,c,lambda,z1,z2;
    public Tc aa1,aa2,aa3,cc1,cc2,zz1,zz2;
    public int bifI, bifJ, bifStep;
    public double theta;
    public double xoff,yoff,scale;

    /*
     *                          Sous-classes
     */

    /*
     * La sous-classe Complex est vraiment moche...
     */
    class Complex {
	double re;
	double im;
	Complex(double x, double y) {
	    re=x;
	    im=y;
	}
	public Complex copy() {
	    return new Complex(re,im);
	}
    }

    Complex mul(Complex a, Complex b) {
	return new Complex(a.re*b.re-a.im*b.im,a.re*b.im+a.im*b.re);
    }

    Complex add(Complex a, Complex b) {
	return new Complex(a.re+b.re,a.im+b.im);
    }

    double norm(Complex a) {
	return(a.re*a.re+a.im*a.im);
    }

    Complex conj(Complex a) {
	return new Complex(a.re,-a.im);
    } 

    Complex inv(Complex a) {
	double d=norm(a);
	return new Complex(a.re/d,-a.im/d);
    }

    Complex opp(Complex a) {
	return new Complex(-a.re,-a.im);
    }

    Complex r2c(double x) {
	return new Complex(x,0);
    }

    class Tc {
	Complex z;
	Complex dz;
	Tc(Complex a, Complex b) {
	    z=a;
	    dz=b;
	}
	public Tc copy() {
	    return new Tc(z,dz);
	}
    }

    Tc mul(Tc a, Tc b) {
	return new Tc(mul(a.z,b.z),add(mul(a.z,b.dz),mul(a.dz,b.z)));
    }

    Tc add(Tc a, Tc b) {
	return new Tc(add(a.z,b.z),add(a.dz,b.dz));
    }

    Tc inv(Tc a) {
	Complex b=inv(a.z);
	return new Tc(b,mul(opp(a.dz),mul(b,b)));
    }

    Tc opp(Tc a) {
	return new Tc(opp(a.z),opp(a.dz));
    }

    Tc c2Tc(Complex z) {
	return new Tc(z,r2c(0));
    }
    Tc r2Tc(double x) {
	return new Tc(new Complex(x,0),r2c(0));
    }

    public static double thick=0.1;

    boolean nul(Tc u) {
	return(norm(u.dz)*thick>norm(u.z));
    }

    /*
     *                           Méthodes
     */

    /* the polynomial, Horner's method */
    public Complex f(Complex z) {
	return(mul(z,add(a1,mul(z,add(a2,mul(z,a3))))));
    }
    public Tc ff(Tc z) {
	return(mul(z,add(aa1,mul(z,add(aa2,mul(z,aa3))))));
    }

    /* */
    public synchronized void newC() {
	lambda=new Complex(Math.cos(2*Math.PI*theta),Math.sin(2*Math.PI*theta));
	a1=lambda.copy(); 
	a2=mul(mul(lambda,add(r2c(1),inv(c))),r2c(-0.5));
	a3=mul(lambda,inv(mul(r2c(3),c)));
	//	count=0;
	z1=new Complex(1,0);
	z2=c.copy();
    }

    /* Conversions */
    public int toX(double x) {
	return((int)(((x-xoff)/scale)*(double) XSIZE));
    }
    public int toY(double y) {
	return((int)((1-((y-yoff)/scale))*(double) YSIZE));
    }

    public void computeScale() {
	scale=scale0*Math.exp(LOG*scaleLevel);
	xoff=-scale/2;
	yoff=-scale/2;
	newCFlag=true;
	bifRestart=true;
    }

    /*
     * Mémorise la taille de la zone d'affichage et calcule 
     * la position des objets dans icelle
     */
    public void storeBounds() {
	bounds=getBounds(); // prendre la taille de la zone
                            // d'affichage de la fenêtre

    }

    /*
     * 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 : C'est un explorateur de disques de Siegels de période 1\n"
+ "  chez les polynômes cubiques.\n"
+ "\n"
+ "  -  Un tel polynôme cubique possède deux points critiques.\n"
+ "  -  On place en 0 le point fixe indifférent de nombre de rotation\n"
+ "     le nombre d'or.\n"
+ "  -  On place en 1 l'un des points critique (le rouge).\n"
+ "  -  On place en c l'autre (le bleu).\n"
+ "  -  Est affichée en rouge l'orbite du point critique rouge.\n"
+ "  -  Est affichée en bleu ...\n"
+ "  -  L'orbite d'un des deux (parfois des deux) est le bord du disque de Siegel.\n"
+ "  -  Le lieu de bifurcation est figuré en clair en arrière plan.\n"
+ "\n"
);
	auxString.append(
  "\n"
+ "Commandes :\n"
+ "\n"
+ "  -  Presser '+'/'-' pour zoomer/dézoomer.\n"
+ "  -  Presser 'b' pour (dés)activer l'affichage de l'image du fond\n"
+ "  -  Cliquer pour positionner le point critique bleu.\n"
+ "  -  La touche '?' ou un click droit ouvrent cette fenêtre d'information.\n"
+ "\n" 
+ "Zak2, ©2006 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());

	//
	setLayout(this);

	// Admin stuff : sans ces appels, les évènements ne sont pas
	// redirigés sur le programme

	addKeyListener(this);
	addMouseListener(this);
	addMouseMotionListener(this);

	/*
	 * Initialisation des variables utilisateurs
	 */

	theta=(Math.sqrt(5)-1)/2.0;

	aire=createImage(XSIZE,YSIZE);
	bkgnd=createImage(XSIZE,YSIZE);

	back=true;

	// La fonte
	zeFont=new Font("Courier",Font.PLAIN,16);

	/*
	myBlue=new Color(0,64,255);
	*/
	COL1=new Color(255,128,0);
	COL2=new Color(255,160,160);

	/* Maths */

	c=new Complex(1,0);
	scaleLevel=0;
	computeScale();

	/* Launch thread */

	thread=new Thread(this); 
	thread.start();

    }

    /*
     * Pour gérer l'affichage de la class Base,
     * il faut modifier la méthode offPaint
     */
    public void offPaint(Graphics g) {
  

	storeBounds();

	// Fond en gris

	g.setColor(Color.lightGray);
	g.fillRect(0,0,bounds.width,bounds.height);

	// Contour en noir du rectangle mathématique

	g.setColor(Color.black);
	g.drawRect(XOFF-1,YOFF-1,XSIZE+2,YSIZE+2);

	// L'aire

	g.drawImage(aire,XOFF,YOFF,this);

    }

    /*
    public boolean testBut(Rectangle but, int x, int y) {
	return(x>but.x && x<but.x+but.width && y>but.y && y<but.y+but.height);
    }
    */

    /*
     *                Implémentation des "interfaces"
     */

    // Interface MouseListener

    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	mouseDragged(e);
    }
    public void mouseReleased(MouseEvent e) {
    }


    // Interface MouseMotionListener

    public void mouseDragged(MouseEvent e) {
	int x=e.getX();
	int y=e.getY();
	if(x>XOFF && x<XOFF+XSIZE && y>YOFF && y<YOFF+YSIZE) {
	    c=new Complex(scale*((x-XOFF)/(double) XSIZE)+xoff,
                          scale*(1-((y-YOFF)/(double) YSIZE))+yoff);
	    newCFlag=true;
	}

    }
    public void mouseMoved(MouseEvent e) {
    }

    // Interface KeyListener

    public void keyPressed(KeyEvent e) {
    }
    public void keyReleased(KeyEvent e) {
    }
    public void keyTyped(KeyEvent e) {
	if(e.getKeyChar()=='+') {
	    scaleLevel-=1;
	    computeScale();
	}
	else if(e.getKeyChar()=='-') {
	    scaleLevel+=1;
	    computeScale();
	}
	else if(e.getKeyChar()=='b') {
	    back=!back;
	    newCFlag=true;
	}
    }

    // Interface Runnable

    public void run() {
	Thread curThread = Thread.currentThread();
	while(thread==curThread) {

	if(bifRestart) {
	    Graphics g2=bkgnd.getGraphics();
	    int w,h;
	    w=bkgnd.getWidth(this);
	    h=bkgnd.getHeight(this);
	    g2.setColor(new Color(0.9f,0.9f,0.9f));
	    g2.fillRect(0,0,w,h);
	    bifFini=false;
	    bifRestart=false;
	    bifStep=1;
	    for(; bifStep<XSIZE; ) {
		bifStep=bifStep*2;
	    }
	    bifI=0;
	    bifJ=0;
	}

	Graphics g=aire.getGraphics();

	if(newCFlag) {
	    newC();
	    int w,h;
	    w=aire.getWidth(this);
	    h=aire.getHeight(this);
	    if(back) {
		g.drawImage(bkgnd,0,0,this);		
	    }
	    else {	
		g.setColor(Color.white);
		g.fillRect(0,0,w,h);		
	    }
	    int x,y;
	    x=toX(0);
	    y=toY(0);
	    g.setColor(Color.lightGray);
            g.drawLine(x,0,x,h);
            g.drawLine(0,y,w,y);
	    x=toX(1);
	    y=toY(0);
	    g.setColor(new Color(1.0f,0.5f,0.5f));
            g.drawLine(x,0,x,h);
	    newCFlag=false;
	    x=toX(z2.re);
	    y=toY(z2.im);
	    g.setColor(new Color(0.5f,0.5f,1.0f));
            g.drawLine(x,0,x,h);
            g.drawLine(0,y,w,y);
	}

	if(!bifFini) {
	    Graphics g2=bkgnd.getGraphics();
	    int counter=0,k;
	    double R=10000;
	    Color pixelColor;
	    for(;counter<STEPS && !bifFini;) {
		if((bifI % (2*bifStep))!=0 || (bifJ % (2*bifStep))!=0) {
		    cc1=r2Tc(1);
	cc2=new Tc(new Complex(scale*((bifI)/(double) XSIZE)+xoff,
                               scale*(1-((bifJ)/(double) YSIZE))+yoff),
		   new Complex(scale/XSIZE,0));
                    aa1=c2Tc(lambda); 
		    aa2=mul(mul(aa1,add(r2Tc(1),inv(cc2))),r2Tc(-0.5));
		    aa3=mul(aa1,inv(mul(r2Tc(3),cc2)));
		    zz1=cc2.copy();
		    zz2=cc1.copy();
		    int fl=0;
		    loop:
		    for(k=0; k<ITER && fl==0; k++) {
			zz1=ff(zz1);
			zz2=ff(zz2);
			if(norm(zz1.z)>R) {fl=3; break loop;}
			if(norm(zz2.z)>R) {fl=3; break loop;}
			if(nul(add(zz1,opp(cc1)))) {fl=1; break loop;}
			if(nul(add(zz1,opp(cc2)))) {fl=1; break loop;}
			if(nul(add(zz2,opp(cc1)))) {fl=2; break loop;}
			if(nul(add(zz2,opp(cc2)))) {fl=2; break loop;}
		    }
		    
		    switch(fl) {
		    case 1 : pixelColor = new Color(0.8f,0.6f,0.6f); break;
		    case 2 : pixelColor = new Color(0.6f,0.6f,1.0f); break;
		    case 3 : pixelColor = new Color(1.0f,1.0f,1.0f); break;
		    default : pixelColor = new Color(0.8f,0.8f,0.8f); break;
		    }
		    
		    g2.setColor(pixelColor);
		    g2.fillRect(bifI,bifJ,bifStep,bifStep);
		    
		    counter+=k;
		}

		bifI+=bifStep;
		if(bifI>=XSIZE) {
		    bifI=0;
		    bifJ+=bifStep;
		    if(bifJ>=YSIZE) {
			bifJ=0;
			newCFlag=true;
			if(bifStep<2) {
			    bifFini=true;
			}
			else {
			    bifStep=bifStep/2;
			}
		    }
		}
	    }
	}

	int x,y;
	for(int i=0; i<STEPS; i++) {
	    if(norm(z1)<10000) {
		x=toX(z1.re);
		y=toY(z1.im);
		if(x>=0 && x<XSIZE && y>=0 && y<YSIZE) {
		    g.setColor(Color.red);
		    g.fillRect(x,y,2,2);
		}
		z1=f(z1);
	    }
	    if(norm(z2)<10000) {
		x=toX(z2.re);
		y=toY(z2.im);
		if(x>=0 && x<XSIZE && y>=0 && y<YSIZE) {
		    g.setColor(Color.blue);
		    g.fillRect(x,y,2,2);
		}
		z2=f(z2);
	    }
	}
	repaint();
	}
    }

    // 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() {
    }
    
    /* Cette fonction parle d'elle-même
     * elle n'est pas utilisée par les navigateurs actuels
     */
    public String getAppletInfo() {
         return "Zak2, ©2006 Arnaud Chéritat";
    }
}
