/*
GanttProject is an opensource project management tool.
Copyright (C) 2010-2011 Dmitry Barashev

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.sourceforge.ganttproject.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;

import net.sourceforge.ganttproject.language.GanttLanguage;

public abstract class EditableList<T> {

  private final Object UNDEFINED_VALUE = new Object() {
    @Override
    public String toString() {
      return myUndefinedValueLabel;
    }
  };
  private final List<T> myValues;
  private final TableModelImpl myTableModel;
  private JTable resourcesTable;
  private AbstractTableAndActionsComponent<T> myTableAndActions;
  private JScrollPane resourcesScrollPane;
  private int[] mySelectedRows;
  private JComboBox myComboBox;
  private final List<T> myPossibleValues;
  private String myTitle;
  private String myUndefinedValueLabel = GanttLanguage.getInstance().getText("editableList.undefinedValueLabel");

  public EditableList(List<T> assigned_values, List<T> possibleValues) {
    myValues = assigned_values;
    myPossibleValues = possibleValues;
    myTableModel = new TableModelImpl();
  }

  public void setUndefinedValueLabel(String label) {
    myUndefinedValueLabel = label;
  }

  public void setTitle(String title) {
    myTitle = title;
  }

  public String getTitle() {
    return myTitle;
  }

  public JComponent getTableComponent() {
    initComponent();
    return resourcesScrollPane;
  }

  public JComponent getActionsComponent() {
    initComponent();
    return myTableAndActions.getActionsComponent();
  }

  public void stopEditing() {
    if (resourcesTable.isEditing()) {
      resourcesTable.getCellEditor().stopCellEditing();
    }
  }

  public JComponent createDefaultComponent() {
    JPanel result = new JPanel(new BorderLayout());
    result.add(getTableComponent(), BorderLayout.CENTER);
    JComponent actionsComponent = getActionsComponent();
    actionsComponent.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 0));
    result.add(actionsComponent, BorderLayout.NORTH);
    result.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    return result;
  }

  public AbstractTableAndActionsComponent<T> getTableAndActions() {
    initComponent();
    return myTableAndActions;
  }

  private void initComponent() {
    if (myTableAndActions == null) {
      // JXTable jnTable = new JNTable(myTableModel);
      resourcesTable = new JTable(myTableModel) {
        @Override
        public String getToolTipText(MouseEvent event) {
          try {
            return super.getToolTipText(event);
          } catch (NullPointerException e) {
            return null;
          }
        }
      };
      UIUtil.setupTableUI(resourcesTable);
      resourcesTable.setTableHeader(null);
      resourcesTable.getColumnModel().getColumn(0).setPreferredWidth(240);
      resourcesTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
          assert column == 0;
          super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
          if (row >= myValues.size()) {
            return this;
          }
          T typedValue = EditableList.this.myValues.get(row);
          return EditableList.this.getTableCellRendererComponent(this, typedValue, isSelected, hasFocus, row);
        }
      });
      JTextField editorField = new JTextField();
      resourcesTable.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(editorField) {
        private boolean isCanceled;

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
          isCanceled = false;
          JTextField result = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
          if (UNDEFINED_VALUE == value) {
            result.setText("");
          }
          return result;
        }

        @Override
        public void cancelCellEditing() {
          super.cancelCellEditing();
          isCanceled = true;
        }

        @Override
        public Object getCellEditorValue() {
          if (isCanceled) {
            return UNDEFINED_VALUE;
          }
          return super.getCellEditorValue();
        }

      });
      if (!myPossibleValues.isEmpty()) {
        setupEditor(myPossibleValues, resourcesTable);
      }
      // resourcesTable.setHighlighters(HighlighterFactory.createSimpleStriping());
      resourcesScrollPane = new JScrollPane(resourcesTable);
      // resourcesScrollPane.setPreferredSize(new Dimension(300, 130));

      myTableAndActions = new TableAndActionsImpl();
    }
  }

  protected Component getTableCellRendererComponent(DefaultTableCellRenderer defaultRenderer, T typedValue,
      boolean isSelected, boolean hasFocus, int row) {
    defaultRenderer.setText(getStringValue(typedValue));
    return defaultRenderer;
  }

  List<T> getSelectedObjects() {
    int[] selectedRows = resourcesTable.getSelectedRows();
    if (selectedRows.length == 0) {
      return Collections.emptyList();
    }
    ArrayList<T> result = new ArrayList<T>();
    for (int nextRow : selectedRows) {
      if (nextRow >= 0 && nextRow < myValues.size()) {
        result.add(myValues.get(nextRow));
      }
    }
    return result;
  }

  public T getSelectedObject() {
    int selIndex = resourcesTable.getSelectedRow();
    return selIndex >= 0 && selIndex < myValues.size() ? myValues.get(selIndex) : null;
  }

  class TableModelImpl extends AbstractTableModel {
    @Override
    public int getColumnCount() {
      return 1;
    }

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

    @Override
    public Object getValueAt(int row, int col) {
      if (row >= 0 && row < myValues.size()) {
        return new ComboItem(myValues.get(row));
      }
      if (row == myValues.size()) {
        return UNDEFINED_VALUE;
      }
      throw new IllegalArgumentException("I can't return data in row=" + row);
    }

    @Override
    public boolean isCellEditable(int row, int col) {
      if (row == myValues.size()) {
        return true;
      }
      return EditableList.this.isEditable(myValues.get(row));
    }

    @Override
    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;
      }
      T prototype = createPrototype(value);
      if (row >= myValues.size()) {
        if (prototype != null) {
          T newValue = createValue(prototype);
          if (newValue != null) {
            myValues.add(newValue);
            fireTableRowsInserted(myValues.size(), myValues.size());
          }
        }
      } else if (row >= 0) {
        if (prototype != myValues.get(row)) {
          T updatedValue = updateValue(prototype, 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);
    }

    @Override
    public String toString() {
      return myText;
    }
  }

  protected T createPrototype(Object editValue) {
    ComboItem setItem = null;
    if (ComboItem.class.equals(editValue.getClass())) {
      setItem = (ComboItem) editValue;
    } else {
      for (int i = 0; i < myComboBox.getModel().getSize(); i++) {
        if (((ComboItem) myComboBox.getModel().getElementAt(i)).myText.equals(editValue)) {
          setItem = (ComboItem) myComboBox.getModel().getElementAt(i);
          break;
        }
      }
    }
    return setItem == null ? null : setItem.myObject;
  }

  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 DefaultCellEditor(myComboBox));
  }

  protected String getStringValue(T t) {
    return String.valueOf(t);
  }

  protected boolean isEditable(T t) {
    return true;
  }

  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() {
  }

  class TableAndActionsImpl extends AbstractTableAndActionsComponent<T> {
    TableAndActionsImpl() {
      super(resourcesTable);
    }

    @Override
    protected void onAddEvent() {
      int lastRow = resourcesTable.getRowCount() - 1;
      if (myComboBox != null) {
        resourcesTable.setValueAt(myComboBox.getItemAt(0), lastRow, 0);
        myComboBox.requestFocus();
      } else {
        // resourcesTable.setValueAt("<column name>", lastRow, 0);
      }

      Rectangle cellRect = resourcesTable.getCellRect(lastRow, 0, true);
      resourcesTable.scrollRectToVisible(cellRect);
      resourcesTable.getSelectionModel().setSelectionInterval(lastRow, lastRow);
      resourcesTable.editCellAt(lastRow, 0);
      resourcesTable.getEditorComponent().requestFocus();

    }

    @Override
    protected void onDeleteEvent() {
      for (int selectedRow : mySelectedRows) {
        resourcesTable.getModel().setValueAt(null, selectedRow, 0);
      }
    }

    @Override
    protected void onSelectionChanged() {
      mySelectedRows = resourcesTable.getSelectedRows();
      List<T> selectedObjects = getSelectedObjects();
      fireSelectionChanged(selectedObjects);
    }

  }
}