/***********************************************
 * Ising.java                                  *
 * -----------------------------------         *
 * Created by Bernd Nottelmann in 1996         *
 * Last update (by Peter Young): March 25, 1997*
 ***********************************************/


import java.lang.*;
import java.awt.*;
import java.awt.image.*;


public class Ising extends java.applet.Applet implements Runnable {
  /* Gitterparameter und Feldinhalt */
  int dim;                             // Dimension des Modells
  int[] ndim;                          // Ausdehnung in jede Raumrichtung
  int period;                          // Zeitvariablen
  
  public IsingLattice lattice;         // Eigentliches Gitter, hier spielt
                                       // sich alles Physikalische ab

  /* Oeffentliche Parameter */
  public int n;                        // Gitterausdehnung in jede Richtung
 
  /* Physikalische Parameter des Modells */
  public double Temp;

  /* Java-Objekte und -Parameter */
  Thread is;
  public boolean stopped=false;        // Halte-Flag

  short plotValue=1;                   // Spin-Wert
  boolean plotFlag=false;              // Plot-Flag
  
  BorderLayout IsLay;                  // Layout
  
  IsingCanvas can;                     // Hier kommt das Gitter hin ...
  IsingPanel pan;                      // ... und hier die Bedienleiste

// int[] Panxy={100,70,100,60};         // Platz fuer die Bedienelemente
  int[] Panxy={100,15,50,10};         // Platz fuer die Bedienelemente
                                       // muss auch sein
  int timeout=50;                      // Pause-Intervall fuer die
                                       // Bilderzeugung in ms
  public int t;                        // Zeitvariable
  

  public void init() {
    int j;                             // Indexvariable
    
    dim=2;                             // Quadratisch, praktisch, ...
    n=Integer.valueOf(getParameter("latt")).intValue();
                                       // Gitterausdehnung holen
    Temp=Double.valueOf(getParameter("temp")).doubleValue();
                                       // Temp. aus dem Applet Tag holen

    ndim=new int[dim];
    for (j=0; j<dim; j++)
      ndim[j]=n;
    period=2*n;

    /* Gitter installieren */
    lattice=new IsingLattice(dim,ndim,period,Temp);
    lattice.initHot();
    t=0;
    
    /* Java-Initialisierungsteil */
    setLayout(IsLay=new BorderLayout());
    can=new IsingCanvas(this,Panxy);
    pan=new IsingPanel(this,can);

    can.init();
    pan.init();
    
    add("Center",can);
    add("South",pan);
  }

  public void start() {
    if (!stopped) {
      is=new Thread(this);
      is.start();
    }
    pan.enable();
  }

  public void stop() {
    pan.disable();
    if (is.isAlive()) {
      is.stop();
      System.gc();
    }
  }
  
  public void brk() {
    is.stop();
    stopped=true;
    System.gc();
  }

  public void cont() {
    stopped=false;
    is=new Thread(this);
    is.start();
  }

  public void setTemp(double Temp) {
    lattice.setTemp(Temp);
    pan.TempText.setText(new Double(lattice.Temp).toString());
    pan.TempText.show();
    can.redraw(true,false,false,false);
  }

  public void run() {
    while (true) {
      lattice.ssdUpdate();
      ++t;
      can.Hamilton.addValue(lattice.Hamilton(lattice.t));
      can.Magnetization.addValue(lattice.Magnetization(lattice.t));
      try{
	Thread.currentThread().sleep(timeout);
	}
      catch
	(InterruptedException e) {}
	{
      can.redraw(true,true,true,true);
      }
    }
  }

  public void paint() {
    lattice.ssdUpdate();
    ++t;
    can.Hamilton.addValue(lattice.Hamilton(lattice.t));
    can.Magnetization.addValue(lattice.Magnetization(lattice.t));
    can.redraw(false,true,true,true);
  }

