package cnp.ew.list;

import java.awt.*;
import java.util.*;
import cnp.ew.scrolling.*;
import cnp.ew.util.*;
import cnp.ew.lightweight.*;
import cnp.ew.displayer.*;
import cnp.ew.converter.*;
import cnp.ew.properties.*;

public class CpListPane extends CpAbstractScrollable implements CpObserver
{
    static final int DEFAULT_HSCROLLUNIT = 50;

    CpListModel listModel;
    CpSelectionModel selectionModel;
    public CpModelSelectableDisplayable displayer;  // should not be public.  Why is it?
    CpToStringConverter converter;
    CpEditor editor;
    int indexBeingEdited = -1;

    // This only changes the selection if the mouse event causes a different cell to
    // be hit.  This stores the last cell hit.
    int lastCellHit;

    // This flag controls whether the list draws items in the entire
    // visible area, even if there are not enough items to fill it.  This allows
    // a displayer to draw a grid, for example (i.e. the property list)
    public boolean stopAtSize = true;

    // Cell size is settable.  If it is not set, it is based on font and the
    // width of the visible window.
    public Dimension cellSize;

    public CpListPane()
    {
        displayer = new CpGeneralStringDisplayer();
        ((CpGeneralStringDisplayer)displayer).setSelectionExpansion(2);
        ((CpGeneralStringDisplayer)displayer).selectionDisplayStyle = CpGeneralStringDisplayer.SELECT_CELL;
       // displayer.setMargin(2);
        selectionModel = new CpDefaultSelectionModel();
        selectionModel.addObserver(this);
        listModel = new CpVectorListModel();
        listModel.addObserver(this);

        converter = new CpStringToStringConverter();
    }

    public void setListModel(CpListModel newListModel)
    {
        listModel = newListModel;
        listModel.addObserver(this);
    }

    /***************** PUBLIC API  ********************/

    public void setSelectionExpansion(int x)
    {
        ((CpGeneralStringDisplayer)displayer).setSelectionExpansion(x);
    }

    public void setSelectionDisplayStyle(int style)
    {
        ((CpGeneralStringDisplayer)displayer).selectionDisplayStyle = style;
    }

    public void setItems(Vector v)
    {
        // This seems odd to cast.  Issue.
        ((CpVectorListModel)listModel).setItems(v);
        if (listModel.getSize() > 0) {
            selectionModel.initializeSelection();
        }
    }

    public Dimension preferredSize()
    {
        Dimension d = getCellSize();
    //    System.out.println("list preferred is = " + new Dimension(d.width, d.height * listModel.getSize()));
        return new Dimension(d.width, d.height * listModel.getSize() + 2);
    }

    public void setCellSize(Dimension d)
    {
        cellSize = d;
    }

    /***************** SIZING  ********************/

    public Dimension getCellSize()
    {
        if (cellSize != null) {
            return cellSize;
        } else {
            if (displayer != null) {
                return new Dimension(DEFAULT_HSCROLLUNIT, displayer.preferredSize(this).height);
            } else {
                // FIX THIS:
                return new Dimension(DEFAULT_HSCROLLUNIT, 80);
            }
        }
    }

    // subclassed in columnar list to compute width.  Issue: should compute width of longest text for h scrolling?
    public int getWidth()
    {
        return getVisibleRect().width;
    }

    // Issue:  SEE Scroller, also.  Should this just be done by layout()?
    public void scrollerSized(Dimension newSize)
    {
        recomputeSize();
    }

    void recomputeSize()
    {
        Dimension newSize = new Dimension(getWidth(), listModel.getSize() * getCellSize().height);
        if (newSize.height != size().height || newSize.width != size().width) {
            resize(newSize);
            getScroller().scrollableSized();
        }
    }

    /************* SETTING MODELS *****************/

    public void setDisplayer(CpModelSelectableDisplayable newDisplayer)
    {
        displayer = newDisplayer;
    }

    public void setConverter(CpToStringConverter newConverter)
    {
        converter = newConverter;
    }

    public void setEditor(CpEditor newEditor)
    {
        editor = newEditor;
        editor.addObserver(this);
        editor.hide(true);
        add(editor);
    }

    /********************* SELECTION ************************/

