package emn.fr.ascola.view;

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.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 emn.fr.ascola.view.PrefuseViewer.DefaultForceDirectedLayout;

import prefuse.Constants;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataSizeAction;
import prefuse.action.assignment.FontAction;
import prefuse.activity.Activity;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
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.NotPredicate;
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.TupleSet;

import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.ui.JSearchPanel;
import prefuse.visual.VisualItem;

/**
 * @author jroyer
 * 29/5/2009 from BasicPrefuseViewer for components
 * 30/5/2009 split with makeIt
 * 1/6/2009
 * Simplify communication 
 * add a menu to list the communications
 * add a popup for provided
 * 
 * TODO default circle radial sont interessants
  */

public class BasicCommunicationViewer extends PrefuseViewer {
	private static final long serialVersionUID = 1L;

	/**
	 * To get access activity. 
	 */
	private ActionList animate = new ActionList(Activity.INFINITY);

	/**
	 * Default constructor.
	 */
	public BasicCommunicationViewer(String name) {
		super(name);
		// define the schema and instantiate it
		this.defineSchema();
	}
	
	/**
	 * To build and color.
	 */
	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);
 		
 		// now create the main animate routine
		// edge layout 
        animate.add(new DefaultForceDirectedLayout(STRING_GRAPH, true, 100));
		animate.add(new RepaintAction());
		// for hightligthing current node and neighbors
		animate.add(nFill);
 
		m_vis.putAction("animate", animate);
		m_vis.runAfter("draw", "animate");

		// set up the display
		this.pan(250, 250);
		this.setHighQuality(true);
		this.addControlListener(new ZoomControl());
		this.addControlListener(new PanControl());
		this.addControlListener(new DragControl());
		// for hightlighting neighbours
		//this.addControlListener(new NeighborHighlightControl());
	       
        // to do an initial zoom
        Point2D down = new Point2D.Float(100,100);
        this.zoom(down, 0.2);
        
		// set things running
		m_vis.run("draw");
	       
        // 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 constructor BasicGraphViewer.buildAndColor


	/**
	 * The display function.
	 */
	public void display() {
		this.buildAndColor();
		// name the frame 
		JFrame frame = new JFrame("Basic 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);
				// should be a nodes (artefact or link)
				if (item.getGroup().equals(PrefuseViewer.STRING_NODES)) {
					// color source and targets 
					// TODO couleur composite // communication 
					Node n = (Node) item;
					n.setBoolean("sourceColor", true);
					Graph graph = n.getGraph();
					// can have several 
					Iterator links = graph.outNeighbors(n);
					while (links.hasNext()) {
						Node loc = (Node) links.next();
						loc.setBoolean("targetColor", true);
					}
				} else {
					// for a binary edge
					Edge ed = (Edge) item;
					ed.getSourceNode().setBoolean("sourceColor", true);
					ed.getTargetNode().setBoolean("targetColor", true);
				}
			} // ------end itemEntered
			// item exited
			public void itemExited(VisualItem item, MouseEvent e) {
				if (item.getGroup().equals(PrefuseViewer.STRING_NODES)) {
					// for a node 
					Node n = (Node) item;
					n.setBoolean("sourceColor", false);
					Graph graph = n.getGraph();
					// can have several 
					Iterator links = graph.outNeighbors(n);
					while (links.hasNext()) {
						Node loc = (Node) links.next();
						loc.setBoolean("targetColor", false);
					}
				} else {
					// for a binary edge 
					Edge ed = (Edge) item;
					ed.getSourceNode().setBoolean("sourceColor", false);
					ed.getTargetNode().setBoolean("targetColor", false);
				}
			} // ------end itemExited
			// add another controller with popuMenu
			public void itemClicked(VisualItem item, MouseEvent e) {
				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);
					}
					//jpop.show(e.getComponent(), e.getX(), e.getY());
					// provided menu for component and data type nodes
				} else if (item.getGroup().equals(PrefuseViewer.STRING_NODES)) { 
					//	&& item.getBoolean(STRING_KIND)) {
					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 itemClicked
		});	 
       
	    // 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(600, 50+50));   
	    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(600, 750);
		frame.setSize(800, 750);
		frame.setContentPane(box);
		frame.pack();
		frame.setVisible(true);
	} // end of display method
	
	/**
	 * Create a communication edge and store information
	 * Used with BasicCommunicationViewer
	 */
	public void edgeCommunication(String lname, String from, String to, 
			Vector<String> comms) {
		// nodes should exist
		if (!this.tableArtefact.containsKey(from)) {
			System.err.println("BasicCommunicationViewer.edgeCommunication " + from + " not existing node");
		}
		int src = this.tableArtefact.get(from);
		if (!this.tableArtefact.containsKey(from)) {
			System.err.println("BasicCommunicationViewer.edgeCommunication " + to + " not existing node");
		}
		int tar = this.tableArtefact.get(to);
		// generate simple directional link
		int rowe = this.edgedata.addRow();
		this.edgedata.set(rowe, "DEFAULT_SOURCE_KEY", src);
		this.edgedata.set(rowe, "DEFAULT_TARGET_KEY", tar);
		this.edgedata.set(rowe, STRING_KIND, COMMUNICATION);
		this.edgedata.set(rowe, STRING_NAME, lname);
		this.edgedata.set(rowe, STRING_COUNT, table[src][tar]);	
		// update count
		this.table[src][tar]++;
		this.edgedata.set(rowe, STRING_PTYPES, "");
		this.edgedata.set(rowe, STRING_RTYPE, "");
		this.edgedata.set(rowe, STRING_COMM, comms);
		this.edgedata.set(rowe, STRING_EMITTER, from);
		this.edgedata.set(rowe, STRING_RECEIVER, to);
	}
	
	/**
	 * Artefact node creation.
	 * @param indice key
	 * @param type the type and identifier
	 * @param level in the hierarchy
	 * @param data data type or component
	 * @param provided interface
	 */
	public void artefactNodeCreation(String type, int level, boolean data, int indice,
			Vector<String> provided) {
		// artefact node creation
		int row = this.nodedata.addRow();
		this.nodedata.set(row, "DEFAULT_NODE_KEY", indice);
		// use level here
		this.nodedata.set(row, STRING_LEVEL, MAX_NODE_SIZE);
		this.nodedata.set(row, STRING_TYPE, type);
		this.nodedata.set(row, STRING_KIND, data);
		this.nodedata.set(row, STRING_PROVIDED, provided);
		this.nodedata.set(row, STRING_REQUIRED, null);
	}
	
	/**
	 * Analyse an artefact, get its name, if already created
	 * and else create a new node for it.
	 * HYPO component names are unique !!!!!!
	 * @param art
	 * @return 
	 * @return artefact node indice
	 * USELESS ? at least return ?
	 */
	public int artefactAnalysis(String type, int level, boolean data, Vector<String> provided) {
		// check if artefact already in it and compute dot node
		// indice
		boolean check = this.tableArtefact.containsKey(type);
		int indice;
		if (check) {
			indice = this.tableArtefact.get(type);
		} else {
			indice = this.ind;
			this.tableArtefact.put(type, indice);
			this.ind++;
			// artefact node creation
			this.artefactNodeCreation(type, level, data, indice, provided);
		}
		return indice;
	}
	
	// ---------- auxilliary inner classes 

	/**
     * Set node fill colors for searching
     */
    public static class NodeColorAction extends ColorAction {
        public NodeColorAction(String group) {
            super(group, VisualItem.FILLCOLOR, ColorLib.rgba(255,255,255,0));
            add("ingroup('_search_')", ColorLib.rgb(255,100,100));
         }
    } 
    
    /**
     * Set edges fill colors
     */
    public static class EdgeColorAction extends ColorAction {
        public EdgeColorAction(String group) {
        	super(group, VisualItem.STROKECOLOR, ColorLib.gray(0));
    		add(linkSearchPredicate, ColorLib.rgb(255,100,100));
    		add(notLinkSearchPredicate, ColorLib.gray(255));
        }
    } 
    
    /**
     * Set arrow fill colors
     */
    public static class ArrowColorAction extends ColorAction {
        public ArrowColorAction(String group) {
        	super(group, VisualItem.FILLCOLOR, ColorLib.gray(0));
    		add(linkSearchPredicate, ColorLib.rgb(255,100,100));
    		add(notLinkSearchPredicate, ColorLib.gray(255));
        }
    } 
 
} // end of PrefuseLinksGraphViewer class
