// Cluster-size distribution,  Evgeny Demidov  14 Nov 2001
import java.awt.*;
import java.awt.event.*;
public class Distrib extends java.applet.Applet
 implements MouseListener, ItemListener, KeyListener, ActionListener{
  int L = 25,L1, d, w, maxColor = 18, hd, hBut = 30, nSpan, nFin, avW, iter,
    cl[][], np[], size[], distr[], maxCl = 100000, periodic = 0;
  double p = .5,  ss, ss2,  avP, avS, Pi, Si;
  Image buffImage;     Graphics buffGraphics;
  Color col[];
  boolean painted = false, ln = true;
  Checkbox  cbLn;
  Button   btClear, btPrint;
  Choice chL,chB;    Label  lbL, lbP;    TextField tfP;
  long generTime;

public void init() {
  w = getSize().width - 150;
  cl = new int[w+1][w];
  np = new int[maxCl];  size = new int[maxCl];  distr = new int[w];
  String s=getParameter("L");
  if (s != null) L = Integer.parseInt(s);
  d = w/L;  hd = w-d; L1 = L+1;
  s=getParameter("p");
  if (s != null) p = Double.valueOf(s).doubleValue();
  col = new Color[maxColor];
  col[0] = Color.red;  col[3] = new Color(0,200,0);  col[6] = Color.blue;
  col[9] = new Color(220,220,0);  col[12] = new Color(0,220,220);
  col[15] = new Color(255,0,255);
  col[1] = new Color(255,150,150);  col[4] = new Color(120,255,120);
  col[7] = new Color(150,150,255);  col[10] = new Color(255,255,120);
  col[13] = new Color(120,255,255); col[16] = new Color(255,150,255);
  for (int i = 0; i < 18; i += 3) col[i+2] = col[i].darker();
  buffImage = createImage(w, w);   buffGraphics = buffImage.getGraphics();
  addMouseListener(this);
  lbP = new Label("p");  add(lbP);
  tfP = new TextField( "" + (float)p, 5);  add(tfP);
  tfP.addKeyListener(this);
  chL = new Choice();
  if (w == 400)
   for (int i = 0, l = 25; i < 5; i++){
    chL.addItem(Integer.toString(l));  l *= 2;}
  else
   for (int i = 0, l = 20; i < 6; i++){
    chL.addItem(Integer.toString(l));  l *= 2;};
  chL.select(""+L);
  chL.addItemListener(this);
  lbL = new Label("L");  add(lbL);  add(chL);
  chB = new Choice();
  chB.addItem("square  ");  chB.addItem("cylinder");  chB.addItem("torus   ");
  chB.select(periodic);
  chB.addItemListener(this);  add(chB);
  cbLn = new Checkbox("ln",ln);  add(cbLn);
  cbLn.addItemListener(this);
  btClear = new Button("Clear");   btClear.addActionListener(this);
  add(btClear);
  btPrint = new Button("Print");   btPrint.addActionListener(this);
  add(btPrint);
}
public void destroy() {
  removeMouseListener(this);
}
public void mouseClicked(MouseEvent e){}       // event handling
public void mousePressed(MouseEvent e) {
  if ( e.isAltDown() ) iter = 100;
  painted = false;
  repaint();
  e.consume();
}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e)  {}
public void keyTyped(KeyEvent e){}
public void keyPressed(KeyEvent e){}
public void keyReleased(KeyEvent e){
  final int keyEnter = 10;
  if (e.getKeyCode() == keyEnter){
    try{ p = Double.valueOf(tfP.getText()).doubleValue();
    }catch(NumberFormatException ne){}
    for (int i = 0; i < 400; i++) distr[i] = 0;
    avW = 0;    avP = avS = 0.;
    painted = false;
    repaint();
  }
  e.consume();
}
public void actionPerformed(ActionEvent e){
  if ( e.getActionCommand().equals("Clear") ){
    for (int i = 0; i < 400; i++) distr[i] = 0;
    avW = 0;    avP = avS = 0.;
    showStatus( "avr="+ distr[0]);}
  if ( e.getActionCommand().equals("Print") ){
    showStatus( "p="+ (float)p +"  W="+ (float)avW/distr[0] +
     "  P="+ (float)avP/distr[0] + "  S="+ (float)avS/distr[0]
     +"  avr="+ distr[0] );
    System.out.println(""+ (float)p +"  "+ (float)avW/distr[0] +
     "  "+ (float)avP/distr[0] + "  "+ (float)avS/distr[0] );
    for (int i = 1; i < 400; i++){
     int di = distr[i];
     if ( ln ){
      if ( di > 0 )
       System.out.println(""+i+"  "+(float)Math.log(di/((double)distr[0]*L*L)) );
      else
       System.out.println(""+i+"  -20");}
     else System.out.println(""+i+"  "+ di/((float)distr[0]*L*L) );}}
}
public void itemStateChanged(ItemEvent e){
  L = Integer.parseInt(chL.getSelectedItem());
  d = w/L;  hd = w-d; L1 = L+1;
  periodic = chB.getSelectedIndex();
  ln = cbLn.getState();
  for (int i = 0; i < 400; i++) distr[i] = 0;
  avW = 0;    avP = avS = 0.;
  painted = false;
  repaint();
}