    public void itemHit(int index, int modifiers, int x)
    {
        // Should this test go here?
        if (index >= 0 && index < listModel.getSize()) {
            select(index, modifiers);
        }
    }

    public void select(int index, CpColumnModel model, int modifiers)
    {
        select(index, modifiers);
    }

    int getSelectionType(Event evt)
    {
        if (evt.controlDown()) {
            return CpSelectionModel.TOGGLE_SELECT;
        } else if (evt.shiftDown()) {
            return CpSelectionModel.EXTEND_SELECT;
        } else {
            return CpSelectionModel.NORMAL_SELECT;
        }
    }
    public void select(int index, int modifiers)
    {
        if (selectionModel.isSelected(index) && editor != null) {
            editor.setObject(listModel.getItem(index));
            Point editorLoc = cellToPoint(0, index);
            editor.reshape(editorLoc.x, editorLoc.y - 2, size().width, getCellHeight(index) + 4);
            editor.hide(false);
            editor.requestFocus();
            indexBeingEdited = index;
        } else {
            if (indexBeingEdited > 0) {
                listModel.setItem(indexBeingEdited, editor.getObject());
                notifyObservers(CpEvent.LIST_ITEM_EDITED, editor.getObject());
                editor.hide(true);
                indexBeingEdited = -1;
            }
            selectionModel.select(index, modifiers);
        }
    }

    /************************* EVENTS **************************/


    public void update(CpObservable o, int facet, Object arg)
    {
        switch (facet) {
            case CpEvent.LISTMODEL_SET_ITEMS :
            case CpEvent.LISTMODEL_INSERT :
            case CpEvent.LISTMODEL_DELETE: {
                recomputeSize();
                repaint();
                break;
            }
            case CpEvent.LIST_SELECTION_CHANGED : {
                // It passes in two vectors
                Vector selectionVectors = (Vector)arg;
                Vector selectedIndexes = (Vector)selectionVectors.firstElement();
                Vector deselectedIndexes = (Vector)selectionVectors.lastElement();

                int cellHeight = getCellSize().height;
                int width = size().width;
                if (selectedIndexes.size() <= 1 && deselectedIndexes.size() <= 1) {
                    if (!deselectedIndexes.isEmpty()) {
                        damage(0, ((Integer)deselectedIndexes.firstElement()).intValue() * cellHeight, width, cellHeight);
                        repairDamage();
                    }
                    if (!selectedIndexes.isEmpty()) {
                        damage(0, ((Integer)selectedIndexes.firstElement()).intValue() * cellHeight, width, cellHeight);
                        makeCellVisible(0, selectionModel.lastSelected()); //((Integer)selectedIndexes.firstElement()).intValue());
                        repairDamage();
                    }
                } else {
                    for (int i = 0; i < deselectedIndexes.size(); i++) {
                        damage(0, ((Integer)deselectedIndexes.elementAt(i)).intValue() * cellHeight, width, cellHeight);
                    }
                    for (int i = 0; i < selectedIndexes.size(); i++) {
                        damage(0, ((Integer)selectedIndexes.elementAt(i)).intValue() * cellHeight, width, cellHeight);
                    }
                    repairDamage();
                }
                notifyObservers(OBJECT_CHANGED);
                break;
            }

        }
    }

    public boolean mouseDown(Event evt, int x, int y)
    {
        lastCellHit = y / getCellSize().height;
        itemHit(lastCellHit, getSelectionType(evt), x);
        return true;
    }

    public boolean mouseDrag(Event evt, int x, int y)
    {
        if (y / getCellSize().height != lastCellHit) {
            lastCellHit = y / getCellSize().height;
            itemHit(lastCellHit, getSelectionType(evt), x);
          //  makeCellVisible(0, lastCellHit);
        }

        return true;
    }

    public boolean usesFocus()
    {
        return true;
    }

    public boolean wantsLeftAndRightArrows()
    {
        return true;
    }

    public boolean gotFocus()
    {
        redisplaySelection();
        return true;
    }

    public boolean lostFocus()
    {
        redisplaySelection();
        return true;
    }


    void redisplaySelection()
    {
        int firstVisibleRow = getFirstVisibleCell().y;
        int lastVisibleRow = getLastVisibleCell().y;

        for (int i = firstVisibleRow; i < lastVisibleRow; i++) {
            if (selectionModel.isSelected(i)) {
                damageLine(i);
            }
        }
        repairDamage();
    }