  public boolean handleEvent(Event ev) {
    switch (ev.id) {
    case Event.WINDOW_DESTROY:
      System.exit(0);
    case Event.MOUSE_DRAG:
      if (can.Tempxy[0]<=ev.x && ev.x<=can.Tempxy[2] &&
	  can.Tempxy[1]<=ev.y && ev.y<=can.Tempxy[3]) {
	setTemp((double)(can.Tempxy[3]-ev.y)/
		(double)(can.Tempxy[3]-can.Tempxy[1])*
		(lattice.TempMax-lattice.TempMin));
	return true;
      }
      else
	if (can.TempCritxy[0]<=ev.x && ev.x<=can.TempCritxy[2] &&
	    can.TempCritxy[1]<=ev.y && ev.y<=can.TempCritxy[3]) {
	  setTemp(lattice.TempCrit);
	  return true;
	}
	else
	  if (Panxy[0]<=ev.x && ev.x<Panxy[0]+can.wh &&
	      Panxy[1]<=ev.y && ev.y<Panxy[1]+can.wh) {
	    can.plot((int)((ev.x-Panxy[0])/can.dxy),
		     (int)((ev.y-Panxy[1])/can.dxy),plotValue);
	    can.redraw(false,true,false,false);
	  }
      break;
    case Event.MOUSE_DOWN:
      if (Panxy[0]<=ev.x && ev.x<Panxy[0]+can.wh &&
	  Panxy[1]<=ev.y && ev.y<Panxy[1]+can.wh) {
	plotFlag=true;
	plotValue=(short)-lattice.getValue((int)((ev.x-Panxy[0])/can.dxy),
					   (int)((ev.y-Panxy[1])/can.dxy));
	return true;
      }
      break;
    case Event.MOUSE_UP:
      if (Panxy[0]<=ev.x && ev.x<Panxy[0]+can.wh &&
	  Panxy[1]<=ev.y && ev.y<Panxy[1]+can.wh) {
	plotFlag=false;
	return true;
      }
      break;
    case Event.KEY_PRESS:
      if (ev.key == 10) {
	setTemp(Double.valueOf(pan.TempText.getText().trim()).doubleValue());
	return true;
      }
      break;
    case Event.KEY_RELEASE:
      break;
    default:
      if (ev.target instanceof Button) {
	String label=(String)ev.arg;
	if (label.equals("Init cold")) {
	  if (is.isAlive()) brk();
	  lattice.initCold(-1);
	  t=0;
	  can.reset();
	  can.redraw(false,true,true,true);
	}
	else
	  if (label.equals("Init hot")) {
	    if (is.isAlive()) brk();
	    lattice.initHot();
	    t=0;
	    can.reset();
	    can.redraw(false,true,true,true);
	  }
	  else
	    if (label.equals("Init warm")) {
	      if (is.isAlive()) brk();
	      lattice.initWarm();
	      t=0;
	      can.reset();
	      can.redraw(false,true,true,true);
	    }
	    else
	      if (label.equals("Start")) {
		if (!is.isAlive()) cont();
	      }
	      else
		if (label.equals("Stop")) {
		  brk();
		  can.SpinsDrawn=false;
		  can.redraw(false,true,true,true);
		}
		else
		  if (label.equals("Step")) {
		    if (is.isAlive()) brk();
		    paint();
		  }
		  else
		    return false;
	return true;
      }
      else
	if (ev.target instanceof Checkbox) {
	  if (!is.isAlive())
	    can.redraw(false,true,false,false);
	  return true;
	}
    }
    return false;
  }

  public static void main(String args[]) {
    //System.out.println("Here is main!");
    Frame frame=new Frame("Ising-Modell");
    Ising ising=new Ising();

    ising.init();
    ising.start();

    frame.add("Center",ising);
    frame.resize(frame.preferredSize());
    frame.show();
  }
}


/** Index-Klasse (wird fuers Permutieren benoetigt) **/
class IsingIntIndex {
  public int[] value;                  // Feldwert
  public int[] prev, next;             // Vorheriger/naechster Index
  int n, filled;                       // Feldgroesse, Fuellstand
  int first, last;                     // Erstes/letztes Element
  int i, actual;                       // Indexvariablen
  int[] retval;                        // Rueckgabefeld
  
  public IsingIntIndex(int n) {        // Konstruktor, n: Feldgroesse
    value=new int[n];
    prev=new int[n];
    next=new int[n];
    this.n=n;
    first=last=-1;
    filled=0;
    retval=new int[n];
  }

  public void init(int val) {          // Mit erstem Eintrag initialisieren
    value[0]=val;
    prev[0]=next[0]=-1;
    first=last=0;
    filled=1;
  }

  public void insert(int val, int k) { // Element val an Stelle k einfuegen
    actual=first;
    for (i=0; i<k; ++i)
      if (actual > -1) actual=next[actual];
    value[filled]=val;
    if (actual > -1) {
      if (actual!=first) {
	next[prev[actual]]=filled;
	prev[filled]=prev[actual];
      }
      else  
	first=filled;
      prev[actual]=filled;
    }
    else {
      prev[filled]=last;
      next[last]=filled;
      last=filled;
    }
    next[filled++]=actual;
  }

  public int[] get() {                 // Feld holen
    actual=first;
    for (i=0; i<filled; ++i) {
      retval[i]=value[actual];
      actual=next[actual];
    }
    return retval;
  }
}


