/* Neurite.java

Applet and application (combined)
Package for generating neuritic trees.
Written for Java 2.

version JTB1 BPG 22-10-05
  - basic version containing code used for JTB paper:
    Graham and van Ooyen, Journal of Theoretical Biology 230:421-432, 2004
  

*/

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;


public class Neurite extends Applet implements Runnable {

  // Tree types
  public static final int BES_type = 0;
  public static final int AD_type = 1;
  public static final int ADcm_type = 2;

  // Tree structure
  Tree[] branches;

  // Tree parameters
  int tree_type=-1;
  int nTrees=1;  // number of trees to generate
  int ibr=0;  // tree index
  int nTerms, nInts;  // number of terminals and intermediates

  // Generated tree data
  float[] termV;   // values in terminal nodes
  int[][] timeTerm; // no. of terminals over time
  float[] mtimeTerm; // mean no. of terminals over time

  // Tree statistics
  int[] totSegments;  // number of tree segments generated
  float mSegs, sdSegs;  // mean and standard deviation
  int[] totTerms;  // number of terminal nodes (degree)
  float mTerms, sdTerms;  // mean and standard deviation
  float[] totLength;  // total tree length
  float mLength, sdLength;  // mean and standard deviation
  float[] termLength;  // terminal node length
  float mTermL, sdTermL;  // mean and standard deviation
  float[] intLength;  // intermediate node length
  float mIntL, sdIntL;  // mean and standard deviation
  float[] maxPath;  // maximum path length of a tree
  float[] pathLength;  // path length of all paths
  float mPathL, sdPathL;  // mean and standard deviation
  float[] asym;  // tree asymmetry index
  float mAsym, sdAsym;  // mean and standard deviation
  int[] maxCO;  // maximum centrifugal order of a tree
  int[] centorder;  // centrifugal order of all paths
  float mCO, sdCO;  // mean and standard deviation

  // Menu display
  public static int mainw=600;
  public static int mainh=220;

  // Graphics display
  private boolean refresh=true;  // refresh display
  public static int dispw=600;  // display width
  public static int disph=350;  // display height
  public static int dispoffx=10;  // x offset
  public static int dispoffy=10;  // y offset
  public static int dtw=580;
  public static int dth=330;
  public static String dname="";  // parameter to display
  public static float dmval=1.0f;  // maximum parameter value
  public static int dtdisp=1;  // display every dtdisp time steps
  public static boolean dispfl=false;  // turn display on or off
  private TreeDisplay treeDisp;	// graphic display window

  // Plots
  public static int pw=300;  // plot width
  public static int ph=150;  // plot height
  public static String pname="";  // plot variable
  public static float pmval=1.0f;  // maximum plot value
  public static int dtplot=1;  // plot every dtplot time steps
  public static int somafl=0;  // include soma
  public static final int NPMAX = 10;
  private TermPlot[] tp;
  public static int nplots=0;  // number of plots

  // Simulation parameters
  private Thread construct_thread=null;  // tree constructor
  float dt=1;   // time step
  double Tstop=0;  // total simulation time
  double tsim=0;  // elapsed simulation time
  double Tstep=0;  // time to step through
  double Tend=0;  // current stop time
  boolean dostep=false;  // step through growth
  boolean stepping=false;  // in process of stepping
  boolean new_type=true;  // flag new tree type

  // File I/O
  int sSflag=0;  // store statistics data flag
  int sCflag=0;  // store concentration data flag
  int sLflag=0;  // store length data flag
  String fSstem="test";
  String fCstem="test";
  String fLstem="test";
  FileOutputStream flen;
  PrintWriter plen;
  FileOutputStream fC;
  PrintWriter pC;
  int sTflag=0;  // store terminal data flag
  double Tterm=0;  // terminal number collection time
  int sPflag=0;  // store plot data flag
  String fPstem="test";
  static final int maxfP=20;
  FileOutputStream[] fP;
  PrintWriter[] pP;
  int sDflag=0;  // store tree display flag
  String fDstem="test";
  static final int maxfD=100;  // currently not used
  FileOutputStream fD;
  // Reading and saving simulation parameters (BPG 3-4-01)
  String fMstem="model";
  FileInputStream fMi;
  FileOutputStream fMo;
  PrintWriter pM;

  // GUI stuff
  Button load_button;  // load simulation parameters from file
  Button save_button;  // save simulation parameters to file
  Button quit_button;
  Button sim_button;
  Choice tree_choice;
  Button tree_button;
  Button stop_button;
  Button display_button;
  Button dispfl_button;
  Button plot_button;
  Button draw_button;
  Button help_button;
  TextField ibr_text;
  TextArea message_text;
  StringBuffer message;
  String de_title;
  DataEntry d;
  Graphics g;
  Rectangle r;


