package net.sourceforge.ganttproject.datafilter.ui;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.action.task.TaskPropertiesAction;
import net.sourceforge.ganttproject.datafilter.AutoFix;
import net.sourceforge.ganttproject.datafilter.AutoFixAction;
import net.sourceforge.ganttproject.datafilter.ExportFilterAction;
import net.sourceforge.ganttproject.datafilter.FilterOptionsDialogAction;
import net.sourceforge.ganttproject.datafilter.FilteringEvent;
import net.sourceforge.ganttproject.datafilter.FilteringEventListener;
import net.sourceforge.ganttproject.datafilter.TaskGroup;
import net.sourceforge.ganttproject.font.Fonts;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.options.model.GPOptionGroup;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskNode;
import net.sourceforge.ganttproject.task.TaskSelectionManager;

/**
 * Class extending JTree and implementing methods useful for task filter tree.
 * 
 * Created 12.2007
 * 
 * @author Joanna Muras 
 */
public class FilterTree extends JTree implements TreeSelectionListener, FilteringEventListener {

    /** Root of task filter model */
    private DefaultMutableTreeNode myRoot;
    /** Task filter model */
    private DefaultTreeModel myTreeModel;
    private final TaskSelectionManager mySelectionManager;
    private final TaskSelectionManager myFilterSelectionManager;
    private final UIFacade myFacade;

    private TaskPropertiesAction myTaskPropertiesAction;
    private List<AutoFix> myAutoFixList = new ArrayList<AutoFix>();

