// $Id: PmiiTreeCtrl.java,v 1.20 2004/05/09 22:31:56 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" 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 com.meyling.principia.gui.jtree;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

import com.meyling.principia.gui.PmiiController;
import com.meyling.principia.gui.jtabbedpane.PmiiTabbedPaneView;
import com.meyling.principia.log.Trace;
import com.meyling.principia.module.ModuleProperties;


/**
 * Controller for a certain JTree.
 *
 * A Controller, which represents the classes connecting the model and the view, and is used to
 * communicate between classes in the model and view.
 *
 * @version $Revision: 1.20 $
 * @author  Michael Meyling
 */
public final class PmiiTreeCtrl implements TreeModelListener {

    private final PmiiTreeView treeView;

    private final PmiiTreeModel treeModel;

    private final PmiiTabbedPaneView pane;

    private final ActionListener removeAction;

    private final ActionListener addAction;

    private final ActionListener makeHtmlAction;
  

    public PmiiTreeCtrl(final PmiiTreeView treeView, final PmiiTreeModel treeModel,
            final PmiiTabbedPaneView pane, final PmiiController controller) {

        this.treeView = treeView;
        this.treeModel = treeModel;
        this.treeModel.addTreeModelListener(this);
        this.treeView.addActionCommandToContextMenus(new PmiiActionCommand());
        this.treeView.treeAddMouseListener(new PmiiMouseListener());
        this.treeView.addTreeSelectionListener(new SelectionChangedCommand());
        this.pane = pane;
        this.removeAction = new RemoveAction();
        this.addAction = controller.getAddAction();
        this.makeHtmlAction = controller.getHtmlAction();
    }


    public final ActionListener getRemoveAction() {
        return this.removeAction;
    }


    public final ActionListener getAddAction() {
        return this.addAction;
    }


    public final ActionListener getHtmlAction() {
        return this.makeHtmlAction;
    }


    public final ModuleProperties getSelected() throws NothingSelectedException {
        final String METHOD = "getSelected";
        Trace.traceBegin(this, METHOD);
        try {
            TreePath[] selected = treeView.getSelectionPaths();
            if (selected != null && selected.length > 0) {
                Trace.trace(this, "actionPerformed", "selection=" + selected[selected.length - 1]);
        
                final DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                         (selected[selected.length - 1].getLastPathComponent());
        
                if (node instanceof PmiiTreeNode) {
                    final ModuleProperties prop = (ModuleProperties) node.getUserObject();
                    return prop;
                }
            }
            throw new NothingSelectedException();
        } catch (RuntimeException ex) {
            Trace.trace(this, METHOD, ex);
            throw new NothingSelectedException();
        } finally {
            Trace.traceEnd(this, METHOD);
        }
    }
    
    
    public final void setSelected(final ModuleProperties prop) {
        final String METHOD = "setSelected";
        Trace.traceBegin(this, METHOD);
        try {
            treeView.setSelectionPath(treeModel.getSelectionPath(prop));
            treeView.setSelectionRow(2);
        } catch (RuntimeException ex) {
            Trace.trace(this, METHOD, ex);
        } finally {
            Trace.traceEnd(this, METHOD);
        }
    }
    
    
    /**
     * Get edited qedeq text.
     * 
     * @return  qedeq text
     * @throws  IllegalStateException   text is not editable
     */
    public final String getEditedQedeq() {
        return this.pane.getEditedQedeq();
    }


    /**
     * Changes node view, depending from choosen object.
     */
    private class SelectionChangedCommand implements TreeSelectionListener {

        public SelectionChangedCommand()  {
        }

        public void valueChanged (TreeSelectionEvent event) {
            Trace.trace(this, "valueChanged", event);
            TreePath path = event.getPath();
            PmiiTreeNode treeNode = (PmiiTreeNode) path.getLastPathComponent();
            String pathStr = treeNode.toString();
            System.out.println("Path=" + pathStr);
            if (event.isAddedPath()
                    && treeNode.getUserObject() instanceof ModuleProperties) {
                ModuleProperties prop = (ModuleProperties) treeNode.getUserObject();
                pane.setModel(prop);
            } else {
                pane.setModel(null);
            }
        }
    }


