
package emn.fr.ascola.view;

import java.awt.BasicStroke;
import java.util.HashMap;

import javax.swing.JButton;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.layout.Layout;
import prefuse.action.layout.graph.BalloonTreeLayout;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;

/**
 * New viewer for architecture.
 * Only binary communications else should use hyperlinks
 * @author jroyer 29/5/2009
 * 1/6/2009 coloration for datatype/component
 * type is the identifier
 * 
 * TODO metre des multi-liens : voir STSLIB pour ca !!!
 * TODO mettre des infos sur signature ...
 */
public abstract class PrefuseViewer extends Display {
	private static final long serialVersionUID = 1L;
	
	// some constants
	public static final String STRING_GRAPH = "graph";
	public static final String STRING_NODES = "graph.nodes";
	public static final String STRING_EDGES = "graph.edges";
	public static final String STRING_TYPE = "type";
	// for edges it stores the type name to recolor the linkSearch
	public static final String STRING_NAME = "name";
	public static final String STRING_KIND = "kind";
	public static final String STRING_LEVEL = "level";
	public static final String STRING_COUNT = "count";
	public static final String linkSearch = "linkType";
	public static final int LABEL_MAX_SIZE = 10;
    public static final String STOP = "Stop Activity"; 
    public static final String START = "Start Activity"; 
    public static final double MAX_NODE_SIZE = 100;
    public static final double MIN_NODE_SIZE = 10;
    public static final String NULL_STRING = "";
    public static final String STRING_INNER = "inner";
    public static final String STRING_BASIC = "Default", STRING_SMALL = "Small", 
    				STRING_MIDDLE = "Middle", STRING_RADIAL = "Radial", STRING_CIRCLE = "Circle", 
    				STRING_BALLOON = "Balloon";
	// node schema
	protected Schema s_node = new Schema();
	// edge schema
	protected Schema s_edge = new Schema();
	
	// instantiation of the schemas
	protected Table nodedata;
	protected Table edgedata;
	// node counter nodes and edges
	protected int ind = 1;
	// Hashmap to store artefact node references
	protected HashMap<String, Integer> tableArtefact = new HashMap<String, Integer>();
	// table for counting multi edges
	protected int [][] table;
	
	public static final String linear = "linear";
	
	// to distinguish composite from communication
	public final static Predicate communication = ExpressionParser.predicate("INGROUP('" + STRING_EDGES + "') AND kind == false");
	public final static Predicate composite = ExpressionParser.predicate("INGROUP('" + STRING_EDGES + "') AND kind == true");
	//public final static Predicate composite = new NotPredicate(communication); pas glop !!!!
	public final static Predicate datatype = ExpressionParser.predicate("INGROUP('" + STRING_NODES + "') AND kind == false");
	public final static Predicate component = ExpressionParser.predicate("INGROUP('" + STRING_NODES + "') AND kind == true");
	
	/**
	 * button to control activity.
	 */
	protected final JButton stop = new JButton(STOP); 

	/**
	 * Default constructor.
	 */
	public PrefuseViewer() {
		super(new Visualization());
	}

	/**
	 * Display the view.
	 */
	abstract public void display();

	/**
	 * Transform the architecture into Prefuse datas.
	 * Should be called between instance creation and display
	 * TODO node size action ?
	 */
	public void makeIt() {
		//construction
		this.artefactAnalysis("TRUC", 10, true);
		this.artefactAnalysis("Machin", 9, false);
		this.artefactAnalysis("Bar", 9, false);
		// Composite TODO no need of label !!!
		this.edgeCreation(true, "foo", "TRUC", "Machin");
		this.edgeCreation(true, "bar", "TRUC", "Bar");
		// communication examples
		this.artefactAnalysis("A", 1, true);
		this.artefactAnalysis("B",  1, true);
		this.edgeCreation(false, "msg(a, b)", "TRUC", "A");
		this.edgeCreation(false, "envoi(themachin)", "B", "A");
		this.edgeCreation(false, "autre", "B", "A");
		// test size
		this.artefactAnalysis("10", 10, true);
		this.edgeCreation(true, "dix", "truc", "10");
		this.artefactAnalysis("20", 10, true);
		this.edgeCreation(true, "vingt", "truc", "20");
	} // end of makeIt

	/**
	 * Build the visual graph
	 */
	protected void buildGraph () {
		// directed graph creation 
		Graph g = new Graph(nodedata, edgedata, true, "DEFAULT_NODE_KEY",
				"DEFAULT_SOURCE_KEY", "DEFAULT_TARGET_KEY");
		this.m_vis.addGraph(STRING_GRAPH, g);
	}
	
	/**
	 * Define the data schema.
	 */
	protected void defineSchema() {
		// Schemas for nodes : artefacts and links
		this.s_node.addColumn("DEFAULT_NODE_KEY", int.class);
		// to size node TODO trouver un ajustement sur le niveau 
		this.s_node.addColumn(STRING_LEVEL, double.class);
		// the component type name this is the identifier
		this.s_node.addColumn(STRING_TYPE, String.class);
		// instance name
		//this.s_node.addColumn(STRING_NAME, String.class);
		// data type (false) or component type (true)
		this.s_node.addColumn(STRING_KIND, boolean.class);
		// and edges
		this.s_edge.addColumn("DEFAULT_SOURCE_KEY", int.class);
		this.s_edge.addColumn("DEFAULT_TARGET_KEY", int.class);
		// composite links (true) and communication links (false)
		this.s_edge.addColumn(STRING_KIND, boolean.class);
		this.s_edge.addColumn(STRING_NAME, String.class);
		this.s_edge.addColumn(STRING_COUNT, int.class);
		
		// to color neighbors
		s_node.addColumn("sourceColor", boolean.class);
		s_node.addColumn("targetColor", boolean.class);
		
		// schemas instantiation
		this.nodedata = s_node.instantiate();
		this.edgedata = s_edge.instantiate();
	}
	