    public FilterTree(IGanttProject project, UIFacade facade, TaskSelectionManager selectionManager, TaskSelectionManager filterSelectionManager) {
        mySelectionManager = selectionManager;
        myFilterSelectionManager = filterSelectionManager;
        myFacade = facade;
        /* Create root node */
        myRoot = new DefaultMutableTreeNode();
        /* Create filter tree table model using root node */
        myTreeModel =  new DefaultTreeModel(myRoot);
        /* Set filter tree table model */
        this.setModel(myTreeModel);

        /* Set new cell renderer for filter tree */
        setCellRenderer(new FilterTreeCellRenderer());
        /* Set tool tips */
        ToolTipManager.sharedInstance().registerComponent(this);

        /* Set actions */
        myTaskPropertiesAction = new TaskPropertiesAction(project, myFilterSelectionManager, facade);
        /* Set action with popup menu with auto fix options */
        Action popupAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                Point mousePosition = getMousePosition();
                if (mousePosition == null) {
                    return;
                }
                /* Set mouse position values as integers */
                int x = (int)mousePosition.getX();
                int y = (int)mousePosition.getY();
                /* Get tree selection path */
                TreePath selectionPath = getPathForLocation(x, y);
                /* Refresh selection manager */
                setFilterSelectionManager(selectionPath);
                /* Create popup menu with auto fix options is some task selected */
                if (mySelectionManager.getSelectedTasks().size() == 1) {
                    createAutoFixPopupMenu(x, y);
                    /* Disable tooltips when popup menu activated */
                    ToolTipManager.sharedInstance().setEnabled(false);
                    ToolTipManager.sharedInstance().setEnabled(true);
                }
            }
        };
        popupAction.putValue(Action.NAME, "popupAction");

        /* Map popup action name to popup action */
        getActionMap().put(popupAction.getValue(Action.NAME), popupAction);
        /* Map key pressed (Ctrl+1) to popup action name */
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.CTRL_DOWN_MASK) , popupAction.getValue(Action.NAME));

        /* Add selection listener to filter tree */
        this.addTreeSelectionListener(this);   

        /* Actions on click on the task node in filter tree */
        MouseListener ml = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                /* Set tree selection */
                TreePath selectionPath = getPathForLocation(e.getX(), e.getY());
                /* Refresh selection manager */
                setFilterSelectionManager(selectionPath);
                /* On mouse double click when one task is selected */
                if (myFilterSelectionManager.getSelectedTasks().size() == 1 && e.getClickCount() == 2) {
                    /* Show task properties dialog */
                    myTaskPropertiesAction.actionPerformed(null);
                }
                /* On right mouse button click */
                if (e.getButton() == MouseEvent.BUTTON3) {
                    /* Show popup memu with options */
                    createPopupMenu(e.getX(), e.getY());
                }
            }
        };
        addMouseListener(ml);
    }

    /**
     * Add task under selection path to selection manager
     * @param selectionPath
     */
    private void setFilterSelectionManager(TreePath selectionPath) {
        /* Get task selected by mouse click */
        Task task = getSelectedTask(selectionPath);
        myFilterSelectionManager.clear();
        if (task != null) {
            myFilterSelectionManager.addTask(task);
            /* Set task selected */
            getSelectionModel().setSelectionPath(selectionPath);
        }
    }

    /**
     * Return task under tree path
     * @param selectionPath
     * @return
     */
    private Task getSelectedTask(TreePath selectionPath) {
        Task selectedTask = null;
        if (selectionPath != null && selectionPath.getPathCount() == 3) {
            /* Get component on second level (counting from zero level) */
            DefaultMutableTreeNode selectedTaskNode = (DefaultMutableTreeNode)selectionPath.getPathComponent(2);
            selectedTask = (Task)selectedTaskNode.getUserObject();
        }
        return selectedTask;
    }

    /** 
     * Create a popup menu with auto fix options
     */
    private void createAutoFixPopupMenu(int x, int y) {
        /* Get auto fix options */
        AutoFixAction[] autoFixActions = createAutoFixActions();
        if (autoFixActions != null && autoFixActions.length > 0) {
            myFacade.showPopupMenu(this, autoFixActions, x, y);
        }
    }

    /** 
     * Create a popup menu when right button of mouse clicked 
     */
    private void createPopupMenu(int x, int y) {
        List<Action> actions = new ArrayList<Action>();
        AutoFixAction[] autoFixActions = createAutoFixActions();
        /* Add auto fix options */ 
        if (autoFixActions != null && autoFixActions.length>0) {
            for (AutoFixAction autoFixAction : autoFixActions) {
                actions.add(autoFixAction);
            }
            actions.add(null);
        }
        /* Add properties menu item if any task selected */
        if (!myFilterSelectionManager.getSelectedTasks().isEmpty()) {
            actions.add(myTaskPropertiesAction);
        }

        Action[] popupMenuActions = actions.toArray(new Action[0]);
        if (popupMenuActions.length > 0) {
            myFacade.showPopupMenu(this, popupMenuActions, x, y);
        }
    }

    /**
     * Set list of auto fix options
     * @param autoFix
     */
    public void setAutoFixList(List<AutoFix> autoFix) {
        myAutoFixList = autoFix;
    }

    /**
     * Set action on choice of auto fix from menu
     */
    private AutoFixAction[] createAutoFixActions() {
        if (myAutoFixList == null || myAutoFixList.size() == 0 || myFilterSelectionManager.getSelectedTasks().size() != 1) {
            return null;
        }
        List<AutoFixAction> autoFixActions = new ArrayList<AutoFixAction>();
        /* Get selected task */
        Task task = (Task) myFilterSelectionManager.getSelectedTasks().get(0);

        /* Check if there are any auto fix options for selected task */
        for (AutoFix autoFix : myAutoFixList) {
            if (task == autoFix.getTask()) {
                /* Add autofix action for selected task */
                autoFixActions.add(new AutoFixAction(autoFix));
            }
        }
        return autoFixActions.toArray(new AutoFixAction[0]);
    }

    /**
     * Check if task has any auto fix options     
     * @param task
     * @return true if task has auto fix options
     */
    private boolean hasAutoFixOptions(Task task) {
        if (myAutoFixList == null || myAutoFixList.size() == 0) {
            return false;
        }
        /* Check if there are any auto fix options for selected task */
        for (AutoFix autoFix : myAutoFixList) {
            if (task == autoFix.getTask()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Set model for filter tree table
     * @param groups of tasks filtered
     */
    public void setModel (TaskGroup[] filteredTasks) {
        /* Remove all childen of root node */
        myRoot.removeAllChildren();

        /* For all groups */
        for (int i=0; i<filteredTasks.length; i++) {
            /* Get task group */
            TaskGroup taskGroup = filteredTasks[i];
            /* Create node with group name */
            DefaultMutableTreeNode child = new DefaultMutableTreeNode(GanttLanguage.getInstance().getText("datafilter."+taskGroup.getName()));
            /* Add node to root */
            myRoot.insert(child, i);
            /* Get tasks for group */
            Task[] tasks = taskGroup.getTasks();

            /* For all tasks in group */
            for (int j=0; j<tasks.length; j++) {
                /* Get task */
                Task task = tasks[j];
                /* Create node with task */
                MutableTreeNode grandChild = new DefaultMutableTreeNode(task);
                /* Add task node to group note */
                child.insert(grandChild, j);
            }
        }
        /* Refresh task model */
        myTreeModel.reload();
        /* Expand all nodes in filer task tree */
        this.expandAllNodes();
        /* Do not show root node */
        this.setRootVisible(false);
    }

    /**
     * Expand all nodes in tree
     */
    public void expandAllNodes() {
        /* For each filter group */
        for (int i=0; i<myRoot.getChildCount(); i++) {
            /* Get node with filter group name */
            DefaultMutableTreeNode filterNode = (DefaultMutableTreeNode)myRoot.getChildAt(i);
            /* Get first task node from group */
            DefaultMutableTreeNode taskNode = (filterNode).getFirstLeaf();
            /* Expand taks node */
            this.setExpandedState(new TreePath(taskNode.getPath()), true);
        }
    }

    /**
     * Action on filter tree selection. Select (highlight) task chosen in filter tree in task tree 
     */
    public void valueChanged(TreeSelectionEvent e) {
        /* Get task selected in filter task tree */
        Task selectedTask = getSelectedTask(e.getPath());
        if (selectedTask != null) {
            /* Set task selected in main task tree */
            mySelectionManager.clear();
            mySelectionManager.addTask(selectedTask);
        }
    }

    /**
     * Action on any change of task hierarchy or their properties.
     */
    public void filterChanged(final FilteringEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                /* Filter all tasks according to user settings */
                TaskGroup[] taskGroups = e.getTaskGroups();
                for (int i=0; i<taskGroups.length; i++) {
                    taskGroups[i].filterTasks(); 
                }
                /* Create list of auto fix options for all tasks in filter tree */
                e.getTasks().updateAutoFixList();
                /* Set model for filter task tree - use task groups */
                setModel(taskGroups);
            }
        });
    }

    
    @Override
    public String convertValueToText(
            Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
        Object item = ((DefaultMutableTreeNode)value).getUserObject();
        if (item==null) {
            return "";
        }
        return item.toString();
    }


    /**
     * Render the cell of the filter tree
     */
    public class FilterTreeCellRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean selected, boolean expanded, boolean leaf, int row,
                boolean hasFocus) {
            Icon leafIcon = getLeafIcon();
            /* Set text for groups and tasks */
            Object item = ((DefaultMutableTreeNode)value).getUserObject();
            if (item == null ) {
                return this;
            }
            setToolTipText(getToolTipText(item));
            /* Settings for tasks */
            if (item instanceof Task) {
                /* Regular font */
                setFont(Fonts.GANTT_TREE_FONT);
                Task task = (Task)item;
                if (hasAutoFixOptions(task)) {
                    /* Set light bulb icon if task has auto fix options */
                    setIcon(new ImageIcon(getClass().getResource("/icons/light_16.gif")));
                    setLeafIcon(new ImageIcon(getClass().getResource("/icons/light_16.gif")));
                } else {
                    setIcon(null);
                }
                /* Settings for groups */
            } else {
                /* Bold font */
                setFont(Fonts.GANTT_TREE_FONT2);
                setIcon(null);
            }
            super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
            setLeafIcon(leafIcon);
            return this;
        }

        /**
         * Tooltips for filter tree
         * @param item
         * @return
         */
        private String getToolTipText(Object item) {
            if (item instanceof Task && hasAutoFixOptions((Task)item)) {
                return "<html><body>" + GanttLanguage.getInstance().getText("datafilter.quickFixOptions") + "</body></html>";
            }
            return null;
        }
    }
}