  // Data entry
    
  String SIM_data[][] = {
    {"Number of trees:", "1", "1"},
    {"Time step (dt):", "1", "1"},
    {"Simulation time (Tstop):", "200", "200"},
    {"Store statistics (1=yes):", "0", "0"},
    {"Statistics file stem:", "AD", "AD"},
    {"Store concentrations (1=yes):", "0", "0"},
    {"Concentration file stem:", "AD", "AD"},
    {"Store lengths (1=yes):", "0", "0"},
    {"Length file stem:", "AD", "AD"},
    {"Store no. terminals over time (1=yes):", "0", "0"},
    {"Storage interval (time):", "1", "1"}};
  private final int Nsim = 11;

  String GRAPHIC_data[][] = {
    {"Display width:", "600", "600"},
    {"Display height:", "350", "350"},
    {"Display parameter:", "Cbr", "Cbr"},
    {"Maximum display value:", "1.0", "1.0"},
    {"Display interval (steps):", "1", "1"},
    {"Display delay (msecs):", "0", "0"},
    {"Maximum path length (um):", "100", "100"},
    {"Initial branch angle (deg):", "20", "20"},
    {"Angle scaling factor:", "-0.4", "-0.4"},
    {"Soma scaling factor:", "1.0", "1.0"},
    {"Dendrite scaling factor:", "5.0", "5.0"}};
  private final int Ngraphic = 11;

  String PLOT_data[][] = {
    {"Plot width:", "300", "300"},
    {"Plot height:", "150", "150"},
    {"Plot parameter:", "Cbr", "Cbr"},
    {"Maximum plot value:", "1.0", "1.0"},
    {"Plot interval (steps):", "100", "100"},
    {"Include soma (1=yes):", "0", "0"},
    {"Save plot values (1=yes):", "0", "0"},
    {"Save file stem:", "AD", "AD"}};
  private final int Nplot = 8;



  // Main program for application
  public static void main(String args[]) {
    NeuriteFrame app = new NeuriteFrame();
    app.setSize(mainw, mainh);
    app.show();
  }



