/*
Copyright (C) 2008  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 2
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.
*/
package net.sourceforge.ganttproject;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.AttributedCharacterIterator;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.gui.GanttDialogCustomColumn;
import net.sourceforge.ganttproject.gui.TableHeaderUIFacade;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder;
import net.sourceforge.ganttproject.gui.options.model.DateOption;
import net.sourceforge.ganttproject.gui.options.model.DefaultDateOption;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.CustomPropertyEvent;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.calendar.DatePickerFormatter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.table.TableColumnExt;
import org.jdesktop.swingx.table.TableColumnModelExt;
import org.jdesktop.swingx.treetable.TreeTableModel;


public abstract class GPTreeTableBase extends JXTreeTable implements CustomPropertyListener{
    private final IGanttProject myProject;
    private final UIFacade myUIFacade;
    private final CustomPropertyManager myCustomPropertyManager;
    private final TableHeaderUIFacade myVisibleFields = new VisibleFieldsImpl();
    
    
    protected IGanttProject getProject() {
        return myProject;
    }
    
    
    @Override
    public void addColumn(TableColumn column) {
        super.addColumn(column);
        //new Exception("column="+column.getIdentifier()).printStackTrace();
    }


    protected GPTreeTableBase(IGanttProject project, UIFacade uifacade, TreeTableModel model, CustomPropertyManager customPropertyManager) {
        super(model);/* {
            protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
                if (e.isAltDown() || e.isControlDown()) {
                    putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
    }
                boolean result = super.processKeyBinding(ks, e, condition, pressed);
                putClientProperty("JTable.autoStartsEdit", Boolean.TRUE);
                return result;
            }
            
        });
        */
        myCustomPropertyManager = customPropertyManager;
        myProject = project;
        myUIFacade = uifacade;
        myCustomPropertyManager.addListener(this);        
        myProject.addProjectEventListener(new ProjectEventListener.Stub(){
            public void projectClosed() {
                clearColumns();
            }
            public void projectModified() {
            }
            public void projectSaved() {
            }
            public void projectWillBeOpened() {
            }
            public void projectOpened() {
                //createDefaultColumns();
            }
            
        });
        
        getTree().setScrollsOnExpand(true);
    }
    
    protected UIFacade getUIFacade() {
        return myUIFacade;
    }

    protected CustomPropertyManager getCustomPropertyManager() {
        return myCustomPropertyManager;
    }
    protected TableColumnExt newTableColumnExt(int modelIndex) {
        return newTableColumnExt(modelIndex, getTreeTableModel().getColumnClass(modelIndex));
    }
    
    protected TableColumnExt newTableColumnExt(int modelIndex, Class valueClass) {
        TableColumnExt result = new TableColumnExt(modelIndex);
        TableCellEditor defaultEditor = getDefaultEditor(valueClass);
        if (defaultEditor!=null) {
            result.setCellEditor(new TreeTableCellEditorImpl(defaultEditor));
        }
        
        return result;
    }
    
    
    public void customPropertyChange(CustomPropertyEvent event) {
        switch(event.getType()) {
        case CustomPropertyEvent.EVENT_ADD:
            addNewCustomColumn(event.getDefinition());
            break;
        case CustomPropertyEvent.EVENT_REMOVE:
            deleteCustomColumn(event.getDefinition());
            break;
        }
    }

    protected void onAddCustomColumnAction(final int contextColumn) {
        GanttDialogCustomColumn d = new GanttDialogCustomColumn(
                getUIFacade(), myCustomPropertyManager) {
            @Override
            protected void ok(CustomPropertyDefinition definition) {
                getColumnModel().moveColumn(getTable().getColumnCount()-1, contextColumn);
            }
        };
        d.setVisible(true);
    }
    
    protected void onDeleteCustomColumnAction(CustomPropertyDefinition def) {
        myCustomPropertyManager.deleteDefinition(def);
    }   
    
    protected void addNewCustomColumn(CustomPropertyDefinition customPropertyDefinition) {
        assert customPropertyDefinition!=null;
        if (customPropertyDefinition.getName() != null) {
            TableColumnExt newColumn = newTableColumnExt(getColumnCount(true), customPropertyDefinition.getType());
            newColumn.setIdentifier(customPropertyDefinition.getID());
            newColumn.setHeaderValue(customPropertyDefinition.getName());
            addColumn(newColumn);

            getProject().setModified();

        }

        Runnable t = new Runnable() {
            public void run() {
                calculateWidth();
                revalidate();
            }
        };
        SwingUtilities.invokeLater(t);

    }

    protected void deleteCustomColumn(CustomPropertyDefinition customPropertyDefinition) {
        TableColumn tableColumn = getColumn(customPropertyDefinition.getID());
        removeColumn(tableColumn);
    }
    
