// http://js-graph-it.sf.net 
//
// // License: GNU Library or Lesser General Public License (LGPL)
//

/*********************
 * browser detection *
 *********************/
JSGraphIt = function() {
	return this;
}

//var ie=document.all;
JSGraphIt.nn6 = document.getElementById&&!document.all;

/****************************************************
 * This class is a scanner for the visitor pattern. *
 ****************************************************/
 
/**
 * Constructor, parameters are:
 * visitor: the visitor implementation, it must be a class with a visit(element) method.
 * scanElementsOnly: a flag telling whether to scan html elements only or all html nodes.
 */
JSGraphIt.DocumentScanner = function(visitor, scanElementsOnly)
{
	this.visitor = visitor;
	this.scanElementsOnly = scanElementsOnly;

	/**
	 * Scans the element
	 */
	this.scan = function(element)
	{
		var i;
		if(this.visitor.visit(element))
		{
			// visit child elements
			var children = element.childNodes;
			for(i = 0; i < children.length; i++)
			{
				if(!this.scanElementsOnly || children[i].nodeType == 1)
				{
					this.scan(children[i]);
				}
			}
		}		
	}	
}

/*****************
 * drag and drop *
 *****************/
 
JSGraphIt.isdrag = false;		// this flag indicates that the mouse movement is actually a drag.
JSGraphIt.mouseStartX = -1;		// mouse position when drag starts
JSGraphIt.mouseStartY = -1;
JSGraphIt.elementStartX = -1;	// element position when drag starts
JSGraphIt.elementStartY = -1;

/**
 * the html element being dragged.
 */
JSGraphIt.elementToMove = null;

/**
 * an array containing the blocks being dragged. This is used to notify them of move.
 */
JSGraphIt.blocksToMove = [];

/**
 * this variable stores the orginal z-index of the object being dragged in order
 * to restore it upon drop.
 */ 
JSGraphIt.originalZIndex = 0;

/**
 * an array containing bounds to be respected while dragging elements,
 * these bounds are left, top, left + width, top + height of the parent element.
 */
JSGraphIt.bounds = new Array(4);

/**
 * this visitor is used to find blocks nested in the element being moved.
 */
JSGraphIt.BlocksToMoveVisitor = function()
{
	this.visit = function(element)
	{
		if(JSGraphIt.isBlock(element))
		{
			JSGraphIt.blocksToMove.push(JSGraphIt.findBlock(element.id));
			return false;
		}
		return true;
	}
}

JSGraphIt.blocksToMoveScanner = new JSGraphIt.DocumentScanner(new JSGraphIt.BlocksToMoveVisitor(), true);

JSGraphIt.movemouse = function(e)
{
	if (JSGraphIt.isdrag)
	{
		var currentMouseX = JSGraphIt.nn6 ? e.clientX : event.clientX;
		var currentMouseY = JSGraphIt.nn6 ? e.clientY : event.clientY;
		var newElementX = JSGraphIt.elementStartX + currentMouseX - JSGraphIt.mouseStartX;
		var newElementY = JSGraphIt.elementStartY + currentMouseY - JSGraphIt.mouseStartY;

		// check bounds
		// note: the "-1" and "+1" is to avoid borders overlap
		if(newElementX < JSGraphIt.bounds[0])
			newElementX = JSGraphIt.bounds[0] + 1;
		if(newElementX + JSGraphIt.elementToMove.offsetWidth > JSGraphIt.bounds[2])
			newElementX = JSGraphIt.bounds[2] - JSGraphIt.elementToMove.offsetWidth - 1;
		if(newElementY < JSGraphIt.bounds[1])
			newElementY = JSGraphIt.bounds[1] + 1;
		if(newElementY + JSGraphIt.elementToMove.offsetHeight > JSGraphIt.bounds[3])
			newElementY = JSGraphIt.bounds[3] - JSGraphIt.elementToMove.offsetHeight - 1;
		
		// move element
		JSGraphIt.elementToMove.style.left = newElementX + 'px';
		JSGraphIt.elementToMove.style.top  = newElementY + 'px';

//		JSGraphIt.elementToMove.style.left = newElementX / JSGraphIt.elementToMove.parentNode.offsetWidth * 100 + '%';
//		JSGraphIt.elementToMove.style.top  = newElementY / JSGraphIt.elementToMove.parentNode.offsetHeight * 100 + '%';
	
		JSGraphIt.elementToMove.style.right = null;
		JSGraphIt.elementToMove.style.bottom = null;
		
		var i;
		for(i = 0; i < JSGraphIt.blocksToMove.length; i++)
		{
			JSGraphIt.blocksToMove[i].onMove();
		}
		return false;
	}
}

/**
 * finds the innermost draggable element starting from the one that generated the event "e"
 * (i.e.: the html element under mouse pointer), then setup the document's onmousemove function to
 * move the element around.
 */
JSGraphIt.startDrag = function(e) 
{	
	var eventSource = JSGraphIt.nn6 ? e.target : event.srcElement;
	if(eventSource.tagName == 'HTML')
		return;
	
	//Prevent Form Input Being Blocked!
	if(eventSource.tagName == 'INPUT' || eventSource.tagName == 'input')
                return;

	if(JSGraphIt.hasClass(eventSource, "nodrag"))
		return;

	while (eventSource != document.body && !JSGraphIt.hasClass(eventSource, "draggable"))
	{  	
		eventSource = JSGraphIt.nn6 ? eventSource.parentNode : eventSource.parentElement;
	}
	
	// if a draggable element was found, calculate its actual position
	if (JSGraphIt.hasClass(eventSource, "draggable"))
	{
		JSGraphIt.isdrag = true;
		JSGraphIt.elementToMove = eventSource;

		// set absolute positioning on the element		
		JSGraphIt.elementToMove.style.position = "absolute";
				
		// calculate start point
		JSGraphIt.elementStartX = JSGraphIt.elementToMove.offsetLeft;
		JSGraphIt.elementStartY = JSGraphIt.elementToMove.offsetTop;
		
		// calculate mouse start point
		JSGraphIt.mouseStartX = JSGraphIt.nn6 ? e.clientX : event.clientX;
		JSGraphIt.mouseStartY = JSGraphIt.nn6 ? e.clientY : event.clientY;
		
		// calculate JSGraphIt.bounds as left, top, width, height of the parent element
		if(JSGraphIt.getStyle(JSGraphIt.elementToMove.parentNode, "position") == 'absolute')
		{
			JSGraphIt.bounds[0] = 0;
			JSGraphIt.bounds[1] = 0;
		}
		else
		{
			JSGraphIt.bounds[0] = JSGraphIt.calculateOffsetLeft(JSGraphIt.elementToMove.parentNode);
			JSGraphIt.bounds[1] = JSGraphIt.calculateOffsetTop(JSGraphIt.elementToMove.parentNode);
		}
		JSGraphIt.bounds[2] = JSGraphIt.bounds[0] + JSGraphIt.elementToMove.parentNode.offsetWidth;
		JSGraphIt.bounds[3] = JSGraphIt.bounds[1] + JSGraphIt.elementToMove.parentNode.offsetHeight;		
		
		// either find the block related to the dragging element to call its onMove method
		JSGraphIt.blocksToMove = new Array();
		
		JSGraphIt.blocksToMoveScanner.scan(eventSource);
		document.onmousemove = JSGraphIt.movemouse;
		
		JSGraphIt.originalZIndex = JSGraphIt.getStyle(JSGraphIt.elementToMove, "z-index");
		JSGraphIt.elementToMove.style.zIndex = "3";
		
		return false;
	}
}