    public boolean keyDown(Event e, int key)
    {
        switch (key) {
        case Event.UP:
        case Event.LEFT:
            if (selectionModel.lastSelected() > 0) {
                select(selectionModel.lastSelected() - 1, CpSelectionModel.NORMAL_SELECT);
            }
            break;
        case Event.DOWN:
        case Event.RIGHT:
            if (selectionModel.lastSelected() < listModel.getSize() - 1) {
                select(selectionModel.lastSelected() + 1, CpSelectionModel.NORMAL_SELECT);
            }
            break;
        case Event.HOME:
            select(0, CpSelectionModel.NORMAL_SELECT);
            break;
        case Event.END:
            select(listModel.getSize() - 1, CpSelectionModel.NORMAL_SELECT);
            break;
        default:
        // TBD: formalize this size (settable?)
            if (listModel.getSize() < 500) {
                char prefixChar = Character.toLowerCase(((char)key));
                String text;
                for (int i = selectionModel.lastSelected() + 1; i < listModel.getSize(); i++) {
                    if ((text = textFor(i)) != null && (Character.toLowerCase(text.charAt(0)) == prefixChar)) {
                        select(i, CpSelectionModel.NORMAL_SELECT);
                        return true;
                    }
                }
                for (int i = 0; i < selectionModel.lastSelected(); i++) {
                    if ((text = textFor(i)) != null && (Character.toLowerCase(text.charAt(0)) == prefixChar)) {
                        select(i, CpSelectionModel.NORMAL_SELECT);
                        return true;
                    }
                }
            }
        }

        return true;
    }

    public Object getSelectedItem()
    {
        return listModel.getItem(selectionModel.lastSelected());
    }

    /****************************** PAINTING ***************************/

    public void paint(Graphics g, Rectangle clipRect)
    {
        int curX, curY;

        int cellSizeY = getCellSize().height;
        int i = clipRect.y / cellSizeY;
        curY = i * cellSizeY;

        g.setColor(getBackground());
        g.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

     //   System.out.println("In paint, first item = " + i);
        while (true) {
            paintItemIn(i, g, 0, curY, size().width, cellSizeY);
            curY += cellSizeY;
            if (curY >= clipRect.y + clipRect.height) {
              //  System.out.println("In paint, last item = " + i);
                return;
            }
            i++;
            if (stopAtSize && i >= listModel.getSize()) {
                return;
            }
        }
    }

    void setShowFocusIfNecessary(CpDisplayable displayer, int index)
    {
        if (displayer instanceof CpFocusDisplayable) {
            ((CpFocusDisplayable)displayer).setShowFocus(shouldShowFocusForIndex(index));
        }
    }

    boolean shouldShowFocusForIndex(int index)
    {
        return hasFocus() && selectionModel.lastSelected() == index;
    }

    public void paintItemIn(int i, Graphics g, int x, int y, int w, int h)
    {
        CpSelectableDisplayable tempDisplayer;

        if (displayer == null) {
            // Displayer is null, therefore items must be displayers themselves

            if (i >= listModel.getSize()) {
                return;
            }
            tempDisplayer = (CpSelectableDisplayable)listModel.getItem(i);
            tempDisplayer.setIsSelected(selectionModel.isSelected(i));
            setShowFocusIfNecessary(tempDisplayer, i);
            tempDisplayer.paintIn(this, g, x, y, w, h);
        } else {
            Object model;
            if (i >= listModel.getSize()) {
                model = null;
            } else {
                model = listModel.getItem(i);
            }
            if (converter == null) {
                displayer.setModel(model);
            } else {
                displayer.setModel(converter.convert(model));
            }

            displayer.setIsSelected(selectionModel.isSelected(i));
            setShowFocusIfNecessary(displayer, i);
            displayer.paintIn(this, g, x, y, w, h);
        }
    }

    /*************************** MISC ******************************/


    public Object getItem(int i)
    {
        return listModel.getItem(i);
    }

    public String textFor(int i)
    {
        // If converter is null, the objects cannot be converted to text.  Is that right?
        if (converter == null) {
            return null;
        } else {
            return converter.convert(listModel.getItem(i));
        }
    }

    public void expand(int index)
    {
        // Ignored.  Handled by CpTreeListPane
    }

}
