/* InvariantTreeComponent.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * This program 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.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.gui.components;

import be.ugent.caagt.swirl.dnd.DragHandler;
import be.ugent.caagt.swirl.dnd.LocalTransferHandler;

import java.awt.Color;
import java.awt.Component;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.grinvin.graphs.GraphURIType;
import org.grinvin.invariants.Invariant;
import org.grinvin.invariants.InvariantFactory;
import org.grinvin.invariants.InvariantManager;
import org.grinvin.invariants.InvariantManagerListener;
import org.grinvin.list.invariants.InvariantTreeModel;

/**
 * Tree component with leaves of type {@link Invariant}. Provides drag support
 * for invariants. Dropping is not allowed.
 */
public class InvariantTreeComponent extends JTree {
    
    // shared transfer handler
    private static final LocalTransferHandler TRANSFER_HANDLER;
    
    // shared instance
    private static final TreeCellRenderer RENDERER = new TreeRenderer ();
    
    static {
        TRANSFER_HANDLER = new LocalTransferHandler ();
        TRANSFER_HANDLER.setDragHandler (new InvariantTreeDragHandler ());
    }
    
    /** Creates a new instance of InvariantList */
    public InvariantTreeComponent (InvariantTreeModel model) {
        super (model);
        setTransferHandler (TRANSFER_HANDLER);
        setDragEnabled (true);
        
        setCellRenderer (RENDERER);
        
        setRootVisible (false);
        setShowsRootHandles (true);
        
        //expandRow (0);
        
        if (model.getInsertionPoint ("local") != null)
            InvariantManager.getInstance ().addListener (
                    new NewInvariantListener ());
    }
    
    // drag handler for lists of type SomeList
    private static class InvariantTreeDragHandler implements DragHandler {
        
        InvariantTreeDragHandler () {
            // avoid creation of access type
        }
        
        //
        public int getSourceActions (JComponent source) {
            return LocalTransferHandler.COPY;
        }
        
        //
        public void exportDone (JComponent source, JComponent target, Object[] objects, Class<?> type, int action) {
            // MOVE not supported
        }
        
        //
        private static final Object[] DUMMY_ARRAY = new Object[0];
        
        /**
         * Either exports an array of invariants or a single invariant factory.
         */
        public Object getExportedObjects (JComponent source) {
            // TODO: check for incompatible multiple selections
            TreePath[] paths = getSelectedChildren(((InvariantTreeComponent)source).getSelectionPaths(), ((InvariantTreeComponent)source).getModel());
            Object[] results = new Object[paths.length];
            InvariantFactory factory = null;
            int count = 0;
            for (TreePath path : paths) {
                Object obj = ((DefaultMutableTreeNode)path.getLastPathComponent ()).getUserObject ();
                if (obj instanceof Invariant)
                    results[count++] = obj;
                else if (obj instanceof InvariantFactory) {
                    factory = (InvariantFactory)obj;
                }
            }
            if (count == 0) {
                if (factory == null)
                    return DUMMY_ARRAY;
                else
                    return factory;
            } else if (count == results.length) {
                return results; // most common case
            } else {
                // discards the factory
                Object[] newResults = new Object[count];
                System.arraycopy (results, 0, newResults, 0, count);
                return newResults;
            }
        }
        
        //
        private TreePath[] getSelectedChildren(TreePath paths[], TreeModel model) {
            Set<TreePath> result = new LinkedHashSet<TreePath>();
            for(TreePath path : paths)
                recurseChildren(path, model, result);
            return result.toArray(new TreePath[0]);
        }
        
        //
        private void recurseChildren(TreePath path, TreeModel model, Collection<TreePath> result) {
            int childCount = model.getChildCount(path.getLastPathComponent());
            if (childCount == 0)
                result.add(path);
            else
                for(int i = 0; i<childCount; i++)
                    recurseChildren(path.pathByAddingChild(model.getChild(path.getLastPathComponent(), i)), model, result);
        }
        
