package com.limegroup.gnutella.gui;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * This reusable component is a list with buttons for adding and removing
 * elements.  The add button brings up a dialog window to retrieve
 * the element to add from the user.  The remove button is only enabled
 * when there is an item selected in the list.
 */
//2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678|
public final class StandardListEditor {

	/**
	 * Constant handle to the main panel in the editor.
	 */
	private final JPanel MAIN_PANEL = new JPanel(new GridBagLayout());

	/**
	 * Constant handle to the underlying <tt>JList</tt> instance.
	 */
	private final JList LIST = new JList();
	
	/**
	 * Handle to the remove button to allow us to set its state.
	 */
	private final JButton REMOVE_BUTTON;

	/**
	 * Handle to the add button.
	 */
	private final JButton ADD_BUTTON;

	/**
	 * Default add action which delegates to the <code>addListener</code> if it is set.
	 */
	private AddAction _addAction;
	
	/**
	 * Handle to the remove action used for disabling it when no item in the list is selected.
	 */
	private RemoveAction _removeAction;
	
	/**
	 * Handle to the set add listener.
	 */
	private ActionListener _addListener = null;

	/**
	 * Member variable for whether or not the list data has changed
	 * since the last call to reset this value.
	 */
	private boolean _listChanged = false;
	
	/**
	 * Creates a <tt>StandardListEditor</tt> with the list of elements on
	 * the left and buttons for adding and removing elements on the
	 * right.  The key allows for a custom label for the text field
	 * in the dialog window that the "Add..." button pops up.  For example, 
	 * using <p>
	 *
	 * OPTIONS_AUTO_CONNECT_INPUT_FIELD_LABEL=Enter Host Address:<p>
	 *
	 * in the messages bundle will make "Enter Host Address: " appear 
	 * as the label for the text field in dialog window popped up by
	 * the "add" button.  
	 *
	 * @param INPUT_FIELD_KEY the key for the locale-specific label for the dialog
	 * window popped up by the "Add..." button
	 */
	public StandardListEditor(final String INPUT_FIELD_KEY) {
		this("LIST_EDITOR_ADD_BUTTON", "LIST_EDITOR_REMOVE_BUTTON",INPUT_FIELD_KEY);
	}

	/**
	 * Creates a <tt>StandardListEditor</tt> with the default settings
	 * for the names of the add and remove buttons and that uses
	 * the specified <tt>ActionListener</tt> instance as the listener
	 * for the add button.
	 */
	public StandardListEditor(ActionListener listener) {
		this("LIST_EDITOR_ADD_BUTTON", "LIST_EDITOR_REMOVE_BUTTON","");
		setAddActionListener(listener);
	}

	/**
	 * More flexible constructor that allows the text for the add and
	 * remove buttons to be set and that allows a custom <tt>ActionListener</tt>
	 * for the add button.
	 *
	 * @param ADD_BUTTON_KEY the key for the locale-specific string to use for
	 *  the add button
	 * @param REMOVE_BUTTON_KEY the key for the locale-specific string to use 
	 *  for the remove button
	 * @param INPUT_FIELD_KEY the key for the locale-specific string to use
	 *  for the label in the generic text input component used by default
	 */
	public StandardListEditor(final String ADD_BUTTON_KEY, 
							  final String REMOVE_BUTTON_KEY,
							  final String INPUT_FIELD_KEY) {	

		_addAction = new AddAction(ADD_BUTTON_KEY, INPUT_FIELD_KEY);
		_removeAction = new RemoveAction(REMOVE_BUTTON_KEY);

		GUIUtils.bindKeyToAction(LIST,
				KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), _removeAction);
		
		LIST.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		LIST.addListSelectionListener(new ListEditorSelectionListener());
		JScrollPane scrollPane = new JScrollPane(LIST);
		
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.anchor = GridBagConstraints.NORTHWEST;
		gbc.gridheight = GridBagConstraints.REMAINDER;
		gbc.weightx = 1;
		gbc.weighty = 1;
		gbc.fill = GridBagConstraints.BOTH;
		MAIN_PANEL.add(scrollPane, gbc);
		
