/*  Graph JavaScript framework, version 0.0.1_pre_r140
 *  (c) 2006 Aslak Hellesoy <aslak.hellesoy@gmail.com>
 *  (c) 2006 Dave Hoover <dave.hoover@gmail.com>
 *
 *  Graph is freely distributable under the terms of an MIT-style license.
 *  For details, see the Graph wiki: http://dev.buildpatterns.com/trac/wiki/GraphProject
 *
 *  This file is generated. See http://buildpatterns.com/svn/repos/graph/trunk/README
/*--------------------------------------------------------------------------*/


Graph = {};

GraphModel = Class.create();
GraphModel.prototype = {
  initialize: function() {
    this.nodeHash = $H({});
    this.edgeHash = $H({});
  },

  addNode: function(value) {
    var node = this.nodeHash[value];
    if(node == undefined) {
      node = new GraphNode(value);
      this.nodeHash[value] = node;
      this.cache();
    }
    return node;
  },

  // Uniqueness must be ensured by caller
  addEdge: function(source, target) {
    var s = this.addNode(source);
    var t = this.addNode(target);
    var edge = {source: s, target: t};
    this.edgeHash[this.edgeKey(source, target)] = edge;
    this.cache();
    return edge;
  },

  removeEdge: function(source, target) {
	var edge = this.edgeHash[this.edgeKey(source, target)];
	delete this.edgeHash[this.edgeKey(source, target)];
	this.cache();
	return edge;
  },

  edgeKey: function(source, target) {
    return source + "->" + target;
  },

  cache: function() {
	this.nodes = this.nodeHash.collect(function(pair){
      return pair[1];
	});
	this.edges = this.edgeHash.collect(function(pair){
      return pair[1];
	});
  }
};

GraphNode = Class.create();
GraphNode.prototype = {
  initialize: function(value) {
    this.value = value;
  }
};

