package org.jcon.df.edit.control.list;

import org.jcon.df.edit.EditEvent;
import org.jcon.df.edit.control.Control;
import org.jcon.df.edit.control.element.Element;
import org.jcon.df.edit.control.element.ElementEvent;
import org.jcon.df.edit.control.element.ElementListener;
import org.jcon.df.edit.control.element.ElementListModel;
import org.jcon.df.edit.module.BorderPanel;
import org.jcon.param.Param;
import org.jcon.ui.VisualLib;
import org.jcon.util.GenLib;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
* A Control representing a single column list. It supports
* Icons and text per line via the Element subsystem.
* <p>
* List lines may be Elements or Strings (text). Each has
* its own set of appropriate methods. Internally all lines
* are elements, so that insertText() and getSelectedElement()
* is actually possible. Don't become cornfused (sic) about
* this - Stick to all Element or all String use.
* <p>
* The model may be supplied or not, and is an ItemListModel.
* If not supplied 
* <p>
* Note that Border properties may be used to configure 
* the list's border. These are the same as in BorderPanel.
*
* @author Jack Harich
*/
public class ListControl extends Control
    implements ListSelectionListener, MouseListener {

//---------- Package Fields ------------------------------
static final int X_INSET = 2; // Left, right

//---------- Private Fields ------------------------------
// Properties
private ElementListModel model = new ElementListModel();
// Internal
private BList         list = new BList();
private Vector        listeners = new Vector();
private BorderPanel   borderPanel = new BorderPanel();
private JScrollPane   scrollPane = new JScrollPane();

//---------- Initialization ------------------------------
public ListControl() {

    list.setModel(model);
    list.setCellRenderer(new ListControlCellRenderer());
    list.addListSelectionListener(this);
    list.addMouseListener(this);
    scrollPane.getViewport().add(list);
    
    // Was too tight inside list on left and right
    // Insets - top, left, bottom, right
    //EmptyBorder border = new EmptyBorder(new Insets(0, 2, 0, 2));
    //list.setBorder(border);
}
//---------- Abstract Implementation ---------------------
public Component getComponent() {
    return (Component)scrollPane;
}
//---------- Superclass Overrides ------------------------
public void init() {
    borderPanel.initMainPanel(myParam);
    
    // Put this around the recessed border default in JScrollPane
    Border outerBorder    = borderPanel.getBorder();
    Border recessedBorder = scrollPane.getBorder();
    Border newBorder = new CompoundBorder(outerBorder, recessedBorder);
    scrollPane.setBorder(newBorder);
    
    String prefSize = myParam.getString("PreferredSize");
    if (prefSize != null) {
        Dimension size = VisualLib.parseDimension(prefSize);
        list.setPreferredSize(size);
        //print(".init() - Set preferred size " + prefSize);
    }
    // Use this to set a preferred width and get the
    // list to not use the longest String contents
    String prefStringWidth = myParam.getString("PreferredStringWidth");
    if (prefStringWidth != null) {
        list.setPrototypeCellValue(prefStringWidth);
    }  
    // Use this to set the number of visible rows
    // This controls the preferred height
    int rowCount = myParam.getIntDefaultZero("PreferredRowCount");
    if (rowCount > 0) list.setVisibleRowCount(rowCount);
}
/**
* Assumes value is Vector of userObjects. These are inserted
* in the SAME order as received. The userObject.toString()
* is used to set the element text property.
*/
public void setValue(Object value) {
    clear();
    Vector lines = (Vector)value;
    for (int i = 0; i < lines.size(); i++) {
        Object userObject = lines.elementAt(i);
        if (userObject == null) {
            GenLib.error("ListControl.setValue()",
                "Received null userObject in Vector of size " + lines.size());
            continue;
        }
        Element element = new Element();
        element.setUserObject(userObject);
        element.setText(userObject.toString());
        insertElement(element, false); // Not alphabetically
    }
}
/**
* Returns a Vector of userObjects. If setValue(Object) was
* called with the Object a Vector of Strings, then this
* returns a Vector of Strings since the Strings were the
* userObjects. Same as getUserObjects().
*/
public Object getValue() { 
    return getUserObjects();
}
//---------- ListSelectionListener Implementation --------
public void valueChanged(ListSelectionEvent evt) {
    fireEvent(ElementEvent.SELECTION_CHANGED);
    
    if (editListener != null) {
        // Fire event
        EditEvent editEvent = new EditEvent(
            EditEvent.LIST_SELECTION_CHANGED, null); // null for no DataSourceID
        editEvent.setWorkerName(name);
        editListener.processEditEvent(editEvent);       
    } 
}
//---------- MouseListener Implementation ----------------
public void mouseClicked(MouseEvent evt) { }
public void mousePressed(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) {
    if (evt.getClickCount() == 2) {
        fireEvent(ElementEvent.ELEMENT_ACTIVATED);
    }
}
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }

//---------- Properties ----------------------------------
/**
* Use this to set the standard object for all list values.
* A good use for this is to get 2 lists to be equal width,
* or a long list to run faster. Typically a String is
* entered representing the widest value.
* <p>
* Due to a JList bug this MUST be called after something
* is put in the lists. Calling this with an empty list results
* in a ClassCastException in JList line 360. ?????
*/
public void setPrototypeCellValue(Object value) {
    list.setPrototypeCellValue(value);
}
//----- model
public void setModel(ElementListModel model) {
    this.model = model;
    list.setModel(model);
}
public ElementListModel getModel() {
    return model;
}
//---------- Events --------------------------------------
public void addElementListener(ElementListener listener) {
    listeners.addElement(listener);
}
public void removeElementListener(ElementListener listener) {
    listeners.removeElement(listener);
}
//---------- Public Methods ------------------------------
//----- Element oriented
/**
* Inserts the element into the list of elements.
*
* @param element         the element to insert.
* @param alphabetically  if true then insert in alphabetical
*    order. If false insert as the last element.
*/
public void insertElement(Element element, boolean alphabetically) {
    model.insertElement(element, alphabetically);
}
/**
* Returns the selected element or null if none selected.
*/
public Element getSelectedElement() {
    return (Element)list.getSelectedValue();
}
public Element[] getSelectedElements() {
    Object[] values = list.getSelectedValues();
    Element[] elements = new Element[values.length];
    for (int i = 0; i < values.length; i++) {
        elements[i] = (Element)values[i];
    }
    return elements;
}
public void clearSelection() {
    list.clearSelection();
}
/**
* Adds the element to the selection.
*/
public void selectElement(Element element) {
    list.setSelectedValue(element, true); // true for "shouldScroll"
}
public boolean removeElement(Element element) {
    return model.removeElement(element);
}
/**
* Removes the element with the id and returns the removed
* element. Does nothing and returns null if the element is
* not found. The id cannot be null.
*/
public Element removeElementWithID(String id) {
    if (id == null) throw new IllegalArgumentException("The id cannot be null.");
    
    Enumeration list = getElements();
    while (list.hasMoreElements()) {
        Element element = (Element)list.nextElement();
        if (id.equals(element.getID())) {
            removeElement(element);
            return element;
        }
    }
    print(".removeElementWithID() - failed to remove id " + id);
    return null;
}
public Enumeration getElements() {
    return model.elements();
}
/**
* Returns a Vector of all the UserObjects in the list, by
* getting the UserObject from each Element in the list.
*/
public Vector getUserObjects() {
    return model.getUserObjects();
}
//----- String related
public void insertText(String text, boolean alphabetically) {
    model.insertText(text, alphabetically);
}
public String getSelectedText() {
    Element element = getSelectedElement();
    if (element == null) {
        return null;
    } else {
        return element.getText();
    }
}
//----- Other
public void clear() {
    model.clear();
}
//----- static
/**
* Moves all selected elements in fromList to toList, and
* clears all selections in both lists.
*/
public static void moveElements(ListControl fromList, ListControl toList) {
    Element[] elements = fromList.getSelectedElements();
    if (elements == null || elements.length == 0) {
        VisualLib.beep();
        return;
    }
    for (int i = 0; i < elements.length; i++) {  
        fromList.removeElement(elements[i]);
        toList.insertElement(elements[i], true);
    }
    fromList.clearSelection();
    toList.clearSelection();
}
//---------- Private Methods -----------------------------
private void fireEvent(int eventType) {
    ElementEvent evt = new ElementEvent(eventType, this.getName());
    
    Vector list;
    synchronized(this) {
        list = (Vector)listeners.clone();
    }
    for (int i = 0; i < list.size(); i++) {
        ElementListener listener = (ElementListener)list.elementAt(i);
        listener.processElementEvent(evt);
    }
}   
//--- Std
private static void print(String text) {
    System.out.println("ListControl" + text);
}
//========== Inner Classes ===============================
// To control preferred size bug (?)
private class BList extends JList{

private Dimension newSize;

//---------- Superclass Overrides ------------------------
public void setPreferredSize(Dimension newSize) {
    super.setPreferredSize(newSize);
    this.newSize = newSize;
}
public Dimension getPreferredSize() {
    Dimension prefSize = newSize;
    if (newSize == null) {
        prefSize = super.getPreferredSize();
    }
    //System.out.println(".getPreferredSize() - Returning " + prefSize);
    //System.out.println(".getPreferredSize() - Super size = " + super.getPreferredSize());
    return prefSize;
}

} // End inner class

} // End outer class