/** Gitter-Klasse fuers Ising-Modell **/
final class IsingLattice {
  /* Gitterparameter und Feldinhalt */
  public int dim=2;                    // Quadratisch, praktisch, ...
  public int[] ndim;                   // Ausdehnung in jede Raumrichtung
  public int nind;                     // Volumen des Spinfeldes
  public int[][] index;                // Feld benachbarter Indizes
  private int[] index0, index1, index2, index3;
                                       // Verbessert hoffentlich Zeitverhalten

  /* Weitere oeffentliche Parameter */
  public short[][] sigma;              // Spinfeld
  public int n=0;                      // dito, nur in einer Variable
  public int t=0;                      // Aktuelle Zeit;
  
  /* Physikalische Parameter des Modells */
  public static final double TempMin=0.01;
  public static final double TempMax=6;
  public static final double TempCrit=2.2691;
                                       // TempCrit=2/ln(1+sqrt 2)
  public double Temp;
  public double beta;
  double[] tabexp = new double [9];

  int[] randindex;                     // Zufallspermutiertes Indexfeld
  IsingIntIndex helpIndex;             // Hilfsindexfeld fuer die Perms
  int t0=t, period;                    // Zeitvariablen


  public IsingLattice(int dim, int[] ndim, int period, double Temp) {
    int i, j;                          // Indexvariable
    
    /* Gitter initialisieren */
    this.dim=dim;
    this.ndim=ndim;
    this.period=period;
    if (Temp >= TempMin && Temp < TempMax)
      this.Temp=Temp;
    else
      this.Temp=(TempMin+TempMax)/2;   // Die goldene Mitte
    beta=1/this.Temp;
    tabexp[8] = Math.exp(-8*beta) / (Math.exp(-8*beta) + 1. );
    tabexp[6] = Math.exp(-4*beta) / (Math.exp(-4*beta) + 1. );
    tabexp[4] = 0.5;
    tabexp[2] = 1. / (Math.exp(-4*beta) + 1.);
    tabexp[0] = 1. / (Math.exp(-8*beta) + 1.);
    
    nind=1;                            // Volumen des Feldes berechnen
    for (j=0; j<dim; j++)
      nind*=ndim[j];

    sigma=new short[period][nind];     // T=2*n: Ausdehnung in Zeitrichtung
    
    index=new int[2*dim][nind];
    randindex=new int[nind];
    for (i=0; i<nind; i++)             // Zufallsindex erst einmal mit
      randindex[i]=i;                  // 0..nind initialisieren
    helpIndex=new IsingIntIndex(nind);
    
    calcIndex();
    index0=index[0]; index1=index[1]; index2=index[2]; index3=index[3];
  }

  /** Kaltstart
    value: Initialisierungswert
    **/
  public void initCold(int value) {
    int i;

    for (i=0; i<nind; i++)
      sigma[t][i]=(short)value;
  }

  /** Hotstart **/
  public void initHot() {
    int i, j, m, digit, v, sgn;

    for (i=0; i<nind; i++) {
      v=1; m=i; sgn=0;
      for (j=0; j<dim; j++) {
	digit=m%ndim[j];
	sgn^=digit&1;
	m=(m-digit)/ndim[j];
	v*=ndim[j];
      }
      sigma[t][i]=(short)(2*sgn-1);
    }
  }
  
  /** Warmstart **/
  public void initWarm() {
    int i;
    
    for (i=0; i<nind; i++)
      sigma[t][i]=(short)(2*(short)(2*Math.random())-1);
  }

  /** Funktion F mit F(z)/F(1/z)=z **/
  private double F(double z) {
    return (z < 1) ? z : 1;
  }

  /** Indexfelder initialisieren (mit periodischen Randbedingungen) **/
  private void calcIndex() {
    int i, j, m, digit, v;
    
    for (i=0; i<nind; i++) {           // Lineare Nachbarindizes bestimmen
      v=1; m=i;                        // v: "Zwischen"volumen
      for (j=0; j<dim; j++) {
	digit=m%ndim[j];
	index[j][i]=i-(digit-(ndim[j]+digit-1)%ndim[j])*v;
	index[j+dim][i]=i-(digit-(digit+1)%ndim[j])*v;
	m=(m-digit)/ndim[j];
	v*=ndim[j];
      }
    }
  }

  /** Wert holen **/
  public short getValue(int x, int y) {
    if (0 <= x && x < ndim[0] && 0 <= y && y < ndim[1])
      return sigma[t][x+ndim[0]*y];
    else
      return 0;
  }

