package emn.fr.ascola.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

import emn.fr.ascola.view.BasicCommunicationViewer.ArrowColorAction;
import emn.fr.ascola.view.BasicCommunicationViewer.EdgeColorAction;
import emn.fr.ascola.view.BasicCommunicationViewer.NodeColorAction;
import emn.fr.ascola.view.PrefuseViewer.ComponentRenderer;
import emn.fr.ascola.view.PrefuseViewer.DashedEdgeRenderer;
import emn.fr.ascola.view.PrefuseViewer.DataRenderer;
import emn.fr.ascola.view.PrefuseViewer.SubtypingEdgeRenderer;

import prefuse.Constants;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.GroupAction;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.PolarLocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataSizeAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.Layout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.ui.JSearchPanel;
import prefuse.visual.VisualItem;
import prefuse.visual.sort.TreeDepthItemSorter;

/**
 * Radial view for set of trace links.
 * Need a connected graph else not really nice;
 * Node labelling is removed and impacted nodes are red colored.
 * Node can move, there is a simple search function for type names.
 * If you hit the impact button the impacted subgraph is shown.
 * A reset button to reinitialise the view and makes a zoom in
 * Node size modified to differentiate artefact and link
 * jcroyer 2/02/2009
 * 11/5/2009 add 1-to-1 links
 * 20/5/2009 restructuration
 * fix problem of reset and search coloring with null pointer
 * 26/5/2009 fix problem with clickable edges
 * add a NewFocusControl
 * 5/6/2009 adaptation for component extractor
 * 
 * Enlever les noms dans le LabelRenderer
 * 
  */
public class PrefuseRadialViewer extends BasicCommunicationViewer {
	private static final long serialVersionUID = 1L;

	 private ActionList filter;
	 // need a specific animate ?
	 private ActionList animate;
	 private CollapsedSubtreeLayout subLayout;
	 
	 /**
	  * Default constructor.
	  */
	 public PrefuseRadialViewer(String name) {
		 super(name);
	 }

