/*
 * TaskCategoryListPanel.java
 *
 * Created on 31. August 2007, 15:11
 */

package net.sourceforge.ganttproject.gui.options;

import java.awt.Color;
import java.awt.Frame;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractListModel;
import javax.swing.JColorChooser;
import javax.swing.JOptionPane;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.TaskCategory;
import net.sourceforge.ganttproject.task.TaskCategoryManager;

/**
 * Option panel for manging task categories
 *
 * @author  shofmann <sebastian.hofmann@sourcepark.de>
 */
public class TaskCategoryListPanel extends GeneralOptionPanel {
    
    /** Reference to the current project */
    private IGanttProject project;
    
    /** Reference to the currents project TaskCategoryMananger */
    private TaskCategoryManager taskCategoryManager;
    
    /** UI model for this dialog */
    private CategoryListModel categoryListModel;
    
    /** indicates whether this panel has unsaved changes */
    private boolean hasUnsavedChanges = false;
    
    /** Reference to the currents language handler */
    private GanttLanguage i18n;
    
    /**
     * Creates a new instance
     * @param owner the opening frame
     * @param project the current project
     */
    public TaskCategoryListPanel(Frame owner, IGanttProject project) {
        super(GanttLanguage.getInstance().getText("option.taskCategories.dialog.label"),
                GanttLanguage.getInstance().getText("option.taskCategories.dialog.comment"), 
                owner); 
        this.project = project;
        this.i18n = GanttLanguage.getInstance();
        this.taskCategoryManager = project.getTaskCategoryManager();
        this.categoryListModel = new CategoryListModel(getTaskCategoryVOs());
        this.categoryListModel.addListDataListener(new ListDataListener(){
            public void intervalAdded(ListDataEvent e){
                hasUnsavedChanges = true;
            }
            public void intervalRemoved(ListDataEvent e){
                hasUnsavedChanges = true;
            }
            public void contentsChanged(ListDataEvent e){
                hasUnsavedChanges = true;
            }
        });
    }
    
