package com.limegroup.gnutella.gui;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
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.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/** 
 * A list editor is a GUI control for editing lists of strings.
 * It consists of a scrolling pane, a text field, and an add and
 * remove button.<p>
 *
 * You can add ListDataListener's to this.  When you add/remove/modify
 * elements in the list, one of the 
 * intervalAdded/intervalRemoved/contentsChanged
 * methods will be called on each listener, respectively.  The source field
 * of the ListDataEvent passed to these methods will be the underlying
 * model passed to the constructor, an instance of Vector.  The upper
 * and lower index of this event will always be the same, since only one
 * element is modified at a time.
 */ 
public class ListEditor extends JPanel {
    /** INVARIANT: model contains exactly the same elements as realModel. */
    protected Vector /* of String */ model;
    protected DefaultListModel /* of String */ realModel;

    protected Vector /* of ListDataListener */ listeners;

    private static final int DEFAULT_COLUMNS=10;  //size of editor
    protected JTextField editor;
    protected JButton addButton;
    protected JButton removeButton;
    protected JList list;

    /** True if I should append new items to end of the list; false if I should
     *  add them to the end of the list. */
    private boolean addTail=true;

    
    /** 
     * Creates a new list editor with an empty underlying model.
     * New elements are added to the tail of the list by default.
     * @see setModel
     */
    public ListEditor() {
        this(new Vector());
    }

    /**
     * Creates a new list editor with the given underlying model.
     * New elements are added to the tail of the list by default.
     * @see setModel
     */
    public ListEditor(Vector /* of String */ model) {
        this.listeners=new Vector();

        setLayout(new GridBagLayout());

        //Top half of the editor
        editor=new LimeTextField("");
        editor.setColumns(DEFAULT_COLUMNS);
		editor.setPreferredSize(new Dimension(500, 20));
		editor.setMaximumSize(new Dimension(500, 20));
		
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.BOTH;
		gbc.anchor = GridBagConstraints.NORTHWEST;
		gbc.weightx = 1;
		
		add(editor, gbc);
		
		Action addAction = new AddAction();
        addButton =  new JButton(addAction);
        GUIUtils.bindKeyToAction(editor, 
        		KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), addAction);
                
        gbc = new GridBagConstraints();
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(0, ButtonRow.BUTTON_SEP, 0, 0);
        
        add(addButton, gbc);
        
        Action removeAction = new RemoveAction();
        removeButton = new JButton(removeAction); 
        removeButton.setEnabled(false);
        
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        add(removeButton, gbc);
        
        //Bottom half of the editor
        list=new JList();
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.addListSelectionListener(new ListListener());
        GUIUtils.bindKeyToAction(list,
        		KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeAction);
        
        JScrollPane scrollPane=new JScrollPane(list, 
                                  JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                                  JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        setModel(model);
		scrollPane.setPreferredSize(new Dimension(500, 50));
		scrollPane.setMaximumSize(new Dimension(500, 50));

        gbc = new GridBagConstraints();
        gbc.insets = new Insets(ButtonRow.BUTTON_SEP, 0, 0, 0);
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weighty = 1;
        
        add(scrollPane, gbc);
    }

    /** 
     * @requires model not subsequently modified
     * @effects returns the underlying model of this, as a Vector of Strings.
     *  Changes to this will <i>not</i> be reflected in the GUI.  However,
     *  changes made through the GUI will be reflected in the model. 
     */
    public Vector /* of String */ getModel() {
        return model;
    }

    /**
     * @requires model contains only Strings, model not subsequently modified
     * @modifies this
     * @effects sets the underlying model.  This will be reflected in the
     *  GUI. Modifications to this will <i>not</i> be reflected in the GUI.  
     *  However, changes made through the GUI will be reflected in the model. 
     */
     public synchronized void setModel(Vector /* of String */ model) {
         //Copy model into realModel.
         this.model=model;        
         this.realModel=new DefaultListModel();
         for (int i=0; i<model.size(); i++)
             realModel.addElement(model.get(i));
         list.setModel(realModel);
     }

    /** 
     * @modifies this
     * @effects if addTail is true, new elements will be added to
     *  the end of the list.  Otherwise, they'll be added to the
     *  beginning.
     */
    public void setAddTail(boolean addTail) {
        this.addTail=addTail;
    }

    /** Returns true if items are added to the tail of the list,
     *  false otherwise. */
    public boolean getAddTail() {
        return addTail;
    }
    
    /**
     * Removes an item from the list.
     */
    public synchronized void removeItem(int i) {
        model.remove(i);
        realModel.remove(i);
        editor.setText("");
        
        ListDataEvent event=new ListDataEvent(model, 
            ListDataEvent.INTERVAL_REMOVED, i, i);
        for (int j=0; j<listeners.size(); j++) {
            ListDataListener listener=(ListDataListener)listeners.get(j);
            listener.intervalRemoved(event);
        }
    }