JSGraphIt.stopDrag = function(e)
{
	JSGraphIt.isdrag=false; 
	if(JSGraphIt.elementToMove)
		JSGraphIt.elementToMove.style.zIndex=JSGraphIt.originalZIndex;
	JSGraphIt.elementToMove = null;
	document.onmousemove = null;
}

document.onmousedown = JSGraphIt.startDrag;
document.onmouseup = JSGraphIt.stopDrag;



/*************
 * Constants *
 *************/
JSGraphIt.LEFT = 1;
JSGraphIt.RIGHT = 2;
JSGraphIt.UP = 4;
JSGraphIt.DOWN = 8;
JSGraphIt.HORIZONTAL = JSGraphIt.LEFT + JSGraphIt.RIGHT;
JSGraphIt.VERTICAL = JSGraphIt.UP + JSGraphIt.DOWN;
JSGraphIt.AUTO = JSGraphIt.HORIZONTAL + JSGraphIt.VERTICAL;

JSGraphIt.START = 0;
JSGraphIt.END = 1;
JSGraphIt.SCROLLBARS_WIDTH = 18;

/**************
 * Inspectors *
 **************/

JSGraphIt.inspectors = [];

/**
 * The canvas class.
 * This class is built on a div html element.
 */
JSGraphIt.Canvas = function(htmlElement)
{
	/*
	 * initialization
	 */
	this.id = htmlElement.id;
	this.htmlElement = htmlElement;
	this.blocks = new Array();
	this.connectors = new Array();
	this.offsetLeft = JSGraphIt.calculateOffsetLeft(this.htmlElement);
	this.offsetTop = JSGraphIt.calculateOffsetTop(this.htmlElement);	
	
	this.width;
	this.height;

	// create the inner div element
	this.innerDiv = document.createElement("div");
	
	this.initCanvas = function()
	{
		// setup the inner div
		var children = this.htmlElement.childNodes;
		var i;
		var el;
		var n = children.length;
		for(i = 0; i < n; i++)
		{
			el = children[0];
			this.htmlElement.removeChild(el);
			this.innerDiv.appendChild(el);
			if(el.style)
				el.style.zIndex = "2";
		}
		this.htmlElement.appendChild(this.innerDiv);

		this.htmlElement.style.overflow = "auto";
		this.htmlElement.style.position = "relative";
		this.innerDiv.id = this.id + "_innerDiv";
		this.innerDiv.style.border = "none";
		this.innerDiv.style.padding = "0px";
		this.innerDiv.style.margin = "0px";
		this.innerDiv.style.position = "absolute";
		this.innerDiv.style.top = "0px";
		this.innerDiv.style.left = "0px";
		this.width = 0;
		this.height = 0;
		this.offsetLeft = JSGraphIt.calculateOffsetLeft(this.innerDiv);
		this.offsetTop = JSGraphIt.calculateOffsetTop(this.innerDiv);

		// inspect canvas children to identify first level blocks
		new JSGraphIt.DocumentScanner(this, true).scan(this.htmlElement);
		
		// now this.width and this.height are populated with minimum values needed for the inner
		// blocks to fit, add 2 to avoid border overlap;
		this.height += 2;
		this.width += 2;
		
		var visibleWidth = this.htmlElement.offsetWidth - 2; // - 2 is to avoid border overlap
		var visibleHeight = this.htmlElement.offsetHeight - 2; // - 2 is to avoid border overlap
		
		// consider the scrollbars width calculating the inner div size
		if(this.height > visibleHeight)
			visibleWidth -= JSGraphIt.SCROLLBARS_WIDTH;
		if(this.width > visibleWidth)
			visibleHeight -= JSGraphIt.SCROLLBARS_WIDTH;
			
		this.height = Math.max(this.height, visibleHeight);
		this.width = Math.max(this.width, visibleWidth);
		
		this.innerDiv.style.width = this.width + "px";
		this.innerDiv.style.height = this.height + "px";
		
		// init connectors
		for(i = 0; i < this.connectors.length; i++)
		{
			this.connectors[i].initConnector();
		}
	}
	
	this.visit = function(element)
	{
		if(element == this.htmlElement)
			return true;
	
		// check the element dimensions against the acutal size of the canvas
		this.width = Math.max(this.width, JSGraphIt.calculateOffsetLeft(element) - this.offsetLeft + element.offsetWidth);
		this.height = Math.max(this.height, JSGraphIt.calculateOffsetTop(element) - this.offsetTop + element.offsetHeight);
		
		if(JSGraphIt.isBlock(element))
		{
			// block found initialize it
			var newBlock = new JSGraphIt.Block(element, this);
			newBlock.initBlock();
			this.blocks.push(newBlock);
			return false;
		}
		else if(JSGraphIt.isConnector(element))
		{
			// connector found, just create it, source or destination blocks may not 
			// have been initialized yet
			var newConnector = new JSGraphIt.Connector(element, this);
			this.connectors.push(newConnector);
			return false;
		}
		else
		{
			// continue searching nested elements
			return true;
		}
	}
	
	/*
	 * methods
	 */	
	this.print = function()
	{
		var output = '<ul><legend>canvas: ' + this.id + '</legend>';
		var i;
		for(i = 0; i < this.blocks.length; i++)
		{
			output += '<li>';
			output += this.blocks[i].print();
			output += '</li>';
		}
		output += '</ul>';
		return output;
	}
	
	/*
	 * This function searches for a nested block with a given id
	 */
	this.findBlock = function(blockId)
	{
		var result;
		var i;
		for(i = 0; i < this.blocks.length && !result; i++)
		{
			result = this.blocks[i].findBlock(blockId);
		}
		
		return result;
	}
	
	/**
	 * Add a block to this canvas object in the top right hand corner - MattOates@gmail.com 2009-07-14
	 */
	this.addBlock = function(id,classes,html)
	{
		var block = document.createElement('div');
		block.id = id;
		block.className = classes+' block draggable';
		block.innerHTML= html;

		var HTML = $(block);
		HTML.hide();
		$("#"+this.innerDiv.id).append(HTML);

		var newBlock = new JSGraphIt.Block(block, this);
				newBlock.initBlock();
				this.blocks.push(newBlock);
		block.style.left = (Math.floor(this.width*0.7) + Math.floor(Math.random()*this.width*0.25))+'px';
		block.style.top = Math.floor(Math.random()*this.height*0.25)+'px';

		HTML.fadeIn(500);
	}
	
	this.removeBlock = function(block)
	{
		for (b in this.blocks) 
		{
			if (this.blocks[b].removeBlock(block))
				return true;
			if (this.blocks[b] == block) 
			{
				this.blocks[b].removeBlocks();
				delete(this.blocks[b]);
				return true;
			}
		}
	}
	
	this.removeConnectors = function(block)
	{
		for (c in this.connectors) 
		{
			if (this.connectors[c].remove(block)) 
			{
				delete(this.connectors[c]);
			}
		}
	}
	
	this.toString = function()
	{
		return 'canvas: ' + this.id;		
	}
}

/*
 * JSGraphIt.Block class
 */