  /** Temperatur setzen
    Temp: na ratet mal!
    **/
  public void setTemp(double Temp) {
    if (TempMin <= Temp && Temp < TempMax) {
      this.Temp=Temp;
      beta=1/Temp;
      tabexp[8] = Math.exp(-8*beta) / (Math.exp(-8*beta) + 1. );
      tabexp[6] = Math.exp(-4*beta) / (Math.exp(-4*beta) + 1. );
      tabexp[4] = 0.5;
      tabexp[2] = 1. / (Math.exp(-4*beta) + 1.);
      tabexp[0] = 1. / (Math.exp(-8*beta) + 1.);
    }
    else
      if (Temp >= TempMax) {
	this.Temp=TempMax;
	beta=0;
        tabexp[8] = Math.exp(-8*beta) / (Math.exp(-8*beta) + 1. );
        tabexp[6] = Math.exp(-4*beta) / (Math.exp(-4*beta) + 1. );
        tabexp[4] = 0.5;
        tabexp[2] = 1. / (Math.exp(-4*beta) + 1.);
        tabexp[0] = 1. / (Math.exp(-8*beta) + 1.);
      }
  }

  /** 2 dim-Hamiltonian des Ising-Modells
    t: Zeit (ist doch offensichtlich! :-)
    **/
  public double Hamilton(int t) {
    int i;
    double sum=0;
    
    for (i=0; i<nind; ++i)
      sum+=sigma[t][i]*(sigma[t][index0[i]]+sigma[t][index1[i]]);
    
//   return -2*sum;
     return -sum/nind;
  }

  public double HamiltonMin() {
//  return -2*dim*nind;
    return -dim;
  }

  public double HamiltonMax() {
//  return 2*dim*nind;
    return dim;
  }

  public double Magnetization(int t) {
    int i;
    int sum=0;

    for (i=0; i<nind; ++i)
      sum+=sigma[t][i];

    return (double)sum/nind;
  }

  /** Indexfeld zufaellig permutieren **/
  private void perm() {
    int i, k;

    helpIndex.init(randindex[0]);

    for (i=1; i<nind; ++i) {
      k=(int)((i+1)*Math.random());
      helpIndex.insert(randindex[i],k);
    }
     
    randindex=helpIndex.get();
  }

  /** Spinkonfiguration updaten fuer Single-spin-flip dynamics **/
  public synchronized int ssdUpdate() {
    int i, k, de;
    int flag;
    double abuf, rand;
    int sigmasum=0;
    
    t0=t;                              // Zeit urspruengliche Spinkofiguration
    t=++t%period;                      // Zeit aktuelle Spinkonfiguration

    for (i=0; i<nind; ++i)
      sigmasum+=sigma[t][i]=sigma[t0][i];

      for (i=0; i<nind; ++i) {
	de = 4 + sigma[t][i] * 
	    (sigma[t][index0[i]] + sigma[t][index1[i]]
	    +sigma[t][index2[i]] + sigma[t][index3[i]]);
	rand=Math.random();
   	if ( rand < tabexp[de] ) {
	  sigma[t][i]=(short)-sigma[t][i];
	  sigmasum+=2*sigma[t][i];
	}
      }
    return sigmasum;
  }
}


class IsingFever {
  String title;                        // Titel (:-)
  double[] y;                          // y-Werte der "Fieberkurven";
  public int n;                        // Zahl der Werte
  int width, height;                   // Breite und Hoehe
  int offset, top, bottom;             // y-Offset, Begrenzungslinien
  int range;                           // Zeichenbereich
  double xScale, yScale;               // Skalierungsfaktoren
  int index, length;                   // Aktuelle Kennwerte der Kurven
  double yMin, yMax, yMean;            // Extremwerte + deren Mittelwert
  public Image img;
  Graphics g;
  public Color color;
  
  public IsingFever(String title, int n) {
    //System.out.println("IsingFever(int n)");
    this.title=title;
    this.n=n;
    y=new double[n];
    index=-1;
    length=0;
  }

  public void init(double value, double yMin, double yMax) {
    index=0; length=1;
    y[index]=value;
    if (value < yMin || value > yMax || yMin > yMax)
      yMean=this.yMin=this.yMax=value;
    else {
      this.yMin=yMin;
      this.yMax=yMax;
      yMean=(yMin+yMax)/2;
    }
  }

  public void initGraphics(ImageObserver imgObs, Image img, Color color) {
    this.img=img;
    this.g=img.getGraphics();
    this.color=color;
    width=img.getWidth(imgObs);
    height=img.getHeight(imgObs);
    bottom=(int)(0.95*height+0.5)-1;
    top=(int)(0.05*height+0.5)-1;
    range=(int)(0.9*height);
    offset=height/2;
    xScale=(double)width/n;
    g.setColor(Color.black);
    g.fillRect(0,0,width,height);
    drawLines();
    draw(color);
  }

