/*
 *  RapidMiner
 *
 *  Copyright (C) 2001-2011 by Rapid-I and the contributors
 *
 *  Complete list of developers available at our web site:
 *
 *       http://rapid-i.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package com.rapidminer.gui.processeditor;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.actions.ToggleAction;
import com.rapidminer.gui.dnd.OperatorTransferHandler;
import com.rapidminer.gui.operatortree.actions.InfoOperatorAction;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.ExtendedJToolBar;
import com.rapidminer.gui.tools.FilterListener;
import com.rapidminer.gui.tools.FilterTextField;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.ResourceActionAdapter;
import com.rapidminer.gui.tools.SelectionNavigationListener;
import com.rapidminer.gui.tools.components.DropDownButton;
import com.rapidminer.gui.tools.components.ToolTipWindow;
import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorCreationException;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.tools.GroupTree;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.ParameterService;
import com.rapidminer.tools.parameter.ParameterChangeListener;
import com.rapidminer.tools.usagestats.OperatorStatisticsValue;
import com.rapidminer.tools.usagestats.OperatorUsageStatistics;
import com.rapidminer.tools.usagestats.UsageStatistics;
import com.rapidminer.tools.usagestats.UsageStatistics.StatisticsScope;

/**
 * This tree displays all groups and can be used to change the selected operators.
 * 
 * @author Ingo Mierswa, Tobias Malbrecht, Sebastian Land
 */
public class NewOperatorGroupTree extends JPanel implements FilterListener, SelectionNavigationListener {
	
	/**
	 * A checkbox menu item which always resembles the current state of a boolean parameter from ParameterService.
	 * 
	 * @author Marius Helf
	 *
	 */
	private static class CheckBoxMenuItemParameterServiceListener extends JCheckBoxMenuItem implements ParameterChangeListener {
		private static final long serialVersionUID = 1L;

		private String parameterKey;

		/**
		 * Instantiates a new CheckBoxMenuItemParameterServiceListener.
		 * @param parameterKey The key of the parameter whose state this checkbox should resemble.
		 */
		public CheckBoxMenuItemParameterServiceListener(String parameterKey, ResourceAction resourceAction) {
			super(resourceAction);
			this.parameterKey = parameterKey;

			setSelected("true".equals(ParameterService.getParameterValue(parameterKey)));

			
			ParameterService.registerParameterChangeListener(this);
		}

		@Override
		public void informParameterChanged(String key, String value) {
			if (key != null && key.equals(parameterKey)) {
				setSelected("true".equals(value));
			}
		}

		@Override
		public void informParameterSaved() {
			// Do nothing
		}
		
	}
	

    private static final long serialVersionUID = 133086849304885475L;

    private final FilterTextField filterField = new FilterTextField(12);

    private transient final NewOperatorGroupTreeModel model = new NewOperatorGroupTreeModel();

    private final JTree operatorGroupTree = new JTree(model);

    private final NewOperatorEditor editor;

    private transient final Action CLEAR_FILTER_ACTION = new ResourceAction(true, "clear_filter") {
        private static final long serialVersionUID = 3236281211064051583L;

        @Override
        public void actionPerformed(ActionEvent e) {
            filterField.clearFilter();
        }
    };

    private transient final ToggleAction FILTER_DEPRECATED_ACTION = new ToggleAction(true, "filter_deprecated") {
        private static final long serialVersionUID = -35181409559416043L;

        {
            setSelected(true);
            actionToggled(null);
        }

        @Override
        public void actionToggled(ActionEvent e) {
            Enumeration<TreePath> expandedPaths = operatorGroupTree.getExpandedDescendants(new TreePath(operatorGroupTree.getModel().getRoot()));
            TreePath selectedPath = operatorGroupTree.getSelectionPath();
            model.setFilterDeprecated(isSelected());
            while (expandedPaths.hasMoreElements()) {
                operatorGroupTree.expandPath(expandedPaths.nextElement());
            }
            operatorGroupTree.setSelectionPath(selectedPath);
        }
    };

    private transient final ToggleAction SORT_BY_USAGE_ACTION = new ToggleAction(true, "sort_by_usage") {
        private static final long serialVersionUID = 1L;
        {
            setSelected(true);
            actionToggled(null);
        }

        @Override
        public void actionToggled(ActionEvent e) {
            model.setSortByUsage(isSelected());
        }
    };