		gbc = new GridBagConstraints();
		gbc.anchor = GridBagConstraints.NORTHWEST;
		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.insets = new Insets(0, 3, ButtonRow.BUTTON_SEP, 0);
		
		ADD_BUTTON = new JButton(_addAction);
		MAIN_PANEL.add(ADD_BUTTON, gbc);
		
		REMOVE_BUTTON = new JButton(_removeAction);
		REMOVE_BUTTON.setEnabled(false);
		MAIN_PANEL.add(REMOVE_BUTTON, gbc);
	}

	/**
	 * Provides access to the wrapped <tt>Component</tt> of the 
	 * <tt>StandardListEditor</tt>.  This is the <tt>Component</tt>
	 * that other gui elements should add.
	 *
	 * @return the underlying <tt>Component</tt> that contains all 
	 *  of the elements fo the <tt>StandardListEditor</tt>
	 */
	public Component getComponent() {
		return MAIN_PANEL;
	}

	/**
	 * Adds the specified object to the end of the list.
	 *
	 * @param element the <tt>Object</tt> to add
	 */
	private void addElement(Object element) {
		DefaultListModel model = (DefaultListModel)LIST.getModel();
		model.addElement(element);
	}

	/**
	 * Adds the specified <tt>File</tt> instance to the end of the list.
	 *
	 * @param file the <tt>File</tt> to add
	 */
	public void addFile(File file) {
		addElement(file);
	}

	/**
	 * Adds the specified <tt>String</tt> instance to the end of the list.
	 *
	 * @param string the <tt>String</tt> to add
	 */
	public void addString(String string) {
		addElement(string);
	}

	/**
	 * Sets the data in the underlying <tt>ListModel</tt>.
	 *
	 * @param data the <tt>Vector</tt> containing data for the model
	 */
	public void setListData(Vector data) {
		DefaultListModel model = new DefaultListModel();
		for (int i=0; i<data.size(); i++)
			model.addElement(data.get(i));
		LIST.setModel(model);
	}

	/**
	 * Sets the data in the underlying <tt>ListModel</tt>.
	 *
	 * @param data array of strings containing the data for the model
	 */
	private void setListDataObjects(Object[] data) {
		DefaultListModel model = new DefaultListModel();
		for (int i=0; i<data.length; i++)
			model.addElement(data[i]);
		LIST.setModel(model);
	}

	/**
	 * Sets the data in the underlying <tt>ListModel</tt>.
	 *
	 * @param data array of <tt>File</tt> instances containing the data for the 
	 *  model
	 */
	public void setListData(File[] data) {
		setListDataObjects(data);
	}

	/**
	 * Sets the data in the underlying <tt>ListModel</tt>.
	 *
	 * @param data array of <tt>String</tt> instances containing the data for the 
	 *  model
	 */
	public void setListData(String[] data) {
		setListDataObjects(data);
	}
	
	/**
	 * Clears the selections in the list.
	 */
	public void clearSelection() {
		LIST.clearSelection();
	}

	/**
	 * Returns an array of the underlying data represented as strings
	 * by calling toString() on each element.
	 *
	 * @return the list data as an array of strings.
	 */
	public String[] getDataAsStringArray() {
		DefaultListModel model = (DefaultListModel)LIST.getModel();
		Object[] dataObjects = model.toArray();
		String[] dataStrings = new String[dataObjects.length];
		for(int i=0; i<dataObjects.length; i++) {
			dataStrings[i] = dataObjects[i].toString();
		}
		return dataStrings;
	}

	/**
	 * Returns an array of the underlying data represented as <tt>File</tt>
	 * instances.
	 *
	 * @return the list data as an array of <tt>File</tt> instances.
	 */
	public File[] getDataAsFileArray() {
		DefaultListModel model = (DefaultListModel)LIST.getModel();
		Object[] dataObjects = model.toArray();
		File[] dataFiles = new File[dataObjects.length];
		for(int i=0; i<dataObjects.length; i++) {
			dataFiles[i] = (File)dataObjects[i];
		}
		return dataFiles;
	}

	/**
	 * Returns an array of the underlying data represented as objects.
	 *
	 * @return the list data as an array of objects.
	 */
	public Object[] getDataAsObjectArray() {
		DefaultListModel model = (DefaultListModel)LIST.getModel();
		return model.toArray();
	}

	/**
	 * Sets the <tt>ActionListener</tt> to use for the add button.
	 *
	 * @param addAction the <tt>ActionListener</tt> to use for the add button
	 */
  	private void setAddActionListener(ActionListener addListener) {
  		this._addListener = addListener;
  	}

	/**
	 * Returns whether or not the list has changed since the last
	 * call to reset the list.  Note that this will not be 
	 * accurate if you use your own <tt>ActionListener</tt> for the
	 * add button.  In this case, this will return whether or not
	 * elements have been removed from the list.
	 *
	 * @return <tt>true</tt> if the list has changed since the last
	 *  call to reset the list, <tt>false</tt> otherwise
	 */
	public boolean getListChanged() {
		return _listChanged;
	}

	/**
	 * Resets the value for whether or not the list has changed to
	 * <tt>false</tt>.
	 */
	public void resetList() {
		_listChanged = false;
	}

	/**
	 * This class responds to a click of the add button and pops
	 * up a window for the user to enter a new element to add
	 * to the list.
	 */
	private class AddAction extends AbstractAction {
		
		private final String INPUT_FIELD_KEY;
		
		public AddAction(final String name, final String key) {
			putValue(Action.NAME, GUIMediator.getStringResource(name));
			putValue(Action.SHORT_DESCRIPTION, GUIMediator.getStringResource("LIST_EDITOR_ADD_BUTTON_TIP"));
			INPUT_FIELD_KEY = key;
		}
		
		public void actionPerformed(ActionEvent e) {
			
			// delegate event if there is a special addListener
			if (_addListener != null) {
				_addListener.actionPerformed(e);
			}
			else {
			
			InputFieldDialog dialog = new InputFieldDialog(INPUT_FIELD_KEY);
			int returnCode = dialog.showDialog();

			if(returnCode == InputFieldDialog.TEXT_ENTERED) {
				_listChanged = true;
				addElement(dialog.getText());
			}
		}
	}
	}


	/**
	 * This class responds to a click of the remove button and removes
	 * the selected element from the list.
	 */
	private class RemoveAction extends AbstractAction {
		
		public RemoveAction(final String name)
		{
			 putValue(Action.NAME, GUIMediator.getStringResource(name));
			 putValue(Action.SHORT_DESCRIPTION, GUIMediator.getStringResource("LIST_EDITOR_REMOVE_BUTTON_TIP"));
		}
		
		public void actionPerformed(ActionEvent e) {
			_listChanged = true;
			// return if nothing is selected
			if(LIST.isSelectionEmpty()) return;
			
			DefaultListModel model = (DefaultListModel)LIST.getModel();
			model.remove(LIST.getSelectedIndex());

			if(LIST.isSelectionEmpty()) {
				REMOVE_BUTTON.setEnabled(false);
			}
		}
	}

	/**
	 * This private class handles selection of items in the list.  It
	 * controls the state of the remove button as well, disabling it
	 * if nothing is selected.
	 */
	private class ListEditorSelectionListener implements ListSelectionListener {

		/**
		 * Implements the <tt>ListSelectionListener</tt> interface.  
		 * Responds to selections in the list.
		 */
		public void valueChanged(ListSelectionEvent e) {
			if (e.getValueIsAdjusting())
				return;
			if (LIST.isSelectionEmpty()) {
				REMOVE_BUTTON.setEnabled(false);
			} else {
				REMOVE_BUTTON.setEnabled(true);
			}
		}
	}
}