    private class PmiiActionCommand implements ActionListener {

        public void actionPerformed (ActionEvent event) {
            System.out.println("action=" + event);

            if( event.getActionCommand() == PmiiTreeView.DELETE_ACTION)  {     // delete
                getRemoveAction().actionPerformed(event);
            } else if (event.getActionCommand() == PmiiTreeView.REFRESH_ACTION)  {    // refresh
                // TODO
            } else if (event.getActionCommand() == PmiiTreeView.ADD_ACTION) {
                getAddAction().actionPerformed(event);
                // TODO
            } else if (event.getActionCommand() == PmiiTreeView.HTML_ACTION) {
                getHtmlAction().actionPerformed(event);
                // TODO
            }
            // TODO
        }
    }



    /**
     * RemoveAction removes the selected node from the tree.  If
     * The root or nothing is selected nothing is removed.
     */
    class RemoveAction extends Object implements ActionListener {

        /**
          * Removes the selected item as long as it isn't root.
          */
        public void actionPerformed(ActionEvent e) {
            TreePath[] selected = treeView.getSelectionPaths();

            if (selected != null && selected.length > 0) {
                TreePath shallowest;

                // The remove process consists of the following steps:
                // 1 - find the shallowest selected TreePath, the shallowest
                //     path is the path with the smallest number of path
                //     components.
                // 2 - Find the siblings of this TreePath
                // 3 - Remove from selected the TreePaths that are descendants
                //     of the paths that are going to be removed. They will
                //     be removed as a result of their ancestors being
                //     removed.
                // 4 - continue until selected contains only null paths.
                while ((shallowest = findShallowestPath(selected)) != null) {
                    removeSiblings(shallowest, selected);
                }
            }
        }

        /**
         * Removes the sibling TreePaths of <code>path</code>, that are
         * located in <code>paths</code>.
         */
        private void removeSiblings(TreePath path, TreePath[] paths) {
            // Find the siblings
            if (path.getPathCount() == 1) {
                // Special case, set the root to null
                for (int counter = paths.length - 1; counter >= 0; counter--) {
                    paths[counter] = null;
                }
                treeModel.setRoot(null);
            }
            else {
                // Find the siblings of path.
                TreePath parent = path.getParentPath();
                MutableTreeNode parentNode = (MutableTreeNode)parent.
                                getLastPathComponent();
                ArrayList toRemove = new ArrayList();
                int depth = parent.getPathCount();

                // First pass, find paths with a parent TreePath of parent
                for (int counter = paths.length - 1; counter >= 0; counter--) {
                    if (paths[counter] != null && paths[counter].
                              getParentPath().equals(parent)) {
                        toRemove.add(paths[counter]);
                        paths[counter] = null;
                    }
                }

                // Second pass, remove any paths that are descendants of the
                // paths that are going to be removed. These paths are
                // implicitly removed as a result of removing the paths in
                // toRemove
                int rCount = toRemove.size();
                for (int counter = paths.length - 1; counter >= 0; counter--) {
                    if (paths[counter] != null) {
                        for (int rCounter = rCount - 1; rCounter >= 0;
                             rCounter--) {
                            if (((TreePath)toRemove.get(rCounter)).
                                           isDescendant(paths[counter])) {
                                paths[counter] = null;
                            }
                        }
                    }
                }

                // Sort the siblings based on position in the model
                if (rCount > 1) {
                    Collections.sort(toRemove, (Comparator) new PositionComparator());
                }
                int[] indices = new int[rCount];
                Object[] removedNodes = new Object[rCount];
                for (int counter = rCount - 1; counter >= 0; counter--) {
                    removedNodes[counter] = ((TreePath)toRemove.get(counter)).
                                getLastPathComponent();
                    indices[counter] = treeModel.getIndexOfChild
                                        (parentNode, removedNodes[counter]);
                    parentNode.remove(indices[counter]);
                }
                treeModel.nodesWereRemoved(parentNode, indices, removedNodes);
            }
        }