JSGraphIt.Block = function(htmlElement, canvas)
{	
	/*
	 * initialization
	 */
	 
	this.canvas = canvas;
	this.htmlElement = htmlElement;
	this.id = htmlElement.id;
	this.blocks = new Array();
	this.moveListeners = new Array();
	
	if(this.id == 'description2_out1')
		var merda = 0;
	this.currentTop = JSGraphIt.calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop;
	this.currentLeft = JSGraphIt.calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft;
	
	this.visit = function(element)
	{
		if(element == this.htmlElement)
		{
			// exclude itself
			return true;
		}

		if(JSGraphIt.isBlock(element))
		{
			var innerBlock = new JSGraphIt.Block(element, this.canvas);
			innerBlock.initBlock();
			this.blocks.push(innerBlock);
			this.moveListeners.push(innerBlock);
			return false;
		}
		else
			return true;
	}
	
	this.initBlock = function()
	{
		// inspect block children to identify nested blocks
		
		new JSGraphIt.DocumentScanner(this, true).scan(this.htmlElement);
	}
	
	this.top = function()
	{
		return this.currentTop;
	}
	
	this.left = function()
	{
		return this.currentLeft;
	}
	
	this.width = function()
	{
		return this.htmlElement.offsetWidth;		
	}
	
	this.height = function()
	{
		return this.htmlElement.offsetHeight;
	}
	
	/*
	 * methods
	 */	
	this.print = function()
	{
		var output = 'block: ' + this.id;
		if(this.blocks.length > 0)
		{
			output += '<ul>';
			var i;
			for(i = 0; i < this.blocks.length; i++)
			{
				output += '<li>';
				output += this.blocks[i].print();
				output += '</li>';
			}
			output += '</ul>';
		}
		return output;
	}
	
	/*
	 * This function searches for a nested block (or the block itself) with a given id
	 */
	this.findBlock = function(blockId)
	{
		if(this.id == blockId)
			return this;
			
		var result;
		var i;
		for(i = 0; i < this.blocks.length && !result; i++)
		{
			result = this.blocks[i].findBlock(blockId);
		}
		
		return result;
	}
	
	this.removeBlock = function(block)
	{
		if (block == this)
		{
			$(this.htmlElement).remove();
			this.removeBlocks();
			return true;
		}
		
		if (this.blocks != null) {
			for (b in this.blocks) 
			{
				if (this.blocks[b].removeBlock(block))
					return true;
			}
		}
		
		return false;
	}
	
	this.removeBlocks = function()
	{
		if (this.blocks == null) return true;
		
		for (b in this.blocks)
		{
			this.blocks[b].removeBlocks();
			delete(this.blocks[b]);
		}
		return true;
	}
	
	this.move = function(left, top)
	{
		this.htmlElement.style.left = left;
		this.htmlElement.style.top = top;
		
		this.onMove();
	}
		
	this.onMove = function()
	{
		var i;
		this.currentLeft = JSGraphIt.calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft;
		this.currentTop = JSGraphIt.calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop;
		// notify listeners
		for(i = 0; i < this.moveListeners.length; i++)
		{
			this.moveListeners[i].onMove();
		}
	}
	
	this.toString = function()
	{
		return 'block: ' + this.id;
	}
}

/**
 * This class represents a connector segment, it is drawn via a div element.
 * A segment has a starting point defined by the properties startX and startY, a length,
 * a thickness and an orientation.
 * Allowed values for the orientation property are defined by the constants JSGraphIt.UP, JSGraphIt.LEFT, JSGraphIt.DOWN and JSGraphIt.RIGHT.
 */
JSGraphIt.Segment = function(id, parentElement)
{
	this.id = id;
	this.htmlElement = document.createElement('div');
	this.htmlElement.id = id;
	this.htmlElement.style.position = 'absolute';
	this.htmlElement.style.overflow = 'hidden';
	parentElement.appendChild(this.htmlElement);

	this.startX;
	this.startY;
	this.length;
	this.thickness;
	this.orientation;
	this.nextSegment;
	this.visible = true;
	
	/**
	 * draw the segment. This operation is cascaded to next segment if any.
	 */
	this.draw = function()
	{
		// set properties to next segment
		if(this.nextSegment)
		{
			this.nextSegment.startX = this.getEndX();
			this.nextSegment.startY = this.getEndY();			
		}
		
		if(this.visible)
			this.htmlElement.style.display = 'block';
		else
			this.htmlElement.style.display = 'none';
	
		switch(this.orientation)
		{
			case JSGraphIt.LEFT:
				this.htmlElement.style.left = (this.startX - this.length) + "px";				
				this.htmlElement.style.top = this.startY + "px";
				this.htmlElement.style.width = this.length + "px";
				this.htmlElement.style.height = this.thickness + "px";
				break;
			case JSGraphIt.RIGHT:
				this.htmlElement.style.left = this.startX + "px";
				this.htmlElement.style.top = this.startY + "px";
				if(this.nextSegment)
					this.htmlElement.style.width = this.length + this.thickness + "px";
				else
					this.htmlElement.style.width = this.length + "px";
				this.htmlElement.style.height = this.thickness + "px";
				break;
			case JSGraphIt.UP:
				this.htmlElement.style.left = this.startX + "px";
				this.htmlElement.style.top = (this.startY - this.length) + "px";
				this.htmlElement.style.width = this.thickness + "px";
				this.htmlElement.style.height = this.length + "px";
				break;
			case JSGraphIt.DOWN:
				this.htmlElement.style.left = this.startX + "px";
				this.htmlElement.style.top = this.startY + "px";
				this.htmlElement.style.width = this.thickness + "px";
				if(this.nextSegment)
					this.htmlElement.style.height = this.length + this.thickness + "px";
				else
					this.htmlElement.style.height = this.length + "px";
				break;
		}
		
		if(this.nextSegment)
			this.nextSegment.draw();
	}
	
	/**
	 * Returns the "left" coordinate of the end point of this segment
	 */
	this.getEndX = function()
	{		
		switch(this.orientation)
		{
			case JSGraphIt.LEFT: return this.startX - this.length;
			case JSGraphIt.RIGHT: return this.startX + this.length;
			case JSGraphIt.DOWN: return this.startX;
			case JSGraphIt.UP: return this.startX;
		}
	}
	
	/**
	 * Returns the "top" coordinate of the end point of this segment
	 */
	this.getEndY = function()
	{		
		switch(this.orientation)
		{
			case JSGraphIt.LEFT: return this.startY;
			case JSGraphIt.RIGHT: return this.startY;
			case JSGraphIt.DOWN: return this.startY + this.length;
			case JSGraphIt.UP: return this.startY - this.length;
		}
	}
		
	/**
	 * Append another segment to the end point of this.
	 * If another segment is already appended to this, cascades the operation so
	 * the given next segment will be appended to the tail of the segments chain.
	 */
	this.append = function(nextSegment)
	{
		if(!nextSegment)
			return;
		if(!this.nextSegment)
		{
			this.nextSegment = nextSegment;
			this.nextSegment.startX = this.getEndX();
			this.nextSegment.startY = this.getEndY();
		}
		else
			this.nextSegment.append(nextSegment);
	}
	
	this.detach = function()
	{
		var s = this.nextSegment;
		this.nextSegment = null;
		return s;
	}
	
	/**
	 * hides this segment and all the following
	 */
	this.cascadeHide = function()
	{
		this.visible = false;
		if(this.nextSegment)
			this.nextSegment.cascadeHide();
	}
	
	this.remove = function()
	{
		$(this.htmlElement).remove();
		alert("Removing HTML?");
	}
}
/**
 * Connector class.
 * The init function takes two JSGraphIt.Block objects as arguments representing 
 * the source and destination of the connector
 */
