/* GameOfLife - demonstrator for later Mips+LCD game of life
 *
 * 21.02.06 - new class
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;


public class GameOfLife {

  JFrame   frame;
  JPanel   buttonPanel;
  JButton  initButton, startButton, stopButton, stepButton, exitButton;

  LCDCanvas  lcd;
  Callback   callback;
  Worker     worker;

  int     nx = 128;
  int     ny =  64;
  int     matrix[][], future[][];
  boolean running = false;
  int     sleepMillis = 20; // 20 msec, or 50 repaints per sec (theoretically)

  int     generation = 0;
  int     population = 0;
  long    lastMillis = System.currentTimeMillis();


  public GameOfLife() {
    matrix = new int[nx][ny];
    future = new int[nx][ny];
  }


  public void createGUI() {
    frame = new JFrame( "Game of Life" );
    lcd   = new LCDCanvas();
    callback = new Callback();

    initButton = makeButton( "Init", callback );
    stepButton  = makeButton( "Step", callback );
    startButton = makeButton( "Start", callback );
    stopButton  = makeButton( "Stop", callback );
    exitButton  = makeButton( "Exit", callback );

    buttonPanel = new JPanel( new FlowLayout( FlowLayout.CENTER ));
    buttonPanel.add( initButton );
    buttonPanel.add( stepButton );
    buttonPanel.add( new JLabel( " " ) );
    buttonPanel.add( startButton );
    buttonPanel.add( stopButton );
    buttonPanel.add( new JLabel( " " ) );
    buttonPanel.add( exitButton );

    frame.getContentPane().add( "Center", lcd );
    frame.getContentPane().add( "South", buttonPanel );
    frame.pack();
    frame.show();
  }


  public JButton makeButton( String label, ActionListener al ) {
    JButton tmp = new JButton( label );
    tmp.addActionListener( al );
    return tmp;
  }


  public void setSleepMillis( int millis ) {
    sleepMillis = millis;
  }


  public class Callback implements ActionListener {
    public void actionPerformed( ActionEvent evt ) {
      String cmd = evt.getActionCommand();
      if      ("Exit".equals(cmd)) { doExit(); }
      else if ("Init".equals(cmd)) { doInitializeBoard(); }
      else if ("Step".equals(cmd)) { doSingleStep(); }
      else if ("Stop".equals(cmd)) { doStopAnimation(); }
      else if ("Start".equals(cmd)) { doStartAnimation(); }
      else {
        System.out.println( "unknown command: " + evt );
      }
    }
  }


  public class Worker extends Thread {
    public void run() {
      while( running ) {
        iteration();
        display();
     
        if (sleepMillis > 0) {
          try { Thread.sleep( sleepMillis ); }
          catch( InterruptedException ie ) { running = false; }
        }
      }
    }
  }



  public class LCDCanvas extends JPanel {
    private BufferedImage buffer;
    private int   sx, sy;
    private Color activePixel  = new Color(  30,  30,  40 );
    private Color passivePixel = new Color( 200, 180, 240 );
    private Color gridPixel    = new Color( 255, 240, 240 );

    public LCDCanvas() {
      super();
      sx     = 4*nx;
      sy     = 4*ny;
      // buffer = new BufferedImage( sx, sy, BufferedImage.TYPE_INT_RGB );
    }


    /** not used at the moment: image painting is mmuucchh slower than "raw"
     *  AWT painting on all my JDKs and classpath+jamvm...
     */
    public void updateBuffer() {
      Graphics g = buffer.getGraphics();
      g.setColor( gridPixel );
      g.fillRect( 0, 0, sx, sy );

      Color pixelColor = null;
      for( int ix=0; ix < nx; ix++ ) {
        for( int iy=0; iy < ny; iy++ ) {
          if (matrix[ix][iy] != 0) pixelColor = activePixel;
          else                     pixelColor = passivePixel;

          g.setColor( pixelColor );
          g.fillRect( 4*ix, 4*iy, 3, 3 );
        }
      }
    }


    public void paintComponent( Graphics g ) {
      // for buffered drawing - not used atm
      // g.drawImage( buffer, 0, 0, null );

      g.setColor( gridPixel );
      g.fillRect( 0, 0, sx, sy );

      Color pixelColor = null;
      for( int ix=0; ix < nx; ix++ ) {
        for( int iy=0; iy < ny; iy++ ) {
          if (matrix[ix][iy] != 0) pixelColor = activePixel;
          else                     pixelColor = passivePixel;

          g.setColor( pixelColor );
          g.fillRect( 4*ix, 4*iy, 3, 3 );
        }
      }
    }

    public Dimension getPreferredSize() { return new Dimension(sx,sy); }
    public Dimension getMinimumSize() { return new Dimension(sx,sy); }
  }



  public void doExit() {
    System.exit( 0 );
  }
 
  public void doInitializeBoard() {
    initialize();
    display();
  }

  public void doSingleStep() {
    iteration();
    display();
  }


  public void doStartAnimation() {
    running = true;
    worker = new Worker();
    worker.start();
  }


  public void doStopAnimation() {
    running = false;
    if (worker != null) {
      worker.interrupt();
      worker = null;
    }
  }


  public void display() {
    // lcd.updateBuffer();
    lcd.repaint();
  }


  /** 
   * the actual game of life algorithm: cell is born when three neighbors,
   * survives when exactly two live neighbors, dies otherwise.
   */
  public void iteration() {
    // future = new int[nx][ny];

    int lifes = 0;
    for( int i=0; i < nx; i++ ) {
      for( int j=0; j < ny; j++ ) {
        // count neighbors
        int n = countNeighbors( i, j );

        if ((n != 0) && (Math.random() > 0.99)) { // some slight random offset
          if (Math.random() > 0.5) future[i][j] = 1;
          else                     future[i][j] = 0;
        }
        else if (n == 3)                          future[i][j] = 1;
        else if ((n == 2) && (matrix[i][j] == 1)) future[i][j] = 1;
        else                                      future[i][j] = 0;

        if (future[i][j] == 1) lifes++;
      }
    }
    generation ++;

    if ((generation % 100) == 0) {
       long t = System.currentTimeMillis();
       System.out.println( 
          " population= " + population + 
          " generation= " + generation + 
          " iterations/sec= " + 100.0*1000/(t-lastMillis) );
       lastMillis = t;
    }


    // avoid extinction :-)
    if (lifes < 20) initialize();
    else { // swap buffers
      int [][] tmp = matrix;
      matrix = future;
      future = tmp;
    }
  }


  private int countNeighbors( int i, int j ) {
    int im = i-1;
    int ip = i+1;
   
    int jm = j-1;
    int jp = j+1;
 
    // wrap-around
    if (ip >= nx) ip = 0;
    if (jp >= ny) jp = 0;

    if (im < 0) im = nx-1;
    if (jm < 0) jm = ny-1;

    // check eight neighbors
    int n = 0;
    n += matrix[im][jm];  // nw
    n += matrix[im][j];   // west
    n += matrix[im][jp];  // sw

    n += matrix[i ][jm];  // north
 // n += matrix[i ][j ];  // center
    n += matrix[i ][jp];  // south

    n += matrix[ip][jm];  // ne
    n += matrix[ip][j ];  // east
    n += matrix[ip][jp];  // se

    return n;
  }


  public void initialize() {
    for( int x=0; x < nx; x++ ) {
      for( int y=0; y < ny; y++ ) {
        double d = Math.random();
        if (d > 0.85)  matrix[x][y] = 1;
        else           matrix[x][y] = 0;
      }
    }
    generation = 0;
    population ++;
  }

  public static void main( String args[] ) {
    GameOfLife gol = new GameOfLife();
    if (args.length > 0) {
      if ("-sleep".equals(args[0])) 
        gol.setSleepMillis( Integer.parseInt(args[1]) );
    }

    gol.createGUI();
    gol.doStartAnimation();
    // gol.initialize();
    // gol.display();
  }
 
}
