/*----------------------------------------------------------------------+
|       Title:  Phylap.java                                             |
|               Applet                                                  |
|               (uses PhylPanel.java and Phyltree.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/                       |
|                                                                       |
|       Date:   January, 1996. Version 0.1f                             |
|               November, 2002. Version 0.2                             |
+----------------------------------------------------------------------*/

import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class Phylap extends Applet
       implements ActionListener, KeyListener, ItemListener, WindowListener {
  int species;
  int chars;
  int alts;
  double rate;
  boolean showActual, showGenome, showMatrix, showConstructed;
  int method;

  int genome[][];
  double diffMatrix[][], timeMatrix[][];

  TextField inputSpecies,inputChars, inputAlts, inputRate;
  TextArea matrixPanel;
  Button newPhyltreeButton, mutateButton;
  Panel actualControls, mutationControls, constructionControls;
  Phyltree actualTree, constructedTree;
  PhylPanel actualTreePanel, constructedTreePanel;
  GenomePanel genomePanel;
  Frame remoteFrame;
  Choice methodChoice = new Choice();

  final static int remoteFrameWidth=100;
  final static int remoteFrameHeight=400;

  final static Color actColor = new Color(250,250,200);
  final static Color matColor = new Color(250,230,240);
  final static Color conColor = new Color(200,230,240);

  public String getAppletInfo() {
    return "Phylap. Copyright 1996, 2002, David Joyce, Clark University. Version 0.2";
  }

  public String[][] getParameterInfo() {
    String[][] pinfo = {
        {"species",        "int",   "number of species"},
        {"chars",          "int",   "number of characters"},
        {"alts",           "int",   "number of alternatives/trait"},
        {"rate",           "int",   "mutation rate * 1000"},
        {"method",         "int",   "reconstruction method (-1,0,or 1)"},
        {"showActual",     "bool",  "show actual tree"},
        {"showGenome",     "bool",  "show genome sequences"},
        {"showMatrix",     "bool",  "show distance matrix"},
        {"showConstructed","bool",  "show constructed tree"}
    };
    return pinfo;
  }

  public void init() {

    String param = getParameter("species");
    species = (param == null) ? 10 : Integer.parseInt(param);
    if (species > 100) species = 100;
    inputSpecies = new TextField(String.valueOf(species));
    inputSpecies.addKeyListener(this);

    param = getParameter("chars");
    chars = (param == null) ? 10 : Integer.parseInt(param);
    inputChars = new TextField(String.valueOf(chars));
    inputChars.addKeyListener(this);

    param = getParameter("alts");
    alts = (param == null) ? 4 : Integer.parseInt(param);
    inputAlts = new TextField(String.valueOf(alts));
    inputAlts.addKeyListener(this);

    param = getParameter("rate");
    rate = (param == null) ? 0.1 : Integer.parseInt(param)/1000.0;
    inputRate = new TextField(String.valueOf((int)(rate*1000)));
    inputRate.addKeyListener(this);

    param = getParameter("method");
    method = (param == null) ? 0 : Integer.parseInt(param);

    param = getParameter("showActual");
    showActual = (param != null &
       (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")));
    param = getParameter("showGenome");
    showGenome = (param != null &
       (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")));
    param = getParameter("showMatrix");
    showMatrix = (param != null &
       (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")));
    param = getParameter("showConstructed");
    showConstructed = (param != null &
       (param.equalsIgnoreCase("yes") || param.equalsIgnoreCase("true")));

    // Prepare the panels to be inserted
    setFont(new Font("TimesRoman",Font.PLAIN,10));
    actualControls = new Panel();
    actualControls.setBackground(actColor);
    actualControls.setLayout(new GridLayout(2,2,4,4));
    actualControls.add(new Label(" "));
    newPhyltreeButton = new Button("New Phyltree");
    newPhyltreeButton.addActionListener(this);
    actualControls.add(newPhyltreeButton);
    actualControls.add(new Label("Species:",Label.RIGHT));
    actualControls.add(inputSpecies);

    actualTree = new Phyltree(species);
    actualTreePanel = new PhylPanel(actualTree);
    actualTreePanel.setBackground(actColor);

    mutationControls = new Panel();
    mutationControls.setBackground(matColor);
    mutationControls.setLayout(new GridLayout(2,4,4,4));
    mutationControls.add(new Label(" "));
    mutateButton = new Button("Mutate");
    mutateButton.addActionListener(this);
    mutationControls.add(mutateButton);
    mutationControls.add(new Label("Chars:",Label.RIGHT));
    mutationControls.add(inputChars);
    mutationControls.add(new Label("Alts:",Label.RIGHT));
    mutationControls.add(inputAlts);
    mutationControls.add(new Label("Rate:",Label.RIGHT));
    mutationControls.add(inputRate);

    genomePanel = new GenomePanel();

    matrixPanel = new TextArea(8,80);
    matrixPanel.setBackground(matColor);
    matrixPanel.setFont(new Font("Courier",Font.PLAIN,10));
    matrixPanel.setEditable(false);

    constructionControls = new Panel();
    constructionControls.setBackground(conColor);
    constructionControls.setLayout(new GridLayout(2,1,4,4));
    constructionControls.add(new Label("Reconstruction method:"));
    methodChoice.addItem("Mimimum");
    methodChoice.addItem("Average");
    methodChoice.addItem("Maximum");
    if (method<0) methodChoice.select("Mimimum");
    else if (method>0) methodChoice.select("Maximum");
    else methodChoice.select("Average");
    methodChoice.addItemListener(this);
    constructionControls.add(methodChoice);

    constructedTree = new Phyltree(species);
    constructedTreePanel = new PhylPanel(constructedTree);
    constructedTreePanel.setBackground(conColor);


    // Put them in the applet panel and the remote panel
    setLayout(new BorderLayout(10,10));
    Panel controlPanel = new Panel();
    controlPanel.setLayout(new FlowLayout(FlowLayout.CENTER,10,10));
    controlPanel.add(actualControls);
    if (showMatrix || showGenome || showConstructed)
      controlPanel.add(mutationControls);
    if (showConstructed)
      controlPanel.add(constructionControls);
    add("North",controlPanel);

    Panel centerPanel = new Panel();
    centerPanel.setLayout(new GridLayout(1,2,10,10));
    if (showActual)
      centerPanel.add(actualTreePanel);
    if (showConstructed)
      centerPanel.add(constructedTreePanel);
    add("Center",centerPanel);

    if (showMatrix)
      add("South",matrixPanel);

    if (showGenome) {
      remoteFrame = new Frame("Phyltree");
      remoteFrame.setSize(remoteFrameWidth,remoteFrameHeight);
      remoteFrame.add(genomePanel);
      remoteFrame.setVisible(true);
    }

    makeTree();
  } // init

  private void makeTree() {
    actualTree.setSize(species);
    actualTree.random(0.1);
    actualTreePanel.repaint();
    mutate();
  }

  private void mutate() {
    genome = actualTree.createRandomGenome(chars,alts,rate);
    genomePanel.setGenome(genome,species,alts);
    genomePanel.repaint();
    reconstruct();
  }


 /* Change the entries in the distance matrix from differences to
  * estimated times according to the formula
  *
  *    t = -(1/r) log(1-m/(n(m-1)) y)
  *
  * where r is the mutation rate, m is the number of traits,
  * m is the the number of alternates, and y is the number of different
  * characters.
  */
  private double[][] distToTime (double dist[][], int traits, int alts, double rate) {
    double time[][] = new double[species][];
    for (int i=0; i<species; ++i) {
      time[i] = new double[i];
      for (int j=0; j<i; ++j)
        time[i][j] = -Math.log(1.0-alts*dist[i][j]/(traits*(alts-1))) / rate;
    }
    return time;
  } // distToTime

  private void reconstruct() {
    diffMatrix = Phyltree.differenceMatrix(genome);
    timeMatrix = distToTime (diffMatrix, chars,alts,rate);
    setMatrixText(diffMatrix);
    matrixPanel.repaint();
    constructedTree.setSize(species);
    constructedTree.construct(timeMatrix,method);
    if (showActual && showConstructed) {
      actualTreePanel.setComparison(actualTree.compareWith(constructedTree));
      constructedTreePanel.setComparison(constructedTree.compareWith(actualTree));
    }
    actualTreePanel.repaint();
    constructedTreePanel.repaint();
  }

  private String formatInt (int i, int width) {
    int l;
    String temp = String.valueOf(i);
    if ((l=temp.length()) < width)
      return "         ".substring(0,width-l-1)+temp;
    else
      return temp.substring(0,width);
  }

  private void setMatrixText (double dist[][]) {
    StringBuffer S = new StringBuffer(species*(species+5)*5);
    int i,j;
    S.append("   ");
    for (j=0; j<species; ++j)
      S.append(formatInt(j+1,5));
    S.append("\n   ");
    for (j=0; j<species; ++j)
      S.append("  --");
    S.append("\n");
    for (i=0; i<species; ++i) {
      S.append(formatInt(i+1,3)+" | ");
      for (j=0; j<i; ++j)  {
        S.append(formatInt((int)dist[i][j],3));
        S.append("  ");
      }
      S.append(" *  ");
      for (j=i+1; j<species; ++j) {
        S.append(formatInt((int)dist[j][i],3));
        S.append("  ");
      }
      S.append("\n");
    }
    S.append("   ");
    for (j=0; j<species; ++j)
      S.append("  --");
    S.append("\n   ");
    for (j=0; j<species; ++j)
      S.append(formatInt(j+1,5));
    matrixPanel.setText(S.toString());
  } // setMatrixText

  public void actionPerformed(ActionEvent evt) {
     String command = evt.getActionCommand();
    if (command.equals("New Phyltree"))
      makeTree();
    else if (command.equals("Mutate"))
      mutate();
  } // actionPerformed


  // key listener methods
  public void keyPressed(KeyEvent evt) {}
  public void keyTyped(KeyEvent evt) {}

  public void keyReleased(KeyEvent evt) {
    TextField field = (TextField)evt.getComponent();
    String text = field.getText();
    if (field == inputSpecies) {
        try {
          species = Integer.parseInt(text);
        } catch(NumberFormatException e) {
          inputSpecies.setText(""+species);
        }
        makeTree();
    } else if (field == inputChars) {
        try {
          chars = Integer.parseInt(text);
        } catch(NumberFormatException e) {
          inputChars.setText(""+chars);
        }
        mutate();
    } else if (field == inputAlts) {
        try {
          alts = Integer.parseInt(text);
        } catch(NumberFormatException e) {
          inputAlts.setText(""+alts);
        }
        mutate();
    } else if (field == inputRate) {
        try {
          rate = Integer.parseInt(text)/1000.0;
        } catch(NumberFormatException e) {
          inputRate.setText("");
        }
        mutate();
    } // if/else
  } // keyReleased

  public void itemStateChanged(ItemEvent evt) {
    System.out.println(" Entering itemStateChanged with evt="+evt);
    method = methodChoice.getSelectedIndex() - 1;
    reconstruct();
  } // itemStateChanged

  // window listener methods
  public void windowActivated(WindowEvent e) {}
  public void windowClosed(WindowEvent e) {}
  public void windowClosing(WindowEvent e) {
    remoteFrame.setVisible(false);
  }
  public void windowDeactivated(WindowEvent e) {}
  public void windowDeiconified(WindowEvent e) {}
  public void windowIconified(WindowEvent e) {}
  public void windowOpened(WindowEvent e) {}
} // Phylap
