/*----------------------------------------------------------------------+
|      Title:	Genebat.java                                            |
|                                                                       |
|      Author:	David E. Joyce                                          |
|           Department of Mathematics and Computer Science              |
|           Clark University                                            |
|           Worcester, MA 01610-1477                                    |
|           U.S.A.                                                      |
|                                                                       |
|           http://aleph0.clarku.edu/~djoyce/                           |
|           djoyce@clarku.edu                                           |
|                                                                       |
|      Date:    May, 2002.                                              |
+----------------------------------------------------------------------*/

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Genebat extends Applet
  implements ActionListener, AdjustmentListener, KeyListener, Runnable {
  // parameters and their initial
  int players, iPlayers = 40; // number of players, must be even
  int gamesPerMatch, igamesPerMatch = 20;
  int deathRate, ideathRate = 10;
  int ipayoff[][] = {{3,0},{5,1}};
  int payoff[][] = new int[2][2];

  // components
  TextArea  reportArea;
  Button startButton, resetButton, pauseButton;
  Label players_label = new Label();
  Scrollbar players_scrollbar;
  Label gamesPerMatch_label = new Label();
  Scrollbar gamesPerMatch_scrollbar;
  Label deathRate_label = new Label();
  Scrollbar deathRate_scrollbar;
  Panel payoffMatrixPanel;
  TextField payoffField[][] = new TextField[2][2];
  final static int statWidth=300, statHeight=150;
  TimeGraph timeGraph;
  
  // data
  Range range;
  int matchNumber;

  // Initialize the applet
  public void init() {
    // construct and place the components in the applet window
    BorderLayout appletLayout = new BorderLayout();
    appletLayout.setHgap(5);
    appletLayout.setVgap(5);
    setLayout(appletLayout);
    setFont(new Font("Serif",Font.PLAIN,10));

    // Set up the control panel
    Panel controlPanel = new Panel();
    BorderLayout controlLayout = new BorderLayout();
    controlLayout.setHgap(20);
    controlPanel.setLayout(controlLayout);
    add("North",controlPanel);

    // The buttonPanel will make up part of the control panel
    Panel buttonPanel = new Panel();
    buttonPanel.setLayout(new GridLayout(2,1));
    controlPanel.add("West",buttonPanel);

    startButton = new Button("Start");
    startButton.addActionListener(this);
    buttonPanel.add(startButton);

    pauseButton = new Button("Pause");
    pauseButton.addActionListener(this);
    pauseButton.setEnabled(false);
    buttonPanel.add(pauseButton);

    resetButton = new Button("Reset");
    resetButton.addActionListener(this);
    buttonPanel.add(resetButton);

    // Each parameter has a label and a scrollbar
    Panel parameterPanel = new Panel();
    parameterPanel.setLayout(new GridLayout(3,2));
    controlPanel.add(parameterPanel);

    players = iPlayers;
    parameterPanel.add(players_label);
    players_label.setText("players: "+players);
    players_scrollbar = new Scrollbar (Scrollbar.HORIZONTAL,players,20,2,519);
    players_scrollbar.addAdjustmentListener(this);
    parameterPanel.add(players_scrollbar);

    gamesPerMatch = igamesPerMatch;
    parameterPanel.add(gamesPerMatch_label);
    gamesPerMatch_label.setText("games/match: "+gamesPerMatch);
    gamesPerMatch_scrollbar = new Scrollbar (Scrollbar.HORIZONTAL,gamesPerMatch,10,1,210);
    gamesPerMatch_scrollbar.addAdjustmentListener(this);
    parameterPanel.add(gamesPerMatch_scrollbar);

    deathRate = ideathRate;
    parameterPanel.add(deathRate_label);
    deathRate_label.setText("death rate: "+deathRate+"%");
    deathRate_scrollbar = new Scrollbar (Scrollbar.HORIZONTAL,deathRate,10,1,101);
    deathRate_scrollbar.addAdjustmentListener(this);
    parameterPanel.add(deathRate_scrollbar);

    // set up the payoff matrix and its panel
    payoffMatrixPanel = new Panel();
    payoffMatrixPanel.setLayout(new GridLayout(2,2));
    for (int i=0; i<=1; ++i)
      for (int j=0; j<=1; ++j) {
        payoff[i][j] = ipayoff[i][j];
        payoffField[i][j] = new TextField(3);
        payoffField[i][j].setText(Integer.toString(payoff[i][j]));
        payoffField[i][j].addKeyListener(this);
        payoffMatrixPanel.add(payoffField[i][j]);
    }
    controlPanel.add("East",payoffMatrixPanel);

    // set up the report area
    reportArea = new TextArea(5,20);
    reportArea.setEditable(false);
    //reportArea.setFont(new Font("Serif",Font.PLAIN,10));
    add(reportArea);

    // set up the statistics timegraph
    timeGraph = new TimeGraph();
    timeGraph.setSize(statWidth,statHeight);
    add("South",timeGraph);
  }//init

// thread control variables and methods

  private Thread thread = null;
  private boolean doStop = false;

  public void go() {
    if (thread == null) {
      thread = new Thread(this);
      thread.start();
      doStop = false;
    }
  }
  
  public void stop() {
    doStop = true;
  }

  private void startRun () {
    timeGraph.init(Color.gray,rainbow(players),100);
    range = new Range(players);
    matchNumber = 0;
    go();  
  }

  public void run() {
    reportArea.append("\n");
    boolean done;
    do {
      matchNumber++;
      range.updateData(timeGraph,reportArea);
      done = range.doSimulation(players*deathRate/100,gamesPerMatch,payoff);
      if (matchNumber%5 == 0) {
        timeGraph.repaint();
        try {thread.sleep(50);} // wait this many milliseconds
        catch(Exception e) {};
      } // if
    } while (!doStop && !done);
    if (done) {
      reportArea.append("Stable after "+matchNumber+" matches\n");
      startButton.setLabel("Start");
      resetButton.setEnabled(true);
      players_scrollbar.setEnabled(true);
    }
    timeGraph.repaint();
    thread = null;
    doStop = false;
  } // run


  // create a rainbow of n colors
  private Color[] rainbow(int n) {
     Color bow[] = new Color[n];
     for (int i=0; i<n; ++i) {
       double hue = ((double)i)/n;
       int cidx = Color.HSBtoRGB ((float)(hue),		// hue
			         (float)(1.0),          // sat
			         (float)(1.0));         // bright
       bow[i] = new Color(cidx);
     }
     return bow;
  }
  
  public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("Start")) {
      startButton.setLabel("Stop");
      pauseButton.setLabel("Pause");
      pauseButton.setEnabled(true);
      resetButton.setEnabled(false);
      players_scrollbar.setEnabled(false);
      startRun();
    } else if (e.getActionCommand().equals("Stop")) {
      stop();
      startButton.setLabel("Start");
      pauseButton.setLabel("Resume");
      resetButton.setEnabled(true);
      players_scrollbar.setEnabled(true);
    } else if (e.getActionCommand().equals("Pause")) {
      stop();
      pauseButton.setLabel("Resume");
    } else if (e.getActionCommand().equals("Resume")) {
      go();
      pauseButton.setLabel("Pause");
    } else if (e.getActionCommand().equals("Reset")) {
      players = iPlayers;
      gamesPerMatch = igamesPerMatch;
      deathRate = ideathRate;
      for (int i=0; i<=1; ++i)
        for (int j=0; j<=1; ++j)
          payoff[i][j] = ipayoff[i][j];
      resetControlPanel();
    }
  } // actionPerformed

  public void adjustmentValueChanged(AdjustmentEvent e) {
    if (e.getSource() == players_scrollbar) {
      players = e.getValue();
      players += (players%2); // make sure it's even
      resetControlPanel();
    } else if (e.getSource() == gamesPerMatch_scrollbar) {
      gamesPerMatch = e.getValue();
      resetControlPanel();
    } else if (e.getSource() == deathRate_scrollbar) {
      deathRate = e.getValue();
      resetControlPanel();
    }
  } // adjustmentValueChanged

  void resetControlPanel () {
    players_label.setText("players: "+players);
    players_scrollbar.setValue(players);
    gamesPerMatch_label.setText("games/match: "+gamesPerMatch);
    gamesPerMatch_scrollbar.setValue(gamesPerMatch);
    deathRate_label.setText("death rate: "+deathRate+"%");
    deathRate_scrollbar.setValue(deathRate);
    for (int i=0; i<=1; ++i)
      for (int j=0; j<=1; ++j) {
        payoffField[i][j].setText(Integer.toString(payoff[i][j]));
    }
    repaint();
  } //resetScrollBars

  public void keyPressed(KeyEvent e) {}

  public void keyReleased(KeyEvent e) {
    for (int i=0; i<=1; ++i)
      for (int j=0; j<=1; ++j)
        if (e.getSource() == payoffField[i][j]) {
          String text = payoffField[i][j].getText();
          int newvalue;
          try { payoff[i][j] = Integer.parseInt(text);}
          catch (NumberFormatException exc) {
            payoffField[i][j].setText(Integer.toString(payoff[i][j]));
          }
          break;
        }
  }

  public void keyTyped(KeyEvent e) {}

} // applet