  private void draw(Color color) {
    int i, j, k;
    
    if (yMax > yMin)
      yScale=range/(yMax-yMin);
    else
      yScale=1;
    if (g != null) {
      g.setColor(color);
      i=index;
      k=length;
      while (--k > 0) {
	j=(n+i-1)%n;
	g.drawLine((int)(xScale*k),
		   offset-(int)((y[i]-yMean)*yScale+0.5),
		   (int)(xScale*(k-1)),
		   offset-(int)((y[j]-yMean)*yScale+0.5));
	i=j;
      }
    }
  }

  private void drawLines() {
    if (g != null) {
      g.setColor(Color.lightGray);
      g.drawLine(0,offset,width-1,offset);
      g.setColor(Color.darkGray);
      g.drawLine(0,top,width-1,top);
      g.drawLine(0,bottom,width-1,bottom);
    }
  }

  /* Wert hinzufuegen und gleichzeitig neue Kurve zeichnen */
  public synchronized void addValue(double value) {
    //System.out.println("addValue(double value)");
    draw(Color.black);            // Erstmal alte Kurve loeschen
    drawLines();
    index=(++index)%n;
    y[index]=value;
    if (value < yMin) yMin=value;
    else
      if (value > yMax) yMax=value;
    yMean=(yMin+yMax)/2;
    if (length < n) ++length;
    draw(color);                  // Neue Kurve zeichnen
  }

  /* Beschriftung */
  public void drawParams(Graphics g, int x, int y) {
    Font f;
    FontMetrics fm;
    int x0, y0;
    String s1, s2;
    int w1, w2, wMax;

    f=new Font("TimesRoman",Font.PLAIN,17);
    g.setFont(f);
    fm=g.getFontMetrics();
    s1=new Double(yMax).toString();
    s2=new Double(yMin).toString();
    w1=fm.stringWidth(s1);
    w2=fm.stringWidth(s2);
    wMax=(w1 <= w2) ? w1 : w2;
    
    g.setColor(Color.black);
    x0=x+width+8+wMax-w1;
    y0=y+top+f.getSize()/2;
    g.drawString(s1,x0,y0);
    x0=x+width+8+wMax-w2;
    y0=y+bottom+f.getSize()/2;
    g.drawString(s2,x0,y0);
    x0=x+width+8;
    y0=y+offset+f.getSize()/2;
    g.drawString(title,x0,y0);
  }
}


final class IsingCanvas extends Canvas implements ImageObserver {
  Ising is;
  int n;
  public int[] Panxy;
  public double dx, dy, dxy;
  public int wh;
  Rectangle Rect;
  Image img, HamiltonImg, MagnetizationImg;
  Graphics imgGra;
  public IsingFever Hamilton, Magnetization;
  boolean GridForbidden=false;
  public boolean GridFlag=true;
//public boolean GridFlag=false;
  boolean ThermoFlag=true, sigmaFlag=true, FeverFlag=true, TimeFlag=true;
  public boolean SpinsDrawn=false;
  short[] Spin;
  public int[] Tempxy=new int[4];
  public int[] TempCritxy=new int[4];
  
  public IsingCanvas(Ising is, int[] Panxy) {
    this.is=is;
    n=is.n;
    this.Panxy=Panxy;
    Hamilton=new IsingFever("Energy per spin",(int)(0.8*Panxy[2]));
    Magnetization=new IsingFever("Magnetization",(int)(0.8*Panxy[2]));
    Spin=new short[is.lattice.nind];
  }

  public void init() {
    Hamilton.init(is.lattice.Hamilton(is.lattice.t),
  	  is.lattice.HamiltonMin(),is.lattice.HamiltonMax());
    Magnetization.init(is.lattice.Magnetization(is.lattice.t),-1,1);
    SpinsDrawn=false;
  }

  public void reset() {
    init();
    Hamilton.initGraphics(this,
			  createImage(Hamilton.width,
				      Hamilton.height),
			  Hamilton.color);
    Magnetization.initGraphics(this,
			       createImage(Magnetization.width,
					   Magnetization.height),
			       Magnetization.color);
    SpinsDrawn=false;
  }

  public void setGridFlag(boolean GridFlag) {
    if (!GridForbidden) {
      this.GridFlag=GridFlag;
      if (GridFlag) {
	img.flush();
	img=createImage(wh+1,wh+1);
	imgGra=img.getGraphics();
	paintGrid(imgGra);
      }
      else {
	img.flush();
	img=createImage(wh,wh);
	imgGra=img.getGraphics();
      }
    }
    SpinsDrawn=false;
  }
  
