/* GraphTreeComponent.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.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JToolTip;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.grinvin.worksheet.actions.ShowGraphPropertiesAction;
import org.grinvin.graphs.GraphURI;
import org.grinvin.gui.MultilineToolTip;
import org.grinvin.gui.windows.GraphPropertiesWindow;
import org.grinvin.list.graphs.GraphGroup;
import org.grinvin.list.graphs.GraphListElement;
import org.grinvin.list.graphs.GraphTreeModel;

/**
 *
 */
public class GraphTreeComponent extends JTree implements MouseListener {
    
    // 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 GraphTreeDragHandler());
    }
    
    //
    private PopupMenu popupMenu;
    
    /** Creates a new instance of GraphTreeComponent */
    public GraphTreeComponent(GraphTreeModel model) {
        super(model);
        
        setCellRenderer(RENDERER);
        
        setTransferHandler(TRANSFER_HANDLER);
        setDragEnabled(true);
        
        setRootVisible(false);
        setShowsRootHandles(true);
        
        popupMenu = new PopupMenu();
        
        addMouseListener(this);
        
        ToolTipManager.sharedInstance().registerComponent(this);
    }
    
    //
    private GraphListElement getElementAtPoint(Point point) {
        TreePath path = getClosestPathForLocation((int)point.getX(), (int)point.getY());
        if (path != null && getPathBounds(path).contains(point)) {
            Object obj = path.getLastPathComponent();
            if (obj instanceof GraphListElement)
                return (GraphListElement)obj;
            else
                return null;
        } else {
            return null;
        }
    }
    
    private GraphListElement[] getSelectedElements() {
        TreePath[] paths = getSelectionPaths();
        List<GraphListElement> result = new ArrayList<GraphListElement>();
        
        for (TreePath path : paths) {
            Object obj = path.getLastPathComponent();
                if (obj instanceof GraphListElement)
                    result.add((GraphListElement)obj);
        }
        return result.toArray(new GraphListElement[0]);
    }

    public void mouseClicked(MouseEvent e) {
        if (isEnabled() && e.getClickCount() == 2 && !e.isPopupTrigger()) {
            GraphListElement gle = getElementAtPoint(e.getPoint());
            if (gle != null)
                new GraphPropertiesWindow(gle).setVisible(true);
        }
    }

    public void mousePressed(MouseEvent e) {
        if (isEnabled() && e.isPopupTrigger()) {
            TreePath path = getClosestPathForLocation(e.getX(), e.getY());
            TreePath[] selectionPaths = getSelectionPaths();
            if ((selectionPaths != null && !Arrays.asList(selectionPaths).contains(path)) || (selectionPaths == null))
                setSelectionPath(path);
            popupMenu.show(getSelectedElements(), this,e.getX(),e.getY());
        }
    }

    public void mouseReleased(MouseEvent e) {
        if (isEnabled() && e.isPopupTrigger()) {
            TreePath path = getClosestPathForLocation(e.getX(), e.getY());
            TreePath[] selectionPaths = getSelectionPaths();
            if ((selectionPaths != null && !Arrays.asList(selectionPaths).contains(path)) || (selectionPaths == null))
                setSelectionPath(path);
            popupMenu.show(getSelectedElements(), this,e.getX(),e.getY());
        }
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }
    
    
    public JToolTip createToolTip() {
        return new MultilineToolTip(150);
        //TODO: don't hardcode width
    }
    
    private static class PopupMenu extends JPopupMenu {
        
        //
        private ShowGraphPropertiesAction sAction;
        
        //
        public PopupMenu() {
            this.sAction = new ShowGraphPropertiesAction();
            add(sAction);
        }
        
        //
        public void show(GraphListElement[] graphListElements, Component invoker, int x, int y) {
            sAction.setGraphListElements(graphListElements);
            show(invoker, x, y);
        }
    }

    
    private static class GraphTreeDragHandler implements DragHandler {
        
        private GraphTreeDragHandler () { }
        
        //
        public int getSourceActions (JComponent source) {
            return LocalTransferHandler.COPY;
        }
        
        //
        public void exportDone (JComponent source, JComponent target, Object[] objects, Class<?> type, int action) {
            // MOVE not supported
        }
        
        //
        public Object getExportedObjects (JComponent source) {
            TreePath[] paths = getSelectedChildren(((GraphTreeComponent)source).getSelectionPaths(), ((GraphTreeComponent)source).getModel());
            Object[] results = new Object[paths.length];
            for (int i=0;i<results.length;i++) {
                results[i] = paths[i].getLastPathComponent();
            }
            return results; // most common case
        }
        
        //
        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 GRAPHLISTELEMENT = GraphListElement.class;
        
        //
        public Class getExportedClass (JComponent source) {
            return GRAPHLISTELEMENT;
        }
        
        //
        public boolean isTransferAllowed (JComponent source, JComponent target) {
            return true;
        }
        
    }

    //
    private static class TreeRenderer extends DefaultTreeCellRenderer {
        public Component getTreeCellRendererComponent (JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            super.getTreeCellRendererComponent (tree, value, sel, expanded, leaf, row, hasFocus);
            
            // displays graphs as leaf nodes
            if (value instanceof GraphListElement) {
                GraphListElement gle = (GraphListElement)value;
                setText(gle.getName());
                setToolTipText(gle.getDescription());
                setLeafIcon(GraphURI.getType(gle.getURI()).getSmallIcon());
            } else if (value instanceof GraphGroup) {
                GraphGroup group = (GraphGroup)value;
                setText(group.getName());
                setToolTipText(null);
            }
            
            return this;
        }
    }
    
}