    void calculateWidth() {
        int width = 0;
        final int nbCol = getColumnCount();
        for (int i = 0; i < nbCol; i++) {
            TableColumnExt tce = getColumnExt(i);
            if (tce.isVisible()) {
                width += tce.getPreferredWidth();
            }
        }
        setPreferredScrollableViewportSize(new Dimension(width, 0));
    }
    
    protected TableCellEditor newDateCellEditor() {
        return new DateCellEditor() {
            protected Date parseDate(String dateString) {
                DateFormat[] formats = new DateFormat[] {
                        GanttLanguage.getInstance().getLongDateFormat(),
                        GanttLanguage.getInstance().getMediumDateFormat(),
                        GanttLanguage.getInstance().getShortDateFormat(),
                };
                for (int i=0; i<formats.length; i++) {
                    try {
                        Date typedDate = formats[i].parse(dateString);
                        Calendar typedCal = GanttLanguage.getInstance().newCalendar();
                        typedCal.setTime(typedDate);
                        Calendar projectStartCal = GanttLanguage.getInstance().newCalendar();
                        projectStartCal.setTime(myProject.getTaskManager().getProjectStart());
                        int yearDiff = Math.abs(typedCal.get(Calendar.YEAR) - projectStartCal.get(Calendar.YEAR));
                        if (yearDiff > 1500) {
                            AttributedCharacterIterator iter = formats[i].formatToCharacterIterator(typedDate);
                            int additionalZeroes = -1;
                            StringBuffer result = new StringBuffer();
                            for (char c = iter.first(); c!=iter.DONE; c = iter.next()) {
                                if (iter.getAttribute(DateFormat.Field.YEAR)!=null && additionalZeroes==-1) {
                                    additionalZeroes = iter.getRunLimit(DateFormat.Field.YEAR) - iter.getIndex();
                                    for (int j=0; j<additionalZeroes; j++) {
                                        result.append('0');
                                    }
                                }
                                result.append(c);
                            }
                            if (!result.toString().equals(dateString)) {
                                typedCal.add(Calendar.YEAR, 2000);
                                return typedCal.getTime();
                            }
                        }
                        return typedDate;
                    }
                    catch (ParseException e) {
                        if (i+1 == formats.length) {
                            return null;
                        }
                    }
                }
                return null;
                
            }
        };
    }
    
    protected TableCellRenderer newDateCellRenderer() {
        return new DateCellRenderer();
    }

    private static class DateCellRenderer extends DefaultTableCellRenderer {
        @Override
        public void setValue(Object value) {
            if (value==null) {
                setText("");
            }
            else {
                assert value instanceof GanttCalendar;
                try {
                    String text = new DatePickerFormatter().valueToString(((GanttCalendar)value).getTime()); 
                    setText(text);
                }
                catch (ParseException e) {
                    setText("");
                }
            }
        }    	
    }
    
    private class DateCellEditor extends AbstractCellEditor implements TableCellEditor{
        private DateOption myDate = null;

        public DateCellEditor() {
        }

        public Component getTableCellEditorComponent(JTable table, Object value,
                  boolean isSelected,
                  int row, int column) {
            assert value instanceof GanttCalendar;
            myDate = new DefaultDateOption("", ((GanttCalendar)value).getTime());
            myDate.lock();
            OptionsPageBuilder builder = new OptionsPageBuilder();
            Component result = builder.createDateComponent(myDate, false);
            return result;
        }
        
        public Object getCellEditorValue() {
            return new GanttCalendar(myDate.getValue());
        }

            protected Date parseDate(String dateString) {
                DateFormat[] formats = new DateFormat[] {
                        GanttLanguage.getInstance().getLongDateFormat(),
                        GanttLanguage.getInstance().getMediumDateFormat(),
                        GanttLanguage.getInstance().getShortDateFormat(),
                };
                for (int i=0; i<formats.length; i++) {
                    try {
                        Date typedDate = formats[i].parse(dateString);
                        Calendar typedCal = GanttLanguage.getInstance().newCalendar();
                        typedCal.setTime(typedDate);
                        Calendar projectStartCal = GanttLanguage.getInstance().newCalendar();
                        projectStartCal.setTime(myProject.getTaskManager().getProjectStart());
                        int yearDiff = Math.abs(typedCal.get(Calendar.YEAR) - projectStartCal.get(Calendar.YEAR));
                        if (yearDiff > 1500) {
                            AttributedCharacterIterator iter = formats[i].formatToCharacterIterator(typedDate);
                            int additionalZeroes = -1;
                            StringBuffer result = new StringBuffer();
                            for (char c = iter.first(); c!=iter.DONE; c = iter.next()) {
                                if (iter.getAttribute(DateFormat.Field.YEAR)!=null && additionalZeroes==-1) {
                                    additionalZeroes = iter.getRunLimit(DateFormat.Field.YEAR) - iter.getIndex();
                                    for (int j=0; j<additionalZeroes; j++) {
                                        result.append('0');
                                    }
                                }
                                result.append(c);
                            }
                            if (!result.toString().equals(dateString)) {
                                typedCal.add(Calendar.YEAR, 2000);
                                return typedCal.getTime();
                            }
                        }
                        return typedDate;
                    }
                    catch (ParseException e) {
                        if (i+1 == formats.length) {
                            return null;
                        }
                    }
                }
                return null;
                
            }

