/*
Copyright 2003-2012 Dmitry Barashev, GanttProject Team

This file is part of GanttProject, an opensource project management tool.

GanttProject 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.

GanttProject 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 GanttProject.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.sourceforge.ganttproject.gui.tableView;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;

import biz.ganttproject.core.option.ChangeValueEvent;
import biz.ganttproject.core.option.ChangeValueListener;
import biz.ganttproject.core.option.DefaultBooleanOption;
import biz.ganttproject.core.option.DefaultEnumerationOption;
import biz.ganttproject.core.option.DefaultStringOption;
import biz.ganttproject.core.option.GPOption;
import biz.ganttproject.core.option.GPOptionGroup;
import biz.ganttproject.core.table.ColumnList;
import biz.ganttproject.core.table.ColumnList.Column;

import com.google.common.base.Function;

import net.sourceforge.ganttproject.CustomPropertyClass;
import net.sourceforge.ganttproject.CustomPropertyDefinition;
import net.sourceforge.ganttproject.CustomPropertyManager;
import net.sourceforge.ganttproject.DefaultCustomPropertyDefinition;
import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.gui.AbstractTableAndActionsComponent;
import net.sourceforge.ganttproject.gui.AbstractTableAndActionsComponent.SelectionListener;
import net.sourceforge.ganttproject.gui.EditableList;
import net.sourceforge.ganttproject.gui.ListAndFieldsPanel;
import net.sourceforge.ganttproject.gui.UIUtil;
import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder;
import net.sourceforge.ganttproject.gui.options.model.CustomPropertyDefaultValueAdapter;

/**
 * UI component inside  "Custom Fields Manager" dialog which allows for showing/hiding
 * table columns and for the management of custom fields.
 *
 * @author dbarashev (Dmitry Barashev)
 */
public class ColumnManagerPanel {
  private IsVisibleOption myIsVisibleOption = new IsVisibleOption();
  private NameOption myNameOption = new NameOption();
  private DefaultValueOption myEnableDefaultValueOption = new DefaultValueOption();
  private PropertyClassOption myType = new PropertyClassOption();
  private GPOption<?> myDefaultValueOption;

  private JPanel panelDefaultValue = null;

  private CardLayout cardLayoutDefaultValue = null;
  private final CustomPropertyManager myManager;
  private final ColumnList myVisibleFields;
  private JComponent myFields;

  public ColumnManagerPanel(CustomPropertyManager columnManager, ColumnList visibleFields) {
    myManager = columnManager;
    myVisibleFields = visibleFields;
  }

  public void commitCustomPropertyEdit() {
    myIsVisibleOption.commit();
    myNameOption.commit();
    myEnableDefaultValueOption.commit();
    myType.commit();
  }