    public transient final Action INFO_OPERATOR_ACTION = new InfoOperatorAction() {
        private static final long serialVersionUID = 7157100643209732656L;

        @Override
        protected Operator getOperator() {
            return getSelectedOperator();
        }
    };

    private final JCheckBoxMenuItem autoWireInputsItem = new CheckBoxMenuItemParameterServiceListener(RapidMinerGUI.PROPERTY_AUTOWIRE_INPUT, new ResourceAction("auto_wire_inputs_on_add") {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
            ParameterService.setParameterValue(RapidMinerGUI.PROPERTY_AUTOWIRE_INPUT, Boolean.toString(autoWireInputsItem.isSelected()));
            ParameterService.saveParameters();
        }
    });

    private final JCheckBoxMenuItem autoWireOutputsItem = new CheckBoxMenuItemParameterServiceListener(RapidMinerGUI.PROPERTY_AUTOWIRE_OUTPUT, new ResourceAction("auto_wire_outputs_on_add") {
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
            ParameterService.setParameterValue(RapidMinerGUI.PROPERTY_AUTOWIRE_OUTPUT, Boolean.toString(autoWireOutputsItem.isSelected()));
            ParameterService.saveParameters();
        }
    });

    private NewOperatorGroupTreeRenderer renderer;

    public NewOperatorGroupTree(NewOperatorEditor editor) {
        this.editor = editor;
        setLayout(new BorderLayout());

        // operatorGroupTree.setRootVisible(true);
        operatorGroupTree.setShowsRootHandles(true);
        renderer = new NewOperatorGroupTreeRenderer();
        operatorGroupTree.setCellRenderer(renderer);
        operatorGroupTree.expandRow(0);

        JScrollPane scrollPane = new ExtendedJScrollPane(operatorGroupTree);
        scrollPane.setBorder(null);
        add(scrollPane, BorderLayout.CENTER);

        filterField.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.field.filter_deprecated.tip"));
        filterField.addFilterListener(this);
        filterField.addSelectionNavigationListener(this);

        JToolBar toolBar = new ExtendedJToolBar();
        toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY));
        toolBar.setFloatable(false);
        DropDownButton autoWireMenuButton = DropDownButton.makeDropDownButton(new ResourceActionAdapter(true, "auto_wire_on_add"));
        autoWireMenuButton.add(autoWireInputsItem);
        autoWireMenuButton.add(autoWireOutputsItem);
        autoWireMenuButton.addToToolBar(toolBar);
        // final JToggleButton filterDeprecatedButton = new JToggleButton(new ResourceActionAdapter(true,
        // "filter_deprecated"));
        // filterDeprecatedButton.addActionListener(new ActionListener() {
        // @Override
        // public void actionPerformed(ActionEvent e) {
        //
        // }
        // });
        // filterDeprecatedButton.setText(null);
        // filterDeprecatedButton.setSelected(true);
        toolBar.addSeparator();
        toolBar.add(filterField);
        toolBar.add(CLEAR_FILTER_ACTION);
        JToggleButton filterDeprecatedButton = FILTER_DEPRECATED_ACTION.createToggleButton();
        filterDeprecatedButton.setText("");
        toolBar.add(filterDeprecatedButton);

        JToggleButton sortButton = SORT_BY_USAGE_ACTION.createToggleButton();
        sortButton.setText("");
        toolBar.add(sortButton);

        add(toolBar, BorderLayout.NORTH);

        operatorGroupTree.setRootVisible(false);
        operatorGroupTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        operatorGroupTree.setDragEnabled(true);
        operatorGroupTree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                Operator op = getSelectedOperator();
                if (op != null) {
                    RapidMinerGUI.getMainFrame().getOperatorDocViewer().setDisplayedOperator(op);
                }
            }
        });
        operatorGroupTree.addTreeExpansionListener(new TreeExpansionListener() {

            @Override
            public void treeExpanded(TreeExpansionEvent event) {
                updateMaxUsageCount();
            }

            @Override
            public void treeCollapsed(TreeExpansionEvent event) {
                updateMaxUsageCount();
            }
        });

        operatorGroupTree.setTransferHandler(new OperatorTransferHandler() {
            private static final long serialVersionUID = 1L;

            @Override
            protected List<Operator> getDraggedOperators() {
                Operator selectedOperator = NewOperatorGroupTree.this.getSelectedOperator();
                if (selectedOperator == null) {
                    return Collections.emptyList();
                } else {
                    return Collections.singletonList(selectedOperator);
                }
            }
        });
        operatorGroupTree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    insertSelected();
                } else {
                    TreePath selPath = operatorGroupTree.getPathForLocation(e.getX(), e.getY());
                    if (selPath != null) {
                        operatorGroupTree.setSelectionPath(selPath);
                    }
                    evaluatePopup(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                TreePath selPath = operatorGroupTree.getPathForLocation(e.getX(), e.getY());
                if (selPath != null) {
                    operatorGroupTree.setSelectionPath(selPath);
                }
                evaluatePopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                TreePath selPath = operatorGroupTree.getPathForLocation(e.getX(), e.getY());
                if (selPath != null) {
                    operatorGroupTree.setSelectionPath(selPath);
                }
                evaluatePopup(e);
            }
        });
        operatorGroupTree.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
            }

            @Override
            public void keyReleased(KeyEvent e) {
                // insert selected operator upon press of enter or space
                switch (e.getKeyCode()) {
                case KeyEvent.VK_ENTER:
                case KeyEvent.VK_SPACE:
                	if (getSelectedOperator() != null) {
	                    insertSelected();
                	} else {
                		// folder or nothing selected
                		
                		TreePath path = operatorGroupTree.getSelectionPath();
                		if (path != null) {
                			// folder selected
                		
                			if (operatorGroupTree.isExpanded(path)) {
                				operatorGroupTree.collapsePath(path);
                			} else {
                				operatorGroupTree.expandPath(path);
                			}
                		}
                	}
                	e.consume();
                	return;
                }
            }

            @Override
            public void keyTyped(KeyEvent e) {
            }

        });

        new ToolTipWindow(new TipProvider() {
            @Override
            public String getTip(Object o) {
                OperatorDescription opDesc;
                if (o instanceof OperatorDescription) {
                    opDesc = (OperatorDescription) o;
                } else if (o instanceof GroupTree) {
                    GroupTree groupTree = (GroupTree) o;
                    return "<h3>" + groupTree.getName() + "</h3><p>" + groupTree.getDescription() + "</p>";
                } else {
                    return null;
                }
                StringBuilder b = new StringBuilder();
                b.append("<h3>").append(opDesc.getName()).append("</h3><p>");
                b.append(opDesc.getLongDescriptionHTML()).append("</p>");
                return b.toString();
            }

            @Override
            public Object getIdUnder(Point point) {
                TreePath path = operatorGroupTree.getPathForLocation((int) point.getX(), (int) point.getY());
                if (path != null) {
                    return path.getLastPathComponent();
                } else {
                    return null;
                }
            }

            @Override
            public Component getCustomComponent(Object id) {
                return null;
            }
        }, operatorGroupTree);
    }

    @Override
    public void valueChanged(String value) {
        TreePath[] selectionPaths = operatorGroupTree.getSelectionPaths();

        model.applyFilter(value);

        if (value != null && value.length() > 0)
            for (int i = 0; i < operatorGroupTree.getRowCount(); i++) {
                operatorGroupTree.expandRow(i);
            }

        if (operatorGroupTree.getRowCount() > getHeight() / (operatorGroupTree.getRowHeight() + 2)) {
            for (int i = operatorGroupTree.getRowCount(); i >= 0; i--) {
                operatorGroupTree.collapseRow(i);
            }
        }

        GroupTree root = (GroupTree) this.operatorGroupTree.getModel().getRoot();
        TreePath path = new TreePath(root);
        showNodes(root, path);
        if (selectionPaths != null) {
            for (TreePath selectionPath : selectionPaths) {
                Object lastPathComponent = selectionPath.getLastPathComponent();
                if (model.contains(lastPathComponent)) {
                    operatorGroupTree.addSelectionPath(selectionPath);
                }
            }
        }
    }

    private void showNodes(GroupTree tree, TreePath path) {
        if (tree.getSubGroups().size() == 0) {
            int row = this.operatorGroupTree.getRowForPath(path);
            this.operatorGroupTree.expandRow(row);
            this.editor.setOperatorList(tree);
        } else if (tree.getSubGroups().size() == 1 && tree.getOperatorDescriptions().size() == 0) {
            int row = this.operatorGroupTree.getRowForPath(path);
            this.operatorGroupTree.expandRow(row);
            GroupTree child = tree.getSubGroup(0);
            path = path.pathByAddingChild(child);
            showNodes(child, path);
        } else {
            int row = this.operatorGroupTree.getRowForPath(path);
            this.operatorGroupTree.expandRow(row);
            this.editor.setOperatorList(null);
        }
    }

    public boolean shouldAutoConnectNewOperatorsInputs() {
    	return "true".equals(ParameterService.getParameterValue(RapidMinerGUI.PROPERTY_AUTOWIRE_INPUT));
    }

    public boolean shouldAutoConnectNewOperatorsOutputs() {
    	return "true".equals(ParameterService.getParameterValue(RapidMinerGUI.PROPERTY_AUTOWIRE_OUTPUT));
    }

    public JTree getTree() {
        return this.operatorGroupTree;
    }

    /** Creates a new popup menu for the selected operator. */
    private JPopupMenu createOperatorPopupMenu() {
        JPopupMenu menu = new JPopupMenu();
        menu.add(this.INFO_OPERATOR_ACTION);
        menu.addSeparator();
        menu.add(new ResourceAction(true, "add_operator_now") {
            private static final long serialVersionUID = 4363124048356045034L;

            @Override
            public void actionPerformed(ActionEvent e) {
                insertSelected();
            }
        });
        return menu;
    }

    /** Checks if the given mouse event is a popup trigger and creates a new popup menu if necessary. */
    private void evaluatePopup(MouseEvent e) {
        if (e.isPopupTrigger()) {
            if (getSelectedOperator() != null) {
                createOperatorPopupMenu().show(operatorGroupTree, e.getX(), e.getY());
            }
        }
    }

    private Operator getSelectedOperator() {
        if (operatorGroupTree.getSelectionPath() == null) {
            return null;
        }
        Object selectedOperator = operatorGroupTree.getSelectionPath().getLastPathComponent();
        if (selectedOperator != null && selectedOperator instanceof OperatorDescription) {
            try {
                return ((OperatorDescription) selectedOperator).createOperatorInstance();
            } catch (OperatorCreationException e) {
                e.printStackTrace();
                return null;
            }
        } else {
            return null;
        }
    }

    private void insertSelected() {
        Operator operator = getSelectedOperator();
        if (operator == null) {
            return;
        }
        MainFrame mainFrame = RapidMinerGUI.getMainFrame();
        mainFrame.getActions().insert(Collections.singletonList(operator));
    }

    @Override
    public void down() {
        int[] selectionRows = operatorGroupTree.getSelectionRows();
        if (selectionRows != null) {
            if (selectionRows.length > 0 && selectionRows[0] < operatorGroupTree.getRowCount() - 1) {
                operatorGroupTree.setSelectionRow(selectionRows[0] + 1);
            }
        } else {
            if (operatorGroupTree.getRowCount() > 0) {
                operatorGroupTree.setSelectionRow(0);
            }
        }
    }

    @Override
    public void left() {
    }

    @Override
    public void right() {
    }

    @Override
    public void up() {
        int[] selectionRows = operatorGroupTree.getSelectionRows();
        if (selectionRows != null) {
            if (selectionRows.length > 0 && selectionRows[0] > 0) {
                operatorGroupTree.setSelectionRow(selectionRows[0] - 1);
            }
        } else {
            if (operatorGroupTree.getRowCount() > 0) {
                operatorGroupTree.setSelectionRow(operatorGroupTree.getRowCount());
            }
        }
    }

    @Override
    public void selected() {
        insertSelected();
    }

    private void updateMaxUsageCount() {
        if (SORT_BY_USAGE_ACTION.isSelected()) {
            renderer.setMaxVisibleUsageCount(getMaxVisibleUsage());
        } else {
            renderer.setMaxVisibleUsageCount(0);
        }
    }

    private int getMaxVisibleUsage() {
        int max = 0;
        for (int i = 0; i < operatorGroupTree.getRowCount(); i++) {
            TreePath path = operatorGroupTree.getPathForRow(i);
            Object leaf = path.getLastPathComponent();
            if (leaf instanceof OperatorDescription) {
                OperatorUsageStatistics operatorStatistics1 = UsageStatistics.getInstance().getOperatorStatistics(StatisticsScope.ALL_TIME, (OperatorDescription) leaf);
                int usageCount1 = operatorStatistics1 == null ? 0 : operatorStatistics1.getStatistics(OperatorStatisticsValue.EXECUTION);
                max = Math.max(max, usageCount1);
            }
        }
        return max;
    }
}