	/**
	 * Artefact node creation.
	 * @param art artefact
	 * @param indice key
	 * @param type the type and identifier
	 * @param level in the hierarchy
	 */
	private void artefactNodeCreation(String type, int level, boolean data, int indice) {
		// 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);
	}
	
	/**
	 * 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) {
		// 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);
		}
		return indice;
	}
	
	/**
	 * Edge creation
	 * @param kind true for composite
	 * @param name
	 * @param from
	 * @param to
	 */
	public void edgeCreation(boolean kind, String lname, String from, String to) {
		// nodes should exist
		int src = this.tableArtefact.get(from);
		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, kind);
		this.edgedata.set(rowe, STRING_NAME, lname);
		this.edgedata.set(rowe, STRING_COUNT, 1);
	}
	
	/**
	 * Initialize table for count before adding communications
	 */
	public void setTableCount () {
		int nb = this.nodedata.getTupleCount() + 1;
		this.table = new int [nb] [nb];
	}
	
	/**
	 * Create edges and add the count for MultiEdgeRenderer.
	 * this is only for communications
	 * TODO
	 */
	public void edgeCommunication(String lname, String from, String to) {
		// nodes should exist
		int src = this.tableArtefact.get(from);
		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, false);
		this.edgedata.set(rowe, STRING_NAME, lname);
		this.edgedata.set(rowe, STRING_COUNT, table[src][tar]);
		// update count
		this.table[src][tar]++;
	}
	
	/**
	 * 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);
		// for edge composite
		EdgeRenderer edger = new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_FORWARD);
		edger.setDefaultLineWidth(4);
		edger.setArrowHeadSize(20, 20);
		// for edge communication
		EdgeRenderer other = new DashedEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		other.setDefaultLineWidth(2);
		other.setArrowHeadSize(15, 15);
		// for edges multiple and loops
		MultiEdgeRenderer otherM = new MultiEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
		otherM.setDefaultLineWidth(2);
		otherM.setArrowHeadSize(15, 15);
		// TODO
		// renderer
		DefaultRendererFactory drf = new DefaultRendererFactory(nodeRenderer);
		//  renderer for edges
		drf.add(composite, edger);
		//drf.add(communication, other); 
		drf.add(communication, otherM); 
		// for other node
		LabelRenderer otherRdr = new DataRenderer();
		otherRdr.setRoundedCorner((int) MIN_NODE_SIZE, (int) MIN_NODE_SIZE);
		drf.add(datatype, otherRdr);
		this.m_vis.setRendererFactory(drf);
	}
	
	// ---------- auxilliary inner classes 
 	/**
	 *  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) {
			String type = vi.getString(STRING_TYPE);
			int last = type.lastIndexOf(".");
			String name = type;
			if (last != -1) {
				name = type.substring(last + 1);
			}
			return "\n " +  name + " \n";
		}
		// modification border line
		public BasicStroke getStroke(VisualItem vi) {
			// TODO mettre le level ici !
			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(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, new float[]{5.0f, 5.0f}, 0.0f);
			}
		}
	
	/**
	 * Edge renderer for communications.
	 * @author jroyer
	 *
	 */
	public class DashedEdgeRenderer extends EdgeRenderer {
		public DashedEdgeRenderer (int edgetype, int arrowtype) {
			super(edgetype, arrowtype);
		}
		// redefine stroke
		public BasicStroke getStroke(VisualItem vi) {
			// only for communication
			if (!vi.getBoolean(STRING_KIND)) {
				return new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, new float[]{10.0f, 10.0f}, 0.0f);
			} else {
				return super.getStroke(vi);
			}
		}
	}
	
	// To adjust the edge length
	public class DefaultForceDirectedLayout extends ForceDirectedLayout {
		private int nodes = 10;
		
		public DefaultForceDirectedLayout(String group, boolean enforceBounds, int nb) {
			super(group, enforceBounds);
			this.nodes = nb;
		}
		
		/**
		 * Links size adjustement.
		 */
		protected float getSpringLength(EdgeItem e) {
			Node s = e.getSourceNode();
			Node t = e.getTargetNode();
			int degree = s.getDegree() + t.getDegree();
			int k = (degree / this.nodes);
			return 50 + 3 * this.nodes + 100 * k;
		}
	}
	
	/**
	 * Long fixe length for radial.
	 * @author jroyer
	 */
	public class LongRadialTreeLayout extends RadialTreeLayout {
		public LongRadialTreeLayout(String group) {
			super(group);
		}
		// try to parameterized with the size of the edges
		protected float getSpringLength(EdgeItem e) {
			return 600.0f;
		}
	}
	
	/**
	 * Choose the layout.
	 * @param the key of strength
	 * @param s size
	 */
	public Layout getLayout (String key, int s) {
		if (key.equals(STRING_BASIC)) {
			return new DefaultForceDirectedLayout(STRING_GRAPH, true, s);
		} else if (key.equals(STRING_CIRCLE)) {
			return new LongRadialTreeLayout(STRING_GRAPH);
		} else if (key.equals(STRING_RADIAL)) {
				LongRadialTreeLayout tmp = new LongRadialTreeLayout(STRING_GRAPH);
				//tmp.setAngularBounds(-Math.PI/2, Math.PI);
				tmp.setAngularBounds(-Math.PI/2, 5/3*Math.PI);
				return tmp;
		} else {
			// TODO see parameters!
			return new BalloonTreeLayout(STRING_GRAPH);
		}
	}

	
} // end of class PrefuseViewer