  // Initialisation
  public void init() {

    // No trees initially
    branches = null;
    nTerms = 0;

    // Initial simulation things
    nTrees = Integer.parseInt(SIM_data[0][2]);
    dt = Float.valueOf(SIM_data[1][2]).floatValue();
    Tstop = Double.valueOf(SIM_data[2][2]).floatValue();
    
    // Initial graphics things
    g = this.getGraphics();
    r = this.getBounds();
    dispw = Integer.parseInt(GRAPHIC_data[0][2]);
    disph = Integer.parseInt(GRAPHIC_data[1][2]);
    dname = GRAPHIC_data[2][2];
    dmval = Float.valueOf(GRAPHIC_data[3][2]).floatValue();
    dtdisp = Integer.parseInt(GRAPHIC_data[4][2]);
    Tree.drawdel = (long)Integer.parseInt(GRAPHIC_data[5][2]);
    Tree.maxLength = Float.valueOf(GRAPHIC_data[6][2]).floatValue();
    Tree.bangle = Float.valueOf(GRAPHIC_data[7][2]).floatValue();
    Tree.badel = Float.valueOf(GRAPHIC_data[8][2]).floatValue();
    Tree.scsoma = Float.valueOf(GRAPHIC_data[9][2]).floatValue();
    Tree.scdend = Float.valueOf(GRAPHIC_data[10][2]).floatValue();

    tp = new TermPlot[NPMAX];
    nplots = 0;
    pw = Integer.parseInt(PLOT_data[0][2]);
    ph = Integer.parseInt(PLOT_data[1][2]);
    pname = PLOT_data[2][2];
    pmval = Float.valueOf(PLOT_data[3][2]).floatValue();
    dtplot = Integer.parseInt(PLOT_data[4][2]);
    somafl = Integer.parseInt(PLOT_data[5][2]);
    sPflag = Integer.parseInt(PLOT_data[6][2]);
    fPstem = PLOT_data[7][2];

    fP = new FileOutputStream[maxfP];
    pP = new PrintWriter[maxfP];


    // Add useful menus and buttons
    
    // quit
    quit_button = new Button("Quit");
    quit_button.setForeground(Color.black);
    quit_button.setBackground(Color.lightGray);
    add(quit_button);
    quit_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        System.exit(0);
      }
    });
    
    // read parameters from a file
    load_button = new Button("Load");
    load_button.setForeground(Color.black);
    load_button.setBackground(Color.lightGray);
    add(load_button);
    load_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        String currl;   // current line
        FileDialog get_fn = new FileDialog(new Frame(),"Load File",FileDialog.LOAD);
        get_fn.setDirectory("Params");
        get_fn.show();
        String fname = get_fn.getFile();
        get_fn.hide();
        System.out.println(fname);
        
        try {
        FileInputStream fi = new FileInputStream("Params/"+fname);
        BufferedReader r = new BufferedReader(new InputStreamReader(fi));
        
        // model parameters
        String[][] MOD_data = BESTree.DEparams;
        int Nmod = BESTree.Nparams;
        currl = r.readLine();
        currl = r.readLine();
        currl = r.readLine();
        currl = r.readLine();
        currl = r.readLine();
        tree_type = Integer.parseInt(currl);   // model type number
        tree_choice.select(tree_type);   // set model in menu selection
        switch (tree_type) {
        case BES_type:
          default:
          MOD_data = BESTree.DEparams;
          Nmod = BESTree.Nparams;
          break;
        case AD_type:
          MOD_data = ADTree.DEparams;
          Nmod = ADTree.Nparams;
          break;
        case ADcm_type:
            MOD_data = ADcmTree.DEparams;
            Nmod = ADcmTree.Nparams;
            break;
         };
        for (int i = 0; i < Nmod; i++) {
          currl = r.readLine();
          currl = r.readLine();
          MOD_data[i][2] = currl;
        };
        
        // simulation parameters
        currl = r.readLine();
        currl = r.readLine();
        for (int i = 0; i < Nsim; i++) {
          currl = r.readLine();
          currl = r.readLine();
          SIM_data[i][2] = currl;
        };
        nTrees = Integer.parseInt(SIM_data[0][2]);
        dt = Float.valueOf(SIM_data[1][2]).floatValue();
        Tstop = Double.valueOf(SIM_data[2][2]).floatValue();
        sSflag = Integer.parseInt(SIM_data[3][2]);
        fSstem = SIM_data[4][2];
        sCflag = Integer.parseInt(SIM_data[5][2]);
        fCstem = SIM_data[6][2];
        sLflag = Integer.parseInt(SIM_data[7][2]);
        fLstem = SIM_data[8][2];
        sTflag = Integer.parseInt(SIM_data[9][2]);
        Tterm = Double.valueOf(SIM_data[10][2]).floatValue();
	    timeTerm = new int[nTrees][(int)(Tstop/Tterm)+1];
	    for (int i = 0; i < nTrees; i++) timeTerm[i][0] = 1;

        // graphic display parameters
        currl = r.readLine();
        currl = r.readLine();
        for (int i = 0; i < Ngraphic; i++) {
          currl = r.readLine();
          currl = r.readLine();
          GRAPHIC_data[i][2] = currl;
        };
        dispw = Integer.parseInt(GRAPHIC_data[0][2]);
        disph = Integer.parseInt(GRAPHIC_data[1][2]);
        dname = GRAPHIC_data[2][2];
        dmval = Float.valueOf(GRAPHIC_data[3][2]).floatValue();
        dtdisp = Integer.parseInt(GRAPHIC_data[4][2]);
        Tree.drawdel = (long)Integer.parseInt(GRAPHIC_data[5][2]);
        Tree.maxLength = Float.valueOf(GRAPHIC_data[6][2]).floatValue();
        Tree.bangle = Float.valueOf(GRAPHIC_data[7][2]).floatValue();
        Tree.badel = Float.valueOf(GRAPHIC_data[8][2]).floatValue();
        Tree.scsoma = Float.valueOf(GRAPHIC_data[9][2]).floatValue();
        Tree.scdend = Float.valueOf(GRAPHIC_data[10][2]).floatValue();        
        fi.close();
        }
        catch(IOException e) {
          System.out.println("Something went wrong loading the data!");
        };
        
      }
    });
    
    // save parameters to a file
    save_button = new Button("Save");
    save_button.setForeground(Color.black);
    save_button.setBackground(Color.lightGray);
    add(save_button);
    save_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        FileDialog get_fn = new FileDialog(new Frame(),"Save File",FileDialog.SAVE);
        get_fn.setDirectory("Params");
        get_fn.show();
        String fname = get_fn.getFile();
        get_fn.hide();
        System.out.println(fname);
        
        try {
        FileOutputStream fo = new FileOutputStream("Params/"+fname);
        PrintStream po = new PrintStream(fo);
        
        // version number
        po.println("# Neurite version JTB1");
        
        // model parameters
        String[][] MOD_data = BESTree.DEparams;
        int Nmod = BESTree.Nparams;
        po.println("");   // blank line
        po.println("# MODEL PARAMETERS");
        switch (tree_type) {
        case BES_type:
          default:
          po.println("# MODEL: BES");
          MOD_data = BESTree.DEparams;
          Nmod = BESTree.Nparams;
          break;
        case AD_type:
          po.println("# MODEL: AD");
          MOD_data = ADTree.DEparams;
          Nmod = ADTree.Nparams;
          break;
        case ADcm_type:
            po.println("# MODEL: ADcm");
            MOD_data = ADcmTree.DEparams;
            Nmod = ADcmTree.Nparams;
            break;
        };
        po.println(tree_type);   // model type number
        for (int i = 0; i < Nmod; i++) {
          po.println("# "+MOD_data[i][0]);
          po.println(MOD_data[i][2]);
        };
        
        // simulation parameters
        po.println("");   // blank line
        po.println("# SIMULATION PARAMETERS");
        for (int i = 0; i < Nsim; i++) {
          po.println("# "+SIM_data[i][0]);
          po.println(SIM_data[i][2]);
        };
        
        // graphic display parameters
        po.println("");   // blank line
        po.println("# DISPLAY PARAMETERS");
        for (int i = 0; i < Ngraphic; i++) {
          po.println("# "+GRAPHIC_data[i][0]);
          po.println(GRAPHIC_data[i][2]);
        };
        
        fo.close();
        }
        catch(IOException e) {
          System.out.println("Something went wrong saving the data!");
        };
        
      }
    });

    //   choices of tree type
    tree_choice = new Choice();
    tree_choice.addItem("BESTL");
    tree_choice.addItem("AD");
    tree_choice.addItem("ADcm");
    add(tree_choice);
    tree_choice.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
      de_title = tree_choice.getSelectedItem();
      if (tree_type != tree_choice.getSelectedIndex())
        new_type = true;
      else
        new_type = false;
      tree_type = tree_choice.getSelectedIndex();
      switch (tree_type) {
        case BES_type:
        default:
          d = new DataEntry(new Frame(), de_title, BESTree.DEparams, BESTree.Nparams);
          break;
        case AD_type:
          d = new DataEntry(new Frame(), de_title, ADTree.DEparams, ADTree.Nparams);
          break;
        case ADcm_type:
          d = new DataEntry(new Frame(), de_title, ADcmTree.DEparams, ADcmTree.Nparams);
          break;
      };
      d.show();
      }
    });
    
    // button for editing simulation parameters
    sim_button = new Button("Simulation");
    sim_button.setForeground(Color.black);
    sim_button.setBackground(Color.lightGray);
    add(sim_button);
    sim_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        d = new DataEntry(new Frame(), "Simulation", SIM_data, Nsim);
        d.show();
        nTrees = Integer.parseInt(SIM_data[0][2]);
        dt = Float.valueOf(SIM_data[1][2]).floatValue();
        Tstop = Double.valueOf(SIM_data[2][2]).floatValue();
        sSflag = Integer.parseInt(SIM_data[3][2]);
        fSstem = SIM_data[4][2];
        sCflag = Integer.parseInt(SIM_data[5][2]);
        fCstem = SIM_data[6][2];
        sLflag = Integer.parseInt(SIM_data[7][2]);
        fLstem = SIM_data[8][2];
        sTflag = Integer.parseInt(SIM_data[9][2]);
        Tterm = Double.valueOf(SIM_data[10][2]).floatValue();
        timeTerm = new int[nTrees][(int)(Tstop/Tterm)+1];
        for (int i = 0; i < nTrees; i++) timeTerm[i][0] = 1;
	  }
    });

    //   button for editing display parameters
    display_button = new Button("Display");
    display_button.setForeground(Color.black);
    display_button.setBackground(Color.lightGray);
    add(display_button);
    display_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        d = new DataEntry(new Frame(), "Display", GRAPHIC_data, Ngraphic);
        d.show();
        dispw = Integer.parseInt(GRAPHIC_data[0][2]);
        disph = Integer.parseInt(GRAPHIC_data[1][2]);
        dname = GRAPHIC_data[2][2];
        dmval = Float.valueOf(GRAPHIC_data[3][2]).floatValue();
        dtdisp = Integer.parseInt(GRAPHIC_data[4][2]);
        Tree.drawdel = (long)Integer.parseInt(GRAPHIC_data[5][2]);
        Tree.maxLength = Float.valueOf(GRAPHIC_data[6][2]).floatValue();
        Tree.bangle = Float.valueOf(GRAPHIC_data[7][2]).floatValue();
        Tree.badel = Float.valueOf(GRAPHIC_data[8][2]).floatValue();
        Tree.scsoma = Float.valueOf(GRAPHIC_data[9][2]).floatValue();
        Tree.scdend = Float.valueOf(GRAPHIC_data[10][2]).floatValue();
      }
    });

    //   button for turning display on or off
    if (dispfl)
      dispfl_button = new Button("On");
    else
      dispfl_button = new Button("Off");
    dispfl_button.setForeground(Color.black);
    dispfl_button.setBackground(Color.lightGray);
    add(dispfl_button);
    dispfl_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        if (dispfl) {
          dispfl = false;
          dispfl_button.setLabel("Off");
          treeDisp.dispose();
        } else {
          dispfl = true;
          dispfl_button.setLabel("On");
          treeDisp = new TreeDisplay(dname, dispw, disph, dname, dmval, dtplot);
        };
      }
    });

    //   button for editing plot parameters
    plot_button = new Button("Plot");
    plot_button.setForeground(Color.black);
    plot_button.setBackground(Color.lightGray);
    add(plot_button);
    plot_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        if (nplots >= NPMAX)  {  // too many plots
          message_text.setText("Too many plots!");
        } else {
          d = new DataEntry(new Frame(), "Plot", PLOT_data, Nplot);
          d.show();
          pw = Integer.parseInt(PLOT_data[0][2]);
          ph = Integer.parseInt(PLOT_data[1][2]);
          pname = PLOT_data[2][2];
          pmval = Float.valueOf(PLOT_data[3][2]).floatValue();
          dtplot = Integer.parseInt(PLOT_data[4][2]);
          somafl = Integer.parseInt(PLOT_data[5][2]);
          sPflag = Integer.parseInt(PLOT_data[6][2]);
          fPstem = PLOT_data[7][2];
          // create new plot
          tp[nplots++] = new TermPlot(pname, pw, ph, pname, pmval, dtplot, somafl, sPflag, fPstem);
        };
      }
    });

    //   button for constructing trees
    tree_button = new Button("Construct");
    tree_button.setForeground(Color.black);
    tree_button.setBackground(Color.lightGray);
    add(tree_button);
    tree_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        if (dostep)  {  // already constructing
          message_text.setText("Still constructing!");
        } else {
          if (!stepping) refresh_plots();
          stepping = false;
          constructTree(Tstop);
        };
      }
    });

    //   button for stopping construction
    stop_button = new Button("Stop");
    stop_button.setForeground(Color.black);
    stop_button.setBackground(Color.lightGray);
    add(stop_button);
    stop_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        dostep = false;
      }
    });

    //   button for drawing trees
    draw_button = new Button("Draw");
    draw_button.setForeground(Color.black);
    draw_button.setBackground(Color.lightGray);
    add(draw_button);
    draw_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        if (getTreeIndex() == true) {
          if (branches != null && branches[ibr] != null) {
            indTreeStats(ibr);  // display statistics
            refresh = true;
            nTerms = 0;
            paint(g);
          } else
            message_text.setText("Tree not constructed!");
        }; 
      }
    });

    //   index of tree to display
    add(new Label("Display tree:"));
    ibr_text = new TextField("0", 5);
    add(ibr_text);

    //   button for online "help"
    help_button = new Button("Help");
    help_button.setForeground(Color.black);
    help_button.setBackground(Color.lightGray);
    add(help_button);
    help_button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        HelpWindow help = new HelpWindow();
      }
    });

    // Messages
    message_text = new TextArea(6, 80);
    message_text.setEditable(false);
    message_text.setForeground(Color.black);
    message_text.setBackground(Color.lightGray);
    add(message_text);

  }


  // Graphics
  public void paint(Graphics g) {

    Graphics2D gt, gp;
    Stroke treeStroke;


    if (branches != null && branches[ibr] != null) {
      g = treeDisp.dc.getGraphics();
      // get drawing area size
      Rectangle r = treeDisp.dc.getBounds();  // applet size
      // refresh if new tree, or window has changed size
      if (dispfl && (refresh || dispw != r.width || disph != r.height)) {
        dispw = r.width;
        disph = r.height;
        dth = disph - (2*dispoffy);
        dtw = dispw - (2*dispoffx);
        // offscreen image for tree
        treeDisp.dc.imageTree = treeDisp.dc.createImage(dispw, disph);
        gt = (Graphics2D) treeDisp.dc.imageTree.getGraphics();
        treeStroke = new BasicStroke(5.0f);
        gt.setStroke(treeStroke);
        // colour scale for drawing
        ColScale.drawCS(gt, 0, dth/10, dtw/2);
        g.drawImage(treeDisp.dc.imageTree,dispoffx,dispoffy,treeDisp.dc);
        refresh = false;
      };
      // draw current tree
      if (dispfl && ((long)(tsim/(double)dt) % dtdisp == 0)) {
        gt = (Graphics2D) treeDisp.dc.imageTree.getGraphics();
        gt.clearRect(0, 0, dtw, dth);
        treeStroke = new BasicStroke(5.0f);
        gt.setStroke(treeStroke);
        ColScale.drawCS(gt, 0, dth/10, dtw/2);
        branches[ibr].displayTree(gt, 0, 0, dtw, dth, dname, dmval);
        g.drawImage(treeDisp.dc.imageTree,dispoffx,dispoffy,treeDisp.dc);
        treeDisp.dc.getToolkit().sync();  // draw now
      };
      // plot terminal values
      for (int i = 0; i < nplots; i++) {
        if (dostep && ((long)(tsim/(double)dt) % tp[i].dtplot == 0)) {
          gp = (Graphics2D) tp[i].gp.imagePlot.getGraphics();
          tp[i].plotTerms(gp, branches[ibr], tsim, Tstop, tp[i].pname, tp[i].pmval);
          tp[i].repaint();
          if (tp[i].sPflag == 1)
           tp[i].saveTerms(branches[ibr], tsim, tp[i].pname);
        };
      };
    };  

  }



  // Refresh plot windows if new simulation
  private void refresh_plots() {
    for (int i = 0; i < nplots; i++) tp[i].refresh = true;
  }
    


  // Construct required number of trees
  public void constructTree(double t) {

    switch (tree_type) {
      case BES_type:
      default:
        BESTree.updateParams();
        BESTree.setTimeStep(dt);
        break;
      case AD_type:
        ADTree.updateParams();
        ADTree.setTimeStep(dt);
        break;
      case ADcm_type:
        ADcmTree.updateParams();
        ADcmTree.setTimeStep(dt);
        break;
    };

    // Set up for new construction or continuation
    if (!stepping) {  // new construction
      branches = new Tree[nTrees];
      // construct cell bodies (with initial neurites)
      for (ibr = 0; ibr < nTrees; ibr++) {
        switch (tree_type) {
     case BES_type:
          default:
            branches[ibr] = new BESTree(0, 0);
            break;
     case AD_type:
            branches[ibr] = new ADTree(0, 0);
            break;
     case ADcm_type:
         branches[ibr] = new ADcmTree(0, 0);
         break;
        };
      };
      ibr = 0;
      tsim = 0;
      refresh = true;
      stepping = true;
      nTerms = 0;
    } else {  // continuation of single tree
      refresh = false;
    };
    Tend = tsim + t;
    if (Tend > Tstop) Tend = Tstop;          
    dostep = true;

    // Construct trees
    construct_thread = new Thread(this);
    construct_thread.start();

  }



  // Thread to construct trees
  public void run() {

    for (ibr = 0; ibr < nTrees && dostep; ibr++) {
      message_text.setText("Tree "+(ibr+1));
      growTree(ibr, Tend);  // construct tree
      if (tsim >= Tstop) {
        tsim = 0;  // finished tree
        for (int i = 0; i < nplots; i++) {
          if (tp[i].sPflag == 1)
           tp[i].flushTerms();
        };
      };
    };

    if (!dostep || Tend >= Tstop) {
      stepping = false;  // stopped or finished
      message_text.setText("Finished!");
      if (Tend >= Tstop) {  // completed normally
        calcTreeStats();
        if (nTrees == 1)
          indTreeStats(0);
        else {
          multTreeStats();
          if (sSflag == 1) storeTreeData(fSstem);
        };
      };
    };
    dostep = false;
    ibr = 0;
    
    // close any data files
    int i = 0;
    while (pP[i] != null) {
      pP[i].flush();
      pP[i].close();
      i++;
    };

  }
  


  // Grow a tree and display growth
  public void growTree(int ibr, double Tend) {

    while (dostep && tsim < Tend) {
      tsim += (double)dt;   // update simulation time
      branches[ibr].branchTree(tsim);
      branches[ibr].elongateTree(tsim);
      branches[ibr].diamTree(tsim);
      branches[ibr].updateTree(tsim);
      if (sTflag == 1 && (Math.round(tsim/(double)dt) % Math.round(Tterm/(double)dt) == 0)) {
        timeTerm[ibr][(int)(tsim/Tterm)] = branches[ibr].countTerminals();
      };
      if (nTrees == 1 && dispfl && ((long)(tsim/(double)dt) % dtdisp == 0)) {
        paint(g);
      };
    };

  }



  // Get and validate tree index
  boolean getTreeIndex() {
    ibr = Integer.parseInt(ibr_text.getText())-1;  // tree index
    if (0 <= ibr && ibr < nTrees)  {  // valid index
      return true;
    }
    else { // bad index
      message_text.setText("Index should be between 1 and "+nTrees);
    };
    return false;
  }



  // Calculate tree statistics
  void calcTreeStats() {

    // data structures for one point per tree
    totSegments = new int[nTrees];
    totTerms = new int[nTrees];
    totLength = new float[nTrees];
    asym = new float[nTrees];
    maxCO = new int[nTrees];
    maxPath = new float[nTrees];

    // calculate data (one data point per tree)
    for (ibr = 0; ibr < nTrees; ibr++) {
      totSegments[ibr] = branches[ibr].countSegments();
      totTerms[ibr] = branches[ibr].countTerminals();
      totLength[ibr] = branches[ibr].totPathLength();
      asym[ibr] = branches[ibr].asymIndex();
      maxCO[ibr] = branches[ibr].maxOrder();
      maxPath[ibr] = branches[ibr].maxPathLength();
    };

    // calculate numbers of data points for multiple points per tree
    nTerms = 0; nInts = 0;
    for (ibr = 0; ibr < nTrees; ibr++) {
      nTerms = nTerms + totTerms[ibr];  // number of terminals
      nInts = nInts + totSegments[ibr] - totTerms[ibr];  // intermediates
    };

    // data structures for multiple points per tree
    centorder = new int[nTerms];
    pathLength = new float[nTerms];
    termLength = new float[nTerms];
    intLength = new float[nInts];

    // calculate and store this data
    int i = 0;
    int j = 0;
    branches[0].pathLengths(pathLength, 0);
    branches[0].termLengths(termLength, 0);
    branches[0].intLengths(intLength, 0);
    branches[0].centOrders(centorder, 0);
    for (ibr = 1; ibr < nTrees; ibr++) {
      i = i + totTerms[ibr-1];
      branches[ibr].pathLengths(pathLength, i);
      branches[ibr].termLengths(termLength, i);
      branches[ibr].centOrders(centorder, i);
      j = j + totSegments[ibr-1] - totTerms[ibr-1];
      branches[ibr].intLengths(intLength, j);
    };

    // stats on data points
    mSegs = (float)ArrayStats.mean(totSegments, nTrees);
    sdSegs = (float)ArrayStats.std(totSegments, nTrees);
    mTerms = (float)ArrayStats.mean(totTerms, nTrees);
    sdTerms = (float)ArrayStats.std(totTerms, nTrees);
    mLength = (float)ArrayStats.mean(totLength, nTrees);
    sdLength = (float)ArrayStats.std(totLength, nTrees);
    mAsym = (float)ArrayStats.mean(asym, nTrees);
    sdAsym = (float)ArrayStats.std(asym, nTrees);
    mCO = (float)ArrayStats.mean(centorder, nTerms);
    sdCO = (float)ArrayStats.std(centorder, nTerms);
    mPathL = (float)ArrayStats.mean(pathLength, nTerms);
    sdPathL = (float)ArrayStats.std(pathLength, nTerms);
    mTermL = (float)ArrayStats.mean(termLength, nTerms);
    sdTermL = (float)ArrayStats.std(termLength, nTerms);
    mIntL = (float)ArrayStats.mean(intLength, nInts);
    sdIntL = (float)ArrayStats.std(intLength, nInts);

    // calculate average number of terminals over time
    mtimeTerm = new float[(int)(Tstop/Tterm)+1];
    for (int it = 0; it <= (int)(Tstop/Tterm); it++) {
      int numT = 0;
      for (ibr = 0; ibr < nTrees; ibr++) numT += timeTerm[ibr][it];
      mtimeTerm[it] = (float)numT / (float)nTrees;
    };

  }



  // Show individual tree statistics
  void indTreeStats(int ibr) {
    message = new StringBuffer();
    message.append("Segments = " + Integer.toString(totSegments[ibr]));
    message.append("; Degree=" + Integer.toString(totTerms[ibr]));
    message.append("; Asymmetry=" + Float.toString(asym[ibr]));
    message.append("; Order=" + Integer.toString(maxCO[ibr]));
    message.append("\nTotal Length = " + Float.toString(totLength[ibr]));
    message.append("; Max Path Length = " + Float.toString(maxPath[ibr]));
    message.append("\nPath length: m=" + Float.toString(mPathL));
    message.append(", std=" + Float.toString(sdPathL));
    message.append("\nIntermediate length: m=" + Float.toString(mIntL));
    message.append(", std=" + Float.toString(sdIntL));
    message.append("\nTerminal length: m=" + Float.toString(mTermL));
    message.append(", std=" + Float.toString(sdTermL));
    message_text.setText(message.toString());
  }



  // Show multiple tree statistics
  void multTreeStats() {
    message = new StringBuffer();     
    message.append("Degree: mean=" + Float.toString(mTerms));
    message.append(", std=" + Float.toString(sdTerms));
    message.append("\nAsymmetry: mean=" + Float.toString(mAsym));
    message.append(", std=" + Float.toString(sdAsym));
    message.append("\nCentrifugal order: mean=" + Float.toString(mCO));
    message.append(", std=" + Float.toString(sdCO));
    message.append("\nTotal length: mean=" + Float.toString(mLength));
    message.append(", std=" + Float.toString(sdLength));
    message.append("\nPath length: m=" + Float.toString(mPathL));
    message.append(", std=" + Float.toString(sdPathL));
    message.append("\nIntermediate length: m=" + Float.toString(mIntL));
    message.append(", std=" + Float.toString(sdIntL));
    message.append("\nTerminal length: m=" + Float.toString(mTermL));
    message.append(", std=" + Float.toString(sdTermL));
    message_text.setText(message.toString());
  }



  // Store tree data in files
  void storeTreeData(String fstem) {
    try {
      switch(tree_type) {
        case BES_type:
        default:
          storeInfo(fstem+".inf", BESTree.DEparams, BESTree.Nparams);
          break;
        case AD_type:
          storeInfo(fstem+".inf", ADTree.DEparams, ADTree.Nparams);
          break;
        case ADcm_type:
            storeInfo(fstem+".inf", ADcmTree.DEparams, ADcmTree.Nparams);
            break;
      };
      storeArray(fstem+"_deg.dat", totTerms, nTrees);
      storeArray(fstem+"_co.dat", centorder, nTerms);
      storeArray(fstem+"_asym.dat", asym, nTrees);
      storeArray(fstem+"_totL.dat", totLength, nTrees);
      storeArray(fstem+"_termL.dat", termLength, nTerms);
      storeArray(fstem+"_intL.dat", intLength, nInts);
      storeArray(fstem+"_pathL.dat", pathLength, nTerms);
	  if (sTflag == 1)
          storeArray(fstem+"_termT.dat", mtimeTerm, (int)(Tstop/Tterm)+1);
    }
    catch(IOException e){};
  }



  // Store model parameter information in a file
  void storeInfo(String fname, String[][] pinfo, int ndata)
      throws IOException {
    int i;
    FileOutputStream fout = null;
    PrintWriter pout = null;

    fout = new FileOutputStream(fname);
    pout = new PrintWriter(fout);
    for (i = 0; i < ndata; i++)
      pout.println(pinfo[i][0]+" "+pinfo[i][2]);
    //    if (tree_type != Br1_type) {
      pout.println("\nResults:");
      multTreeStats();
      pout.println(message.toString());
      //      };
    pout.close();
  }



  // Store an array of float data in a file
  void storeArray(String fname, float[] adata, int ndata)
      throws IOException {
    int i;
    FileOutputStream fout = null;
    PrintWriter pout = null;

    fout = new FileOutputStream(fname);
    pout = new PrintWriter(fout);
    for (i = 0; i < ndata; i++)
      pout.println(adata[i]);
    pout.close();
  }



  // Store an array of int data in a file
  void storeArray(String fname, int[] adata, int ndata)
      throws IOException {
    int i;
    FileOutputStream fout = null;
    PrintWriter pout = null;

    fout = new FileOutputStream(fname);
    pout = new PrintWriter(fout);
    for (i = 0; i < ndata; i++)
      pout.println(adata[i]);
    pout.close();
  }


}



// Frame class for application
class NeuriteFrame extends Frame {
    public NeuriteFrame() {
      super("Neurite");

      Neurite applet = new Neurite();

      add(applet, "Center");
      applet.init();

      addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent event) {
          dispose();
          System.exit(0);
        }
      });
    }
}