    /**
     * @modifies this
     * @effects adds listener to the list of listeners to be notified when
     *  the underlying model changes.
     */
    public synchronized void addListDataListener(ListDataListener listener) {
        listeners.add(listener);
    }

    
    /**
     * Enables the remove button if the selection of the lis is not empty, otherwise disables it.
     * Also sets the text of the currently selected value in the edit field.
     */
    private class ListListener implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
        	
			if (list.isSelectionEmpty()) {
				removeButton.setEnabled(false);
			}
			else {
				removeButton.setEnabled(true);
			}
			
            //Put it in the editor.
            Object val=list.getSelectedValue();
            if (val==null) 
                return;
            else
                editor.setText((String)val);
        }
    }

    /** Someone tried to add something to the list. */
    private class AddAction extends AbstractAction {
    	
    	public AddAction()
    	{
    		putValue(Action.NAME, GUIMediator.getStringResource("LIST_EDITOR_ADD_BUTTON_2"));
    	}
    	
        public void actionPerformed(ActionEvent e) {
            String text=editor.getText();
            //If nothing in editor, ignore
            if (text.trim().equals(""))
                return;
            //If something is selected, replace it.  Notify listeners.
            int i=list.getSelectedIndex();
            if (i!=-1) {                
                model.setElementAt(text,i);
                realModel.setElementAt(text,i);                

                ListDataEvent event=new ListDataEvent(model, 
                  ListDataEvent.CONTENTS_CHANGED, i, i);
                for (int j=0; j<listeners.size(); j++) {
                    ListDataListener listener=(ListDataListener)listeners.get(j);
                    listener.contentsChanged(event);
                }                    
            }
            // Otherwise add text to the end/beginning of the list. 
	           // Notify listeners.
            else {
                int last;
                if (addTail) {
                    //add to tail
                    model.addElement(text);
                    realModel.addElement(text);                    
                    last=model.size()-1;
                } else {
                    //add to head
                    model.add(0, text);
                    realModel.add(0, text);
                    last=0;
                }
                ListDataEvent event=new ListDataEvent(model, 
                  ListDataEvent.INTERVAL_ADDED, last, last);
                for (int j=0; j<listeners.size(); j++) {
                    ListDataListener listener=(ListDataListener)listeners.get(j);
                    listener.intervalAdded(event);
                }                    
            }
            editor.setText("");
            list.clearSelection();
        }
    }

    private class RemoveAction extends AbstractAction
	{
    	
    	public RemoveAction()
    	{
    		putValue(Action.NAME, GUIMediator.getStringResource("LIST_EDITOR_REMOVE_BUTTON"));
    	}
    	
        /** Someone tried to remove something from the list. */
        public void actionPerformed(ActionEvent e) {
            //If something is selected, remove it. Notify listeners.
            int i=list.getSelectedIndex();
            if (i!=-1)
                removeItem(i);
        }
    }

//          Vector model=new Vector();
//          model.addElement("britney");
//          model.addElement("n'sync");
//          model.addElement("money");
//          model.addElement("spam");
//          model.addElement("Republican");
//          model.addElement("communist");
//          ListEditor panel=new ListEditor(model);
//          panel.setAddTail(false);
//          panel.addListDataListener(new TestListener(model));

//          JFrame frame = new JFrame("Spam Config Mock-up");
//          frame.addWindowListener(new WindowAdapter() {
//              public void windowClosing(WindowEvent e) {System.exit(0);}});

//          frame.getContentPane().add(panel, BorderLayout.NORTH);
//          frame.pack();
//          frame.setVisible(true);
//      }

//      static class TestListener implements ListDataListener {
//          private Vector model;
//          public TestListener(Vector model) {
//              this.model=model;
//          }

//          private void verify(ListDataEvent e) {
//              Assert.that(e.getIndex0()==e.getIndex1());
//              Assert.that(e.getSource()==model);
//          }

//          public void contentsChanged(ListDataEvent e) {
//              verify(e);  Assert.that(e.getType()==ListDataEvent.CONTENTS_CHANGED);
//              System.out.println("You just modified element "+e.getIndex0());
//              System.out.println("The new list is "+model.toString());
//          }

//          public void intervalAdded(ListDataEvent e) {
//              verify(e);  Assert.that(e.getType()==ListDataEvent.INTERVAL_ADDED);
//              System.out.println("You just added element "+e.getIndex0());
//              System.out.println("The new list is "+model.toString());
//          }

//          public void intervalRemoved(ListDataEvent e) {
//              verify(e);  Assert.that(e.getType()==ListDataEvent.INTERVAL_REMOVED);
//              System.out.println("You just removed element "+e.getIndex0());
//              System.out.println("The new list is "+model.toString());
//          }              
//      }
}
