package net.sourceforge.ganttproject.gui.taskproperties;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;

import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.jdesktop.swingx.autocomplete.ComboBoxCellEditor;
import org.jdesktop.swingx.decorator.HighlighterFactory;

import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.task.Task;

abstract class EditableList<T>  {
    private List<T> myValues;
    private TableModelImpl myTableModel;
    private JXTable resourcesTable;
    private JScrollPane resourcesScrollPane;
    protected int[] mySelectedRows;
    private JComboBox myComboBox;
    private JPanel myComponent;
    private List<SelectionListener<T>> myListeners = new ArrayList<SelectionListener<T>>();

    public EditableList(List<T> assigned_values, List<T> possibleValues) {
        myValues = assigned_values;
        myTableModel = new TableModelImpl();
        resourcesTable = new JXTable(myTableModel);
        resourcesTable.setRowHeight(23);
        resourcesTable.getColumnModel().getColumn(0).setPreferredWidth(240);
        setupEditor(possibleValues, resourcesTable);
        resourcesTable.setHighlighters(HighlighterFactory.createSimpleStriping());
        resourcesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                T selObject = getSelectedObject();
                if (selObject!=null) {
                    for (SelectionListener<T> l : myListeners) {
                        l.selectionChanged(selObject);
                    }
                }
                mySelectedRows = resourcesTable.getSelectedRows();
            }
        });
        resourcesScrollPane = new JScrollPane(resourcesTable);
        resourcesScrollPane.setPreferredSize(new Dimension(300, 130));
        JPanel tablePanel = new JPanel(new BorderLayout());
        tablePanel.add(resourcesScrollPane, BorderLayout.CENTER);
        {
            JPanel buttonBox = new JPanel(new GridLayout(1, 2, 3, 0));
            buttonBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
            buttonBox.add(new JButton(getAddResourceAction()));
            buttonBox.add(new JButton(getDeleteResourceAction()));
            buttonBox.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
            tablePanel.add(buttonBox, BorderLayout.NORTH);
        }
        myComponent = tablePanel;
    }

    JComponent getComponent() {
        return myComponent;
    }
    
    T getSelectedObject() {
        int selIndex = resourcesTable.getSelectedRow();
        return selIndex >= 0 && selIndex < myValues.size() ? myValues.get(selIndex) : null;
    }
    class TableModelImpl extends AbstractTableModel {
        public int getColumnCount() {
            return 1;
        }

        public int getRowCount() {
            return myValues.size()+1;
        }

        public Object getValueAt(int row, int col) {
            if (row >= 0 && row < myValues.size()) {
                return new ComboItem(myValues.get(row));
            }
            if (row==myValues.size()) {
                return "<Type name here>";
            } 
            throw new IllegalArgumentException("I can't return data in row="
                    + row);
        }
        
        public boolean isCellEditable(int row, int col) {
            return true;
        }

        public void setValueAt(Object value, int row, int col) {
            assert col==0;
            if (value==null) {
                deleteValue(myValues.get(row));
                myValues.remove(row);
                fireTableRowsDeleted(row, row);
                return;
            }
            ComboItem setItem = null;
            if (ComboItem.class.equals(value.getClass())) {
                setItem = (ComboItem) value;
            }
            else {
                for (int i=0; i<myComboBox.getModel().getSize(); i++) {
                    if (((ComboItem)myComboBox.getModel().getElementAt(i)).myText.equals(value)) {
                        setItem = (ComboItem)myComboBox.getModel().getElementAt(i);
                        break;
                    }
                }
            }            
            if (row >= myValues.size()) {
                System.err.println("set item=");
                if (setItem!=null) {
                    T newValue = createValue(setItem.myObject);
                    if (newValue != null) {
                        myValues.add(newValue);
                        fireTableRowsInserted(myValues.size(), myValues.size());
                    }                    
                }
            }
            else if (row>=0) {
                if (setItem.myObject!=myValues.get(row)) {
                    T updatedValue = updateValue(setItem.myObject, myValues.get(row));
                    myValues.set(row, updatedValue);
                    fireTableRowsUpdated(row, row);
                }
            }
            else {
                throw new IllegalArgumentException("I can't set data in row=" + row);
            }
        }
    }

    class ComboItem {
        final String myText;

        final T myObject;

        ComboItem(T t) {
            myObject = t;
            myText = getStringValue(t);
        }

        public String toString() {
            return myText;
        }
    }
    
    private void setupEditor(List<T> possibleValues, final JTable table) {
        myComboBox = new JComboBox();
        for (T value : possibleValues) {
            myComboBox.addItem(new ComboItem(value));
        }
        myComboBox.setEditable(true);
        AutoCompleteDecorator.decorate(myComboBox);
        TableColumn column = table.getColumnModel().getColumn(0);
        column.setCellEditor(new ComboBoxCellEditor(myComboBox));
    }
    
    private Action getAddResourceAction() {
        return new GPAction("add") {
            @Override
            protected String getLocalizedName() {
                return getI18n("add");
            }
            @Override
            protected String getIconFilePrefix() {
                return null;
            }
            public void actionPerformed(ActionEvent e) {
                int lastRow = resourcesTable.getRowCount()-1;
                resourcesTable.setValueAt(myComboBox.getItemAt(0), lastRow, 0);
                myComboBox.requestFocus();
                resourcesTable.editCellAt(lastRow, 0);
            }
        };
    };
    
    private Action getDeleteResourceAction() {
        return new GPAction("delete") {
            @Override
            protected String getLocalizedName() {
                return getI18n("delete");
            }
            @Override
            protected String getIconFilePrefix() {
                return null;
            }
            public void actionPerformed(ActionEvent e) {
                int[] selectedRow = mySelectedRows;
                for (int i = 0; i < selectedRow.length; ++i) {
                    resourcesTable.getModel().setValueAt(null, selectedRow[i], 0);
                }
            }
        };
    }
    
    protected String getStringValue(T t) {
        return String.valueOf(t);
    }

    protected abstract T updateValue(T newValue, T curValue);

    protected abstract T createValue(T prototype);
    
    protected abstract void deleteValue(T value);
    
    protected void reloadValues() {
    }

    protected void applyValues() {
    }
    
    public void addSelectionListener(SelectionListener<T> listener) {
        myListeners.add(listener);
        listener.selectionChanged(getSelectedObject());
    }
    
    static interface SelectionListener<T> {
        void selectionChanged(T selectedElement);
    }
}