JSGraphIt.Connector = function(htmlElement, canvas)
{
	/**
	 * declaring html element
	 */
	this.htmlElement = htmlElement;
	
	/**
	 * the canvas this connector is in
	 */
	this.canvas = canvas;
	
	/**
	 * the source block
	 */
	this.source = null;
	
	/**
	 * the destination block
	 */
	this.destination = null;	
	
	/**
	 * preferred orientation
	 */
	this.preferredSourceOrientation = JSGraphIt.AUTO;
	this.preferredDestinationOrientation = JSGraphIt.AUTO;
	
	/**
	 * css class to be applied to the connector's segments
	 */
	this.connectorClass;
	
	/**
	 * minimum length for a connector segment.
	 */
	this.minSegmentLength = 10;

	/**
	 * size of the connector, i.e.: thickness of the segments.
	 */
	this.size = 2;
	
	/**
	 * connector's color
	 */
	this.color = 'black';
	
	/**
	 * move listeners, they are notify when connector moves
	 */
	this.moveListeners = new Array();
	
	this.firstSegment;
	
	this.segmentsPool;
	
	this.segmentsNumber = 0;
	
	this.strategy;
		
	this.initConnector = function()
	{
		// detect the connector id
		if(this.htmlElement.id)
			this.id = this.htmlElement.id;
		else
			this.id = this.htmlElement.className;
			
		// split the class name to get the ids of the source and destination blocks
		var splitted = htmlElement.className.split(' ');
		if(splitted.length < 3)
		{
			alert('Unable to create connector \'' + id + '\', class is not in the correct format: connector <sourceBlockId>, <destBlockId>');
			return;
		}
		
		this.connectorClass = splitted[0] + ' ' + splitted[1] + ' ' + splitted[2];
		
		this.source = this.canvas.findBlock(splitted[1]);
		if(!this.source)
		{
			alert('cannot find source block with id \'' + splitted[1] + '\'');
			return;
		}
		
		this.destination = this.canvas.findBlock(splitted[2]);
		if(!this.destination)
		{
			alert('cannot find destination block with id \'' + splitted[2] + '\'');
			return;
		}
		
		// check preferred orientation
		if(JSGraphIt.hasClass(this.htmlElement, 'vertical'))
		{
			this.preferredSourceOrientation = JSGraphIt.VERTICAL;
			this.preferredDestinationOrientation = JSGraphIt.VERTICAL;
		}
		else if(JSGraphIt.hasClass(this.htmlElement, 'horizontal'))
		{
			this.preferredSourceOrientation = JSGraphIt.HORIZONTAL;
			this.preferredDestinationOrientation = JSGraphIt.HORIZONTAL;
		}
		else
		{
			// check preferred orientation on source side
			if(JSGraphIt.hasClass(this.htmlElement, 'vertical_start'))
				this.preferredSourceOrientation = JSGraphIt.VERTICAL;
			else if(JSGraphIt.hasClass(this.htmlElement, 'horizontal_start'))
				this.preferredSourceOrientation = JSGraphIt.HORIZONTAL;
			else if(JSGraphIt.hasClass(this.htmlElement, 'left_start'))
				this.preferredSourceOrientation = JSGraphIt.LEFT;
			else if(JSGraphIt.hasClass(this.htmlElement, 'right_start'))
				this.preferredSourceOrientation = JSGraphIt.RIGHT;
			else if(JSGraphIt.hasClass(this.htmlElement, 'up_start'))
				this.preferredSourceOrientation = JSGraphIt.UP;
			else if(JSGraphIt.hasClass(this.htmlElement, 'down_start'))
				this.preferredSourceOrientation = JSGraphIt.DOWN;
			
			// check preferred orientation on destination side
			if(JSGraphIt.hasClass(this.htmlElement, 'vertical_end'))
				this.preferredDestinationOrientation = JSGraphIt.VERTICAL;
			else if(JSGraphIt.hasClass(this.htmlElement, 'horizontal_end'))
				this.preferredDestinationOrientation = JSGraphIt.HORIZONTAL;
			else if(JSGraphIt.hasClass(this.htmlElement, 'left_end'))
				this.preferredDestinationOrientation = JSGraphIt.LEFT;
			else if(JSGraphIt.hasClass(this.htmlElement, 'right_end'))
				this.preferredDestinationOrientation = JSGraphIt.RIGHT;
			else if(JSGraphIt.hasClass(this.htmlElement, 'up_end'))
				this.preferredDestinationOrientation = JSGraphIt.UP;
			else if(JSGraphIt.hasClass(this.htmlElement, 'down_end'))
				this.preferredDestinationOrientation = JSGraphIt.DOWN;
		}
		
		// get the first strategy as default
		this.strategy = JSGraphIt.strategies[0](this);
		this.repaint();
		
		this.source.moveListeners.push(this);
		this.destination.moveListeners.push(this);
		
		// call JSGraphIt.inspectors for this connector
		var i;
		for(i = 0; i < JSGraphIt.inspectors.length; i++)
		{
			JSGraphIt.inspectors[i].inspect(this);
		}
		
		// remove old html element
		this.htmlElement.parentNode.removeChild(this.htmlElement);
	}
	
	this.remove = function(block)
	{
		if((this.source == block) || (this.destination == block))
		{
			this.deleteSegments();
			return true;
		}
		return false;
	}
	
	this.deleteSegments = function()
	{
		for (s in this.segmentsPool)
		{
			this.segmentsPool[s].remove();
			delete(this.segmentsPool[s]);
		}
	}
	
	this.getStartSegment = function()
	{
		return this.firstSegment;
	}
	
	this.getEndSegment = function()
	{
		var s = this.firstSegment;
		while(s.nextSegment)
			s = s.nextSegment;
		return s;
	}
	
	this.getMiddleSegment = function()
	{
		if(!this.strategy)
			return null;
		else
			return this.strategy.getMiddleSegment();
	}
	
	this.createSegment = function()
	{
		var segment;
		
		// if the pool contains more objects, borrow the segment, create it otherwise
		if(this.segmentsPool)
		{
			segment = this.segmentsPool;
			this.segmentsPool = this.segmentsPool.detach();
		}
		else
		{		
			segment = new JSGraphIt.Segment(this.id + "_" + (this.segmentsNumber + 1), this.canvas.htmlElement);
			segment.htmlElement.className = this.connectorClass;
			if(!JSGraphIt.getStyle(segment.htmlElement, 'background-color'))
				segment.htmlElement.style.backgroundColor = this.color;
			segment.thickness = this.size;
		}
		this.segmentsNumber++;
		
		if(this.firstSegment)
			this.firstSegment.append(segment);
		else
			this.firstSegment = segment;
		segment.visible = true;
		return segment;
	}
	
	/**
	 * Repaints the connector
	 */
	this.repaint = function()
	{
		// check JSGraphIt.strategies fitness and choose the best fitting one
		var i;
		var maxFitness = 0;
		var fitness;
		var s;
		
		// check if any strategy is possible with preferredOrientation
		for(i = 0; i < JSGraphIt.strategies.length; i++)
		{
			this.clearSegments();
			
			fitness = 0;
			s = JSGraphIt.strategies[i](this);
			if(s.isApplicable())
			{
				fitness++;
				s.paint();
				// check resulting orientation against the preferred orientations
				if((this.firstSegment.orientation & this.preferredSourceOrientation) != 0)
					fitness++;
				if((this.getEndSegment().orientation & this.preferredDestinationOrientation) != 0)
					fitness++;
			}
			
			if(fitness > maxFitness)
			{
				this.strategy = s;
				maxFitness = fitness;
			}
		}			
		
		this.clearSegments();

		this.strategy.paint();
		this.firstSegment.draw();

		// this is needed to actually hide unused html elements	
		if(this.segmentsPool)
			this.segmentsPool.draw();
	}
	
	/**
	 * Hide all the segments and return them to pool
	 */
	this.clearSegments = function()
	{
		if(this.firstSegment)
		{
			this.firstSegment.cascadeHide();
			this.firstSegment.append(this.segmentsPool);
			this.segmentsPool = this.firstSegment;
			this.firstSegment = null;
		}	
	}
		
	this.onMove = function()
	{
		this.repaint();
		
		// notify listeners
		var i;
		for(i = 0; i < this.moveListeners.length; i++)
			this.moveListeners[i].onMove();
	}
}

