package prefuse.demos;

import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.SizeAction;
import prefuse.action.layout.RandomLayout;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.activity.ActivityAdapter;
import prefuse.activity.ActivityListener;
import prefuse.controls.ControlAdapter;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.io.DelimitedTextTableReader;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.PrefuseLib;
import prefuse.util.force.DragForce;
import prefuse.util.force.ForceItem;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.NBodyForce;
import prefuse.util.force.SpringForce;
import prefuse.util.ui.BrowserLauncher;
import prefuse.visual.VisualItem;
import prefuse.visual.sort.ItemSorter;

/**
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class DataMountain extends Display {
    
    public DataMountain(Table t) {
        super(new Visualization());
        m_vis.addTable("data", t);
        
        LabelRenderer nodeRenderer = new LabelRenderer(null, "image");
        nodeRenderer.setTextField(null);
        nodeRenderer.setVerticalAlignment(Constants.BOTTOM);
        nodeRenderer.setHorizontalPadding(0);
        nodeRenderer.setVerticalPadding(0);
        nodeRenderer.setMaxImageDimensions(100,100);
        
        m_vis.setRendererFactory(new DefaultRendererFactory(nodeRenderer));
        
        ActionList init = new ActionList();
        init.add(new RandomLayout());
        init.add(new DataMountainSizeAction());
        m_vis.putAction("init", init);
        
        ActionList update = new ActionList();
        update.add(new DataMountainSizeAction());
        update.add(new ColorAction("data", VisualItem.STROKECOLOR) {
            public int getColor(VisualItem item) {
                return ColorLib.rgb((item.isHover() ? 255 : 0), 0, 0);
            }
        });
        update.add(new RepaintAction());
        m_vis.putAction("update", update);

        // we run this to make sure the forces are stabilized
        ActionList preforce = new ActionList(1000);
        preforce.add(new DataMountainForceLayout(true));
        m_vis.putAction("preforce", preforce);

        // this will cause docs to move out of the way when dragging
        final ForceDirectedLayout fl = new DataMountainForceLayout(false);
        ActivityListener fReset = new ActivityAdapter() {
            public void activityCancelled(Activity a) {
                fl.reset(); 
             } 
        };
        ActionList forces = new ActionList(Activity.INFINITY);
        forces.add(fl);
        forces.add(update);
        forces.addActivityListener(fReset);
        m_vis.putAction("forces", forces);
        
        setSize(640,450);
        setDamageRedraw(false); // disable due to Java2D image clipping errors
        setBorder(BorderFactory.createEmptyBorder(30,20,5,20));
        setItemSorter(new DataMountainSorter());
        addControlListener(new DataMountainControl());
        
        // pre-load images, otherwise they will be loaded asynchronously
        nodeRenderer.getImageFactory().preloadImages(m_vis.items(),"image");
        
        // initialize and present the interface
        m_vis.run("init");
        m_vis.runAfter("preforce", "update");
        m_vis.run("preforce");
    }

    // ------------------------------------------------------------------------
    
    public static void main(String[] args) {       
        JFrame frame = new JFrame("p r e f u s e  |  d a t a m o u n t a i n");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(demo());
        frame.pack();
        frame.setVisible(true);
    }
    
    public static JComponent demo() {
        String datafile = "/amazon.txt";
        Table data = null;
        try {
            data = (new DelimitedTextTableReader()).readTable(datafile);
            data.addColumn("image","CONCAT('/images/',id,'.01.MZZZZZZZ.jpg')");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        return new DataMountain(data);
    }
    
    // ------------------------------------------------------------------------
    
    public static class DataMountainSorter extends ItemSorter {
        public int score(VisualItem item) {
            return (int)(10000*item.getY());
        }
    } // end of inner class DataMountainComparator
    
    public class DataMountainSizeAction extends SizeAction {
        public double getSize(VisualItem item) {
            double y = item.getEndY();
            return 0.2 + y/1400;
        }
    } // end of inner class DataMountainSizeAction
    
    private static final String ANCHORITEM = "_anchorItem";
    private static final Schema ANCHORITEM_SCHEMA = new Schema();
    static {
        ANCHORITEM_SCHEMA.addColumn(ANCHORITEM, ForceItem.class);
    }
    
    public class DataMountainForceLayout extends ForceDirectedLayout {
        
        public DataMountainForceLayout(boolean enforceBounds) {
            super("data",enforceBounds,false);
            
            ForceSimulator fsim = new ForceSimulator();
            fsim.addForce(new NBodyForce(-0.4f, 25f, NBodyForce.DEFAULT_THETA));
            fsim.addForce(new SpringForce(1e-5f,0f));
            fsim.addForce(new DragForce());
            setForceSimulator(fsim);
            
            m_nodeGroup = "data";
            m_edgeGroup = null;
        }
        
        protected float getMassValue(VisualItem n) {
            return n.isHover() ? 5f : 1f;
        }

        public void reset() {
            Iterator iter = m_vis.visibleItems(m_nodeGroup);
            while ( iter.hasNext() ) {
                VisualItem item = (VisualItem)iter.next();
                ForceItem aitem = (ForceItem)item.get(ANCHORITEM);
                if ( aitem != null ) {
                    aitem.location[0] = (float)item.getEndX();
                    aitem.location[1] = (float)item.getEndY();
                }
            }
            super.reset();
        }
        protected void initSimulator(ForceSimulator fsim) {
            // make sure we have force items to work with
            TupleSet t = (TupleSet)m_vis.getGroup(m_group);
            t.addColumns(ANCHORITEM_SCHEMA);
            t.addColumns(FORCEITEM_SCHEMA);
            
            Iterator iter = m_vis.visibleItems(m_nodeGroup);
            while ( iter.hasNext() ) {
                VisualItem item = (VisualItem)iter.next();
                // get force item
                ForceItem fitem = (ForceItem)item.get(FORCEITEM);
                if ( fitem == null ) {
                    fitem = new ForceItem();
                    item.set(FORCEITEM, fitem);
                }
                fitem.location[0] = (float)item.getEndX();
                fitem.location[1] = (float)item.getEndY();
                fitem.mass = getMassValue(item);
                
                // get spring anchor
                ForceItem aitem = (ForceItem)item.get(ANCHORITEM);
                if ( aitem == null ) {
                    aitem = new ForceItem();
                    item.set(ANCHORITEM, aitem);
                    aitem.location[0] = fitem.location[0];
                    aitem.location[1] = fitem.location[1];
                }
                
                fsim.addItem(fitem);
                fsim.addSpring(fitem, aitem, 0);
            }     
        }       
    } // end of inner class DataMountainForceLayout
    
    public class DataMountainControl extends ControlAdapter {
        public static final String URL = "http://www.amazon.com/exec/obidos/tg/detail/-/";
        private VisualItem activeItem;
        private Point2D down = new Point2D.Double();
        private Point2D tmp = new Point2D.Double();
        private boolean wasFixed, dragged;
        private boolean repaint = false;
        
        public void itemEntered(VisualItem item, MouseEvent e) {
            Display d = (Display)e.getSource();
            d.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            d.setToolTipText(item.getString("id"));
            activeItem = item;
            wasFixed = item.isFixed();
        }
        
        public void itemExited(VisualItem item, MouseEvent e) {
            if ( activeItem == item ) {
                activeItem = null;
                item.setFixed(wasFixed);
            }
            Display d = (Display)e.getSource();
            d.setToolTipText(null);
            d.setCursor(Cursor.getDefaultCursor());
        }
        
        public void itemPressed(VisualItem item, MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) return;
            
            // set the focus to the current node
            Visualization vis = item.getVisualization();
            vis.getFocusGroup(Visualization.FOCUS_ITEMS).setTuple(item);
            
            item.setFixed(true);
            dragged = false;
            Display d = (Display)e.getComponent();
            down = d.getAbsoluteCoordinate(e.getPoint(), down);
            
            vis.run("forces");
        }
        
        public void itemReleased(VisualItem item, MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) return;
            if ( dragged ) {
                activeItem = null;
                item.setFixed(wasFixed);
                dragged = false;
            }
            // clear the focus
            Visualization vis = item.getVisualization();
            vis.getFocusGroup(Visualization.FOCUS_ITEMS).clear();

            vis.cancel("forces");
        }
        
        public void itemClicked(VisualItem item, MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) return;
            if ( e.getClickCount() == 2 ) {
                String id = item.getString("id");
                BrowserLauncher.showDocument(URL+id);
            }
        }
        
        public void itemDragged(VisualItem item, MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) return;
            dragged = true;
            Display d = (Display)e.getComponent();
            tmp = d.getAbsoluteCoordinate(e.getPoint(), tmp);
            double dx = tmp.getX()-down.getX();
            double dy = tmp.getY()-down.getY();
            
            PrefuseLib.setX(item, null, item.getX()+dx);
            PrefuseLib.setY(item, null, item.getY()+dy);
            down.setLocation(tmp);
            if ( repaint )
                item.getVisualization().repaint();
        }
    } // end of inner class DataMountainControl
    
} // end of class DataMountain