  public void plot(int x, int y, short value) {
    if (x >= 0 && x < n && y >=0 && y < n) {
      Spin[x+n*y]=is.lattice.sigma[is.lattice.t][x+n*y]=value;
      imgGra.setColor(Color.blue);
      if (GridFlag)
	if (value > 0)
	  imgGra.fillRect((int)(x*dxy)+1,(int)(y*dxy)+1,
			  (int)dxy-1,(int)dxy-1);
	  else
	    imgGra.clearRect((int)(x*dxy)+1,(int)(y*dxy)+1,
			     (int)dxy-1,(int)dxy-1);
      else
	if (value > 0)
	  imgGra.fillRect((int)(x*dxy),(int)(y*dxy),
			  (int)dxy,(int)dxy);
	else
	  imgGra.clearRect((int)(x*dxy),(int)(y*dxy),
			   (int)dxy,(int)dxy);
    }
  }

  private void drawSpins() {
    int i, j, t, pos=0;
    
    imgGra.setColor(Color.blue);
    t=is.lattice.t;
    
    if (GridFlag)
      if (!SpinsDrawn)
	for (j=0; j<n; ++j)
	  for (i=0; i<n; ++i)
	    if (is.lattice.sigma[t][pos++] > 0)
	      imgGra.fillRect((int)(i*dxy)+1,(int)(j*dxy)+1,
			      (int)dxy-1,(int)dxy-1);
	    else
	      imgGra.clearRect((int)(i*dxy)+1,(int)(j*dxy)+1,
			       (int)dxy-1,(int)dxy-1);
      else
	for (j=0; j<n; ++j)
	  for (i=0; i<n; ++i) {
	    if (is.lattice.sigma[t][pos] != Spin[pos])
	      if (is.lattice.sigma[t][pos] > 0)
		imgGra.fillRect((int)(i*dxy)+1,(int)(j*dxy)+1,
			      (int)dxy-1,(int)dxy-1);
	      else
		imgGra.clearRect((int)(i*dxy)+1,(int)(j*dxy)+1,
				 (int)dxy-1,(int)dxy-1);
	    ++pos;
	  }
    else {
      imgGra.clearRect(0,0,wh+1,wh+1);
      for (j=0; j<n; ++j)
	for (i=0; i<n; ++i)
	  if (is.lattice.sigma[t][pos++] > 0)
	    imgGra.fillRect((int)(i*dxy),(int)(j*dxy),
			    (int)dxy,(int)dxy);
    }
    Spin=is.lattice.sigma[t];
    SpinsDrawn=true;
  }
  
  private void paintGrid(Graphics g) {
    int i;

    g.setColor(Color.black);
    for (i=0; i<=n; i++) {
      g.drawLine(0,(int)(i*dxy),wh,(int)(i*dxy));
      g.drawLine((int)(i*dxy),0,(int)(i*dxy),wh);
    }
  }

  /* Thermometer zeichnen */
  private void paintThermo(Graphics g) {
    double x,y,r1,r2,h;
    double factor=Math.PI/180;
    int x0,y0,x1,y1,h0,alpha,gamma;

    r1=0.1*Panxy[0];
    r2=0.05*Panxy[0];
    h=0.8*wh;
    x=Panxy[0]/2;
    y=Panxy[1]+wh-(wh-(h+r1+r2))/2;

    Tempxy[0]=(int)(x-r2);             // Koordinaten des Steuerbereichs
    Tempxy[1]=(int)(y-h);              // des Thermometers festlegen
    Tempxy[2]=(int)(x+r2);
    Tempxy[3]=(int)y;

    alpha=90+(int)(Math.asin(r2/r1)/factor);
    gamma=360-2*(alpha-90);
    x0=(int)(x-r1);
    y0=(int)(y-(r1-r1*Math.cos(factor*(alpha-90))));
    g.setColor(Color.red);
    g.fillArc(x0,y0,(int)(2*r1),(int)(2*r1),alpha,gamma);
    g.setColor(Color.black);
    g.drawArc(x0,y0,(int)(2*r1),(int)(2*r1),alpha,gamma);
    x0=(int)(x-r2);
    y0=(int)(y-h*is.lattice.Temp/is.lattice.TempMax);
    h0=(int)(h*is.lattice.Temp/is.lattice.TempMax+
	     r1*Math.cos(factor*(alpha-90)));
    g.setColor(Color.red);
    g.fillRect(x0,y0,(int)(2*r2),h0);
    y0=(int)(y-h);
    h0=(int)(h-h*is.lattice.Temp/is.lattice.TempMax);
    g.setColor(Color.lightGray);
    g.fillRect(x0,y0,(int)(2*r2),h0);
    g.setColor(Color.black);
    g.drawLine(x0,y0,x0,(int)(y0+h));
    g.drawLine(x0+(int)(2*r2),y0,x0+(int)(2*r2),(int)(y0+h));
    y0=(int)(y-h-r2);
    g.setColor(Color.lightGray);
    g.fillArc(x0,y0,(int)(2*r2),(int)(2*r2),0,180);
    g.setColor(Color.black);
    g.drawArc(x0,y0,(int)(2*r2),(int)(2*r2),0,180);
  }

