//=====================================================================
// File:    CutoffDialog.java
// Class:   CutoffDialog
// Package: AFLPgui
//
// Author:  James J. Benham
// Date:    August 11, 1998
// Contact: james_benham@hmc.edu
//
// Genographer v1.0 - Computer assisted scoring of gels.
// Copyright (C) 1998  Montana State University
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2
// of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The GNU General Public License is distributed in the file GPL
//=====================================================================

package AFLPgui;

import java.awt.Button;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Label;
import java.awt.List;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import AFLPcore.Cutoff;
import AFLPcore.CutoffFunction;
import AFLPcore.DataList;
import AFLPcore.FeatureList;
import AFLPcore.Lane;
import AFLPcore.Option;
import AFLPcore.ProgOptions;

/**
 * This dialog box will display a cutoff. All of the levels of the cutoff
 * are displayed in a list. Any element of the list can be selected, and
 * the height of that element can be modified. Additionally, new elements,
 * cutoff functions, can be added to the list as well as deleted. The
 * dialog box will save the cutoff to a list of lanes when the "Ok" button
 * is clicked.
 *
 * @see Cutoff
 * @see CutoffFunction
 *
 * @author James J. Benham
 * @version 1.0.0
 * @date August 11, 1998
 */

public class CutoffDialog extends Dialog implements ActionListener,
                                                    ItemListener,
                                                    KeyListener,
                                                    WindowListener
{
  private static final String HELP_FILE = "cutoffhelp.html";

  private static int WIDTH = 444;
  private static int HEIGHT = 240;

  // List box parameters
  private static int LIST_H_INSET = 10;
  private static int LIST_V_INSET = 30;
  private static int LIST_WIDTH   = 90;
  private static int LIST_HEIGHT  = 200;

  // Field parameters
  private static int V_SPACE = 10;
  private static int H_SPACE = 4;
  private static int COMP_HEIGHT = 25;
  private static int COMP_H_INSET = 110;
  private static int LABEL_WIDTH = 80;
  private static int FIELD_WIDTH = 80;

  // Button parameters
  private static int BUTTON_WIDTH = 80;
  private static int ENTER_WIDTH = 120;
  private static int SCORE_WIDTH = 110;
  private static int BUTTON_H_INSET = 354;

  private Button okButton;
  private Button cancelButton;
  private Button newButton;
  private Button deleteButton;
  private Button defaultButton;
  private Button helpButton;
  private Button enterButton;
  
  private List cutoffList;

  private TextField heightField;

  private Cutoff currentCutoff;
  private DataList lanes;
  private int index;
  private double size;

  private Frame parent;

  /**
   * Create a new CutoffDialog with the specified parameters.
   *
   * @param parent   the owner of this dialog box
   * @param title    the title of the dialog box
   * @param modal    if true, dialog blocks input to the parent window 
   *                 when shown
   */
  public CutoffDialog(Frame parent, String title, boolean modal)
  {
    super(parent, title, modal);

    this.parent = parent; 
    index = -1;

    addWindowListener(this);

    componentLayout();

    loadSelection(index);
  }

  /**
   * Initializes the CutoffDialog with important information. This
   * should be called before displaying the dialog box.
   *
   * @param ct       the cutoff to display. The levels of the cutoff will
   *                 appear in the list.
   * @param lanes    the lanes to add the cutoff to when the "Ok" button
   *                 is clicked.
   * @param startPos the size in bp where the cutoff should first be
   *                 defined. The cutoff will apply to the lane from
   *                 the start position to the beginning of the next
   *                 cutoff or until the end of the lane.
   */
  public void init(Cutoff ct, DataList lanes, double startPos)
  {
    currentCutoff = (Cutoff) ct.clone();
    this.lanes = lanes;
    size = startPos;

    // refresh the display
    CutoffFunction ctfn;
    cutoffList.setVisible(false);
    cutoffList.removeAll();
    for(int i=0; i < currentCutoff.getNumLevels(); i++)
      {
	ctfn = currentCutoff.getCutoffFunction(i);
	cutoffList.add("" + ctfn.getCutoff(size));
      }
    cutoffList.setVisible(true);
  }
  
  /**
   * Loads the selected function when something in the list is changed.
   */
  public void itemStateChanged(ItemEvent e)
  {
    if(e.getSource() == cutoffList)
      {
	// get the selected item
	index = cutoffList.getSelectedIndex();

	loadSelection(index);
      }
  }

  /**
   * Handles the events generated by the buttons.
   */
  public void actionPerformed(ActionEvent e)
  {
    try{
      if(e.getSource() == okButton)
	{
	  saveToLanes(); // save the info;
	  dispose();
	  setVisible(false);
	}
      else if(e.getSource() == enterButton)
	{
	  storeInfo(index);
	}
      else if(e.getSource() == cancelButton)
	{
	  dispose();
	  setVisible(false);
	}
      else if(e.getSource() == newButton)
	{
	  index = -1;
	  cutoffList.select(index);
	  loadSelection(index);
	}
      else if(e.getSource() == deleteButton)
	{
	  if(index != -1)
	    {
	      cutoffList.remove(index);
	      currentCutoff.deleteFunction(index);
	      index = -1;
	      loadSelection(index);
	    }
	}
      else if(e.getSource() == defaultButton)
	{
	  cutoffList.removeAll();

	  currentCutoff = new Cutoff(size, 1);
	  CutoffFunction ctfn;
	  ctfn = (CutoffFunction) FeatureList.getCutoffMgr().getDefault();
	  ctfn = (CutoffFunction) ctfn.clone();
	  Option opts[] = ctfn.getOptions();
	  opts[0].setValue(1000);
	  ctfn.setOptions(opts);

	  currentCutoff.setCutoffFunction(ctfn, 0);

	  cutoffList.add("1000.0");

	  index = -1;
	  loadSelection(index);
	}
      else if(e.getSource() == helpButton)
	{
	  ProgOptions.showHelp(HELP_FILE);
	}
    }
    catch(Throwable error)
      {
	ErrorDialog eD = new ErrorDialog((Frame) getParent());
	eD.showError(error);
      }
  }

  /**
   * Used to store the info when enter is pressed in the text field.
   */
  public void keyReleased(KeyEvent e)
  {
    // Assume that a release of the enter key constitutes the typing
    // of the enter key. It is very unlikely that it got pushed down
    // somewhere else and released here.

    // Only one text field is sending events
    if (e.getKeyCode() == KeyEvent.VK_ENTER)
      storeInfo(index);
  }

  /**
   * Closes the dialog box. This has the same affect as pressing the cancel
   * button.
   */
  public void windowClosing(WindowEvent e)
  {
    dispose();
    setVisible(false);
  }

  /**
   * Loads the specified cutoff function into the display. In other words,
   * it loads the value into the height text field. If the <code>index</code>
   * is -1, then a blank display is loaded.
   *
   * @param index  the level of the desired cutoff to load.
   */
  private void loadSelection(int index)
    {
      if(index != -1)
	{
	  CutoffFunction ctFn = currentCutoff.getCutoffFunction(index);

	  // update the displays
	  heightField.setText("" + ctFn.getCutoff(size));
	}
      else
	{
	  heightField.setText("");
	}
    }

  /**
   * Reads in the height from the text field and stores it into the
   * cutoff. If the <code>index</code> is -1, then a new level will
   * be created and added to the cutoff.
   *
   * @param index  the level of the current cutoff function, -1 for a
   *               new function
   *
   * @exception NumberFormatException occurs if the method cannot read
   *            a number from the text field.
   */
  public void storeInfo(int index) throws NumberFormatException
  {
    double height = 0;
    try{
      height = (new Double(heightField.getText())).doubleValue();
    }
    catch(NumberFormatException e)
      {
	throw new NumberFormatException("Height must be a number.");
      }

    CutoffFunction ctfn;
    Option opts[];

    if(index != -1)
      {
	// simply modifiy the current function and update the name in the list
	ctfn = currentCutoff.getCutoffFunction(index);
	
	// get the options and use that to set the height
	opts = ctfn.getOptions();
	
	// set the first one, which should be the height and store it back
	opts[0].setValue(height);
	ctfn.setOptions(opts);

	cutoffList.replaceItem("" + ctfn.getCutoff(size), index);
      }
    else
      {
	// We have a new function, so it must be created and then added to the 
	// list. Find the location of the function and insert it there in the 
	// screen list
	ctfn = (CutoffFunction) FeatureList.getCutoffMgr().getDefault();
	ctfn = (CutoffFunction) ctfn.clone();

	// get the options and use that to set the height
	opts = ctfn.getOptions();
	
	// set the first one, which should be the height and store it back
	opts[0].setValue(height);
	ctfn.setOptions(opts);

	// add the function
	currentCutoff.addFunction(ctfn);

	// refresh the display
	cutoffList.setVisible(false);
	cutoffList.removeAll();
	for(int i=0; i < currentCutoff.getNumLevels(); i++)
	  {
	    ctfn = currentCutoff.getCutoffFunction(i);
	    cutoffList.add("" + ctfn.getCutoff(size));
	  }
	cutoffList.setVisible(true);
      }
  }

  /**
   * Shows or hides this dialog box. When it is shown, it will load
   * a blank selection.
   * 
   * @param b  <code>true</code> to show the component.
   */
  public void setVisible(boolean b)
  {
    super.setVisible(b);

    if(b)
      {
	// show blank fields and no selection
	index = -1;
	loadSelection(index);
      }
  }

  /**
   * Takes the cutoff currently defined by this class and adds it to
   * all of the lanes specified in <code>init</code>. The cutoff is
   * added at the point specified in <code>init</code>. If a cutoff
   * already exists at that location, it is replaced. If not, then
   * the cutoff is simply inserted there.
   *
   * @see CutoffDialog#init
   */
  private void saveToLanes()
  {
    Lane ln;
    Cutoff ctff = null;
    for(int i = 0; i < lanes.size(); i++)
      {
	ln = (Lane) lanes.dataAt(i);
	ctff = ln.cutoffUnder(size);

	// see if we need to create a new cutoff function for this region.
 	if(ctff.getStartPos() < size)
 	  {
 	    ln.addCutoff((Cutoff) currentCutoff.clone());
 	  }
	else
	  {
	    // we have to replace the one there, it's easiest.
	    DataList ctList = ln.getCutoffs();
	    int location = ctList.find(ctff.getStartPos()).location;
	    
	    ctList.removeElementAt(location);
	    ln.addCutoff((Cutoff) currentCutoff.clone());
	  }
      }
  }

  /**
   * Adds all of the components to the dialog box and adds the 
   * appropriate listeners.
   */
  private void componentLayout()
  {
    int startY = 0;

    setLayout(null);
    setSize(WIDTH, HEIGHT);
    setResizable(false);

    //====================Add the list==================
    cutoffList = new List();
    add(cutoffList);
    cutoffList.setBounds(LIST_H_INSET, LIST_V_INSET, LIST_WIDTH, LIST_HEIGHT);
    cutoffList.addItemListener(this);

    startY += LIST_V_INSET;

    //===================Add height stuff==================
    Label heightL = new Label("Height:", Label.LEFT);
    heightField = new TextField();
    add(heightL);
    add(heightField);
    heightL.setBounds(COMP_H_INSET, startY, LABEL_WIDTH, COMP_HEIGHT);
    heightField.setBounds(COMP_H_INSET + H_SPACE + LABEL_WIDTH, startY, 
			FIELD_WIDTH, COMP_HEIGHT);
    heightField.addKeyListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    //====================Add buttons======================
    enterButton = new Button("Enter Changes");
    add(enterButton);
    enterButton.setBounds(COMP_H_INSET, startY, ENTER_WIDTH, COMP_HEIGHT);
    enterButton.addActionListener(this);

    startY = LIST_V_INSET;
    okButton = new Button("Ok");
    add(okButton);
    okButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    okButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    cancelButton = new Button("Cancel");
    add(cancelButton);
    cancelButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    cancelButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    newButton = new Button("New");
    add(newButton);
    newButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    newButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    deleteButton = new Button("Delete");
    add(deleteButton);
    deleteButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    deleteButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    defaultButton = new Button("Default");
    add(defaultButton);
    defaultButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    defaultButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    helpButton = new Button("Help");
    add(helpButton);
    helpButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    helpButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;
  }

  // ==================Unused methods required by interfaces=================
  /**Unused*/public void keyPressed(KeyEvent e) {}
  /**Unused*/public void keyTyped(KeyEvent e) {}

  /**Unused*/public void windowOpened(WindowEvent e) {}
  /**Unused*/public void windowClosed(WindowEvent e) {}
  /**Unused*/public void windowIconified(WindowEvent e) {}
  /**Unused*/public void windowDeiconified(WindowEvent e) {}
  /**Unused*/public void windowActivated(WindowEvent e) {}
  /**Unused*/public void windowDeactivated(WindowEvent e) {}
}