	/**
	 * Constructor.
	 * @param lts
	 * @return 
	 */
    public void buildAndColor() {
    	// build the graph
		this.buildGraph();
		// renderer
		this.setRenderer();

		// --------------------------------------------------------------------
		// set up the visual operators
		// first set up all the color actions
		ColorAction nText = new ColorAction(STRING_NODES, VisualItem.TEXTCOLOR);
		nText.setDefaultColor(ColorLib.rgb(250, 100, 0));
		ColorAction nStroke = new ColorAction(STRING_NODES, VisualItem.STROKECOLOR);
		nStroke.setDefaultColor(ColorLib.gray(0));
		ColorAction nFill = new ColorAction(STRING_NODES, VisualItem.FILLCOLOR);
		nFill.setDefaultColor(ColorLib.rgb(200, 250, 200));
		// simple coloring for neighbors
		nFill.add(tocolorsources, ColorLib.rgb(100,255,250)); 
		nFill.add(tocolortargets, ColorLib.rgb(250,100,255)); 
		// egde and arrow coloration
		ColorAction nEdges = new ColorAction(STRING_EDGES, VisualItem.STROKECOLOR);
		nEdges.setDefaultColor(ColorLib.gray(0));
		ColorAction edgeArrowColor = new ColorAction(STRING_EDGES, VisualItem.FILLCOLOR);
		edgeArrowColor.setDefaultColor(ColorLib.gray(0));
		// font
	    FontAction fonts = new FontAction(STRING_NODES, FontLib.getFont("Times", 16));
	    
	    // tuning presentation dor datatype
	    fonts.add(datatype, FontLib.getFont("Tahoma", 11));
	    // change colours for the edge-node
	    nText.add(datatype, ColorLib.rgb(0, 0, 255));
	    nFill.add(datatype, ColorLib.rgb(150, 150, 100));
	    nStroke.add(datatype, ColorLib.gray(100));
	    
	    // TODO : bof pas convaincant 
	    // node size action
		final DataSizeAction dsa = new DataSizeAction(STRING_NODES, STRING_LEVEL);  
		dsa.setMaximumSize(MAX_NODE_SIZE);
		dsa.setMaximumSize(MIN_NODE_SIZE);
		// bundle the color actions
		ActionList draw = new ActionList();
		draw.add(nText);
		draw.add(nStroke);
		draw.add(nFill);
		draw.add(nEdges);
		draw.add(edgeArrowColor); 
		draw.add(fonts);
        draw.add(dsa);
		m_vis.putAction("draw", draw);
		
		// for node searching colors (see at the end the action definition)
        ItemAction nodeColor = new NodeColorAction(STRING_NODES);
		// recolor for search node 
        ActionList recolor = new ActionList();
        recolor.add(nFill);
        recolor.add(nStroke);
        recolor.add(nEdges);
        recolor.add(edgeArrowColor);
        recolor.add(nodeColor);
        recolor.add(nText);
        recolor.add(dsa);
        m_vis.putAction("recolor", recolor);
        // animate paint change (tempo for search)
        ActionList animatePaint = new ActionList(400);
        animatePaint.add(new ColorAnimator(STRING_NODES));
        animatePaint.add(new RepaintAction());
        m_vis.putAction("animatePaint", animatePaint);
        
		// for edge searching colors
        ItemAction nEdgesRecolor = new EdgeColorAction(STRING_EDGES);
        ItemAction edgeArrowRecolor = new ArrowColorAction(STRING_EDGES);
        ActionList edgerecolor = new ActionList();
        edgerecolor.add(nFill);
        edgerecolor.add(nStroke);
        edgerecolor.add(nText);
        edgerecolor.add(nEdgesRecolor);
        edgerecolor.add(edgeArrowRecolor);
        edgerecolor.add(dsa);
        m_vis.putAction("edgerecolor", edgerecolor);
        
        // create the tree layout action
        LongRadialTreeLayout treeLayout = new LongRadialTreeLayout(STRING_GRAPH);
        //treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
        m_vis.putAction("treeLayout", treeLayout);
        
         subLayout = new CollapsedSubtreeLayout(STRING_GRAPH);
        m_vis.putAction("subLayout", subLayout);
        
        // create the filtering and layout
        //ActionList filter = new ActionList();
        filter = new ActionList();
        filter.add(new TreeRootAction(STRING_GRAPH));
        filter.add(treeLayout);
        filter.add(subLayout);
        filter.add(nText);
        filter.add(nFill);
        filter.add(nStroke);
        filter.add(nEdges);
        filter.add(edgeArrowColor);
        filter.add(dsa);
        m_vis.putAction("filter", filter);
        
        // animated transition
        //ActionList animate = new ActionList(500);
        animate = new ActionList(500);
        animate.setPacingFunction(new SlowInSlowOutPacer());
        animate.add(new QualityControlAnimator());
        animate.add(new VisibilityAnimator(STRING_GRAPH));
        animate.add(new PolarLocationAnimator(STRING_NODES, linear));
        animate.add(new ColorAnimator(STRING_NODES));
        animate.add(new RepaintAction());
        m_vis.putAction("animate", animate);
        m_vis.alwaysRunAfter("filter", "animate");
        
         // initialize the display
		this.pan(250, 250);
		this.setHighQuality(true);
        this.setItemSorter(new TreeDepthItemSorter());
        this.addControlListener(new DragControl());
        this.addControlListener(new ZoomControl());
        this.addControlListener(new PanControl());
        this.addControlListener(new NewFocusControl(1, "filter"));
        
        // to do an initial zoom
        Point2D down = new Point2D.Float(100,100);
        double scale = getScale();
        //this.zoom(down, 0.4);
        this.zoom(down, 0.5/scale);
        
         // filter graph and perform layout
        m_vis.run("filter");
        
        // maintain a set of items that should be interpolated linearly
        // this isn't absolutely necessary, but makes the animations nicer
        // the PolarLocationAnimator should read this set and act accordingly
        m_vis.addFocusGroup(linear, new DefaultTupleSet());
        m_vis.getGroup(Visualization.FOCUS_ITEMS).addTupleSetListener(
            new TupleSetListener() {
                public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                    TupleSet linearInterp = m_vis.getGroup(linear);
                    if ( add.length < 1 ) return; linearInterp.clear();
                     for (Node n = (Node)add[0]; n!=null; n=n.getParent() ) {
                    	linearInterp.addTuple(n);
                    }
                }
            });
 	       
