//=====================================================================
// File:    BinDialog.java
// Class:   BinDialog
// 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.Checkbox;
import java.awt.Choice;
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.Bin;
import AFLPcore.DataList;
import AFLPcore.FeatureList;
import AFLPcore.Option;
import AFLPcore.ProgOptions;
import AFLPcore.ScoreFunction;
import AFLPcore.ScoreManager;

/**
 * This is a dialog box that shows a list of bins. It will allow the
 * user to add new bins, delete a bin, delete all of the bins, and change
 * the parameters of a bin: the location, range, and scoring method. 
 * The user can change the scoring method for all of the bins at once.
 * Changes are stored in a seperate list, and are saved only when the "Ok"
 * button is clicked. Most errors are caught and then sent to a dialog
 * box to be displayed. The name of the bin displayed in the list depends 
 * on whether or not the bin has a name defined. If it does, that name will
 * be used. Otherwise, the displayed name is simply the location of the bin.
 * Methods are provided to both load a bin into the display portion of the
 * dialog box and to save the display to a bin.
 *
 * @author James J. Benham
 * @version 1.0.0
 * @date August 11, 1998
 */

public class BinDialog extends Dialog implements ActionListener,
                                                 ItemListener,
                                                 KeyListener,
                                                 WindowListener
{
  private static final String HELP_FILE = "binhelp.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;
  private static int CHOICE_WIDTH = 150;

  // 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;

  // Check Box parameter
  private static int CHECK_INSET = 5;
  private static int CHECK_WIDTH = 200;

  private Button okButton;
  private Button cancelButton;
  private Button newButton;
  private Button deleteButton;
  private Button delAllButton;
  private Button helpButton;
  private Button enterButton;
  private Button scoreOptionButton;
  private Checkbox scoreAllCheck;
  
  private List binList;
  private Choice scoreChoice;

  private TextField nameField;
  private TextField locationField;
  private TextField rangeField;

  private DataList bins;
  private DataList savedBins;
  private Bin currentBin;
  private int index;
  private boolean allMode;
  private String scoreNames[];

  private ScoreFunction scoreFn;

  private Frame parent;
  
  /**
   * Create a new Bin Dialog 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 BinDialog(Frame parent, String title, boolean modal)
  {
    super(parent, title, modal);

    this.parent = parent; 
    bins = new DataList();
    currentBin = null;
    index = -1;

    addWindowListener(this);

    componentLayout();

    loadSelection(index);
  }

  /**
   * The bins to display. Each bin will be given a display name. This
   * will be the name of the bin itself if it is defined, or it will
   * simply be the bin's location.
   *
   * @param bins  the list of bins to dispaly in the dialog box.
   */
  public void setBins(DataList bins)
  {
    savedBins = bins;
    this.bins = bins.completeClone();

    // Add the bins to the list
    Bin temp;
    binList.removeAll();
    for(int i=0; i < this.bins.size(); i++)
      {
	temp = (Bin) this.bins.dataAt(i);
	if(temp.getName().equals(""))
	  binList.add("" + temp.getLocation());
	else
	  binList.add(temp.getName());
      }
  }

  /**
   * Handles all of the action event, which come mostly from the 
   * buttons in the dialog box. To see how each button is handled, 
   * please see the source.
   */
  public void actionPerformed(ActionEvent e)
  {
    try{
      if(e.getSource() == okButton)
	{
	  bins.copyList(savedBins);  // store the changes
	  dispose();
	  setVisible(false);
	}
      else if(e.getSource() == enterButton)
	{
	  storeInfo(index);
	}
      else if(e.getSource() == cancelButton)
	{
	  bins = savedBins; // dispose of changes
	  dispose();
	  setVisible(false);
	}
      else if(e.getSource() == newButton)
	{
	  index = -1;
	  binList.select(index);
	  loadSelection(index);
	}
      else if(e.getSource() == deleteButton)
	{
	  if(index != -1)
	    {
	      binList.remove(index);
	      bins.removeElementAt(index);
	      index = -1;
	      loadSelection(index);
	    }
	}
      else if(e.getSource() == delAllButton)
	{
	  bins = new DataList();
	  binList.removeAll();
	  index = -1;
	  loadSelection(index);
	}
      else if(e.getSource() == helpButton)
	{
	  ProgOptions.showHelp(HELP_FILE);
	}
      else if(e.getSource() == scoreOptionButton)
	{
	  OptionDialog optDialog = new OptionDialog(scoreFn.getOptions(),
						    (Frame) getParent(),
						    scoreFn.getName() + 
						    " Parameters");
	  optDialog.setVisible(true);
	  if(!optDialog.isCanceled())
	    {
	      Option[] opts = optDialog.getOptions();
	      scoreFn.setOptions(opts);
	    }
	}
    }
    catch(Throwable error)
      {
	ErrorDialog eD = new ErrorDialog((Frame) getParent());
	eD.showError(error);
      }
  }

  /**
   * This handles events generated when the list is manipulated or when
   * the check box is changed. It also handles the event when the 
   * scoring method is changed.
   */
  public void itemStateChanged(ItemEvent e)
  {
    if(e.getSource() == binList)
      {
	// get the selected item
	index = binList.getSelectedIndex();

	loadSelection(index);
      }
    else if(e.getSource() == scoreAllCheck)
      {
	allMode = scoreAllCheck.getState();
	if(allMode)
	  {
	    nameField.setText("----------------");
	    nameField.setEditable(false);
	    locationField.setText("----------------");
	    locationField.setEditable(false);
	    rangeField.setText("----------------");
	    rangeField.setEditable(false);
	    binList.setMultipleMode(true);
	    binList.setVisible(false);
	    for(int i=0; i < bins.size(); i++)
	      binList.select(i);
	    binList.setVisible(true);
	    binList.setEnabled(false);
	  }
	else
	  {
	    nameField.setEditable(true);
	    locationField.setEditable(true);
	    rangeField.setEditable(true);
	    binList.setMultipleMode(false);
	    binList.setEnabled(true);
	    binList.select(index);
	    loadSelection(index);
	  }
      }
    else if(e.getSource() == scoreChoice)
      {
	String value = scoreChoice.getSelectedItem();
	scoreFn = (ScoreFunction) FeatureList.getScoreMgr().get(value);
	scoreFn = (ScoreFunction) scoreFn.clone();
      }
  }

  /**
   * This is used to cause the dialog box to store changes made when
   * the Enter key is pushed in one of the fields.
   */
  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.

    // There are only three listeners, so enter the values if the enter
    // key is pushed
    if (e.getKeyCode() == KeyEvent.VK_ENTER)
      storeInfo(index);
  }

  /**
   * Allows the window to close and has the same affect as pressing the
   * cancel button. None of the changes are saved.
   */
  public void windowClosing(WindowEvent e)
  {
    bins = savedBins; // dispose of changes
    dispose();
    setVisible(false);
  }

  /**
   * Displays the specified bin in the dialog box. If the <code>index</code>
   * is -1, then a blank display is created.
   *
   * @param index  the index of the bin in the list of bins.
   *
   * @see BinDialog#setBins
   */
  private void loadSelection(int index)
    {
      if(index != -1)
	{
	  // retrieve the bin
	  currentBin = (Bin) bins.dataAt(index);
	  
	  // update the displays
	  nameField.setText(currentBin.getName());
	  locationField.setText("" + currentBin.getLocation());
	  rangeField.setText("" + currentBin.getRange());
	  scoreFn = currentBin.getScoreMethod();
	  scoreChoice.select(scoreFn.getName());
	}
      else
	{
	  currentBin = null;
	  nameField.setText("");
	  locationField.setText("");
	  rangeField.setText("");
	  scoreFn = (ScoreFunction) FeatureList.getScoreMgr().getDefault();
	  scoreChoice.select(FeatureList.getScoreMgr().getDefaultName());
	}
    }

  /**
   * This will read in the values from the display and store them
   * appropriatly. If the <code>index</code> is -1, then a new bin will
   * be created and added to the list. Otherwise, the values will be read
   * in and stored in the currently selected bin. If the check box for
   * changing all of the scoring methods is selected, then only the
   * score method will be stored, but it will be stored in all of the bins.
   *
   * @param index   the index of the bin in the bin list, -1 for a new bin
   *
   * @exception NumberFormatException occurs if this method cannot read in
   *         a number from one of the text boxes.
   */
  public void storeInfo(int index) throws NumberFormatException
  {
    String scoreName;
    ScoreManager mgr = FeatureList.getScoreMgr();

    if(!allMode)
      {
	// Read the values from the fields
	String name = nameField.getText();
	double location = 0;
	double range = 0;
	try{
	  location = (new Double(locationField.getText())).doubleValue();
	  range = (new Double(rangeField.getText())).doubleValue();
	}
	catch(NumberFormatException e)
	  {
	    throw new NumberFormatException("Location and Range must be" +
					    " numbers.");
	  }
	
	// Check to see if we should display the name or the size.
	String displayName;
	if(name.equals(""))
	  displayName = "" + location;
	else
	  displayName = name;

	if(index != -1)
	  {
	    // simply modifiy the current bin and update the name in the list
	    currentBin.setName(name);
	    currentBin.setLocation(location);
	    currentBin.setRange(range);
	    scoreName = scoreChoice.getSelectedItem();
	    currentBin.setScoreMethod(scoreFn);
	    
	    binList.replaceItem(displayName, index);
	    binList.select(index);
	  }
	else
	  {
	    // We have a new bin, so it must be created and then added to the 
	    // list. Find the location of the bin and insert it there in the 
	    // screen list
	    currentBin = new Bin(location, range);
	    currentBin.setName(name);
	    currentBin.setScoreMethod((ScoreFunction) mgr.getDefault());
	    bins.include(currentBin);
	    index = (bins.find(location)).location;
	    binList.add(displayName, index);
	    binList.select(index);
	  }
      }
    else
      {
	// change the scoring method for all of the bins.
	for(int i=0; i < bins.size(); i++)
	  {
	    scoreFn = (ScoreFunction) scoreFn.clone();
	    ((Bin) bins.dataAt(i)).setScoreMethod(scoreFn);
	  }
      }
  }

  /**
   * Controls wether or not the dialog box is visible. It it is
   * <code>true</code>, then a new blank selection will be loaded.
   *
   * @param b  the visibility state of the dialog box.
   */
  public void setVisible(boolean b)
  {
    super.setVisible(b);

    if(b)
      {
	// show blank fields and no selection
	index = -1;
	loadSelection(index);
      }
  }

  /**
   * Adds all of the components to the dialog box and adds the
   * appropriate listeners as well.
   */
  private void componentLayout()
  {
    int startY = 0;

    setLayout(null);
    setSize(WIDTH, HEIGHT);
    setResizable(false);

    //====================Add the list==================
    binList = new List();
    add(binList);
    binList.setBounds(LIST_H_INSET, LIST_V_INSET, LIST_WIDTH, LIST_HEIGHT);
    binList.addItemListener(this);

    //====================Add name stuff=================
    Label nameL = new Label("Name:", Label.LEFT);
    nameField = new TextField();
    add(nameL);
    add(nameField);
    nameL.setBounds(COMP_H_INSET, LIST_V_INSET, LABEL_WIDTH, COMP_HEIGHT);
    nameField.setBounds(COMP_H_INSET + H_SPACE + LABEL_WIDTH, LIST_V_INSET, 
			FIELD_WIDTH, COMP_HEIGHT);
    nameField.addKeyListener(this);
    startY += LIST_V_INSET + V_SPACE + COMP_HEIGHT;

    //===================Add location stuff==================
    Label locationL = new Label("Location:", Label.LEFT);
    locationField = new TextField();
    add(locationL);
    add(locationField);
    locationL.setBounds(COMP_H_INSET, startY, LABEL_WIDTH, COMP_HEIGHT);
    locationField.setBounds(COMP_H_INSET + H_SPACE + LABEL_WIDTH, startY, 
			FIELD_WIDTH, COMP_HEIGHT);
    locationField.addKeyListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    //===================Add range stuff==================
    Label rangeL = new Label("Range:", Label.LEFT);
    rangeField = new TextField();
    add(rangeL);
    add(rangeField);
    rangeL.setBounds(COMP_H_INSET, startY, LABEL_WIDTH, COMP_HEIGHT);
    rangeField.setBounds(COMP_H_INSET + H_SPACE + LABEL_WIDTH, startY, 
			FIELD_WIDTH, COMP_HEIGHT);
    rangeField.addKeyListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    //===================Add score stuff==================
    Label scoreL = new Label("Score Using:", Label.LEFT);
    scoreChoice = new Choice();
    add(scoreL);
    add(scoreChoice);
    scoreL.setBounds(COMP_H_INSET, startY, LABEL_WIDTH, COMP_HEIGHT);
    scoreChoice.setBounds(COMP_H_INSET + H_SPACE + LABEL_WIDTH, startY, 
			CHOICE_WIDTH, COMP_HEIGHT);
    // add the choices
    scoreNames = FeatureList.getScoreMgr().getNames();
    for(int i=0; i < scoreNames.length; i++)
      scoreChoice.add(scoreNames[i]);

    scoreChoice.select(FeatureList.getScoreMgr().getDefaultName());
    scoreChoice.addItemListener(this);

    startY += V_SPACE + COMP_HEIGHT;    

    //====================Add Score Check Box======================
    scoreAllCheck = new Checkbox("Score all using this method", false);
    add(scoreAllCheck);
    scoreAllCheck.setBounds(COMP_H_INSET + CHECK_INSET, startY,
			    CHECK_WIDTH, COMP_HEIGHT);
    scoreAllCheck.addItemListener(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);

    scoreOptionButton = new Button("Scoring Options");
    add(scoreOptionButton);
    scoreOptionButton.setBounds(COMP_H_INSET + H_SPACE + ENTER_WIDTH,
				startY, SCORE_WIDTH, COMP_HEIGHT);
    scoreOptionButton.addActionListener(this);
    startY += V_SPACE + COMP_HEIGHT;

    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;

    delAllButton = new Button("Delete All");
    add(delAllButton);
    delAllButton.setBounds(BUTTON_H_INSET, startY, BUTTON_WIDTH, COMP_HEIGHT);
    delAllButton.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) {}
}
