package plab.prefuseTests;

import java.util.ArrayList;
import java.util.Iterator;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.BorderLayout;

import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.data.Graph;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.action.ActionList;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;
import prefuse.data.Edge;
import prefuse.Constants;
import prefuse.action.filter.VisibilityFilter;
import prefuse.data.expression.ColumnExpression;
import prefuse.controls.ZoomControl;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.data.column.Column;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.Force;
import prefuse.action.assignment.DataSizeAction;
import prefuse.render.EdgeRenderer;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.tuple.TupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.controls.ZoomToFitControl;
import prefuse.controls.HoverActionControl;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.util.display.ExportDisplayAction;
import prefuse.data.io.GraphMLWriter;


/**
 *
 * @author gestalt
 */
public class TPTWindow extends JFrame {

    private int numPaths = 5;
    private Visualization m_vis;

    public TPTWindow(final Graph g, ArrayList<Tuple> source, ArrayList<Tuple> target) {
	this(g, toTupleSet(source), toTupleSet(target));
    }

    

    public TPTWindow(final Graph g, TupleSet source, TupleSet target)
    {


        // Create necessary data columns
        g.getNodeTable().addColumn("source", boolean.class, false);
        g.getNodeTable().addColumn("target", boolean.class, false);
        g.addColumn("inTPT", boolean.class, false);
        g.addColumn("flux", double.class, 0.0d);

        final TPTFactoryCM tptCalc = new TPTFactoryCM(g, source, target);

        for (Iterator tuples = source.tuples(); tuples.hasNext(); )
            ((Tuple)tuples.next()).set("source", true);

        for (Iterator tuples = target.tuples(); tuples.hasNext(); )
            ((Tuple)tuples.next()).set("target", true);


        setNumPaths(tptCalc, g, numPaths);
      
        m_vis = new Visualization();
        m_vis.add("graph", g);
        m_vis.setValue("graph.nodes", null, VisualItem.SHAPE, Constants.SHAPE_ELLIPSE);


        final LabelRenderer sr = new LabelRenderer();
        sr.setRoundedCorner(8, 8);

        final DefaultRendererFactory drf = new DefaultRendererFactory(sr);
        ((EdgeRenderer)drf.getDefaultEdgeRenderer()).setEdgeType(Constants.EDGE_TYPE_CURVE);
        m_vis.setRendererFactory(drf);

        
        VisibilityFilter filter = new VisibilityFilter(new ColumnExpression("inTPT"));

        ActionList tptLayout = new ActionList(ActionList.INFINITY);
        tptLayout.add(new ForceDirectedLayout("graph"));
        //tptLayout.add(new FruchtermanReingoldLayout("graph"));
        tptLayout.add(new RepaintAction());

        tptLayout.add(filter);
        
        ForceSimulator fsim = ((ForceDirectedLayout)tptLayout.get(0)).getForceSimulator();
        Force[] forces = fsim.getForces();
        
        final Force NBodyForce = forces[0];
        //final Force DragForce = forces[1];
        final Force SpringForce = forces[2];

//        SetForces: {
//            NBodyForce.setMinValue(0, -40.0f);
//            NBodyForce.setParameter(0, -40.0f);
//            NBodyForce.setParameter(1, -1);

//            SpringForce.setParameter(0, .00001f);
            SpringForce.setParameter(1, 200f);
//        }

        final ColorAction dataFill = new ColorAction("graph.nodes",
                VisualItem.FILLCOLOR, ColorLib.rgb(124, 252, 0));
        dataFill.add(VisualItem.HOVER, ColorLib.rgb(80, 200, 0));
        dataFill.add(VisualItem.FIXED, ColorLib.rgb(0, 124, 252));
      

        ColorAction nodeStroke = new ColorAction("graph.nodes", VisualItem.STROKECOLOR, ColorLib.rgb(255, 255, 255));
        nodeStroke.add(VisualItem.HOVER, ColorLib.rgb(226, 86, 0));
        nodeStroke.add(VisualItem.HIGHLIGHT, ColorLib.rgb(0, 200, 98));

        ColorAction text = new ColorAction("graph.nodes",
                VisualItem.TEXTCOLOR, ColorLib.gray(0));

        ColorAction edgeColor = new ColorAction("graph.edges",
                VisualItem.STROKECOLOR, ColorLib.gray(200));

        ColorAction edgeFill = new ColorAction("graph.edges",
                VisualItem.FILLCOLOR, ColorLib.gray(200));

        final ColorAction altDataFill = new ColorAction("graph.nodes", VisualItem.FILLCOLOR, ColorLib.rgb(124, 252, 0));
        altDataFill.add(ExpressionParser.predicate("[source]"), ColorLib.rgb(255, 0, 0));
        altDataFill.add(ExpressionParser.predicate("[target]"), ColorLib.rgb(0, 0, 255));

        DataSizeAction edgeWeight = new DataSizeAction("graph.edges", "flux", 10, Constants.LINEAR_SCALE);
        edgeWeight.setMinimumSize(1.0);
        edgeWeight.setMaximumSize(200);

	final ActionList color = new ActionList();
        color.add(edgeWeight);
        color.add(edgeFill);
        color.add(edgeColor);
        color.add(text);
        color.add(dataFill);

	final ActionList altColor = new ActionList();
	altColor.add(edgeWeight);
	altColor.add(edgeFill);
	altColor.add(edgeColor);
	altColor.add(text);
	altColor.add(altDataFill);

        m_vis.putAction("nodeStroke", nodeStroke);
        m_vis.putAction("tptLayout", tptLayout);
        m_vis.putAction("color", color);

        final DataSizeAction nodeSize = new DataSizeAction("graph.nodes",
                "flux", 20, Constants.LOG_SCALE);
        nodeSize.setMinimumSize(5);
        nodeSize.setMaximumSize(20.0);

        m_vis.putAction("nodeSize", nodeSize);

        Display display = new Display(m_vis);
        display.setSize(700,700);
        display.pan(350, 350);
        display.setForeground(Color.WHITE);
        display.setBackground(Color.WHITE);
        display.setHighQuality(true);

        display.addControlListener(new FocusControlWithDeselect(1));
        display.addControlListener(new DragControl());
        display.addControlListener(new PanControl());
        display.addControlListener(new ZoomControl());
        display.addControlListener(new ZoomToFitControl());
        display.addControlListener(new WheelZoomControl());
        display.addControlListener(new HoverActionControl("color"));
        
        JTextField numPathInput = new JTextField("Num Paths");
        numPathInput.addActionListener( new ActionListener() {
            public void actionPerformed( ActionEvent ae) {
                String input = ((JTextField)ae.getSource()).getText();

                int d = 1;
                try {
                    d = Integer.valueOf(input.trim()).intValue();
                } catch ( NumberFormatException nfe ) {
                    System.err.println( "Illegal number format "
                            + "in JTextField numPathInput");
                }
                numPaths = d;
                tptCalc.reset();
                setNumPaths( tptCalc, g, numPaths );
                
                m_vis.run("color");
            }
        });

        JToggleButton colorMode = new JToggleButton("Color Mode", false);
        colorMode.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent ae) {

                if (((JToggleButton)ae.getSource()).isSelected()) {
			m_vis.removeAction("color");
			m_vis.putAction("color", altColor);
                } else {
			m_vis.removeAction("color");
			m_vis.putAction("color", color);
                }
                m_vis.run("color");
            }
        });

        JToggleButton togglePics = new JToggleButton("Show Images", false);
        togglePics.addActionListener( new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    JToggleButton tp = (JToggleButton) ae.getSource();
                    if(!tp.isSelected())
                    {
                        m_vis.setRendererFactory(drf);
                        sr.setImageField(null);
                        ((DataSizeAction)m_vis.getAction("nodeSize")).setMaximumSize(50.0);
                        m_vis.run("nodeSize");
                    } else
                    {
			    /*
                        ((DataSizeAction)m_vis.getAction("nodeSize")).setMaximumSize(1.0);
                        m_vis.run("nodeSize");
                        sr.setImageField("image");
                        sr.setImagePosition(Constants.TOP);
                        double scale = m_vis.getDisplay(0).getScale();
                        if (scale < 1.0d) scale = 1.0d;
                        sr.getImageFactory().setMaxImageDimensions((int)(100*scale), (int)(100*scale));
                        
			 */
        		
                        TextImageItemRenderer tiir = new TextImageItemRenderer();
                        m_vis.setRendererFactory(new DefaultRendererFactory(tiir));
                        double scale = m_vis.getDisplay(0).getScale();
                        if (scale < 1.0d) scale = 1.0d;
                        tiir.setMaxImageDimensions((int)(100*scale), (int)(100*scale));
                        tiir.setImageSize(1 / (2 * scale));
                        tiir.setRoundedCorner(8, 8);

                        nodeSize.setMinimumSize(1);
                        nodeSize.setMaximumSize(1);
                        m_vis.run("nodeSize");

                    }
                     m_vis.run("color");
                }
        });

        
        JButton exportDisplay = new JButton("Save Image");
        exportDisplay.addActionListener( new ExportDisplayAction(display) );


        final DefaultTupleSet holder = new DefaultTupleSet();
        //DefaultTupleSet holder = new DefaultTupleSet();
        final TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS);
        final JToggleButton fixedBtn = new JToggleButton("Fix Position", false);
        fixedBtn.addActionListener( new ActionListener() {
            public void actionPerformed( ActionEvent ae ){

                //TupleSet focused = m_vis.getFocusGroup(Visualization.FOCUS_ITEMS);
                if ( focusGroup.getTupleCount() == 0) return;
                Tuple toFocus = (Tuple)focusGroup.tuples().next();

                if ( ((JToggleButton)ae.getSource()).isSelected() ) {
                    ((VisualItem)toFocus).setFixed(true);
                    holder.addTuple(toFocus);
                } else {
                    ((VisualItem)toFocus).setFixed(false);
                    holder.removeTuple(toFocus);
                }

                m_vis.run("color");
            }
        });
        fixedBtn.setSelected(false);
	fixedBtn.setEnabled(false);

        focusGroup.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem)
            {
			if (add.length > 0 && holder.containsTuple(add[0])) {//add[0].getBoolean(VisualItem.FIXED) )
				fixedBtn.setEnabled(true);
				fixedBtn.setSelected(true);
			} else if (add.length > 0) {
				fixedBtn.setEnabled(true);
				fixedBtn.setSelected(false);
			} else {
				fixedBtn.setEnabled(false);
			}

			m_vis.run("nodeStroke");
            }
        });

        Box southPanel = new Box(BoxLayout.X_AXIS);
        southPanel.add(fixedBtn);
        southPanel.add(togglePics);
        southPanel.add(exportDisplay);
        southPanel.add(colorMode);
        southPanel.add(numPathInput);

        //JSplitPane panel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, display, southPanel);

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(southPanel, BorderLayout.SOUTH);
        panel.add(display, BorderLayout.CENTER);
        panel.setBackground(Color.white);



        this.setContentPane(panel);

        m_vis.run("tptLayout");
        m_vis.run("color");
        m_vis.run("nodeSize");

        this.pack();
        this.setVisible(true);
    }

    private void setNumPaths( TPTFactoryCM tptCalc, Graph g, int numPaths ) {

         Column edgeField = g.getEdgeTable().getColumn("inTPT");
         Column  nodeField = g.getNodeTable().getColumn("inTPT");

         for ( int i = 0; i < edgeField.getRowCount(); ++i )
             edgeField.revertToDefault(i);


         for ( int i = 0; i < nodeField.getRowCount(); ++i )
             nodeField.revertToDefault(i);

         for (int i = 0; i < numPaths; ++i) {

            ArrayList<Edge> path = tptCalc.getNextEdge();
            if (path.isEmpty()) continue;

            path.get(0).getTargetNode().set("inTPT", true);
            for (Edge e : path) {
                e.set("inTPT", true);
                e.getSourceNode().set("inTPT", true);
            }
        }
    }

    private void processColumn( Column col, int size ) {

        double max = max(col);
        double min = min(col);
        
        double[] palette = new double[size+1];

        for (int i = 0; i < size+1; ++i)
            palette[i] = 2*i + 1.0d;

        double step = (max - min) / size;
        double base = 1.0;

        for (int i = 0; i < col.getRowCount(); ++i)
            col.setDouble(palette[(int)(col.getDouble(i) / step)], i);
    }

    private double max( Column col ) {

        double max = col.getDouble(0);

        for (int i = 1; i < col.getRowCount(); ++i) 
            if (col.getDouble(i) > max)
                max = col.getDouble(i);

        return max;
    }

    private double min( Column col ) {

        double min = col.getDouble(0);

        for (int i = 1; i < col.getRowCount(); ++i)
            if (col.getDouble(i) < min)
                min = col.getDouble(i);

        return min;
    }

    public static TupleSet toTupleSet( ArrayList<Tuple> source ) {

	TupleSet set = new DefaultTupleSet();

	for (Tuple t : source)
		set.addTuple(t);

	return set;
    }
}