    /**
     * This method checks if the value has changed, and asks for commiting changes.
     * @param askForApply true if the user is ask for applying changes or false
     * otherwise
     * @return true if the are unsaved changes or false otherwise
     */
    public boolean applyChanges(boolean askForApply){
        if(askForApply && this.hasUnsavedChanges){
            int n = JOptionPane.showConfirmDialog(
                    this,
                    i18n.getText("option.taskCategories.confirm.message"),
                    i18n.getText("option.taskCategories.confirm.title"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);
            if(n == JOptionPane.YES_OPTION){
                saveChanges();
                this.hasUnsavedChanges = false;
                project.setModified(true);
            }else if(n == JOptionPane.NO_OPTION){
                this.hasUnsavedChanges = false;
            }
        }else if(this.hasUnsavedChanges){
            saveChanges();
            this.hasUnsavedChanges = false;
            project.setModified(true);
        }
        return hasUnsavedChanges;
    }
    
    /**
     * Saves current changes
     */
    private void saveChanges(){
        updateTaskCategories(this.categoryListModel.getTaskCategories());
    }
    
    /** Initialize the component. */
    public void initialize(){
        initComponents();
        this.categoryList.setModel(this.categoryListModel);
        setEditableComponentsEnabled(false);
        this.removeButton.setEnabled(false);
    }
    
    /**
     * Sets all editable components enabled or not
     * @param enabled true if compontents should be enabled or false otherwise
     */
    private void setEditableComponentsEnabled(boolean enabled){
        this.descriptionTextField.setEnabled(enabled);
        this.descriptionLabel.setEnabled(enabled);
        this.colorButton.setEnabled(enabled);
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        listScrollPane = new javax.swing.JScrollPane();
        categoryList = new javax.swing.JList();
        descriptionLabel = new javax.swing.JLabel();
        descriptionTextField = new javax.swing.JTextField();
        colorButton = new javax.swing.JButton();
        buttonPanel = new javax.swing.JPanel();
        removeButton = new javax.swing.JButton();
        addButton = new javax.swing.JButton();

        setLayout(new java.awt.GridBagLayout());

        setMinimumSize(new java.awt.Dimension(161, 350));
        setPreferredSize(new java.awt.Dimension(161, 350));
        categoryList.setModel(new javax.swing.AbstractListModel() {
            String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            public int getSize() { return strings.length; }
            public Object getElementAt(int i) { return strings[i]; }
        });
        categoryList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        categoryList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                categoryListValueChanged(evt);
            }
        });

        listScrollPane.setViewportView(categoryList);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 5;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 5, 0);
        add(listScrollPane, gridBagConstraints);

        descriptionLabel.setText(i18n.getText("option.taskCategories.description"));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
        add(descriptionLabel, gridBagConstraints);

        descriptionTextField.addCaretListener(new javax.swing.event.CaretListener() {
            public void caretUpdate(javax.swing.event.CaretEvent evt) {
                descriptionTextFieldCaretUpdate(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        add(descriptionTextField, gridBagConstraints);

        colorButton.setText(i18n.getText("colorButton"));
        colorButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                colorButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 0, 5);
        add(colorButton, gridBagConstraints);

        buttonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

        removeButton.setText("-");
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonActionPerformed(evt);
            }
        });

        buttonPanel.add(removeButton);

        addButton.setText("+");
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });

        buttonPanel.add(addButton);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = 5;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        add(buttonPanel, gridBagConstraints);

    }// </editor-fold>//GEN-END:initComponents
    
    /**
     * Callback handler when addButton was clicked
     * @param evt the initating event
     */
    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
        int index = this.categoryListModel.createTaskCategory();
        this.categoryList.setSelectedIndex(index);
    }//GEN-LAST:event_addButtonActionPerformed
    
    /**
     * Callback handler when colorButton was clicked
     * @param evt the initating event
     */
    private void colorButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_colorButtonActionPerformed
        TaskCategoryVO vo = (TaskCategoryVO)this.categoryList.getSelectedValue();
        if(vo != null){
            Color newColor = JColorChooser.showDialog(
                    this,
                    i18n.getText("selectColor"),
                    vo.getColor());
            if(newColor != null){
                vo.setColor(newColor);
                this.colorButton.setBackground(newColor);
                this.categoryListModel.updateTaskCategory(vo);
            }
        }
    }//GEN-LAST:event_colorButtonActionPerformed
    
    /**
     * Callback handler when caret of descriptionTextField was changed
     * @param evt the initating event
     */
    private void descriptionTextFieldCaretUpdate(javax.swing.event.CaretEvent evt) {//GEN-FIRST:event_descriptionTextFieldCaretUpdate
        TaskCategoryVO vo = (TaskCategoryVO)this.categoryList.getSelectedValue();
        if(vo != null){
            vo.setDescription(this.descriptionTextField.getText().trim());
            this.categoryListModel.updateTaskCategory(vo);
        }
    }//GEN-LAST:event_descriptionTextFieldCaretUpdate
    
    /**
     * Callback handler when removeButton was clicked
     * @param evt the initating event
     */
    private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
        TaskCategoryVO tc = (TaskCategoryVO)this.categoryList.getSelectedValue();
        if(tc.hasTasksAssigned()){
            int n = JOptionPane.showConfirmDialog(
                    this,
                    i18n.getText("option.taskCategories.confirm.remove.message"),
                    i18n.getText("option.taskCategories.confirm.remove.title"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);
            if(n == JOptionPane.YES_OPTION){
                removeTaskCategory(tc);
            }
        }else{
            removeTaskCategory(tc);
        }
    }//GEN-LAST:event_removeButtonActionPerformed
    
    /**
     * Removes a task category
     * @param tc category to remove
     */
    private void removeTaskCategory(TaskCategoryVO tc){
        this.categoryListModel.removeTaskCategory(tc);
        this.descriptionTextField.setText("");
        setEditableComponentsEnabled(false);
        this.removeButton.setEnabled(false);
        this.colorButton.setBackground(null);
    }
    
    /**
     * Callback handler when lists data has changed
     * @param evt the initating event
     */
    private void categoryListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_categoryListValueChanged
        if (!evt.getValueIsAdjusting()) {
            if (this.categoryList.getSelectedIndex() != -1) {
                setEditableComponentsEnabled(true);
                this.removeButton.setEnabled(true);
                this.descriptionTextField.setText(((TaskCategoryVO)this.categoryList.getSelectedValue()).getDescription());
                this.colorButton.setBackground(((TaskCategoryVO)this.categoryList.getSelectedValue()).getColor());
            }
        }
    }//GEN-LAST:event_categoryListValueChanged
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addButton;
    private javax.swing.JPanel buttonPanel;
    private javax.swing.JList categoryList;
    private javax.swing.JButton colorButton;
    private javax.swing.JLabel descriptionLabel;
    private javax.swing.JTextField descriptionTextField;
    private javax.swing.JScrollPane listScrollPane;
    private javax.swing.JButton removeButton;
    // End of variables declaration//GEN-END:variables
    
    /**
     * UI Model for handling the list of task categories
     */
    class CategoryListModel extends AbstractListModel{
        
        /** List holding all categories */
        private List<TaskCategoryVO> categories;
        
        /**
         * Creates a new instance
         * @param categories initial list of categories
         */
        public CategoryListModel(List<TaskCategoryVO> categories){
            this.categories = categories;
            Collections.sort(this.categories);
        }
        
        /**
         * Removes a given Category from the list
         * @param cat the catehory to remove
         */
        public void removeTaskCategory(TaskCategoryVO cat){
            int position = this.categories.indexOf(cat);
            this.categories.remove(cat);
            fireIntervalRemoved(this, position, position);
        }
        
        /**
         * Returns the size of the list
         * @return size of the list
         */
        public int getSize() {
            return this.categories.size();
        }
        
        /**
         * Returns the object at the given position
         * @param index position of object to return
         * @return object at the given index
         */
        public Object getElementAt(int index) {
            return this.categories.get(index);
        }
        
        /**
         * Returns the list of categories managed by this model
         * @return list of categories
         */
        public List<TaskCategoryVO> getTaskCategories(){
            return this.categories;
        }
        
        /**
         * Update a category
         * @param cat the category to update
         */
        public void updateTaskCategory(TaskCategoryVO cat){
            int position = this.categories.indexOf(cat);
            fireContentsChanged(this, position, position);
        }
        
        /**
         * creates a new category and intializes it with default vaules
         * @return index in list of new category
         */
        public int createTaskCategory(){
            TaskCategoryVO cat = new TaskCategoryVO();
            cat.setDescription(i18n.getText("option.taskCategories.default.description"));
            cat.setColor(Color.LIGHT_GRAY);
            this.categories.add(cat);
            int position = this.categories.indexOf(cat);
            fireIntervalAdded(this, position, position);
            return position;
        }
    }
    
    /**
     * Returns value objects of all available categories
     * @return list of all categories
     */
    private List<TaskCategoryVO> getTaskCategoryVOs(){
        List<TaskCategoryVO> categoryVOs = new ArrayList<TaskCategoryVO>();
        for (TaskCategory cat : this.taskCategoryManager.getCategories()) {
            categoryVOs.add(new TaskCategoryVO(cat));
        }
        return categoryVOs;
    }
    
    /**
     * Updates the available categories. The list of given value objects
     * has to contain all categories. Categories not in list will be
     * deleted.
     * @param categoryVOs list of category value objects
     */
    private void updateTaskCategories(List<TaskCategoryVO> categoryVOs){
        Set<TaskCategory> existing = new HashSet<TaskCategory>(this.taskCategoryManager.getCategories());

        // We update the existing categories and create new ones if necessary 
        for (TaskCategoryVO vo : categoryVOs) {
        	TaskCategory tc = vo.getCategory()==null ? this.taskCategoryManager.createCategory() : vo.getCategory(); 
            tc.setColor(vo.getColor());
            tc.setDescription(vo.getDescription());
            existing.remove(tc);
        }
        
        // And here we remove the rest ones (those that used to exist but were removed in UI)
        for (TaskCategory tc : existing) {
        	tc.delete();
        }
    }
    
    
}