        public boolean stopCellEditing() {
            myDate.commit();
            return super.stopCellEditing();
        }
    }
    
    protected GPTreeTableBase getTable() {
        return this;
    }
    
    protected GPTreeTableBase getTreeTable() {
        return this;
    }

    protected void createDefaultColumns() {
    }
    
    protected void initTreeTable() {
        setAutoCreateColumnsFromModel(false);
        setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
        setShowHorizontalLines(true);
        
        setOpenIcon(null);
        setClosedIcon(null);
        setCollapsedIcon(new ImageIcon(getClass().getResource("/icons/plus.gif")));
        setExpandedIcon(new ImageIcon(getClass().getResource("/icons/minus.gif")));
        setLeafIcon(null);

        setColumnControlVisible(true);
        setBackground(Color.WHITE);
        {
            getModel().addTableModelListener(new TableModelListener() {
                public void tableChanged(TableModelEvent e) {
                    getUIFacade().getMainFrame().repaint();
                }
            });
        }
        setHighlighters(HighlighterFactory.createSimpleStriping());
        
        getTable().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onCellSelectionChanged();
            }
        });
        getTable().getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onCellSelectionChanged();
            }
        });
        setDefaultEditor(GregorianCalendar.class, newDateCellEditor());
        getTableHeader().addMouseListener(new HeaderMouseListener());        
    }

    void clearColumns() {
        List<TableColumn> columns = new ArrayList<TableColumn>(getColumns(true));
        for (TableColumn column : columns) {
            removeColumn(column);
        }
    }
    @Override
    public boolean getAutoCreateColumnsFromModel() {
        return false;
    }
    /**
     * Displays all the table columns.
     */
    protected void displayAllColumns() {
        for (TableColumn c : getColumns(true)) {
            ((TableColumnExt)c).setVisible(true);
        }
    }
    protected void onCellSelectionChanged() {
        if (!getTable().isEditing()) {
            int row = getTable().getSelectedRow();
            int col = getTable().getSelectedColumn();
            Rectangle rect = getTable().getCellRect(row, col, true);
            getTable().scrollRectToVisible(rect);
        }
    }

    
    class HeaderMouseListener extends MouseAdapter {
        /**
         * Creates a new HeaderMouseListener
         */
        public HeaderMouseListener() {
            super();
        }

        public void mousePressed(MouseEvent e) {
            handlePopupTrigger(e);
        }

        public void mouseReleased(MouseEvent e) {
            handlePopupTrigger(e);
        }

        private void handlePopupTrigger(MouseEvent e) {
            if (e.isPopupTrigger()) {
                int contextColumn = getTable().columnAtPoint(e.getPoint());
                Action[] contextActions = createHeaderContextMenuActions(contextColumn);
                getUIFacade().showPopupMenu((Component) e.getSource(), contextActions, e.getX(), e.getY());
                
            }
        }
                
        private Action[] createHeaderContextMenuActions(final int contextColumn) {
            final TableColumnExt contextTableColumn = getTable().getColumnExt(contextColumn); 
            final CustomPropertyDefinition cc = getCustomPropertyManager().getCustomPropertyDefinition(
                    String.valueOf(contextTableColumn.getIdentifier()));     
            GPAction actionAdd = new GPAction("addCustomColumn") {
                public void actionPerformed(ActionEvent e) {
                    getUIFacade().getUndoManager().undoableEdit(
                            "PopUpNewColumn",
                            new Runnable() {
                                public void run() {
                                    onAddCustomColumnAction(contextColumn);
                                }
                            });                    
                }
                protected String getIconFilePrefix() {
                    return null;
                }
                protected String getLocalizedName() {
                    return getI18n("addCustomColumn");
                }
            };
            
            final Runnable runDelete = new Runnable() {
                public void run() {
                    onDeleteCustomColumnAction(cc);
                }
            };
            GPAction actionDelete = new GPAction("deleteCustomColumn") {
                public void actionPerformed(ActionEvent e) {
                    getUIFacade().getUndoManager().undoableEdit("deleteCustomColumn", runDelete); 
                }
                protected String getIconFilePrefix() {
                    return null;
                }
                protected String getLocalizedName() {
                    return getI18n("deleteCustomColumn");
                }
            };
            if (cc == null) {
                actionDelete.setEnabled(false);
            }
            
            final Runnable runShowAll = new Runnable() {
                public void run() {
                    displayAllColumns();
                }
            };
            GPAction actionShowAll = new GPAction("displayAll") {
                protected String getIconFilePrefix() {
                    return null;
                }
                protected String getLocalizedName() {
                    return getI18n("displayAll");
                }
                public void actionPerformed(ActionEvent e) {
                    getUIFacade().getUndoManager().undoableEdit("displayAllColumns", runShowAll); 
                }
            };
                    
            GPAction actionShowHideColumns = new GPAction("showHideColumns") {
                protected String getIconFilePrefix() {
                    return null;
                }
                public void actionPerformed(ActionEvent e) {
                    ShowHideColumnsDialog dlg = new ShowHideColumnsDialog(
                            getUIFacade(), getTable().getColumns(true));
                    dlg.show();
                }
                protected String getLocalizedName() {
                    return getI18n("taskTable.header.contextMenu.showHideColumns");
                }           
            };
                    
            final Runnable runHideContextColumn = new Runnable() {
                public void run() {
                    contextTableColumn.setVisible(false);
                }
            };
            GPAction actionHideContextColumn = new GPAction("hideContextColumn") {
                protected String getIconFilePrefix() {
                    return null;
                }
                public void actionPerformed(ActionEvent e) {
                    getUIFacade().getUndoManager().undoableEdit("hideContextColumn", runHideContextColumn);                
                }
                protected String getLocalizedName() {
                    return MessageFormat.format(
                            getI18n("taskTable.header.contextMenu.hideContextColumn"), 
                            contextTableColumn.getHeaderValue());
                }           
            };      
            return new Action[] {actionAdd, actionDelete, null, 
                    actionHideContextColumn, actionShowHideColumns, actionShowAll};
        }
    }
    

    public TableHeaderUIFacade getVisibleFields() {
        return myVisibleFields;
    }
    
    class VisibleFieldsImpl implements TableHeaderUIFacade {
        public void add(String name, int order, int width) {
            throw new UnsupportedOperationException();
        }

        public void clear() {
            clearColumns();
        }

        public Column getField(final int index) {
            final TableColumnModelExt columnModel = (TableColumnModelExt) getColumnModel();
            List<TableColumn> columns = columnModel.getColumns(true);
            final TableColumnExt column = (TableColumnExt) columns.get(index);
            return new Column() {
                public String getID() {
                    return String.valueOf(column.getIdentifier());
                }
                public String getName() {
                    return column.getTitle();
                }
                public int getOrder() {
                    return columnModel.getColumnIndex(column.getIdentifier());
                }
                public int getWidth() {
                    return column.getWidth();
                }
                public boolean isVisible() {
                    return column.isVisible();
                }
            };
        }

        public int getSize() {
            return getColumnCount(true);
        }
        
        public void importData(TableHeaderUIFacade source) {
            clear();
            createDefaultColumns();
            List<Column> buf = new ArrayList<Column>();
            for (int i=0; i<source.getSize(); i++) {
                buf.add(source.getField(i));
            }
            Collections.sort(buf, new Comparator<Column>() {
                public int compare(Column o1, Column o2) {
                    return o1.getOrder() - o2.getOrder();
                }
            });
            int newPosition = 0;
            for (Column nextField : buf) {
                TableColumnExt newTableColumn = getColumnExt(nextField.getID());
                if (newTableColumn == null) {
                    newTableColumn = newTableColumnExt(getColumnCount());
                    CustomPropertyDefinition def = getCustomPropertyManager().getCustomPropertyDefinition(nextField.getID());
                    if (def!=null) {
                        newTableColumn.setTitle(def.getName());
                        newTableColumn.setHeaderValue(def.getName());
                    }
                    addColumn(newTableColumn);
                }
                newTableColumn.setWidth(nextField.getWidth());
                newTableColumn.setIdentifier(nextField.getID());
                newTableColumn.setVisible(nextField.isVisible());
                getTable().getColumnModel().moveColumn(getTable().getColumnModel().getColumnIndex(nextField.getID()), newPosition);
                newPosition++;
            }
        }
    }

    void setAction(Action action) {
        addAction(action, (KeyStroke) action.getValue(Action.ACCELERATOR_KEY));
    }

    void addAction(Action action, KeyStroke keyStroke) {
        InputMap inputMap = getInputMap();
        inputMap.put(keyStroke, action.getValue(Action.NAME));
        getActionMap().put(action.getValue(Action.NAME), action);
    }    
}