JSGraphIt.ConnectorEnd = function(htmlElement, connector, side)
{
	this.side = side;
	this.htmlElement = htmlElement;
	this.connector = connector;
	connector.canvas.htmlElement.appendChild(htmlElement);
	// strip extension
	if(this.htmlElement.tagName.toLowerCase() == "img")
	{
		this.src = this.htmlElement.src.substring(0, this.htmlElement.src.lastIndexOf('.'));
		this.srcExtension = this.htmlElement.src.substring(this.htmlElement.src.lastIndexOf('.'));
		this.htmlElement.style.zIndex = JSGraphIt.getStyle(this.connector.htmlElement, "z-index");
	}
	
	this.orientation;
	
	this.repaint = function()
	{
		this.htmlElement.style.position = 'absolute';
				
		var left;
		var top;
		var segment;
		var orientation;
		
		if(this.side == JSGraphIt.START)
		{
			segment = connector.getStartSegment();
			left = segment.startX;
			top = segment.startY;
			orientation = segment.orientation;
			// swap orientation
			if((orientation & JSGraphIt.VERTICAL) != 0)
				orientation = (~orientation) & JSGraphIt.VERTICAL;
			else
				orientation = (~orientation) & JSGraphIt.HORIZONTAL;
		}
		else
		{
			segment = connector.getEndSegment();
			left = segment.getEndX();
			top = segment.getEndY();
			orientation = segment.orientation;
		}
		
		switch(orientation)
		{
			case JSGraphIt.LEFT:
				top -= (this.htmlElement.offsetHeight - segment.thickness) / 2;
				break;
			case JSGraphIt.RIGHT:
				left -= this.htmlElement.offsetWidth;
				top -= (this.htmlElement.offsetHeight - segment.thickness) / 2;
				break;
			case JSGraphIt.DOWN:
				top -= this.htmlElement.offsetHeight;
				left -= (this.htmlElement.offsetWidth - segment.thickness) / 2;
				break;
			case JSGraphIt.UP:
				left -= (this.htmlElement.offsetWidth - segment.thickness) / 2;
				break;
		}
		
		this.htmlElement.style.left = Math.ceil(left) + "px";
		this.htmlElement.style.top = Math.ceil(top) + "px";
		
		if(this.htmlElement.tagName.toLowerCase() == "img" && this.orientation != orientation)
		{
			var orientationSuffix;
			switch(orientation)
			{
				case JSGraphIt.UP: orientationSuffix = "u"; break;
				case JSGraphIt.DOWN: orientationSuffix = "d"; break;
				case JSGraphIt.LEFT: orientationSuffix = "l"; break;
				case JSGraphIt.RIGHT: orientationSuffix = "r"; break;
			}
			this.htmlElement.src = this.src + "_" + orientationSuffix + this.srcExtension;
		}
		this.orientation = orientation;
	}
	
	this.onMove = function()
	{
		this.repaint();
	}
}

JSGraphIt.SideConnectorLabel = function(connector, htmlElement, side)
{
	this.connector = connector;
	this.htmlElement = htmlElement;
	this.side = side;
	this.connector.htmlElement.parentNode.appendChild(htmlElement);
		
	this.repaint = function()
	{
		this.htmlElement.style.position = 'absolute';
		var left;
		var top;
		var segment;

		if(this.side == JSGraphIt.START)
		{	
			segment = this.connector.getStartSegment();
			left = segment.startX;
			top = segment.startY;
			if(segment.orientation == JSGraphIt.LEFT)
				left -= this.htmlElement.offsetWidth;
			if(segment.orientation == JSGraphIt.UP)
				top -= this.htmlElement.offsetHeight;
				
			if((segment.orientation & JSGraphIt.HORIZONTAL) != 0 && top < this.connector.getEndSegment().getEndY())
				top -= this.htmlElement.offsetHeight;
			if((segment.orientation & JSGraphIt.VERTICAL) != 0 && left < this.connector.getEndSegment().getEndX())
				left -= this.htmlElement.offsetWidth;
		}
		else
		{	
			segment = this.connector.getEndSegment();
			left = segment.getEndX();
			top = segment.getEndY();
			if(segment.orientation == JSGraphIt.RIGHT)
				left -= this.htmlElement.offsetWidth;
			if(segment.orientation == JSGraphIt.DOWN)
				top -= this.htmlElement.offsetHeight;
			if((segment.orientation & JSGraphIt.HORIZONTAL) != 0 && top < this.connector.getStartSegment().startY)
				top -= this.htmlElement.offsetHeight;
			if((segment.orientation & JSGraphIt.VERTICAL) != 0 && left < this.connector.getStartSegment().startX)
				left -= this.htmlElement.offsetWidth;
		}
		
		this.htmlElement.style.left = Math.ceil(left) + "px";
		this.htmlElement.style.top = Math.ceil(top) + "px";
	}
	
	this.onMove = function()
	{
		this.repaint();
	}
}

JSGraphIt.MiddleConnectorLabel = function(connector, htmlElement)
{
	this.connector = connector;
	this.htmlElement = htmlElement;
	this.connector.canvas.htmlElement.appendChild(htmlElement);
	
	this.repaint = function()
	{
		this.htmlElement.style.position = 'absolute';
		
		var left;
		var top;
		var segment = connector.getMiddleSegment();

		if((segment.orientation & JSGraphIt.VERTICAL) != 0)
		{
			var shiftTop = 0, shiftLeft = 0;
			if (segment.htmlElement.offsetHeight == 0) {
				segment = connector.getEndSegment();
				shiftLeft = -this.htmlElement.offsetWidth / 2;
				shiftTop = 8;
			}
			// put label at middle height on right side of the connector
			top = shiftTop + segment.htmlElement.offsetTop +
					(segment.htmlElement.offsetHeight - this.htmlElement.offsetHeight) / 2;
			left = shiftLeft + segment.htmlElement.offsetLeft;
		}
		else
		{
			// put connector below the connector at middle widths
			top = segment.htmlElement.offsetTop;
			left = segment.htmlElement.offsetLeft + (segment.htmlElement.offsetWidth - this.htmlElement.offsetWidth) / 2;
		}
		
		this.htmlElement.style.left = Math.ceil(left) + "px";
		this.htmlElement.style.top = Math.ceil(top) + "px";

		// Make this label always on top
		this.htmlElement.style.zIndex = '20000';
	}
	
	this.onMove = function()
	{
		this.repaint();
	}
}

/*
 * Inspector classes
 */

JSGraphIt.ConnectorEndsInspector = function()
{
	this.inspect = function(connector)
	{
		var children = connector.htmlElement.childNodes;
		var i;
		for(i = 0; i < children.length; i++)
		{
			if(JSGraphIt.hasClass(children[i], "connector-end"))
			{
				var newElement = new JSGraphIt.ConnectorEnd(children[i], connector, JSGraphIt.END);
				newElement.repaint();
				connector.moveListeners.push(newElement);
			}
			else if(JSGraphIt.hasClass(children[i], "connector-start"))
			{
				var newElement = new JSGraphIt.ConnectorEnd(children[i], connector, JSGraphIt.START);
				newElement.repaint();
				connector.moveListeners.push(newElement);
			}
		}
	}
}