  private void drawTitle(Graphics g, String s, int x, int y, int fs) {
    int red, green, blue;
    Font f;
    FontMetrics fm;
    int i;
    String subs;
    
    red=(int)(Math.random()*150);
    green=(int)(Math.random()*100);
    blue=(int)(Math.random()*50);
    f=new Font("TimesRoman",Font.ITALIC,fs);
    g.setFont(f);
    fm=g.getFontMetrics();
    y+=f.getSize();

    for (i=0; i<s.length(); i++) {
      subs=s.substring(i,i+1);
      g.setColor(new Color((red+x/2)%256,(green+x)%256,(blue+x/4)%256));
      g.drawString(subs,x,y);
      x+=fm.stringWidth(subs);
    }
  }

  private void drawTime(Graphics g) {
    Font f;
    FontMetrics fm;
    String s="t="+new Integer(is.t).toString();
    int x, y;
    
    f=new Font("TimesRoman",Font.PLAIN,17);
    g.setFont(f);
    fm=g.getFontMetrics();
    x=Panxy[0]+(wh-fm.stringWidth(s))/2;
    y=Panxy[1]-4;
    g.setColor(Color.black);
    g.clearRect(Panxy[0],Panxy[1]-f.getSize()-4,wh,f.getSize());
    g.drawString(s,x,y);
  }
  
  private void drawTempCrit(Graphics g) {
    Font f;
    FontMetrics fm;
    int x, y;
    String s1="T", s2="Crit";
    
    f=new Font("TimesRoman",Font.PLAIN,17);
    g.setFont(f);
    fm=g.getFontMetrics();
    x=Tempxy[2]+6;
    y=Tempxy[3]-
      (int)(is.lattice.TempCrit/(is.lattice.TempMax-is.lattice.TempMin)*
	    (Tempxy[3]-Tempxy[1]));

    TempCritxy[0]=Tempxy[2];
    TempCritxy[1]=y-f.getSize()/2;
    g.setColor(new Color(127,0,127));
    g.drawLine(Tempxy[2]+1,y,Tempxy[2]+4,y);
    y+=f.getSize()/2;
    g.drawString(s1,x,y);
    x+=fm.stringWidth(s1);
    f=new Font("TimesRoman",Font.PLAIN,12);
    g.setFont(f);
    fm=g.getFontMetrics();
    y+=f.getSize()/2;
    g.drawString(s2,x,y);
    x+=fm.stringWidth(s2);
    TempCritxy[2]=x;
    TempCritxy[3]=y;
  }

  public void paint(Graphics g) {
    Rect=bounds();
    dx=(Rect.width-Panxy[0]-Panxy[2])/n;
    dy=(Rect.height-Panxy[1]-Panxy[3])/n;
    dxy=(dx <= dy) ? dx : dy;
    wh=(int)(n*dxy);
    if (dxy<=2) {
      GridForbidden=true;
      GridFlag=false;
    }    
    
    if (GridFlag) {
      img=createImage(wh+1,wh+1);
      imgGra=img.getGraphics();
      paintGrid(imgGra);
    }
    else {
      img=createImage(wh,wh);
      imgGra=img.getGraphics();
    }

    Hamilton.initGraphics(this,
			  createImage(Hamilton.n,(int)(0.9*wh/2)),
			  Color.cyan);
    Magnetization.initGraphics(this,
			       createImage(Magnetization.n,(int)(0.9*wh/2)),
			       Color.cyan);

    SpinsDrawn=false;
    drawSpins();
    paintThermo(g);
    drawTempCrit(g);
//   drawTitle(g,"Simple Ising Model  v1.0",
//      Panxy[0],(int)(0.05*Panxy[1]),(int)(0.4*Panxy[1]));
    drawTime(g);
    g.drawImage(img,Panxy[0],Panxy[1],this);
    g.drawImage(Hamilton.img,
		Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1],
		this);
    g.drawImage(Magnetization.img,
		Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1]+wh/2,
		this);
    Hamilton.drawParams(g,Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1]);
    Magnetization.drawParams(g,Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1]+wh/2);
  }

  public void update(Graphics g) {     // Weg mit dem Flackern!!!
    if (ThermoFlag || !is.stopped)
      paintThermo(g);
    if (sigmaFlag || !is.stopped) {
      drawSpins();
      img.flush();
      g.drawImage(img,Panxy[0],Panxy[1],this);
    }
    if (TimeFlag || !is.stopped)
      drawTime(g);
    if (FeverFlag || !is.stopped) {
      Hamilton.img.flush();
      g.drawImage(Hamilton.img,
		  Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1],
		  this);
      Magnetization.img.flush();
      g.drawImage(Magnetization.img,
		  Panxy[0]+wh+(int)(0.2*Panxy[2]),Panxy[1]+wh/2,
		  this);
    }
  }

  public synchronized void redraw(boolean ThermoFlag,
				  boolean sigmaFlag,
				  boolean FeverFlag,
				  boolean TimeFlag) {
    this.ThermoFlag=ThermoFlag;
    this.sigmaFlag=sigmaFlag;
    this.FeverFlag=FeverFlag;
    this.TimeFlag=TimeFlag;
    
    repaint();
  }
}