        // Define the search tuple and recolor the result of the search
       m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, thesearch);
       thesearch.addTupleSetListener(new TupleSetListener() {
           public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
        	   m_vis.cancel("animatePaint");
        	   //  change the stop state
        	   animate.cancel();
        	   stop.setText(START); 
        	   m_vis.run("recolor");
        	   m_vis.run("animatePaint");
           }
       });
       
       // define the link search and recolor the result 
       m_vis.addFocusGroup(linkSearch, theLinkSearch);
       theLinkSearch.addTupleSetListener(new TupleSetListener() {
           public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
        	   m_vis.cancel("animatePaint");
        	   //  change the stop state
        	   animate.cancel();
        	   stop.setText(START); 
        	   m_vis.run("edgerecolor");
        	   m_vis.run("animatePaint");
           }
       });

    } // end of PrefuseRadialViewerAction constructor
    

	/**
	 * The display function.
	 * Change the itemclicked !
	 */
	public void display() {
		this.buildAndColor();
		// name the frame 
		JFrame frame = new JFrame("Radial View for " + this.name);
		
		// -----------
		// create a node search panel for artefact node by name
        final TupleSet ts = this.m_vis.getGroup(STRING_NODES);
        // must select only artefactNode in this set and use it to query
        final SearchQueryBinding sq = new SearchQueryBinding((Table) ts, STRING_TYPE,
             (SearchTupleSet) this.m_vis.getGroup(Visualization.SEARCH_ITEMS));
        search = sq.createSearchPanel();
        search.setLabelText("Search component type:");
        search.setShowResultCount(true);
        search.setBorder(BorderFactory.createEmptyBorder(5,5,4,0));
        search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));
        // -----------
        
	    // create a link type  search panel and recolor edges 
        SearchQueryBinding sq2 = new SearchQueryBinding(
             (Table) this.m_vis.getGroup(STRING_EDGES), STRING_NAME,
             (SearchTupleSet) this.m_vis.getGroup(linkSearch));
        search2 = sq2.createSearchPanel();
        search2.setLabelText("Search link name:");
        search2.setShowResultCount(true);
        search2.setBorder(BorderFactory.createEmptyBorder(5,5,4,0));
        search2.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));
		// -----------
        
		// add a vue for information and the controler
		final ATFItemView lavue = new ATFItemView();
		
		this.addControlListener(new ControlAdapter() {
			public void itemEntered(VisualItem item, MouseEvent e) {
				//System.out.println(" itemEntered " + item);
				lavue.clean();
				lavue.setInfo(item);
			} // ------end itemEntered
			// add another controller with popuMenu on clicking middle button
			public void itemClicked(final VisualItem item, final MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON2) {
					JPopupMenu jpop = new JPopupMenu();
					// for communication edges
					if (item.getGroup().equals(PrefuseViewer.STRING_EDGES)
							&& (item.getInt(STRING_KIND) == COMMUNICATION)) {
						jpop.add("List of communications");
						jpop.addSeparator();
						for (String st : (Vector<String>) item.get(STRING_COMM)) {
							jpop.add(st);
						}
					// provided menu for component and data type nodes
					} else if (item.getGroup().equals(
							PrefuseViewer.STRING_NODES)) {
						jpop.add("Provided interface");
						jpop.addSeparator();
						for (String st : (Vector<String>) item.get(STRING_PROVIDED)) {
							jpop.add(st);
						}
					}
					jpop.show(e.getComponent(), e.getX(), e.getY());
				}
			} // ----- end 
		});	 
       
	    // stop/start button
        stop.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e){ 
        		JButton btn = (JButton)e.getSource(); 
        		if(STOP.equals(btn.getText())){ 
         			animate.cancel(); 
        			btn.setText(START); 
        		} else if(START.equals(btn.getText())){ 
        			m_vis.run("draw");
        			animate.run(); 
        			btn.setText(STOP); 
        		} 
        	} 
        });
 
 		// add a box in the frame with sub elements
	    Box box = new Box(BoxLayout.Y_AXIS);
	    lavue.setAlignmentX(FlowLayout.LEFT);
	    //must be consistent with the size of the view
	    lavue.setPreferredSize(new Dimension(800, 50+50));   
	    box.add(lavue);
	    this.setAlignmentX(FlowLayout.LEFT);
	    box.add(this);
	    // artefact search
	    search.setPreferredSize(new Dimension(300, 30));
 	    search.setMaximumSize(new Dimension(500, 30));
 	    search.setAlignmentX(FlowLayout.LEFT);
	    // link type panel
	    search2.setPreferredSize(new Dimension(300, 30));
 	    search2.setMaximumSize(new Dimension(500, 30));
 	    search2.setAlignmentX(FlowLayout.LEFT);
	    //  define a column of things 
 	    Box bottom = new Box(BoxLayout.Y_AXIS);
 	    bottom.setAlignmentX(FlowLayout.LEFT);
 	    Box thebuttons = new Box(BoxLayout.X_AXIS);
 	    thebuttons.setAlignmentX(FlowLayout.LEFT);
        thebuttons.add(stop);         
        //thebuttons.add(redraw); 
        // add in bottom panel
        bottom.add(thebuttons);  	    
        bottom.add(search);  	    
        bottom.add(search2);
        box.add(bottom);
		// ------
		frame.setLocation(50, 50);
		frame.setSize(800, 750);
		frame.setContentPane(box);
		frame.pack();
		frame.setVisible(true);
	} // end of display method
	
    // ------------------------------------------------------------------------
    
    /**
     * Switch the root of the tree by requesting a new spanning tree
     * at the desired root
     */
    public static class TreeRootAction extends GroupAction {
        public TreeRootAction(String graphGroup) {
            super(graphGroup);
        }
        public void run(double frac) {
            TupleSet focus = m_vis.getGroup(Visualization.FOCUS_ITEMS);
            if ( focus==null || focus.getTupleCount() == 0 ) return;
            
            // m_group is the current group (STRING_GRAPH)
            Graph g = (Graph)m_vis.getGroup(m_group);
            Node f = null;
            Iterator tuples = focus.tuples();
            while (tuples.hasNext() && !g.containsTuple(f=(Node)tuples.next()))
            {
                f = null;
            }
            if ( f == null ) return;
            g.getSpanningTree(f);
        }
    }
    
	/**
	 *  Default label renderer for artefact: information and thickness adjustement.
	 *  Round node for artefacts
	 */
	public class ComponentRenderer extends LabelRenderer {
		// constructor
		public ComponentRenderer() {
			super();
		}
		// node labelling
		public String getText(VisualItem vi) {
			return " " + vi.getInt("DEFAULT_NODE_KEY") + " ";
		}
		// modification border line
		public BasicStroke getStroke(VisualItem vi) {
			return  new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
		}
	} // end MyLabelRenderer class
	
	/**
	 * Renderer for data type.
	 */
	public class DataRenderer extends ComponentRenderer {
		// modification border line
		public BasicStroke getStroke(VisualItem vi) {
			return  new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, new float[]{5.0f, 5.0f}, 0.0f);
			}
		}
	
	/**
	 * Define node and label renderer.
	 * @param artefactumbering
	 * @param linkNumbering
	 */
	public void setRenderer() {
	    // interaction for edges
       // this.m_vis.setInteractive(STRING_EDGES, null, true);
		LabelRenderer nodeRenderer;
		nodeRenderer = new ComponentRenderer();
		nodeRenderer.setHorizontalAlignment(Constants.CENTER);
		nodeRenderer.setRoundedCorner((int) MIN_NODE_SIZE, (int) MIN_NODE_SIZE);
		// for other node
		LabelRenderer otherRdr = new DataRenderer();
		otherRdr.setRoundedCorner((int) MIN_NODE_SIZE, (int) MIN_NODE_SIZE);
		// multiple for edge composite
		MultiEdgeRenderer edgerM = new MultiEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		edgerM.setDefaultLineWidth(4);
		edgerM.setArrowHeadSize(20, 20);
		// for edges multiple and loops
		//MultiDashedEdgeRenderer otherM = new MultiDashedEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		// simple edge for communications
		DashedEdgeRenderer otherM = new DashedEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		otherM.setDefaultLineWidth(3);
		otherM.setArrowHeadSize(15, 15);
		//  add renderer for subtyping
		EdgeRenderer subt = new SubtypingEdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_FORWARD);
		subt.setDefaultLineWidth(4);
		subt.setArrowHeadSize(20, 20);
		// renderer
		DefaultRendererFactory drf = new DefaultRendererFactory(nodeRenderer);
		drf.add(datatype, otherRdr);
		//  renderer for edges
		drf.add(composite, edgerM);
		drf.add(communication, otherM); 
		drf.add(subtyping, subt);
		// set the renderer
		this.m_vis.setRendererFactory(drf);
	}
 
    /**
     * New focus control filtering the egdes.
     * To avoid problems with focus group and tree root action
     * @author jroyer
     */
    public static class NewFocusControl extends FocusControl {
    	public NewFocusControl(int i, String string) {
			super(i, string);
		}
		protected boolean filterCheck(VisualItem item) {
    		return item.getGroup().equals(PrefuseViewer.STRING_NODES);
    	}
    }
 
} // end of class PrefuseRadialGraphView