GraphRenderer = {};
GraphRenderer.Default = Class.create();
GraphRenderer.Default.prototype = {
  setOptions: function(options) {
    this.options = {
      radius: 20,
      arrowHeadLength: null,
      arrowAngle: Math.PI/10,
      mecd: 10, // minimum edge-click distance
      //font: tahoma8,
      edgeColor: 'blue'
    }

    Object.extend(this.options, options || {});
  },

  initialize: function(element, graph, options) {
  
    this.tmpNodeHash = $H({});
    
    this.graph = graph;
	  this.setOptions(options);
    //this.ctx = element.getContext("2d");
    this.element = new Object();
    
    
    this.element.width = 250;
    this.element.height = 250;
    this.factorX = (this.element.width - 2 * this.options.radius) / (graph.layoutMaxX - graph.layoutMinX);
    
    this.factorY = (this.element.height - 2 * this.options.radius) / (graph.layoutMaxY - graph.layoutMinY);

    // Reposition nodes from virtual coords
    
    for (var i = 0; i < this.graph.nodes.length; i++) {
	  var node = this.graph.nodes[i];
	  node.center = this.translate([node.layoutPosX, node.layoutPosY]);
    }

  },

  translate: function(point) {
    return [
      (point[0] - this.graph.layoutMinX) * this.factorX + this.options.radius,
      (point[1] - this.graph.layoutMinY) * this.factorY + this.options.radius
    ];
  },

  rotate: function(point, length, angle) {
    var dx = length * Math.cos(angle);
    var dy = length * Math.sin(angle);
    return [point[0]+dx, point[1]+dy];
  },

  draw: function(svgGraph) {
    //this.ctx.clearRect(0, 0, this.element.width, this.element.height);
    for (var i = 0; i < this.graph.nodes.length; i++) {
      this.drawNode(this.graph.nodes[i],svgGraph);
    }
    for (var i = 0; i < this.graph.edges.length; i++) {
      this.drawEdge(this.graph.edges[i],svgGraph);
    }
  },

  drawNode: function(node,svgGraph) {
    var tmpNode = new Node(svgGraph);
    tmpNode.init1(node.center[0],node.center[1],node.value);
    
    var nodeID = this.tmpNodeHash[node.value];
    if(nodeID == undefined) {
      //node = new GraphNode(node.value);
      this.tmpNodeHash[node.value] = tmpNode.id;
    }
    
    return;
    // Draw the text (or if the value is an element, move it)
    if(typeof node.value == 'string') {
      // hack to make the text appear more centered.
	  //this.ctx.drawString(node.value, this.options.font, node.center[0]-10, node.center[1]-10);
    } else {
      node.value.style.position = 'absolute';
      node.value.style.left     = node.center[0] + 'px';
      node.value.style.top      = node.center[1] + 'px';
    }
    /*
    //this.ctx.strokeStyle = 'black'
    //this.ctx.beginPath();
    //this.ctx.arc(node.center[0], node.center[1], this.options.radius, 0, Math.PI*2, true);
    //this.ctx.closePath();
    //this.ctx.stroke();
    */
  },

  drawEdge: function(edge,svgGraph) {
    
    
    var source = edge.source.center;
    var target = edge.target.center;
    var sNodeID = this.tmpNodeHash[edge.source.value]
    var tNodeID = this.tmpNodeHash[edge.target.value]
    tmpEdge = new Line(svgGraph);
  
    tmpEdge.init1(source[0]+5,source[1]+5,target[0]+5,target[1]+5,svgGraph.getNodeByID(sNodeID),svgGraph.getNodeByID(tNodeID));
    
    //alert(source[0]+":"+source[1]);
    //alert(target[0]+":"+target[1]);
    return;
    /*
    // find the angle of the edge
    var tan = (target[1] - source[1]) / (target[0] - source[0]);
    var theta = Math.atan(tan);
    if(source[0] <= target[0]) {theta += Math.PI}
    source = this.rotate(source, -this.options.radius, theta);
    target = this.rotate(target, this.options.radius, theta);

    // draw the edge
    //var color = edge.color ? edge.color : this.options.edgeColor;
    //this.ctx.strokeStyle = color;
    //this.ctx.fillStyle = color;
    //this.ctx.lineWidth = 1.0;
    //this.ctx.beginPath();
    //this.ctx.moveTo(source[0], source[1]);
    //this.ctx.lineTo(target[0], target[1]);
    //this.ctx.stroke();

    this.drawArrowHead(theta, source[0], source[1], target[0], target[1]);
    */
  },

  drawArrowHead: function(theta, startx, starty, endx, endy) {
	var length = this.options.arrowHeadLength ? this.options.arrowHeadLength : this.options.radius;
    var top = this.rotate([endx, endy], length, theta + this.options.arrowAngle);
    var bottom = this.rotate([endx, endy], length, theta - this.options.arrowAngle);
    //this.ctx.beginPath();
    //this.ctx.moveTo(endx, endy);
    //this.ctx.lineTo(top[0], top[1]);
    //this.ctx.lineTo(bottom[0], bottom[1]);
    //this.ctx.fill();
  }

  
};

/*

CanvasRenderingContext2D.prototype.drawString=function(s, f, x, y){
	y=Math.round(y);
	var z=x=Math.round(x),t,i,j;
	if(!f.f){
		f.f=[t=0],i=0,j=f.w.length;
		while(++i<j)f.f[i]=t+=f.w[i-1];
	}
	s=s.split(''),i=0,j=s.length;
	while(i<j)if((t=f.c.indexOf(s[i++]))>=0)
		this.drawImage(f,f.f[t],0,f.w[t],f.height,x,y,f.w[t],f.height),x+=f.w[t];
		else if(s[i-1]=='\n')x=z,y+=f.h;
}
*/


Graph.Layout = {};
/*
 *  Layout algorithm ported from Graph::Layouter::Spring,
 *    http://search.cpan.org/~pasky/Graph-Layderer-0.02/
 *  The algorithm is based on a spring-style layouter of a Java-based social
 *  network tracker PieSpy written by Paul Mutton <paul@jibble.org>.
 */