        //
        private static final Class INVARIANT = Invariant.class;
        
        //
        private static final Class INVARIANT_FACTORY = InvariantFactory.class;
        
        //
        public Class getExportedClass (JComponent source) {
            TreePath[] paths = ((InvariantTreeComponent)source).getSelectionPaths ();
            boolean hasFactory = false;
            int count = 0;
            for (TreePath path : paths) {
                Object obj = ((DefaultMutableTreeNode)path.getLastPathComponent ()).getUserObject ();
                if (obj instanceof Invariant)
                    count ++;
                else if (obj instanceof InvariantFactory) {
                    hasFactory = true;
                }
            }
            if (count == 0 && hasFactory)
                return INVARIANT_FACTORY;
            else
                return INVARIANT;
        }
        
        //
        public boolean isTransferAllowed (JComponent source, JComponent target) {
            return true;
        }
        
    }
    
    //
    private static class TreeRenderer extends DefaultTreeCellRenderer {
        
        //
        private final Color stdNonSelectionColor;
        
        //
        private final Color stdSelectionColor;

        //
        public TreeRenderer() {
            this.stdNonSelectionColor = getTextNonSelectionColor();
            this.stdSelectionColor = getTextSelectionColor();
        }
        
        //
        public Component getTreeCellRendererComponent (JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            // displays invariants as leaf nodes
            if (value instanceof DefaultMutableTreeNode) {
                Object userObject = ((DefaultMutableTreeNode)value).getUserObject ();
                if (userObject instanceof Invariant) {
                    leaf = true;
                    setLeafIcon(GraphURIType.INVARIANT.getSmallIcon());
                    if (InvariantManager.getInstance().getInvariantComputerFor((Invariant)userObject) == null) {
                        setTextNonSelectionColor(Color.RED);
                        setTextSelectionColor(Color.RED);
                    } else {
                        setTextNonSelectionColor(stdNonSelectionColor);
                        setTextSelectionColor(stdSelectionColor);
                    }
                } else if (userObject instanceof InvariantFactory) {
                    leaf = true;
                    setLeafIcon(GraphURIType.INVARIANT_FACTORY.getSmallIcon());
                    if (InvariantManager.getInstance().getInvariantComputerFactoryFor(((InvariantFactory)userObject).getId()) == null) {
                        setTextNonSelectionColor(Color.RED);
                        setTextSelectionColor(Color.RED);
                    } else {
                        setTextNonSelectionColor(stdNonSelectionColor);
                        setTextSelectionColor(stdSelectionColor);
                    }
                } else {
                    leaf = false;
                    setTextNonSelectionColor(stdNonSelectionColor);
                    setTextSelectionColor(stdSelectionColor);
                }
            }
            super.getTreeCellRendererComponent (tree, value, sel, expanded, leaf, row, hasFocus);
            return this;
        }
        
    }
    
    //
    // append a node to the local subtree
    void appendNode (Object obj) {
        InvariantTreeModel model = (InvariantTreeModel)getModel ();
        MutableTreeNode local = model.getInsertionPoint ("local");
        if (local != null) {
            MutableTreeNode newNode = new DefaultMutableTreeNode (obj, false);
            model.insertNodeInto (newNode , local, local.getChildCount ());
            makeVisible (new TreePath (model.getPathToRoot (newNode)));
        }
    }
    
    /**
     * Listener which reacts to the introduction of new invariants or invariant
     * factories by adding them to the last sub-tree of the tree node.
     */
    private class NewInvariantListener implements InvariantManagerListener {
        
        NewInvariantListener () {
            // avoid creation of access type
        }
        
        public void newInvariantFactory (InvariantFactory factory) {
            appendNode (factory);
        }

        public void newInvariant (Invariant invariant) {
            appendNode (invariant);
        }
        
    }
    
    
}