JSGraphIt.ConnectorLabelsInspector = function()
{
	this.inspect = function(connector)
	{
		var children = connector.htmlElement.childNodes;
		var i;
		for(i = 0; i < children.length; i++)
		{
			if(JSGraphIt.hasClass(children[i], "source-label"))
			{
				var newElement = new JSGraphIt.SideConnectorLabel(connector, children[i], JSGraphIt.START);
				newElement.repaint();
				connector.moveListeners.push(newElement);
			}
			else if(JSGraphIt.hasClass(children[i], "middle-label"))
			{
				var newElement = new JSGraphIt.MiddleConnectorLabel(connector, children[i]);
				newElement.repaint();
				connector.moveListeners.push(newElement);
			}
			else if(JSGraphIt.hasClass(children[i], "destination-label"))
			{
				var newElement = new JSGraphIt.SideConnectorLabel(connector, children[i], JSGraphIt.END);
				newElement.repaint();
				connector.moveListeners.push(newElement);
			}
		}
	}
}

/*
 * Inspector registration
 */

JSGraphIt.inspectors.push(new JSGraphIt.ConnectorEndsInspector());
JSGraphIt.inspectors.push(new JSGraphIt.ConnectorLabelsInspector());

/*
 * an array containing all the JSGraphIt.canvases in document
 */
JSGraphIt.canvases = [];

/*
 * This function initializes the js_graph objects inspecting the html document
 */
JSGraphIt.initPageObjects = function()
{
	if(JSGraphIt.isCanvas(document.body))
	{
		var newCanvas = new JSGraphIt.Canvas(document.body);
		newCanvas.initCanvas();
		JSGraphIt.canvases.push(newCanvas);
	}
	else
	{	
		var divs = document.getElementsByTagName('div');
		var i;
		for(i = 0; i < divs.length; i++)
		{
			if(JSGraphIt.isCanvas(divs[i]) && !JSGraphIt.findCanvas(divs[i].id))
			{
				var newCanvas = new JSGraphIt.Canvas(divs[i]);
				newCanvas.initCanvas();
				JSGraphIt.canvases.push(newCanvas);
			}
		}
	}
}


/*
 * Utility functions
 */


JSGraphIt.findCanvas = function(canvasId)
{	
	var i;
	for(i = 0; i < JSGraphIt.canvases.length; i++)
		if(JSGraphIt.canvases[i].id == canvasId)
			return JSGraphIt.canvases[i];
	return null;
}

JSGraphIt.findBlock = function(blockId)
{
	var i;
	for(i = 0; i < JSGraphIt.canvases.length; i++)
	{
		var block = JSGraphIt.canvases[i].findBlock(blockId);
		if(block)
			return block;
	}
	return null;
}

/**
 * Add a block to the top right of the canvas. - MattOates@gmail.com 2009-07-14
 */
JSGraphIt.addBlock = function(id,canvasid,classes,html) {
	JSGraphIt.findCanvas(canvasid).addBlock(id,classes,html);
}

/**
 * Add block by ajax request through GET for HTML content. - MattOates@gmail.com 2009-07-15
 */
JSGraphIt.addBlockByRequest = function(id,canvasid,classes,geturl,getparams) {
	
	if (getparams == null) getparams = {};
	
	$(document).ready( function() { 
		$.get( geturl, getparams, function( response ) {
			JSGraphIt.findCanvas(canvasid).addBlock(id,classes,response);
		}, "html" );
	} );
}

/**
 * Remove a block with given tag id - MattOates@gmail.com 2009-07-14
 * Makes use of some remove functions, is all horribly done and inefficient! 
 * Should be using associative arrays by id string to maintain the graph instead of itterating over everything.
 */
JSGraphIt.removeBlock = function(blockid)
{
	var block = JSGraphIt.findBlock(blockid);
	for (c in JSGraphIt.canvases) {
		JSGraphIt.canvases[c].removeBlock(block);
	}
}

/**
 * Add a simplistic connector- MattOates@gmail.com 2009-07-14
 * Label and labelclass are optional. Adding in connector id's to start to make everything more managable.
 */
JSGraphIt.addSimpleConnector = function(id,canvasid,sourceid,destinationid,label,labelclass) {
        var canvas = JSGraphIt.findCanvas(canvasid);
        var connectors = canvas.connectors;
        var connector = document.createElement('div');
	connector.id = id;
	if (label == null) {
		label = source + '-' + destination;
	}
	if (labelclass == null) {
		labelclass = "middle-label";
	}
	connector.className = 'connector ' + source + ' ' + destination;
	connector.innerHTML = '<label class="'+labelclass+'">' + label + '</label>';
	$("#"+canvasid).append($(connector));
	var newConnector = new JSGraphIt.Connector(connector, canvas);
			newConnector.initConnector();
			connectors.push(newConnector);
}

/**
 * Removes any connectors regardless of direction if it liks with a block specified by blockid- MattOates@gmail.com 2009-07-14
 */
JSGraphIt.removeConnectors = function(blockid)
{
	var block = JSGraphIt.findBlock(blockid);
	for (c in JSGraphIt.canvases) {
		JSGraphIt.canvases[c].removeConnectors(block);
	}
}

/**
 * Remove any connectors with specified direction by source-dest blockids- MattOates@gmail.com 2009-07-14
 */
JSGraphIt.removeConnector = function(sourceid,destid)
{
}
 
/*
 * This function determines whether a html element is to be considered a canvas
 */
JSGraphIt.isBlock = function(htmlElement)
{
	return JSGraphIt.hasClass(htmlElement, 'block');
}

/*
 * This function determines whether a html element is to be considered a block
 */
JSGraphIt.isCanvas = function(htmlElement)
{
	return JSGraphIt.hasClass(htmlElement, 'canvas');
}

/*
 * This function determines whether a html element is to be considered a connector
 */
JSGraphIt.isConnector = function(htmlElement)
{
	return htmlElement.className && htmlElement.className.match(new RegExp('connector .*'));
}

/*
 * This function calculates the absolute 'top' value for a html node
 */
JSGraphIt.calculateOffsetTop = function(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) 
			curtop += obj.offsetTop
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}

/*
 * This function calculates the absolute 'left' value for a html node
 */
JSGraphIt.calculateOffsetLeft = function(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		curleft = obj.offsetLeft
		while (obj = obj.offsetParent) 
		{
			curleft += obj.offsetLeft;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}

JSGraphIt.parseBorder = function(obj, side)
{
	var sizeString = JSGraphIt.getStyle(obj, "border-" + side + "-width");
	if(sizeString && sizeString != "")
	{
		if(sizeString.substring(sizeString.length - 2) == "px")
			return parseInt(sizeString.substring(0, sizeString.length - 2));
	}
	return 0;
}

JSGraphIt.hasClass = function(element, className)
{
	if(!element || !element.className)
		return false;
		
	var classes = element.className.split(' ');
	var i;
	for(i = 0; i < classes.length; i++)
		if(classes[i] == className)
			return true;
	return false;
}

/**
 * This function retrieves the actual value of a style property even if it is set via css.
 */
JSGraphIt.getStyle = function(node, styleProp)
{
	// if not an element
	if( node.nodeType != 1)
		return;
		
	var value;
	if (node.currentStyle)
	{
		// ie case
		styleProp = JSGraphIt.replaceDashWithCamelNotation(styleProp);
		value = node.currentStyle[styleProp];
	}
	else if (window.getComputedStyle)
	{
		// mozilla case
		value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp);
	}
	
	return value;
}

