import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class JumpToWidget extends Applet implements KeyListener,FocusListener
{
	private String namesString;
	private String valuesString;

	private Vector namesVector;
	private Vector lowercaseNamesVector;
	private Vector valuesVector;
	private Vector optionsValuesVector;
	private Vector optionsNamesVector;

	private java.awt.List optionsList;
	private java.awt.TextField tf;
	private int isCaseSensitive;


	/**
	 * Initializes the user interface and private variables.
	 */
	public void init()
	{
		int numOptions;

		// create user interface using the grid bag layout
		GridBagLayout layout = new GridBagLayout();
		this.setLayout(layout);

		GridBagConstraints constraints = new GridBagConstraints();

		// prepare grid bag constraints for text field
		constraints.weightx = 100;
		constraints.weighty = 0;
		constraints.gridx = 0;
		constraints.gridy = 0;
		constraints.gridwidth = 1;
		constraints.gridheight = 1;
		constraints.fill = GridBagConstraints.HORIZONTAL;
		constraints.anchor = GridBagConstraints.NORTHEAST;

		// the text field
		tf = new TextField("",25);
		tf.addKeyListener(this);
		layout.setConstraints(tf, constraints);
		this.add(tf);
		optionsList = new java.awt.List(30, false);

		// prepare grid bag constraints for list
		constraints.gridy = 1;
		constraints.weighty = 100;
		constraints.fill = GridBagConstraints.BOTH;
		constraints.anchor = GridBagConstraints.CENTER;
		layout.setConstraints(optionsList, constraints);

		// add the list and calculate the number of rows that
		// are actually visible
		this.add(optionsList);
		this.validate();

		Dimension dim = new Dimension();

		dim = optionsList.getSize();

		Dimension dim4 = new Dimension();
		Dimension dim5 = new Dimension();
		dim4 = optionsList.getPreferredSize(4);
		dim5 = optionsList.getPreferredSize(5);
		numOptions = (dim.height/(dim5.height - dim4.height)) - 1;
		if(numOptions <= 0)
		{
			numOptions = 1;
		}

		// remove and re-add the list so that it's number of rows
		// is the same as the number that are visible
		this.remove(optionsList);
		this.validate();
		optionsList = new java.awt.List(numOptions, false);
		optionsList.addFocusListener(this);
		optionsList.setFont(new Font("SansSerif",java.awt.Font.PLAIN,11));

		layout.setConstraints(optionsList, constraints);
		this.add(optionsList);

		// initialize private variables
		namesVector = new Vector();
		lowercaseNamesVector = new Vector();
		valuesVector = new Vector();
		optionsValuesVector = new Vector();
		optionsNamesVector = new Vector();

		// set the case sensitivity
		setCaseSensitivity(getParameter("casesensitivity"));

		// read the parameters from the html page
		loadNamesAndValues(getParameter("names"), getParameter("values"));

	}

	/**
	 * Returns the value of the selected option.
	 * @return <code>null</code> if no option is selected, otherwise
	 *   the value of the currently selected option
	 */
	public String getSelectedValue()
	{
		if(optionsList.getSelectedIndex() != -1)
		{
			return (String)optionsValuesVector.elementAt(optionsList.getSelectedIndex());
		}
		else
		{
			return null;
		}
	}

	/**
	 * Returns the name of the selected option.
	 * @return <code>null</code> if no option is selected, otherwise
	 *   the visible name of the currently selected option
	 */
	public String getSelectedName()
	{
		if(optionsList.getSelectedItem() != null)
		{
			return (String)optionsList.getSelectedItem();
		}
		else
		{
			return null;
		}
	}

	public void setCaseSensitivity(String strCaseSensitivity)
	{
		strCaseSensitivity = strCaseSensitivity.toLowerCase();
		if(strCaseSensitivity.compareTo("yes") == 0
			|| strCaseSensitivity.compareTo("y") == 0
			|| strCaseSensitivity.compareTo("1") == 0
			|| strCaseSensitivity.compareTo("true") == 0 )
		{
			isCaseSensitive = 1;
		}
		else
		{
			isCaseSensitive = 0;
		}
	}

	/**
	 * Reloads the name and value strings into the internal vectors
	 * and refreshes the options list.  This must be called after
	 * the names and values fields are changed. If the names and values
	 * don't agree in number, then an error is printed in the text field.
	 * @see JumpToWidget#namesString
	 * @see JumpToWidget#valuesString
	 */
	public void loadNamesAndValues(String strNames, String strValues)
	{
		this.namesString = new String(strNames);
		this.valuesString = new String(strValues);

		int i;
		int countNames;
		int countValues;
		StringTokenizer st;
		String tempString;

		// clear the data vectors and options list
		namesVector.removeAllElements();
		valuesVector.removeAllElements();
		optionsNamesVector.removeAllElements();
		optionsValuesVector.removeAllElements();
		lowercaseNamesVector.removeAllElements();
		optionsList.removeAll();


		// get the names into an array
		st = new StringTokenizer(this.namesString, "|");
		countNames = 0;
		while (st.hasMoreTokens()) {
			tempString = new String(st.nextToken());
			namesVector.addElement(tempString);
			optionsNamesVector.addElement(tempString);
			if(isCaseSensitive == 0)
			{
				lowercaseNamesVector.addElement(tempString.toLowerCase());
			}
			else
			{
				lowercaseNamesVector.addElement(tempString);
			}
			countNames++;
		}

		// get the values into an array
		st = new StringTokenizer(valuesString, "|");
		countValues = 0;
		while (st.hasMoreTokens()) {
			tempString = new String(st.nextToken());
			valuesVector.addElement(tempString);
			optionsValuesVector.addElement(tempString);
			countValues++;
		}

		// if the number of names doesn't match the number of values
		// add an error to the option box.
		if(countNames != countValues)
		{
			// if there are names, but no values, then
			if(countValues == 0 && countNames != 0)
			{
				// load the names as values
				for(i = 0; i < namesVector.size(); i++)
				{
					valuesVector.addElement(namesVector.elementAt(i));
					optionsValuesVector.addElement(namesVector.elementAt(i));
					countValues++;
				}
			}
			else
			{
				// otherwise, print error
				tf.setText("ERROR: number of values doesn't match number of names");
			}
		}
		else
		{
			// reset the text field
			tf.setText("");
		}

		optionsList.setVisible(false);

		// only add the visible options because adding options is really slow
		for(i = 0; i < optionsList.getRows() && i < optionsNamesVector.size(); i++)
		{
			optionsList.add( (String)optionsNamesVector.elementAt(i) );
		}

		// select the first item
		if(optionsList.getItemCount() > 0)
		{
			optionsList.select(0);
		}

		optionsList.setVisible(true);

	}

	/**
	 * When the list gains focus, then we must populate
	 * the list with the remaining options which match
	 * the substring.
	 */
	public void focusGained(java.awt.event.FocusEvent fe)
	{
		int i;
		int selectedIndex;
		// only reload if the number of options displayed is
		// less than the actual number of options
		if(optionsList.getItemCount() < optionsNamesVector.size())
		{
			// rememember the index of the selected item
			selectedIndex = optionsList.getSelectedIndex();
			// remove the options
			optionsList.removeAll();
			// show loading message
			optionsList.add("Loading...");

			// set temporarily invisible
			optionsList.setVisible(false);
			// set the display to the new names
			for(i = 0; i < optionsNamesVector.size(); i++)
			{
				optionsList.add( (String)optionsNamesVector.elementAt(i) );
			}
			// show the options list
			optionsList.setVisible(true);
			// remove the loading message
			optionsList.remove(0);
			// reselect the selected item
			optionsList.select(selectedIndex);
			// make sure focus stays on the list box
			optionsList.requestFocus();
		}
	}

	/**
	 * Does nothing
	 */
	public void focusLost(java.awt.event.FocusEvent fe)
	{
		// do nothing
	}

	/**
	 * When key is released on the text field, then
	 * the internal options vectors are reloaded and the options
	 * list is updated to show only the options that contain
	 * the substrings in the text field.  To improve performance,
	 * only first few visible options are added back to the options list.
	 */
    public void keyReleased(KeyEvent e)
    {
		int i, j, bAddOption;
		String compareString, tempString;
		Vector compareVector;
		StringTokenizer st;

		if(e.getKeyCode() == e.VK_TAB || e.getKeyCode() == e.VK_DOWN)
		{
			// if tab is pressed, transfer control to the list box
			optionsList.requestFocus();
		}
		else
		{
			// remove all the options
			optionsList.removeAll();
			optionsNamesVector.removeAllElements();
			optionsValuesVector.removeAllElements();

			// disable redrawing temporarily
			optionsList.setVisible(false);

			// set the compare string
			if(isCaseSensitive == 0)
			{
				compareString = new String(tf.getText().toLowerCase());
			}
			else
			{
				compareString = new String(tf.getText());
			}

			// create the compare vector
			compareVector = new Vector();
			st = new StringTokenizer(compareString, " ");
			i = 0;
			while (st.hasMoreTokens()) {
				tempString = new String(st.nextToken());
				compareVector.addElement(tempString);
				i++;
			}
			// if not tokens found, then the add only the
			// full compare string
			if(i == 0)
			{
				compareVector.addElement(compareString);
			}

			// add back those that contain the new substring
			for(i = 0; i < namesVector.size(); i++)
			{
				bAddOption = 1;
				for(j = 0; j < compareVector.size(); j++)
				{
					if(((String)lowercaseNamesVector.elementAt(i)).indexOf((String)compareVector.elementAt(j)) == -1)
					{
						bAddOption = 0;
					}
				}
				// only add if all substrings were in the name
				if(bAddOption == 1)
				{
					// add the option to the options list
					//optionsList.add( (String)namesVector.elementAt(i) );
					optionsNamesVector.addElement( (String)namesVector.elementAt(i) );
					// add the corresponding value
					optionsValuesVector.addElement( (String)valuesVector.elementAt(i) );
				}

			}

			// only add the visible options because adding options is really slow
			for(i = 0; i < optionsList.getRows() && i < optionsNamesVector.size(); i++)
			{
				optionsList.add( (String)optionsNamesVector.elementAt(i) );
			}

			// select the first item
			if(optionsList.getItemCount() > 0)
			{
				optionsList.select(0);
			}

			// reenable redrawing
			optionsList.setVisible(true);

		} // end of else statment

	}

	/**
	 * Do nothing.
	 */
	public void keyTyped(KeyEvent e) { }

	/**
	 * Do nothing.
	 */
    public void keyPressed(KeyEvent e) {}

	/*
	// test timing function
	public void testOptionsSpeed()
	{
		long startTime, endTime;
		Long testLong = new Long(0);
		int i = 0, countOptions;
		Vector testVector = new Vector();
		countOptions = optionsList.getItemCount();
		startTime = System.currentTimeMillis();
		optionsList.removeAll();
		endTime = System.currentTimeMillis();
		System.out.println("removed "+countOptions+" options in "+((endTime-startTime))+" milliseconds.");

		countOptions = 10000;
		startTime = System.currentTimeMillis();
		optionsList.setVisible(false);
		for(i = 0; i < countOptions; i++)
		{
			optionsList.add( "test string: "+i);
		}
		optionsList.setVisible(true);

		endTime = System.currentTimeMillis();
		System.out.println("added "+countOptions+" options in "+((endTime-startTime))+" milliseconds.");
	}
	*/


}