public void paint(Graphics g) {
do{
 if ( !painted || (iter > 0) ){
  generTime = System.currentTimeMillis();
  showStatus( "labeling clusters");
  buffGraphics.setColor(Color.white);
  buffGraphics.fillRect(0, 0, w, w);
  for (int i = 0; i < maxCl; i++) size[i] = np[i] = 0;
  for (int i = 0; i < L1; i++)
   for (int j = 0; j < L; j++) cl[i][j] = -1;
  int newCl = 1, maxCl_1 = maxCl-1;
  for (int i = 1; i < L1; i++){
   int leftCl = -1;
   for (int j = 0; j < L; j++){
    if ( Math.random() > p){ leftCl = -1;  continue;}
    int downCl = cl[i-1][j];
    if ( downCl == -1){
     if (leftCl == -1){
      leftCl = cl[i][j] = newCl;
      if (newCl < maxCl_1) newCl++;}
     else cl[i][j] = leftCl;}
    else{
     downCl = proper(downCl);
     if (leftCl == -1){ leftCl = cl[i][j] = downCl; continue;}
     if (leftCl == downCl){ cl[i][j] = leftCl; continue;}
     if (leftCl < downCl) cl[i][j] = np[downCl] = leftCl;
     else{
      cl[i][j] = np[leftCl] = downCl;
      leftCl = downCl;}} }}
  int clA, clB, L_1 = L-1;
  if (periodic > 0){
   for (int i = 1; i < L1; i++)
    if (((clA = cl[i][0]) != -1)&&((clB = cl[i][L_1]) != -1))
     if ((clA = proper(clA)) != (clB = proper(clB)))
      if (clA > clB) np[clA] = clB;
      else np[clB] = clA;}
  int percCl = -1, topCl;
  boolean isPercolating = false;
perc:
  for (int i = 0; i < L; i++){
   if ((percCl = cl[1][i]) == -1) continue;
   percCl = proper(percCl);
   for (int j = 0; j < L; j++){
    if ((topCl = cl[L][j]) != -1)
     if (percCl == proper(topCl)){
      isPercolating = true;
      avW++;
      break perc;}}}
  if (periodic > 1){
   for (int i = 0; i < L; i++)
    if (((clA = cl[1][i]) != -1)&&((clB = cl[L][i]) != -1))
     if ((clA = proper(clA)) != (clB = proper(clB)))
      if (clA > clB) np[clA] = clB;
      else np[clB] = clA;
   if ( isPercolating ) percCl = proper(percCl);}
  showStatus( "drawing  avr="+ distr[0]);
  nSpan = nFin = 0;  ss = ss2 = 0;
  for (int i = 0; i < L; i++){
   for (int j = 0; j < L; j++){
    int c = cl[i+1][j];
    if ( c > 0){
     c = proper(c);
     if ( isPercolating && (c == percCl)){
       nSpan++;
       buffGraphics.setColor(Color.black);}
     else{
       nFin++;
       size[c]++;
       buffGraphics.setColor(col[c % maxColor]);}
     buffGraphics.fillRect(j*d,hd-i*d, d,d);} }}
  for (int i = 0; i < maxCl; i++){
   int s = size[i];
   if (s > 0){
    ss += s;
    ss2 += (double)s*s;
    if (s < 400) distr[s]++;}}
  distr[0]++;
  Pi = (double)nSpan/(nSpan+nFin);  avP += Pi;
  Si = ss2/ss;  avS += Si;
  generTime = System.currentTimeMillis() - generTime;
  painted = true;}
 g.setColor(Color.white);
 g.fillRect(w,30, 150,w);
 g.drawImage(buffImage, 0, hBut, this);
 double m = 149.0/distr[1];
 if ( ln ) m = 149/Math.log((double)distr[1]/distr[0]);
 g.setColor(Color.red);
 int dy = 2;
 if (w == 640) dy = 3;
 for (int i = 1; i < 200; i++){
  int di = distr[i], x;
  if (di != 0){
   if ( ln ){
    x = (int)(m*Math.log((double)di/distr[0]));
    if ( x < 0 ) x = 0;
    x += w;}
   else x = w + (int)(m*di);
   g.drawLine(x, dy*(i-1)+30, x, dy*i+30);
  }
 }
 showStatus( "p="+ (float)p +"  P="+ (float)Pi +"  S="+ (float)Si
  +"  avr="+ distr[0] +"  n1="+ ((float)distr[1])/(distr[0]*L*L)
  +"  t="+ generTime +"ms");
}while( --iter > 0);
}
public int proper(int label ){
  int l;
  while( (l = np[label]) != 0) label = l;
  return label;
}

public void update(Graphics g){ paint(g); }

}