JSGraphIt.replaceDashWithCamelNotation = function(value)
{
	var pos = value.indexOf('-');
	while(pos > 0 && value.length > pos + 1)
	{
		value = value.substring(0, pos) + value.substring(pos + 1, pos + 2).toUpperCase() + value.substring(pos + 2);
		pos = value.indexOf('-');
	}
	return value;
}


/*******************************
 * Connector paint JSGraphIt.strategies. *
 *******************************/
 
/**
 * Horizontal "S" routing strategy.
 */
JSGraphIt.HorizontalSStrategy = function(connector)
{
	this.connector = connector;
	
	this.startSegment;
	this.middleSegment;
	this.endSegment;
	
	this.strategyName = "horizontal_s";
	
	this.getMiddleSegment = function()
	{
		return this.middleSegment;
	}
	
	this.isApplicable = function()
	{
		var sourceLeft = this.connector.source.left();
		var sourceWidth = this.connector.source.width();
		var destinationLeft = this.connector.destination.left();
		var destinationWidth = this.connector.destination.width();
		
		return Math.abs(2 * destinationLeft + destinationWidth - (2 * sourceLeft + sourceWidth)) - (sourceWidth + destinationWidth) > 4 * this.connector.minSegmentLength;
	}
	
	this.paint = function()
	{
		this.startSegment = connector.createSegment();
		this.middleSegment = connector.createSegment();
		this.endSegment = connector.createSegment();
		
		var sourceLeft = this.connector.source.left();
		var sourceTop = this.connector.source.top();
		var sourceWidth = this.connector.source.width();
		var sourceHeight = this.connector.source.height();
		
		var destinationLeft = this.connector.destination.left();
		var destinationTop = this.connector.destination.top();
		var destinationWidth = this.connector.destination.width();
		var destinationHeight = this.connector.destination.height();
		
		var hLength;
		
		this.startSegment.startY = Math.floor(sourceTop + sourceHeight / 2);
			
		// deduce which face to use on source and destination blocks
		if(sourceLeft + sourceWidth / 2 < destinationLeft + destinationWidth / 2)
		{
			// use left side of the source block and right side of the destination block
			this.startSegment.startX = sourceLeft + sourceWidth;
			hLength = destinationLeft - (sourceLeft + sourceWidth);
		}
		else
		{
			// use right side of the source block and left side of the destination block
			this.startSegment.startX = sourceLeft;
			hLength = destinationLeft + destinationWidth - sourceLeft;
		}

		// first horizontal segment positioning
		this.startSegment.length = Math.floor(Math.abs(hLength) / 2);
		this.startSegment.orientation = hLength > 0 ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
		
		// vertical segment positioning			
		var vLength = Math.floor(destinationTop + destinationHeight / 2 - (sourceTop + sourceHeight / 2));		
		this.middleSegment.length = Math.abs(vLength);
		if(vLength == 0)
			this.middleSegment.visible = false;
		this.middleSegment.orientation = vLength > 0 ? JSGraphIt.DOWN : JSGraphIt.UP;
		
		// second horizontal segment positioning
		this.endSegment.length = Math.floor(Math.abs(hLength) / 2);
		this.endSegment.orientation = hLength > 0 ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
	}
}

/**
 * Vertical "S" routing strategy.
 */
JSGraphIt.VerticalSStrategy = function(connector)
{
	this.connector = connector;
	
	this.startSegment;
	this.middleSegment;
	this.endSegment;
	
	this.strategyName = "vertical_s";
	
	this.getMiddleSegment = function()
	{
		return this.middleSegment;
	}	
	
	this.isApplicable = function()
	{
		var sourceTop = this.connector.source.top();
		var sourceHeight = this.connector.source.height();
		var destinationTop = this.connector.destination.top();
		var destinationHeight = this.connector.destination.height();
		return Math.abs(2 * destinationTop + destinationHeight - (2 * sourceTop + sourceHeight)) - (sourceHeight + destinationHeight) > 4 * this.connector.minSegmentLength;
	}
	
	this.paint = function()
	{
		this.startSegment = connector.createSegment();
		this.middleSegment = connector.createSegment();
		this.endSegment = connector.createSegment();
		
		var sourceLeft = this.connector.source.left();
		var sourceTop = this.connector.source.top();
		var sourceWidth = this.connector.source.width();
		var sourceHeight = this.connector.source.height();
		
		var destinationLeft = this.connector.destination.left();
		var destinationTop = this.connector.destination.top();
		var destinationWidth = this.connector.destination.width();
		var destinationHeight = this.connector.destination.height();
		
		var vLength;
		
		this.startSegment.startX = 	Math.floor(sourceLeft + sourceWidth / 2);
			
		// deduce which face to use on source and destination blocks
		if(sourceTop + sourceHeight / 2 < destinationTop + destinationHeight / 2)
		{
			// use bottom side of the source block and top side of destination block
			this.startSegment.startY = sourceTop + sourceHeight;
			vLength = destinationTop - (sourceTop + sourceHeight);
		}
		else
		{
			// use top side of the source block and bottom side of the destination block
			this.startSegment.startY = sourceTop;
			vLength = destinationTop + destinationHeight - sourceTop;
		}
		
		// first vertical segment positioning
		this.startSegment.length = Math.floor(Math.abs(vLength) / 2);
		this.startSegment.orientation = vLength > 0 ? JSGraphIt.DOWN : JSGraphIt.UP;
		
		// horizontal segment positioning
		var hLength = Math.floor(destinationLeft + destinationWidth / 2 - (sourceLeft + sourceWidth / 2));
		this.middleSegment.length = Math.abs(hLength);
		this.middleSegment.orientation = hLength > 0 ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
					
		// second vertical segment positioning
		this.endSegment.length = Math.floor(Math.abs(vLength) / 2);
		this.endSegment.orientation = vLength > 0 ? JSGraphIt.DOWN : JSGraphIt.UP;
	}
}

/**
 * A horizontal "L" connector routing strategy
 */
JSGraphIt.HorizontalLStrategy = function(connector)
{
	this.connector = connector;
	
	this.destination;
	
	this.startSegment;
	this.endSegment;
	
	this.strategyName = "horizontal_L";
	
	this.isApplicable = function()
	{
		var destMiddle = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);
		var sl = this.connector.source.left();
		var sw = this.connector.source.width();
		var dt = this.connector.destination.top();
		var dh = this.connector.destination.height();
		var sourceMiddle = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);

		if(destMiddle > sl && destMiddle < sl + sw)
			return false;
		if(sourceMiddle > dt && sourceMiddle < dt + dh)
			return false;
		return true;
	}
	
	/**
	 * Chooses the longest segment as the "middle" segment.
	 */
	this.getMiddleSegment = function()
	{
		if(this.startSegment.length > this.endSegment.length)
			return this.startSegment;
		else
			return this.endSegment;
	}
	
	this.paint = function()
	{
		this.startSegment = this.connector.createSegment();
		this.endSegment = this.connector.createSegment();
		
		var destMiddleX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);
		var sl = this.connector.source.left();
		var sw = this.connector.source.width();
		var dt = this.connector.destination.top();
		var dh = this.connector.destination.height();
		
		this.startSegment.startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);
		
		// decide which side of the source block to connect to
		if(Math.abs(destMiddleX - sl) < Math.abs(destMiddleX - (sl + sw)))
		{
			// use the left face
			this.startSegment.orientation = (destMiddleX < sl) ? JSGraphIt.LEFT : JSGraphIt.RIGHT;				
			this.startSegment.startX = sl;
		}
		else
		{
			// use the right face
			this.startSegment.orientation = (destMiddleX > (sl + sw)) ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
			this.startSegment.startX = sl + sw;
		}
		
		this.startSegment.length = Math.abs(destMiddleX - this.startSegment.startX);
		
		// decide which side of the destination block to connect to
		if(Math.abs(this.startSegment.startY - dt) < Math.abs(this.startSegment.startY - (dt + dh)))
		{
			// use the upper face
			this.endSegment.orientation = (this.startSegment.startY < dt) ? JSGraphIt.DOWN : JSGraphIt.UP;
			this.endSegment.length = Math.abs(this.startSegment.startY - dt);
		}
		else
		{
			// use the lower face
			this.endSegment.orientation = (this.startSegment.startY > (dt + dh)) ? JSGraphIt.UP : JSGraphIt.DOWN;
			this.endSegment.length = Math.abs(this.startSegment.startY - (dt + dh));
		}
	}
}

