/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.as.cli.gui;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.jboss.as.cli.gui.ManagementModelNode.AttributeDescription;
import org.jboss.as.cli.gui.ManagementModelNode.UserObject;

/**
 * This class contains a JTree view of the management model that allows you to build commands by
 * clicking nodes and operations.
 *
 * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
 */
public class ManagementModel extends JPanel {
    private static final String GENERAL_HELP_TEXT = "Right-click a node to choose an operation.  Close/Open a folder to refresh.  Hover for help.";
    private static final String FILTER_TOOLTIP_HELP = "Display the root nodes containing the given text.";

    private CliGuiContext cliGuiCtx;

    private JTree tree;

    // used for the true root '/'
    public ManagementModel(CliGuiContext cliGuiCtx) {
        this(new ManagementModelNode(cliGuiCtx), cliGuiCtx);
    }

    public ManagementModel(ManagementModelNode root, CliGuiContext cliGuiCtx) {
        this.cliGuiCtx = cliGuiCtx;
        setLayout(new BorderLayout(10,10));
        this.tree = makeTree(root);
        add(new JLabel(GENERAL_HELP_TEXT), BorderLayout.NORTH);
        add(new JScrollPane(tree), BorderLayout.CENTER);
        add(makeFilterPanel(tree), BorderLayout.SOUTH);
    }

    /**
     * Find a node in the tree.  The node must be "visible" to be found.
     * @param address The full address of the node matching ManagementModelNode.addressPath()
     * @return The node, or null if not found.
     */
    public ManagementModelNode findNode(String address) {
        ManagementModelNode root = (ManagementModelNode)tree.getModel().getRoot();
        Enumeration<javax.swing.tree.TreeNode> allNodes = root.depthFirstEnumeration();
        while (allNodes.hasMoreElements()) {
            ManagementModelNode node = (ManagementModelNode)allNodes.nextElement();
            if (node.addressPath().equals(address)) return node;
        }

        return null;
    }

    private JPanel makeFilterPanel(JTree tree) {
        Box filterBox = Box.createHorizontalBox();
        JLabel filterLabel = new JLabel("Filter:");
        filterLabel.setToolTipText(FILTER_TOOLTIP_HELP);
        filterBox.add(filterLabel);
        filterBox.add(Box.createHorizontalStrut(5));
        JTextField filterInput = new JTextField(30);

        filterBox.add(filterInput);
        JButton clearButton = new JButton("Clear");
        clearButton.setToolTipText("Clear the filter");
        clearButton.addActionListener(new ClearFilterListener(filterInput));
        clearButton.setEnabled(false);

        filterInput.getDocument().addDocumentListener(new FilterDocumentListener(tree, clearButton));

        filterBox.add(clearButton);

        // Make filterBox half of width of panel
        JPanel filterPanel = new JPanel(new GridLayout(1,2));
        filterPanel.add(filterBox);
        filterPanel.add(Box.createGlue());
        return filterPanel;
    }

    private JTree makeTree(ManagementModelNode root) {
        root.explore();
        JTree tree = new CommandBuilderTree(cliGuiCtx, new DefaultTreeModel(root));
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.addTreeExpansionListener(new ManagementTreeExpansionListener((DefaultTreeModel) tree.getModel()));
        tree.addTreeSelectionListener(new ManagementTreeSelectionListener());
        tree.addMouseListener(new ManagementTreeMouseListener(tree));
        return tree;
    }

    /**
     * Get the node that has been selected by the user, or null if
     * nothing is selected.
     * @return The node or <code>null</code>
     */
    public ManagementModelNode getSelectedNode() {
        if (tree.getSelectionPath() == null) return null;
        return (ManagementModelNode)tree.getSelectionPath().getLastPathComponent();
    }

    private class ClearFilterListener implements ActionListener {
        private JTextField filterInput;

        ClearFilterListener(JTextField filterInput) {
            this.filterInput = filterInput;
        }

        public void actionPerformed(ActionEvent e) {
            filterInput.setText("");
        }
    }

    private class FilterDocumentListener implements DocumentListener {
        private JTree tree;
        private JButton clearButton;

        FilterDocumentListener(JTree tree, JButton clearButton) {
            this.tree = tree;
            this.clearButton = clearButton;
        }

        public void insertUpdate(DocumentEvent e) {
            changedUpdate(e);
        }

        public void removeUpdate(DocumentEvent e) {
            changedUpdate(e);
        }

        public void changedUpdate(DocumentEvent e) {
            String fieldText = null;
            try {
                fieldText = e.getDocument().getText(0, e.getDocument().getLength());
            } catch (BadLocationException ble) {
                ble.printStackTrace(); // should never happen
                return;
            }

            clearButton.setEnabled(fieldText.length() > 0);

            DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
            ManagementModelNode root = (ManagementModelNode)model.getRoot();
            root.explore(); // refresh all children

            Enumeration nodes = root.children();
            List<ManagementModelNode> removeList = new ArrayList<ManagementModelNode>();
            while (nodes.hasMoreElements()) {
                ManagementModelNode node = (ManagementModelNode)nodes.nextElement();
                if (!node.getUserObject().toString().contains(fieldText)) {
                    removeList.add(node);
                }
            }

            for (ManagementModelNode node : removeList) {
                root.remove(node);
            }

            model.nodeStructureChanged(root);
        }
    }

    /**
     * Listener that populates (or refreshes) the children when a node is expanded.
     */
    private class ManagementTreeExpansionListener implements TreeExpansionListener {
        private DefaultTreeModel treeModel;

        public ManagementTreeExpansionListener(DefaultTreeModel treeModel) {
            this.treeModel = treeModel;
        }

        public void treeCollapsed(TreeExpansionEvent tee) {
            // do nothing
        }

        public void treeExpanded(TreeExpansionEvent tee) {
            ManagementModelNode node = (ManagementModelNode) tee.getPath().getLastPathComponent();
            node.explore();
            treeModel.nodeStructureChanged(node);
        }
    }

    /**
     * Listener that populates the command line with the address of the selected node.
     */
    private class ManagementTreeSelectionListener implements TreeSelectionListener {

        public void valueChanged(TreeSelectionEvent tse) {
            ManagementModelNode selected = (ManagementModelNode) tse.getPath().getLastPathComponent();
            cliGuiCtx.getCommandLine().getCmdText().setText(selected.addressPath());
        }
    }

    /**
     * Listener that triggers the operationMenu menu containing operations.
     */
    private class ManagementTreeMouseListener extends MouseAdapter {

        private JTree tree;
        private OperationMenu operationMenu;
        private GraphingMenu graphingMenu;

        public ManagementTreeMouseListener(JTree tree) {
            this.tree = tree;
            this.operationMenu = new OperationMenu(cliGuiCtx, tree);
            this.graphingMenu = new GraphingMenu(cliGuiCtx, tree);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (!e.isPopupTrigger()) return;
            showPopup(e);
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (!e.isPopupTrigger()) return;
            showPopup(e);
        }

        private void showPopup(MouseEvent e) {
            int selRow = tree.getRowForLocation(e.getX(), e.getY());
            if (selRow == -1) return;

            TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
            tree.setSelectionPath(selPath);

            ManagementModelNode node = (ManagementModelNode)selPath.getLastPathComponent();

            UserObject usrObj = (UserObject)node.getUserObject();
            AttributeDescription attrDesc = usrObj.getAttributeDescription();
            if ((attrDesc != null) && attrDesc.isGraphable()) {
        //        graphingMenu.show(node, e.getX(), e.getY());
            } else {
                operationMenu.show(node, e.getX(), e.getY());
            }
        }
    }
}
