/*
 * TouchGraph
 * TouchGraph Visualization of data. DOM / Javascript manipulations only, no canvas or SVG required.
 * Dependency: jQuery <http://jQuery.com>
 * Creator - David Decraene
 * Version 0.1
 * Website: http://Ontologyonline.org
 * Thanks to Mathieu 'P01' HENRI <http://www.p01.org/articles/DHTML_techniques/Drawing_lines_in_JavaScript/> for the script that enables DOM-based diagonal lines.
 * Thanks to Sean McCullough for the force directed graph explanation on his blog: http://www.cricketschirping.com/weblog/?p=545
 */


function TouchGraph(el, options){
	var self = this;
	this.settings = $.extend({
		G: 25,
		radiusScale: 1,
		defaultRadius : 15,
		massScale : 1,
		stiffness : 0.05,
		defaultEdgeLength : 50,
		frameRate : 100,
		focusClass : "touchgraph-focus",
		onCreateNode : function($node, id, data){
		}
	}, options);
	this.el = el.css("position", "relative");
	this.Model = {};
	this.Edges = [];

	this.position = this.el.offset();
	this.width = this.el.width();
	this.height = this.el.height();

	var lineImages = [];


	this.Edges.get = function(id1, id2)
	{
		for(var i=0;i<self.Edges.length;i++){
			var edge = self.Edges[i];
			if(edge.node1.id == id1 && edge.node2.id == id2) return edge;
			if(edge.node2.id == id1 && edge.node1.id == id2) return edge;
		}
		return null;
	}

	this.loadImages = function($img){
		$img.each(function(){
			lineImages.push(this.src);
		});
	}

	this.updateLine = function( $lineElement, posA, posB )
		{
			var
				Ax = posA.left, Ay = posA.top, Bx = posB.left, By = posB.top,
				xMin		= Math.min( Ax, Bx ),
				yMin		= Math.min( Ay, By ),
				xMax		= Math.max( Ax, Bx ),
				yMax		= Math.max( Ay, By ),
				boxWidth	= Math.max( xMax-xMin, 1 ),
				boxHeight	= Math.max( yMax-yMin, 1 ),
				tmp			= Math.min( boxWidth, boxHeight, 256 ),
				lineIndex	= (Bx-Ax)*(By-Ay)<0?0:1;

			while( tmp>>=1 )
				{ lineIndex+=2; }

			$lineElement.attr("src",  lineImages[lineIndex]);
			$lineElement.css({width: boxWidth	+"px", height: boxHeight	+"px", left: xMin		+"px", top: yMin		+"px"});
	};

	this.parsejOWL = function(concept, depth){
		if(depth == undefined) depth = 5;
		var JSON = {};
		JSON[concept.name] = {ch : [], focus : true};

		function descend(concept, level){
			var newdepth = level - 1;
			if(level > 0) concept.children().each(function(){
				JSON[this.name] = { ch: []};
				JSON[concept.name].ch.push(this.name);
				descend(this, newdepth);
			});		
		}

		function ascend(concept, level){
			var newdepth = level - 1;
			if(level > 0 && concept.parents) 
			{
				concept.hierarchy(); //improves reasoning
				concept.parents().each(function(){
					if(!JSON[this.name]) JSON[this.name] = { ch: []};
					JSON[this.name].ch.push(concept.name);
					ascend(this, newdepth);
				});	
			}
		}

		descend(concept, 1);	
		ascend(concept, depth);
		this.parseJSON(JSON);
	
	}

	this.addjOWLChildren = function(concept, maxcount){
		var self = this;
		if(maxcount == undefined) maxcount = -1;
		if(concept.children) concept.children().each(function(){				
				if(maxcount != 0) { 
					var child = this;
					if(self.addChildren(concept.name, [this.name])) { 
						maxcount--; 					
						};
					this.parents().each(function(){
						self.mapParent(child.name, this.name);
					});
				}
		});
		if(concept.parents) concept.parents().each(function(){
			self.mapParent(this.name, concept.name);
		});
	}

	this.mapParent = function(entry, parent){
		if(this.Model[parent] && !this.Edges.get(parent, entry)){
				new TouchGraph.Edge(this.Model[parent].node, this.Model[entry].node, this);
			}	
	}
	/** children: array of strings, denoting child ids, or JSON object conforming expected data model syntax {"id" : {ch: [], ...}, "id2" : ...}*/
	this.addChildren = function(entry, children){
		var count = 0;
		var root = this.Model[entry].node;
		if (children instanceof Array)
		{
			for(var i=0;i<children.length;i++){
				var child = children[i];
				if(!this.Model[child]) {
					this.Model[child] = {ch : []};
					new TouchGraph.Node(child, this); count++; 
				}				
				if(!this.Edges.get(entry, child)){
					new TouchGraph.Edge(root, this.Model[child].node, this);
				}
			}
		} else {
			for(x in children){
				if(this.Model[x]){ $.extend(this.Model[x], children[x]);}
				else {
					this.Model[x] = children[x];
					new TouchGraph.Node(x, this);
				}
				count++;								
				if(!this.Edges.get(entry, x)){
					new TouchGraph.Edge(root, this.Model[x].node, this);
				}
			}
		}
		return count;
	}
	/** Format: {"id" : { ch: ["child1", "child2"]}, "id2" : ... }*/
	this.parseJSON = function(JSON){
		this.Model = JSON;
		for(n in JSON){
			new TouchGraph.Node(n, this);			
			if(JSON[n].focus) { 
				this.Model[n].el.addClass(this.settings.focusClass);
				this.Model[n].node.position = TouchGraph.position(this.Model[n].el, new TouchGraph.Vector(this.width/2, this.height/2));
				}
			}
		for(n in JSON){ 			
			var edges = JSON[n].ch;
			for(var i = 0;i<edges.length;i++){
				new TouchGraph.Edge(this.Model[n].node, this.Model[edges[i]].node, this);
			}
		}
	}

	this.edgesFrom = function(node){
		var edges = [];
		for(var i=0;i<this.Edges.length;i++){
			if(Edges[i].node1.id == node.id) edges.push(Edges[i]);
		}
		return edges;
	}
	this.edgesTo = function(node){
		var edges = [];
		for(var i=0;i<this.Edges.length;i++){
			if(Edges[i].node2.id == node.id) edges.push(Edges[i]);
		}
		return edges;
	}
	this.calculate = function(){
		for(x in this.Model){
			var node = this.Model[x].node;
			node.force = new TouchGraph.Vector(0, 0);
			var edges = this.edgesFrom(node);
			for(var i=0;i<edges.length;i++){
				node.force.add(edges[i].forceFrom());
			}
			edges = this.edgesTo(node);
			for(var i=0;i<edges.length;i++){
				node.force.add(edges[i].forceTo());
			}
		}
		for(x in this.Model){
			var node1 = this.Model[x].node;
			for(y in this.Model){
				var node2 = this.Model[y].node;
				if(x != y){
					node1.force.add(node1.forceTo(node2));
				}
			}
		}
		for(x in this.Model){
			this.Model[x].node.draw(); 
		}
		for(var i=0;i<this.Edges.length;i++){
			Edges[i].draw(); 
		}

	}

	this.start = function(){		
		setInterval(this.calculate, this.settings.frameRate);
	}
	return this;
}


