package net.sourceforge.ganttproject;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Vector;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;

import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.language.GanttLanguage.Event;
import net.sourceforge.ganttproject.task.CustomColumn;
import net.sourceforge.ganttproject.task.CustomColumnsException;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskContainmentHierarchyFacade;
import net.sourceforge.ganttproject.task.TaskLength;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskNode;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;

import org.apache.commons.httpclient.methods.GetMethod;
import org.jdesktop.swingx.tree.TreeModelSupport;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableNode;

/**
 * This class is the model for GanttTreeTable to display tasks.
 * 
 * @author bbaranne (Benoit Baranne)
 */
public class GanttTreeTableModel extends DefaultTreeTableModel implements
        TableColumnModelListener,
        GanttLanguage.Listener {

    private static GanttLanguage language = GanttLanguage.getInstance();

    public static String strColType = null;

    public static String strColPriority = null;

    public static String strColNotes = null;

    public static String strColName = null;

    public static String strColBegDate = null;

    public static String strColEndDate = null;

    public static String strColDuration = null;

    public static String strColCompletion = null;

    public static String strColCoordinator = null;

    public static String strColPredecessors = null;

    public static String strColID = null;
    
    public static String strColLineNumber = null;

    /* TODO translation managment */
    public static String strAssignments = "Assignments";

    /** The colums titles */
    public List titles = null;

    /**
     * Custom columns list.
     */
    private Vector customColumns = null;

    /**
     * Number of columns (presently in the model)
     */
    private int nbCol = 13;

    /**
     * Number of columns (at all, even hiden)
     */
    private int nbColTot = nbCol;

    private final TaskManager myTaskManager;

    /**
     * Creates an instance of GanttTreeTableModel with a root.
     * 
     * @param root
     *            The root.
     * @param taskManager 
     */
    public GanttTreeTableModel(TreeTableNode root, TaskManager taskManager) {
        super(root);
        myTaskManager = taskManager;
        titles = new ArrayList();
        customColumns = new Vector();
        changeLanguage(language);
    }

    /**
     * Changes the language.
     * 
     * @param ganttLanguage
     *            New language to use.
     */
    public void changeLanguage(GanttLanguage ganttLanguage) {
        strColType = language.getText("tableColType");
        strColPriority = language.getText("tableColPriority");
        strColNotes = language.getText("tableColNotes");
        strColName = language.getText("tableColName");
        strColBegDate = language.getText("tableColBegDate");
        strColEndDate = language.getText("tableColEndDate");
        strColDuration = language.getText("tableColDuration");
        strColCompletion = language.getText("tableColCompletion");
        strColCoordinator = language.getText("tableColCoordinator");
        strColPredecessors = language.getText("tableColPredecessors");
        strColID = language.getText("tableColID");
        strColLineNumber = language.getText("tableColTaskLineNumber");

        titles.clear();
        String[] cols = new String[] { strColName, strColType, strColPriority, strColNotes,
                strColBegDate, strColEndDate, strColDuration,
                strColCompletion, strColCoordinator, strColPredecessors,
                strColID, strAssignments, strColLineNumber };
        for (int i = 0; i < cols.length; i++)
            titles.add(new String(cols[i]));
    }

//    /**
//     * Invoked this to insert newChild at location index in parents children.
//     * This will then message nodesWereInserted to create the appropriate event.
//     * This is the preferred way to add children as it will create the
//     * appropriate event.
//     */
//    public void insertNodeInto(MutableTreeTableNode newChild,
//            MutableTreeTableNode parent, int index) {
//        parent.insert(newChild, index);
//
//        int[] newIndexs = new int[1];
//
//        newIndexs[0] = index;
//        nodesWereInserted(parent, newIndexs);
//    }

    private TreeModelSupport getTreeModelSupport() {
        return modelSupport;
    }

//    /**
//     * Message this to remove node from its parent. This will message
//     * nodesWereRemoved to create the appropriate event. This is the preferred
//     * way to remove a node as it handles the event creation for you.
//     */
//    public void removeNodeFromParent(MutableTreeNode node) {
//        MutableTreeNode parent = (MutableTreeNode) node.getParent();
//
//        if (parent == null)
//            throw new IllegalArgumentException("node does not have a parent.");
//
//        int[] childIndex = new int[1];
//        Object[] removedArray = new Object[1];
//
//        childIndex[0] = parent.getIndex(node);
//        parent.remove(childIndex[0]);
//        removedArray[0] = node;
//        nodesWereRemoved(parent, childIndex, removedArray);
//    }

    /**
     * Add a custom column to the model.
     * 
     * @param title
     */
    public void addCustomColumn(String title) {
        customColumns.add(title);
        nbColTot++;
    }

    /**
     * Delete a custom column.
     * 
     * @param title
     */
    public void deleteCustomColumn(String title) {
        customColumns.remove(title);
        this.columnRemoved(null);
        nbColTot--;
    }

    public void renameCustomColumn(String oldName, String newName) {
        customColumns.set(customColumns.indexOf(oldName), newName);
    }

    // /**
    // * Returns the number of custom columns.
    // * @return
    // */
    // public int getCustomColumnCount()
    // {
    // return customColumns.size();
    // }

    public int getColumnCount() {
        return nbCol;
    }

    public int getColumnCountTotal() {
        return nbColTot;
    }

    /**
     * {@inheritDoc}
     */
    public Class getColumnClass(int column) {
        switch (column) {
        case 0:
            return String.class;
        case 1:
        case 2:
        case 3:
            return Icon.class;
        case 4:
        case 5:
            return GregorianCalendar.class;
        case 6:
        case 7:
            return Integer.class;
        case 8:
            return String.class;
        case 9:
            return String.class;
        case 10:
            return Integer.class;
        case 11:
            return String.class;
        case 12:
            return Integer.class;
        default: {
            String name = getColumnName(column);
            CustomColumn customColumn = myTaskManager.getCustomColumnStorage().getCustomColumn(name);
            return customColumn==null ? String.class : customColumn.getType();
//            TaskNode tn = (TaskNode) this.getRoot();
//            // tn = (TaskNode)tn.children().nextElement();
//            Object o = this.getValueAt(tn, column);
//            if (o == null) {
//                o = "erreur";
//                System.err.println("!!!!!!!!!!!!!!!!!");
//            }
//            return o.getClass();
        }
        }
    }

    public String getColumnName(int column) {
        if (column < titles.size())
            return (String) titles.get(column);

        try {
            return (String) customColumns.get(column - titles.size());
        } catch (IndexOutOfBoundsException e) {
            return (String) customColumns.get(column - titles.size() - 1);
        }

    }

    /**
     * @inheritDoc
     */
    public boolean isCellEditable(Object node, int column) {
        if (node instanceof TaskNode)
            return column != 8 && column != 9 && column != 10 && column != 2;
        return false;
    }

    // public Object getChild(Object parent, int index)
    // {
    //	 	
    // }
    //	 
    // public int getChildCount(Object parent)
    // {
    //     	
    // }

    /**
     * @inheritDoc
     */
    public Object getValueAt(Object node, int column) {
        Object res = null;
        if (!(node instanceof TaskNode))
            return null;
        TaskNode tn = (TaskNode) node;
        Task t = (Task) tn.getUserObject();
        // if(tn.getParent()!=null){
        switch (column) {
        case 1:
            if (((Task) tn.getUserObject()).isProjectTask()) {
                res = new ImageIcon(getClass().getResource(
                        "/icons/mproject.gif"));
            } else if (!tn.isLeaf())
                res = new ImageIcon(getClass().getResource("/icons/mtask.gif"));
            else if (t.isMilestone())
                res = new ImageIcon(getClass()
                        .getResource("/icons/meeting.gif"));
            else
                res = new ImageIcon(getClass().getResource("/icons/tasks2.png"));
            break;
        case 2:
            Task task = (Task) tn.getUserObject();
            String path = (task.getPriority() == 0 ? "/icons/task2.gif" : task
                    .getPriority() == 1 ? "/icons/task.gif"
                    : "/icons/task1.gif");
            res = new ImageIcon(getClass().getResource(path));
            break;
        case 3: // Info - show note icon if there is any note related to the task
            String notes = t.getNotes();
            if (notes != null && !notes.trim().equals("") && !notes.equals(GanttLanguage.getInstance().getText("option.taskProperties.notes.defaultValue"))) {
                res = new ImageIcon(getClass().getResource("/icons/note_16.gif"));
            }
            break;
        case 0:
            res = tn.getName();
            break;
        case 4:
            res = tn.getStart();
            break;
        case 5:
            res = tn.getEnd();
            break;
        case 6:
            res = new Integer(tn.getDuration());
            break;
        case 7:
            res = new Integer(tn.getCompletionPercentage());
            break;
        case 8: {
            ResourceAssignment[] tAssign = t.getAssignments();
            StringBuffer sb = new StringBuffer();
            int nb = 0;
            for (int i = 0; i < tAssign.length; i++) {
                ResourceAssignment resAss = tAssign[i];
                if (resAss.isCoordinator())
                    sb.append(nb++ == 0 ? "" : ", ").append(
                            resAss.getResource().getName());
            }
            // res = "Not implemented";
            res = sb.toString();
            break;
        }
        case 9: {
            String resStr = "";
            TaskDependency[] dep = t.getDependenciesAsDependant().toArray();
            int i = 0;
            if (dep != null && dep.length > 0) {
                for (i = 0; i < dep.length - 1; i++)
                    resStr += dep[i].getDependee().getTaskID() + ", ";
                resStr += dep[i].getDependee().getTaskID() + "";
            }
            res = resStr;
            break;
        }
        case 10:
            res = new Integer(t.getTaskID());
            break;
        case 11:
            ResourceAssignment[] ras = t.getAssignments();
            if (ras.length > 0) {
                StringBuffer list = new StringBuffer();
                synchronized (list) {
                    for (int i = 0; i < ras.length - 1; i++) {
                        list.append(ras[i].getResource().getName());
                        list.append(",");
                    }
                    list.append(ras[ras.length - 1].getResource().getName());
                }
                res = list;
            } else
                res = "-";
            break;
        case 12:
            //res = new Integer(myTaskManager.getTaskHierarchy().getDocumentOrderNumber(t));
            break;    
        default:
            String colName = this.getColumnName(column);
            // System.out.println(" -> "+colName);
            // System.out.println(t+" : "+t.getCustomValues());
            res = t.getCustomValues().getValue(colName);
            break;
        }
        // }
        // else
        // res ="";
        return res;
    }
    
    /**
     * Creates tooltips for gantt table with tasks
     */
    public String getToolTipAt(Object node, int column) {
        String res = null;
        if (!(node instanceof TaskNode))
            return null;
        TaskNode tn = (TaskNode) node;
        Task t = (Task) tn.getUserObject();
        switch (column) {
        case 1:
            break;
        case 2: // Show priority
            res = (t.getPriority() == 0 ? language.getText("hight") : t
                    .getPriority() == 1 ? language.getText("normal")
                    : language.getText("low"));
            break;
        case 3: // Show notes
            String notes = t.getNotes();
            if (notes != null && !notes.trim().equals("") && !notes.equals(GanttLanguage.getInstance().getText("option.taskProperties.notes.defaultValue"))) {
                int maxLength = 300;
                if (notes.length() > maxLength) {
                    notes = notes.substring(0, maxLength);
                    int i = maxLength - 1;
                    for (; i >= 0 && notes.charAt(i) != ' '; i--)
                        ;
                    notes = notes.substring(0, i);
                    notes += "\n <b>( . . . )</b>";
                }
                notes = notes.replaceAll("\n", "<br>");
                res = notes;
            }
            break;
        case 0: // Show name
            res = tn.getName();
            break;
        case 4: // Show start date
            res = tn.getStart().toString();
            break;
        case 5: // Show end date
            res = tn.getEnd().toString();
            break;
        case 6: // Show duration
            res = String.valueOf(tn.getDuration());
            break;
        case 7: // Show completion
            res = String.valueOf(tn.getCompletionPercentage());
            break;
        case 8: { // Show resources assigned
            ResourceAssignment[] tAssign = t.getAssignments();
            StringBuffer sb = new StringBuffer();
            int nb = 0;
            for (int i = 0; i < tAssign.length; i++) {
                ResourceAssignment resAss = tAssign[i];
                if (resAss.isCoordinator())
                    sb.append(nb++ == 0 ? "" : ", ").append(
                            resAss.getResource().getName());
            }
            res = sb.toString();
            break;
        }
        case 9: { // Show dependants
            String resStr = "";
            TaskDependency[] dep = t.getDependenciesAsDependant().toArray();
            int i = 0;
            if (dep != null && dep.length > 0) {
                for (i = 0; i < dep.length - 1; i++)
                    resStr += dep[i].getDependee().getTaskID() + ", ";
                resStr += dep[i].getDependee().getTaskID() + "";
            }
            res = resStr;
            break;
        }
        case 10: // Show task ID
            res = String.valueOf(t.getTaskID());
            break;
        case 11: // Show resource assigments
            ResourceAssignment[] ras = t.getAssignments();
            if (ras.length > 0) {
                StringBuffer list = new StringBuffer();
                synchronized (list) {
                    for (int i = 0; i < ras.length - 1; i++) {
                        list.append(ras[i].getResource().getName());
                        list.append(",");
                    }
                    list.append(ras[ras.length - 1].getResource().getName());
                }
                res = list.toString();
            } else
                res = "-";
            break;
        case 12:
            break;
        default: // Custom column
            String colName = this.getColumnName(column);
            res = t.getCustomValues().getValue(colName).toString();
            break;
        }
        return res;
    }

    /**
     * @inheritDoc
     */
    public void setValueAt(final Object value, final Object node,
            final int column) {
        if (value==null) {
            return;
        }
        if (isCellEditable(node, column)) {
            // System.out.println("undoable colonne: " + column);
            Mediator.getGanttProjectSingleton().getUndoManager().undoableEdit(
                    "Change properties column", new Runnable() {
                        public void run() {
                            setValue(value, node, column);
                        }
                    });
        } else {
            // System.out.println("NOT undoable colonne: " + column);
            setValue(value, node, column);
        }
        // System.out.println("node : " + node);
        // System.out.println("valeur : " + value);
        Mediator.getGanttProjectSingleton().repaint();
        
        Mediator.getGanttProjectSingleton().setAskForSave(true);
    }

    /**
     * Set value in left pane cell
     * 
     * @param value
     * @param node
     * @param column
     */
    private void setValue(final Object value, final Object node,
            final int column) {
        switch (column) {
        case 1:
        case 2:
        case 3:
            break;
        case 0:
            ((TaskNode) node).setName(value.toString());
            break;
        case 4:
            ((TaskNode) node).setStart((GanttCalendar) value);
            ((TaskNode) node).applyThirdDateConstraint();
            break;
        case 5:
            ((TaskNode) node).setEnd((GanttCalendar) value);
            break;
        case 6:
            Task task = (Task) ((TaskNode) node).getUserObject();
            TaskLength tl = task.getDuration();
            ((TaskNode) node).setDuration(task.getManager().createLength(tl.getTimeUnit(),
                    ((Integer) value).intValue()));
            // set the block position of the chart scrollbar
            break;
        case 7:
            ((TaskNode) node).setCompletionPercentage(((Integer) value)
                    .intValue());
            break;
        case 8:
            break;
        default: // custom colums
            try {
                ((Task) ((TaskNode) node).getUserObject()).getCustomValues()
                        .setValue(this.getColumnName(column), value);
            } catch (CustomColumnsException e) {
                if (!GPLogger.log(e)) {
                    e.printStackTrace(System.err);
                }
            }
        }

    }

    /**
     * @inheritDoc
     */
    public void columnAdded(TableColumnModelEvent arg0) {
        nbCol++;
    }

    /**
     * @inheritDoc
     */
    public void columnRemoved(TableColumnModelEvent arg0) {
        nbCol--;
    }

    /**
     * @inheritDoc
     */
    public void columnMoved(TableColumnModelEvent arg0) {
        // TODO Auto-generated method stub

    }

    /**
     * @inheritDoc
     */
    public void columnMarginChanged(ChangeEvent arg0) {
        // TODO Auto-generated method stub

    }

    /**
     * @inheritDoc
     */
    public void columnSelectionChanged(ListSelectionEvent arg0) {
        // TODO Auto-generated method stub
    }

    /**
     * @inheritDoc
     */
    public Task[] getNestedTasks(Task container) {
        TaskNode r = (TaskNode) root;
        Enumeration e = r.children();

        Vector v = new Vector();
        while (e.hasMoreElements())
            v.add((TaskNode) e.nextElement());
        Task[] res = new Task[v.size()];
        v.toArray(res);
        return res;

    }

    /**
     * Purpose: Should return true if this Tasks has any nested subtasks.
     */
    public boolean hasNestedTasks(Task container) {
        TaskNode r = (TaskNode) root;
        if (r.getChildCount() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @inheritDoc
     */
    public Task getRootTask() {
        return (Task) ((TaskNode) this.getRoot()).getUserObject();
    }

    /**
     * Returns the corresponding task node according to the given task.
     * 
     * @param task
     *            The task whom tasknode we want to get.
     * @return The corresponding task node according to the given task.
     */
    public TaskNode getTaskNodeForTask(Task task) {
        Enumeration enumeration = ((TaskNode) getRoot()).preorderEnumeration();
        while (enumeration.hasMoreElements()) {
            Object next = enumeration.nextElement();
            if (!(next instanceof TaskNode))
                continue;
            TaskNode tn = (TaskNode) next;
            Task t = (Task) tn.getUserObject();
            if (t.equals(task))
                return tn;
        }
        return null;
    }

    /**
     * @inheritDoc
     */
    public Task getContainer(Task nestedTask) {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * @inheritDoc
     */
    public void move(Task whatMove, Task whereMove) {
        // TODO Auto-generated method stub

    }
    public void move(Task whatMove, Task newContainer, int childIndex) {
        // TODO Auto-generated method stub
        
    }


    /**
     * @inheritDoc
     */
    public boolean areUnrelated(Task dependant, Task dependee) {
        // TODO Auto-generated method stub
        return false;
    }

    public int getDepth(Task task) {
        // TODO Auto-generated method stub
        return 0;
    }

    public void languageChanged(Event event) {
        changeLanguage(event.getLanguage());
    }

    public int compareDocumentOrder(Task next, Task dependeeTask) {
        throw new UnsupportedOperationException();
    }

    public boolean contains(Task task) {
        throw new UnsupportedOperationException();
    }

    public List<Task> getTasksInDocumentOrder() {
        throw new UnsupportedOperationException();
    }

}