class EnterTextField extends TextField {
  Component parent;
  
  public EnterTextField(Component parent, String text, int cols) {
    super(text,cols);
    this.parent=parent;
  }

  public boolean keyDown(Event ev, int key) {
    if (key == 10) {
      parent.postEvent(ev);
      return true;
    }
    return false;
  }

  public boolean keyUp(Event ev, int key) {
    if (key == 10) return true;
    return false;
  }
}


class TextOutField extends Canvas {
  String text;
  Font font;
  Color color;
  
  public TextOutField(String text, Font font, Color color) {
    super();
    this.text=text;
    this.font=font;
    this.color=color;
  }

  public void paint(Graphics g) {
    g.setFont(font);
    g.setColor(color);
    g.drawString(text,0,font.getSize());
  }
}


class IsingPanel extends Panel {
  Ising is;
  IsingCanvas can;
  GridBagLayout GBLay;
  GridBagConstraints c;
  Checkbox check;
  Double Temp;
  public TextOutField Blubber;
  public EnterTextField TempText;
  boolean dummy;
  
  public IsingPanel(Ising is, IsingCanvas can) {
    this.is=is;
    this.can=can;
  }

  protected void makebutton(String name,
			    GridBagLayout gb,
			    GridBagConstraints gbc) {
    Button button=new Button(name);
    gb.setConstraints(button,gbc);
    add(button);
  }

  public void init() {
    GBLay=new GridBagLayout();
    c=new GridBagConstraints();
    setLayout(GBLay);

    c.gridx=0; c.gridy=0;
    c.gridheight=1;
    c.fill=GridBagConstraints.BOTH;
    Blubber=new TextOutField("Temperature",
			     new Font("TimesRoman",Font.PLAIN,14),
			     Color.red);
    GBLay.setConstraints(Blubber,c);
    add(Blubber);
    
    c.gridy=1;
    Temp=new Double(is.lattice.Temp);
    if (Temp.doubleValue() == is.lattice.TempMax)
      Temp=new Double(Double.POSITIVE_INFINITY);
    TempText=new EnterTextField(is,Temp.toString(),10);
    GBLay.setConstraints(TempText,c);
    add(TempText);
    
    c.gridx=1; c.gridy=0;
    makebutton("Init cold",GBLay,c);
    c.gridx=2;
//  makebutton("Init hot",GBLay,c);
    c.gridx=3;
    makebutton("Init warm",GBLay,c);

    c.gridx=4; c.gridheight=2;
    check=new Checkbox("Grid");
    check.setState(can.GridFlag);
    GBLay.setConstraints(check,c);
    add(check);
    
    c.gridx=1; c.gridy=1; c.gridheight=1;
    makebutton("Start",GBLay,c);
    c.gridx=2;
    makebutton("Stop",GBLay,c);
    c.gridx=3;
    makebutton("Step",GBLay,c);
  }

  public boolean action(Event ev, Object arg) {
    if (ev.target instanceof Button) {
      is.postEvent(ev);
      return true;
    }
    else
      if (ev.target instanceof Checkbox) {
	if (can.GridForbidden)
	  check.hide();
	else
	  can.setGridFlag(check.getState());
	is.postEvent(ev);
	return true;
      }
      else
	if (ev.id == Event.MOUSE_DRAG ||
	    ev.id == Event.MOUSE_DOWN ||
	    ev.id == Event.MOUSE_UP) {
	  is.postEvent(ev);
	  return true;
	}
    return false;
  }
}