  public Component createComponent() {
    List<CustomPropertyDefinition> emptyList = Collections.emptyList();
    List<CustomPropertyDefinition> defs = new ArrayList<CustomPropertyDefinition>();
    createDefaultFieldDefinitions(myVisibleFields, myManager.getDefinitions(), defs);
    final Set<CustomPropertyDefinition> standardDefs = new HashSet<CustomPropertyDefinition>(defs);
    defs.addAll(myManager.getDefinitions());
    final EditableList<CustomPropertyDefinition> props = new EditableList<CustomPropertyDefinition>(defs, emptyList) {
      @Override
      protected boolean isEditable(CustomPropertyDefinition t) {
        return ColumnManagerPanel.this.isEditable(t);
      }

      @Override
      protected String getStringValue(CustomPropertyDefinition value) {
        return value.getName();
      }

      @Override
      protected CustomPropertyDefinition createPrototype(Object editValue) {
        return new DefaultCustomPropertyDefinition(String.valueOf(editValue));
      }

      @Override
      protected CustomPropertyDefinition createValue(CustomPropertyDefinition prototype) {
        final CustomPropertyDefinition def = myManager.createDefinition(CustomPropertyClass.TEXT.getID(),
            prototype.getName(), null);
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            myVisibleFields.add(def.getID(), -1, -1);
            getTableComponent().requestFocus();
            setSelectedDefinition(def);
          }
        });
        return def;
      }

      @Override
      protected void deleteValue(CustomPropertyDefinition value) {
        myManager.deleteDefinition(value);
      }

      @Override
      protected CustomPropertyDefinition updateValue(CustomPropertyDefinition newValue,
          CustomPropertyDefinition curValue) {
        curValue.setName(newValue.getName());
        return curValue;
      }

      @Override
      protected Component getTableCellRendererComponent(DefaultTableCellRenderer defaultRenderer,
          CustomPropertyDefinition def, boolean isSelected, boolean hasFocus, int row) {
        StringBuffer value = new StringBuffer("<html>");
        Column column = myIsVisibleOption.findColumn(def);
        if (column != null && !column.isVisible()) {
          value.append("<font color=#999999>{0}</font>");
        } else {
          value.append("{0}");
        }
        value.append("</html>");
        defaultRenderer.setText(MessageFormat.format(value.toString(), getStringValue(def)));
        return defaultRenderer;
      }
    };

    class ShowHideSelectionAction extends GPAction implements SelectionListener<CustomPropertyDefinition> {
      private boolean isShow;
      private List<CustomPropertyDefinition> mySelection;

      public ShowHideSelectionAction(boolean isShow, String name) {
        super(name);
        this.isShow = isShow;
      }

      @Override
      public void selectionChanged(List<CustomPropertyDefinition> selection) {
        mySelection = selection;
      }

      @Override
      public void actionPerformed(ActionEvent e) {
        for (CustomPropertyDefinition def : mySelection) {
          myIsVisibleOption.setVisible(def, isShow);
        }
      }
    }
    props.getTableAndActions().addAction(new ShowHideSelectionAction(true, "customPropertyDialog.showSelected"));
    props.getTableAndActions().addAction(new ShowHideSelectionAction(false, "customPropertyDialog.hideSelected"));
    myIsVisibleOption.setVisibleFields(myVisibleFields);
    ChangeValueListener defaultValuePanelEnabler = new ChangeValueListener() {
      @Override
      public void changeValue(ChangeValueEvent event) {
        setDefaultValuePanelEnabled(myEnableDefaultValueOption.isChecked());
      }
    };
    myEnableDefaultValueOption.addChangeValueListener(defaultValuePanelEnabler);
    myType.addChangeValueListener(defaultValuePanelEnabler);
    myFields = getFieldsComponent();
    ListAndFieldsPanel<CustomPropertyDefinition> listAndFields = new ListAndFieldsPanel<CustomPropertyDefinition>(
        props, myFields);

    Function<List<CustomPropertyDefinition>, Boolean> isDeleteEnabled = new Function<List<CustomPropertyDefinition>, Boolean>() {

      @Override
      public Boolean apply(List<CustomPropertyDefinition> input) {
        for (CustomPropertyDefinition def : input) {
          if (standardDefs.contains(def)) {
            return false;
          }
        }
        return !input.isEmpty();
      }
    };
    props.getTableAndActions().getDeleteItemAction().putValue(AbstractTableAndActionsComponent.PROPERTY_IS_ENABLED_FUNCTION, isDeleteEnabled);
    props.getTableAndActions().addSelectionListener(new SelectionListener<CustomPropertyDefinition>() {
      @Override
      public void selectionChanged(List<CustomPropertyDefinition> selection) {
        if (selection.size() != 1) {
          UIUtil.setEnabledTree(myFields, false);
        } else {
          commitCustomPropertyEdit();
          CustomPropertyDefinition selectedElement = selection.get(0);
          setSelectedDefinition(selectedElement);
        }
      }
    });
    return listAndFields.getComponent();
  }

  private void setSelectedDefinition(CustomPropertyDefinition selected) {
    UIUtil.setEnabledTree(myFields, isEditable(selected));
    myNameOption.reloadValue(selected);
    myType.reloadValue(selected);
    myEnableDefaultValueOption.reloadValue(selected);
  }

  /**
   * This method returns default columns (such as name, Id, etc.) in {@code output} list.
   * It does it by scanning the tableHeader where all columns live and filtering out
   * the real custom columns.
   */
  private void createDefaultFieldDefinitions(ColumnList tableHeader,
      List<CustomPropertyDefinition> customFields, List<CustomPropertyDefinition> output) {
    LinkedHashMap<String, Column> name2column = new LinkedHashMap<String, Column>();
    for (int i = 0; i < tableHeader.getSize(); i++) {
      Column column = tableHeader.getField(i);
      name2column.put(column.getName(), column);
    }
    for (CustomPropertyDefinition def : customFields) {
      name2column.remove(def.getName());
    }
    for (Column column : name2column.values()) {
      output.add(new DefaultCustomPropertyDefinition(column.getName()));
    }
  }

  protected boolean isEditable(CustomPropertyDefinition def) {
    return myManager.getCustomPropertyDefinition(def.getID()) != null;
  }

  protected void setDefaultValuePanelEnabled(boolean enabled) {
    UIUtil.setEnabledTree(panelDefaultValue, enabled);
  }

  private JComponent getFieldsComponent() {
    OptionsPageBuilder builder = new OptionsPageBuilder();
    {
      cardLayoutDefaultValue = new CardLayout();
      panelDefaultValue = new JPanel(cardLayoutDefaultValue);
      myType.setUIControls(cardLayoutDefaultValue, panelDefaultValue);
    }

    Component optionsComponent = builder.createGroupComponent(null, myNameOption, myType);

    Box result = Box.createVerticalBox();
    result.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    // result.setLayout(new BorderLayout());
    result.add(optionsComponent);
    result.add(Box.createVerticalStrut(10));
    GPOptionGroup defaultValueGroup = new GPOptionGroup("customPropertyDialog.defaultValue",
        new GPOption[] { myEnableDefaultValueOption });
    defaultValueGroup.setTitled(false);
    result.add(builder.createGroupComponent(defaultValueGroup, myEnableDefaultValueOption));
    result.add(panelDefaultValue);
    setDefaultValuePanelEnabled(false);
    return result;
  }

  class IsVisibleOption extends DefaultBooleanOption {
    private ColumnList myVisibleFields;

    IsVisibleOption() {
      super("customPropertyDialog.isVisible");
    }

    public void setVisibleFields(ColumnList visibleFields) {
      myVisibleFields = visibleFields;
    }

    @Override
    public void commit() {
      if (isChanged()) {
        super.commit();
      }
    }

    void setVisible(CustomPropertyDefinition def, boolean isVisible) {
      Column column = findColumn(def);
      if (column != null) {
        column.setVisible(isVisible);
      }
    }

    Column findColumn(CustomPropertyDefinition def) {
      for (int i = 0; i < myVisibleFields.getSize(); i++) {
        Column nextColumn = myVisibleFields.getField(i);
        if (nextColumn.getName().equals(def.getName())) {
          return nextColumn;
        }
      }
      return null;
    }
  }

  /**
   * Option class controlling changes of a custom field name
   */
  static class NameOption extends DefaultStringOption {
    CustomPropertyDefinition myDefinition;

    NameOption() {
      super("customPropertyDialog.name");
    }

    public void reloadValue(CustomPropertyDefinition selectedElement) {
      myDefinition = selectedElement;
      resetValue(myDefinition.getName(), true);
    }

    @Override
    public void commit() {
      if (isChanged()) {
        super.commit();
        myDefinition.setName(getValue());
      }
    }
  }

  /**
   * Option class controlling changes of a custom field default value
   */
  class DefaultValueOption extends DefaultBooleanOption {
    CustomPropertyDefinition myDefinition;

    DefaultValueOption() {
      super("customPropertyDialog.defaultValue");
    }

    @Override
    public void setValue(Boolean value) {
      super.setValue(value);
      UIUtil.setEnabledTree(panelDefaultValue, value);
    }

    @Override
    public void commit() {
      if (isChanged()) {
        super.commit();
        if (!getValue()) {
          myDefinition.setDefaultValueAsString(null);
        }
      }
    }

    public void reloadValue(CustomPropertyDefinition selectedElement) {
      myDefinition = selectedElement;
      resetValue(myDefinition.getDefaultValue() != null, true);
    }
  }

  /**
   * Option class controlling changes of a custom field type
   */
  class PropertyClassOption extends DefaultEnumerationOption<CustomPropertyClass> {
    private CardLayout myCardLayout;
    private JPanel myCardPanel;
    // private Map<CustomPropertyClass, Component> myDefaultValueEditors =
    // new HashMap<CustomPropertyClass, Component>();
    private CustomPropertyDefinition myDefinition;
    private CustomPropertyDefinition myDefinitionRO;

    public PropertyClassOption() {
      super("taskProperties.customColumn.type", CustomPropertyClass.values());
    }

    @Override
    protected String objectToString(CustomPropertyClass value) {
      return value.getDisplayName();
    }

    @Override
    protected void resetValue(String value, boolean resetInitial, Object triggerId) {
      CustomPropertyClass propertyClass = getCustomPropertyClass(value);
      myDefinition.setPropertyClass(propertyClass);
      // Component defaultValueEditor =
      // myDefaultValueEditors.get(propertyClass);
      // System.err.println("property class option setValue: class=" +
      // propertyClass + " defaultValueEditor=" + defaultValueEditor);
      // if (defaultValueEditor == null) {
      myDefaultValueOption = CustomPropertyDefaultValueAdapter.createDefaultValueOption(propertyClass, myDefinition);
      OptionsPageBuilder builder = new OptionsPageBuilder();
      Component defaultValueEditor = builder.createOptionComponent(null, myDefaultValueOption);
      JPanel defaultValuePanel = new JPanel(new BorderLayout());
      defaultValuePanel.add(defaultValueEditor, BorderLayout.NORTH);
      // myDefaultValueEditors.put(propertyClass, defaultValuePanel);
      myCardPanel.add(defaultValuePanel, propertyClass.getDisplayName());
      // }

      myCardLayout.show(myCardPanel, value);

      super.resetValue(value, resetInitial, triggerId);
    }

    @Override
    public void commit() {
      if (isChanged()) {
        super.commit();
        myDefinitionRO.setPropertyClass(getCustomPropertyClass(getValue()));
      }
      if (myEnableDefaultValueOption.getValue() && myDefaultValueOption != null && myDefaultValueOption.isChanged()) {
        myDefaultValueOption.commit();
        myDefinitionRO.setDefaultValueAsString(myDefinition.getDefaultValueAsString());
      }
    }

    private CustomPropertyClass getCustomPropertyClass(String value) {
      CustomPropertyClass newPropertyClass = null;
      for (CustomPropertyClass propertyClass : CustomPropertyClass.values()) {
        if (propertyClass.getDisplayName().equals(value)) {
          newPropertyClass = propertyClass;
          break;
        }
      }
      assert newPropertyClass != null;
      return newPropertyClass;
    }

    void setUIControls(CardLayout layout, JPanel panel) {
      myCardLayout = layout;
      myCardPanel = panel;
    }

    public void reloadValue(CustomPropertyDefinition def) {
      myDefinitionRO = def;
      myDefinition = new DefaultCustomPropertyDefinition(def.getName(), def.getID(), def);
      resetValue(def.getPropertyClass().getDisplayName(), true);
    }
  }

}