/**
 * Vertical "L" connector routing strategy
 */
JSGraphIt.VerticalLStrategy = function(connector)
{
	this.connector = connector;
	
	this.startSegment;
	this.endSegment;
	
	this.strategyName = "vertical_L";
	
	this.isApplicable = function()
	{
		var sourceMiddle = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
		var dl = this.connector.destination.left();
		var dw = this.connector.destination.width();
		var st = this.connector.source.top();
		var sh = this.connector.source.height();
		var destMiddle = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);

		if(sourceMiddle > dl && sourceMiddle < dl + dw)
			return false;
		if(destMiddle > st && destMiddle < st + sh)
			return false;
		return true;	
	}
	
	/**
	 * Chooses the longest segment as the "middle" segment.
	 */
	this.getMiddleSegment = function()
	{
		if(this.startSegment.length > this.endSegment.length)
			return this.startSegment;
		else
			return this.endSegment;
	}
	
	this.paint = function()
	{
		this.startSegment = this.connector.createSegment();
		this.endSegment = this.connector.createSegment();
		
		var destMiddleY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);
		var dl = this.connector.destination.left();
		var dw = this.connector.destination.width();
		var st = this.connector.source.top();
		var sh = this.connector.source.height();
		
		this.startSegment.startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
		
		// decide which side of the source block to connect to
		if(Math.abs(destMiddleY - st) < Math.abs(destMiddleY - (st + sh)))
		{
			// use the upper face
			this.startSegment.orientation = (destMiddleY < st) ? JSGraphIt.UP : JSGraphIt.DOWN;
			this.startSegment.startY = st;
		}
		else
		{
			// use the lower face
			this.startSegment.orientation = (destMiddleY > (st + sh)) ? JSGraphIt.DOWN : JSGraphIt.UP;
			this.startSegment.startY = st + sh;
		}
		
		this.startSegment.length = Math.abs(destMiddleY - this.startSegment.startY);
		
		// decide which side of the destination block to connect to
		if(Math.abs(this.startSegment.startX - dl) < Math.abs(this.startSegment.startX - (dl + dw)))
		{
			// use the left face
			this.endSegment.orientation = (this.startSegment.startX < dl) ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
			this.endSegment.length = Math.abs(this.startSegment.startX - dl);
		}
		else
		{
			// use the right face
			this.endSegment.orientation = (this.startSegment.startX > dl + dw) ? JSGraphIt.LEFT : JSGraphIt.RIGHT;
			this.endSegment.length = Math.abs(this.startSegment.startX - (dl + dw));
		}
	}
}

JSGraphIt.HorizontalCStrategy = function(connector, startOrientation)
{
	this.connector = connector;
	
	this.startSegment;
	this.middleSegment;
	this.endSegment;
	
	this.strategyName = "horizontal_c";
	
	this.getMiddleSegment = function()
	{
		return this.middleSegment;
	}	
	
	this.isApplicable = function()
	{
		return true;
	}
	
	this.paint = function()
	{
		this.startSegment = connector.createSegment();
		this.middleSegment = connector.createSegment();
		this.endSegment = connector.createSegment();
		
		var sign = 1;
		if(startOrientation == JSGraphIt.RIGHT)
			sign = -1;
		
		var startX = this.connector.source.left();
		if(startOrientation == JSGraphIt.RIGHT)
			startX += this.connector.source.width();
		var startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);
		
		var endX = this.connector.destination.left();
		if(startOrientation == JSGraphIt.RIGHT)
			endX += this.connector.destination.width();
		var endY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);

		this.startSegment.startX = startX;
		this.startSegment.startY = startY;
		this.startSegment.orientation = startOrientation;
		this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startX - endX));
		
		var vLength = endY - startY;
		this.middleSegment.orientation = vLength > 0 ? JSGraphIt.DOWN : JSGraphIt.UP;
		this.middleSegment.length = Math.abs(vLength);
		
		this.endSegment.orientation = startOrientation == JSGraphIt.LEFT ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
		this.endSegment.length = Math.max(0, sign * (endX - startX)) + this.connector.minSegmentLength;
	}
}

JSGraphIt.VerticalCStrategy = function(connector, startOrientation)
{
	this.connector = connector;
	
	this.startSegment;
	this.middleSegment;
	this.endSegment;
	
	this.strategyName = "vertical_c";
	
	this.getMiddleSegment = function()
	{
		return this.middleSegment;
	}	
	
	this.isApplicable = function()
	{
		return true;
	}
	
	this.paint = function()
	{
		this.startSegment = connector.createSegment();
		this.middleSegment = connector.createSegment();
		this.endSegment = connector.createSegment();
		
		var sign = 1;
		if(startOrientation == JSGraphIt.DOWN)
			sign = -1;
		
		var startY = this.connector.source.top();
		if(startOrientation == JSGraphIt.DOWN)
			startY += this.connector.source.height();
		var startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
		
		var endY = this.connector.destination.top();
		if(startOrientation == JSGraphIt.DOWN)
			endY += this.connector.destination.height();
		var endX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);

		this.startSegment.startX = startX;
		this.startSegment.startY = startY;
		this.startSegment.orientation = startOrientation;
		this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startY - endY));
		
		var hLength = endX - startX;
		this.middleSegment.orientation = hLength > 0 ? JSGraphIt.RIGHT : JSGraphIt.LEFT;
		this.middleSegment.length = Math.abs(hLength);
		
		this.endSegment.orientation = startOrientation == JSGraphIt.UP ? JSGraphIt.DOWN : JSGraphIt.UP;
		this.endSegment.length = Math.max(0, sign * (endY - startY)) + this.connector.minSegmentLength;
	}
}


JSGraphIt.strategies = [
	function(connector) {return new JSGraphIt.HorizontalSStrategy(connector)},
	function(connector) {return new JSGraphIt.VerticalSStrategy(connector)},
	function(connector) {return new JSGraphIt.HorizontalLStrategy(connector)},
	function(connector) {return new JSGraphIt.VerticalLStrategy(connector)},
	function(connector) {return new JSGraphIt.HorizontalCStrategy(connector, JSGraphIt.LEFT)},
	function(connector) {return new JSGraphIt.HorizontalCStrategy(connector, JSGraphIt.RIGHT)},
	function(connector) {return new JSGraphIt.VerticalCStrategy(connector, JSGraphIt.UP)},
	function(connector) {return new JSGraphIt.VerticalCStrategy(connector, JSGraphIt.DOWN)}
];