TouchGraph.position = function(node, pos){
	node.css({left: pos.left+"px", top:pos.top+"px"});
	return pos;
}

TouchGraph.getCenter = function(item){
	return new TouchGraph.Vector(item.position.left + item.width/2, item.position.top + item.height/2);
}

TouchGraph.getDistance = function(node1, node2){
	return new TouchGraph.Vector(node2.position.left - node1.position.left, node2.position.top - node1.position.top);
}

TouchGraph.Vector = function(left, top){
	this.left = left; this.top = top;
}

TouchGraph.Vector.prototype = {
	length : function(){
		return Math.sqrt(this.left*this.left + this.top*this.top);
	},
	add : function(vector){
		this.left += vector.left;
		this.top += vector.top;
		return this;
	}
}

TouchGraph.Node = function(id, graph){	
	this.id = id;
	this.graph = graph;
	if(!graph.Model[id]) graph.Model[id] = {ch : []};
	this.position = new TouchGraph.Vector(Math.random()*graph.width, Math.random()*graph.height);
	this.el = graph.Model[id].el = $("<div class='node' id='"+id+"'/>").appendTo(graph.el);
	graph.settings.onCreateNode(this.el, id, graph.Model[id]);
	TouchGraph.position(this.el, this.position);
	this.force = new TouchGraph.Vector(0, 0);
	this.mass = 1; 
	this.massChange();
	graph.Model[id].node = this;
}

TouchGraph.Node.prototype = {
	addMass : function(m){
		this.mass += m*this.graph.settings.massScale;
		this.massChange();
	},
	massChange : function(){
		var radius = this.mass*this.graph.settings.radiusScale;	  
		this.width = this.height = this.graph.settings.defaultRadius  + radius;
		this.el.css({width: this.width, height: this.height});	
	},
	forceTo :  function(node){
		var d = TouchGraph.getDistance(this, node);
		var r = d.length();
		if(!r) return new TouchGraph.Vector(0, 0);
		var f = this.graph.settings.G * (this.mass * node.mass) / (r*r);
		return new TouchGraph.Vector(-d.left*f, -d.top*f);		
	
	},
	draw : function(){
		this.position = this.position.add(this.force);
		TouchGraph.position(this.el, this.position);
	}
}

TouchGraph.Edge = function(node1, node2, graph){
	this.node1 = node1;
	this.node2 = node2;
	this.node1.addMass(1);
	this.graph = graph;
	this.id = 'edge-'+node1.id+'-'+node2.id;
	this.stiffness = 0.1;
	this.naturalLength = this.graph.settings.defaultEdgeLength;
	this.el = $('#'+this.id);
	if(!this.el.length){
		this.el = $("<img id='"+this.id+"' class='line' />").appendTo(graph.el);
	}
	graph.Edges.push(this);
}

TouchGraph.Edge.prototype = {
	forceTo : function(){
		var d = TouchGraph.getDistance(this.node1, this.node2);
		var r = d.length();
		var f = this.graph.settings.stiffness * (r - this.naturalLength);
		return new TouchGraph.Vector(-f*d.left/r, -f*d.top/r);
	},
	forceFrom : function(){
		var d = TouchGraph.getDistance(this.node1, this.node2);
		var r = d.length();
		var f = this.graph.settings.stiffness * (r - this.naturalLength);
		return new TouchGraph.Vector(f*d.left/r, f*d.top/r);	
	},
	draw : function(){
		this.graph.updateLine(this.el, TouchGraph.getCenter(this.node1), TouchGraph.getCenter(this.node2));
	}
}