        /**
         * Returns the TreePath with the smallest path count in
         * <code>paths</code>. Will return null if there is no non-null
         * TreePath is <code>paths</code>.
         */
        private TreePath findShallowestPath(TreePath[] paths) {
            int shallowest = -1;
            TreePath shallowestPath = null;

            for (int counter = paths.length - 1; counter >= 0; counter--) {
                if (paths[counter] != null) {
                    if (shallowest != -1) {
                        if (paths[counter].getPathCount() < shallowest) {
                            shallowest = paths[counter].getPathCount();
                            shallowestPath = paths[counter];
                            if (shallowest == 1) {
                                return shallowestPath;
                            }
                        }
                    }
                    else {
                        shallowestPath = paths[counter];
                        shallowest = paths[counter].getPathCount();
                    }
                }
            }
            return shallowestPath;
        }


        /**
         * An Comparator that bases the return value on the index of the
         * passed in objects in the TreeModel.
         * <p>
         * This is actually rather expensive, it would be more efficient
         * to extract the indices and then do the comparision.
         */
        private class PositionComparator implements Comparator {
            public int compare(Object o1, Object o2) {
                TreePath p1 = (TreePath)o1;
                int o1Index = treeModel.getIndexOfChild(p1.getParentPath().
                          getLastPathComponent(), p1.getLastPathComponent());
                TreePath p2 = (TreePath)o2;
                int o2Index = treeModel.getIndexOfChild(p2.getParentPath().
                          getLastPathComponent(), p2.getLastPathComponent());
                return o1Index - o2Index;
            }

            public boolean equals(Object obj) {
                return super.equals(obj);
            }
        }

    }






    private class PmiiMouseListener extends MouseAdapter {

        public void mousePressed(java.awt.event.MouseEvent evt) {

            final TreePath path =  treeView.getPathForLocation(evt.getX(), evt.getY());

            if (SwingUtilities.isRightMouseButton( evt )) {
                if (path != null )  {
                    treeView.setSelectionPath(path);
                } // TODO other ContextMenu if no selection was done
                treeView.getContextMenu().show(evt.getComponent(),
                                  evt.getX(), evt.getY() );
            }
//            super.mousePressed(evt);
        }

    }


    /* (non-Javadoc)
     * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesChanged(TreeModelEvent e) {
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                 (e.getTreePath().getLastPathComponent());

        /*
         * If the event lists children, then the changed
         * node is the child of the node we've already
         * gotten.  Otherwise, the changed node and the
         * specified node are the same.
         */

        try {
            int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode)
                   (node.getChildAt(index));
        } catch (NullPointerException exc) {}

        Trace.trace(this, "treeNodesChanged", node.getUserObject());

        TreePath path = e.getTreePath();
        PmiiTreeNode treeNode = (PmiiTreeNode) path.getLastPathComponent();

        pane.updateView();      // TODO
/*
        if (treeNode.getUserObject() instanceof ModuleProperties) {
            ModuleProperties prop = (ModuleProperties) treeNode.getUserObject();
    //        pane.setModel(prop);
            pane.updateView();      // TODO
    //        if (pane.getModel() == prop) {
    //            pane.updateView();
    //        }
        } else {
//            pane.setModel(null);
        }
*/

    }

    /* (non-Javadoc)
     * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesInserted(TreeModelEvent e) {
        Trace.traceBegin(this, "treeNodesInserted");
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                 (e.getTreePath().getLastPathComponent());
        if (((DefaultMutableTreeNode) treeModel.getRoot()).getChildCount() == 1) {
            treeView.expandPath(new TreePath(treeModel.getRoot()));
        }
        Trace.traceEnd(this, "treeNodesInserted");
    }


    /* (non-Javadoc)
     * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent)
     */
    public void treeNodesRemoved(TreeModelEvent e) {
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                 (e.getTreePath().getLastPathComponent());
        Trace.trace(this, "treeNodeRemoved", node.getUserObject());
    }


    /* (non-Javadoc)
     * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)
     */
    public void treeStructureChanged(TreeModelEvent e) {
        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                 (e.getTreePath().getLastPathComponent());
        Trace.trace(this, "treeStructureChanged", node.getUserObject());
    }


}