GraphLayoutSpring = Class.create();
GraphLayoutSpring.prototype = {
  initialize: function(graph) {
    this.graph = graph;
    this.iterations = 500;
    this.maxRepulsiveForceDistance = 6;
    this.k = 2;
    this.c = 0.01;
    this.maxVertexMovement = 0.5;
  },

  layout: function() {
    this.layoutPrepare();
    for (var i = 0; i < this.iterations; i++) {
      this.layoutIteration();
    }
    this.layoutCalcBounds();
  },

  layoutPrepare: function() {
    for (var i = 0; i < this.graph.nodes.length; i++) {
      var node = this.graph.nodes[i];
      node.layoutPosX = 0;
      node.layoutPosY = 0;
      node.layoutForceX = 0;
      node.layoutForceY = 0;
    }
  },

  layoutCalcBounds: function() {
    var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;

    for (var i = 0; i < this.graph.nodes.length; i++) {
      var x = this.graph.nodes[i].layoutPosX;
      var y = this.graph.nodes[i].layoutPosY;

      if(x > maxx) maxx = x;
      if(x < minx) minx = x;
      if(y > maxy) maxy = y;
      if(y < miny) miny = y;
    }

    this.graph.layoutMinX = minx;
    this.graph.layoutMaxX = maxx;
    this.graph.layoutMinY = miny;
    this.graph.layoutMaxY = maxy;
  },

  layoutIteration: function() {
    // Forces on nodes due to node-node repulsions
    for (var i = 0; i < this.graph.nodes.length; i++) {
      var node1 = this.graph.nodes[i];
      for (var j = i + 1; j < this.graph.nodes.length; j++) {
        var node2 = this.graph.nodes[j];
        this.layoutRepulsive(node1, node2);
      }
    }
    // Forces on nodes due to edge attractions
    for (var i = 0; i < this.graph.edges.length; i++) {
      var edge = this.graph.edges[i];
      this.layoutAttractive(edge);
    }

    // Move by the given force
    for (var i = 0; i < this.graph.nodes.length; i++) {
      var node = this.graph.nodes[i];
      var xmove = this.c * node.layoutForceX;
      var ymove = this.c * node.layoutForceY;

      var max = this.maxVertexMovement;
      if(xmove > max) xmove = max;
      if(xmove < -max) xmove = -max;
      if(ymove > max) ymove = max;
      if(ymove < -max) ymove = -max;

      node.layoutPosX += xmove;
      node.layoutPosY += ymove;
      node.layoutForceX = 0;
      node.layoutForceY = 0;
    }
  },

  layoutRepulsive: function(node1, node2) {
    var dx = node2.layoutPosX - node1.layoutPosX;
    var dy = node2.layoutPosY - node1.layoutPosY;
    var d2 = dx * dx + dy * dy;
    if(d2 < 0.01) {
      dx = 0.1 * Math.random() + 0.1;
      dy = 0.1 * Math.random() + 0.1;
      var d2 = dx * dx + dy * dy;
    }
    var d = Math.sqrt(d2);
    if(d < this.maxRepulsiveForceDistance) {
      var repulsiveForce = this.k * this.k / d;
      node2.layoutForceX += repulsiveForce * dx / d;
      node2.layoutForceY += repulsiveForce * dy / d;
      node1.layoutForceX -= repulsiveForce * dx / d;
      node1.layoutForceY -= repulsiveForce * dy / d;
    }
  },

  layoutAttractive: function(edge) {
    var node1 = edge.source;
    var node2 = edge.target;

    var dx = node2.layoutPosX - node1.layoutPosX;
    var dy = node2.layoutPosY - node1.layoutPosY;
    var d2 = dx * dx + dy * dy;
    if(d2 < 0.01) {
      dx = 0.1 * Math.random() + 0.1;
      dy = 0.1 * Math.random() + 0.1;
      var d2 = dx * dx + dy * dy;
    }
    var d = Math.sqrt(d2);
    if(d > this.maxRepulsiveForceDistance) {
      d = this.maxRepulsiveForceDistance;
      d2 = d * d;
    }
    var attractiveForce = (d2 - this.k * this.k) / this.k;
    if(edge.weight == undefined || edge.weight < 1) edge.weight = 1;
    attractiveForce *= Math.log(edge.weight) * 0.5 + 1;

    node2.layoutForceX -= attractiveForce * dx / d;
    node2.layoutForceY -= attractiveForce * dy / d;
    node1.layoutForceX += attractiveForce * dx / d;
    node1.layoutForceY += attractiveForce * dy / d;
  }
};
