/*
 * jsPlumb 1.0.4
 * 
 * Provides a way to visually connect elements on an HTML page.
 * 
 * http://morrisonpitt.com/jsPlumb/demo.html
 * http://code.google.com/p/jsPlumb
 * 
 */
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function( v, b, s ) {
	 for( var i = +b || 0, l = this.length; i < l; i++ ) {
	  if( this[i]===v || s && this[i]==v ) { return i; }
	 }
	 return -1;
	};
}
(function() {
	
	var repaintFunction = function() { jsPlumb.repaintEverything(); };
	var automaticRepaint = true;
    function repaintEverything() {
    	if (automaticRepaint)
    		repaintFunction();
    };
    var resizeTimer = null;
    $(window).bind('resize', function() {
    	if (resizeTimer) clearTimeout(resizeTimer);
	    resizeTimer = setTimeout(repaintEverything, 100);
     });
	
	var connections = {};
	var draggableStates = {};
	var _draggableByDefault = true;
	var offsets = [];
	var sizes = [];
	
	var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.)	
	
	/**
     * applies all the styles to the given context.  this just wraps the $.extend function. 
     * 
     * @param context
     * @param styles
     */
    var applyPaintStyle = function(context, styles) {
        $.extend(context, styles);
    };
    
    /**
     * Handles the drawing of an element.  
     * @param element jQuery element
     * @param ui UI object from jQuery's event system. may be null.
     */
    var _draw = function(element, ui) {
    	var id = element.attr("id");
    	var l = connections[id];
    	for (var i = 0; i < l.length; i++)
    		l[i].paint(id, ui);
    };
    
    /**
     * Sets whether or not the given element(s) should be draggable, regardless of what a particular
     * plumb command may request.
     * 
     * @param element May be a string, an jQuery object, or a list of strings.
     * @param draggable Whether or not the given element(s) should be draggable.
     */
    var _setDraggable = function(element, draggable) {
    	
    	var _helper = function(el, id) {
    		draggableStates[id] = draggable;
        	if (el.draggable) {
        		el.draggable("option", "disabled", !draggable);
        	}
    	};
    	
    	if (typeof element == 'object' && element.length) {
    		for (var i = 0; i < element.length; i++) {
    			_helper($(element[i]), element[i]);
    		}
    	}
    	else {
	    	var el = typeof element == 'string' ? $("#" + element) : element;
	    	var id = el.attr("id");
	    	_helper(el, id);
    	}
    };
	
	/**
	 * private method to do the business of hiding/showing.
	 * @param elId Id of the element in question
	 * @param state String specifying a value for the css 'display' property ('block' or 'none').
	 */
	var _setVisible = function(elId, state) {
    	var jpcs = connections[elId];
    	for (var i = 0; i < jpcs.length; i++) {
    		jpcs[i].canvas.style.display=state;
    		if (jpcs[i].drawEndpoints) {
    			jpcs[i].sourceEndpointCanvas.style.display=state;
    			jpcs[i].targetEndpointCanvas.style.display=state;
    		}
    	}
    };        
    
    /**
     * helper to create a canvas.
     * @param clazz optional class name for the canvas.
     */
    var _newCanvas = function(clazz) {
        var canvas = document.createElement("canvas");
        document.body.appendChild(canvas);
        canvas.style.position="absolute";
        if (clazz) { canvas.className=clazz; }
        
        if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        	// for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels
        	// is not big enough for the biggest connector/endpoint canvas you have at startup.
        	jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
        	canvas = G_vmlCanvasManager.initElement(canvas);          
        }
        
        return canvas;
    };               
    
    /**
     * helper to remove a canvas from the DOM.
     */
    var _removeCanvas = function(canvas) {
    	if (canvas != null) { 
    		try { document.body.removeChild(canvas); }
    		catch (e) { }
    	}
    	
    }; 
	
	/**
	 * generic anchor - can be situated anywhere.  params should contain three values, and may optionally have an 'offsets' argument:
	 * 
	 * x - the x location of the anchor as a percentage of the total width.  
	 * y - the y location of the anchor as a percentage of the total height.
	 * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
	 * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out.  may be null.
	 * 
	 */	
	var Anchor = function(params) {
		var self = this;
		this.x = params.x || 0; this.y = params.y || 0; this.orientation = params.orientation || [0,0]; this.offsets = params.offsets || [0,0];
		this.compute = function(xy, wh, txy, twh) {
			return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
		}
	};
	
	/**
	 * jsPlumb public API
	 */
    var jsPlumb = window.jsPlumb = {

	connectorClass : '_jsPlumb_connector',
	endpointClass : '_jsPlumb_endpoint',
	DEFAULT_PAINT_STYLE : { lineWidth : 10, strokeStyle : "red" },
    DEFAULT_ENDPOINT_STYLE : { fillStyle : null }, // meaning it will be derived from the stroke style of the connector.
    DEFAULT_ENDPOINT_STYLES : [ null, null ], // meaning it will be derived from the stroke style of the connector.
    DEFAULT_DRAG_OPTIONS : { },
    DEFAULT_CONNECTOR : null,
    DEFAULT_ENDPOINT : null,    
    DEFAULT_ENDPOINTS : [null, null],  // new in 0.0.4, the ability to specify diff. endpoints.  DEFAULT_ENDPOINT is here for backwards compatibility.            
    
    /**
    * Places you can anchor a connection to.  These are helpers for common locations; they all just return an instance
    * of Anchor that has been configured appropriately.  
    * 
    * You can write your own one of these; you
    * just need to provide a 'compute' method and an 'orientation'.  so you'd say something like this:
    * 
    * jsPlumb.Anchors.MY_ANCHOR = {
    * 	compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
    *   orientation : [ox, oy]
    * };
    *
    * compute takes the [x,y] position of the top left corner of the anchored element,
    * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
    * and returns where the anchor should be located.
    *
    * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
    * should go in, if possible.  it is an [x,y] matrix where a value of 0 means no preference,
    * -1 means go in a negative direction for the given axis, and 1 means go in a positive
    * direction.  so consider a TopCenter anchor: the orientation matrix for it is [0,-1],
    * meaning connections naturally want to go upwards on screen.  in a Bezier implementation, for example, 
    * the curve would start out going in that direction, before bending towards the target anchor.
    */
    Anchors :
	{    			
    	TopCenter : new Anchor({x:0.5, y:0, orientation:[0,-1] }),
    	BottomCenter : new Anchor({x:0.5, y:1, orientation:[0, 1] }),
    	LeftMiddle: new Anchor({x:0, y:0.5, orientation:[-1,0] }),
    	RightMiddle : new Anchor({x:1, y:0.5, orientation:[1,0] }),
    	Center : new Anchor({x:0.5, y:0.5, orientation:[0,0] }),
    	TopRight : new Anchor({x:1, y:0, orientation:[0,-1] }),
    	BottomRight : new Anchor({x:1, y:1, orientation:[0,1] }),
    	TopLeft : new Anchor({x:0, y:0, orientation:[0,-1] }),
    	BottomLeft : new Anchor({x:0, y:1, orientation:[0,1] })
    },

    /**
     * Types of connectors, eg. Straight, Bezier.
     */
    Connectors :
    {
        /**
         * The Straight connector draws a simple straight line between the two anchor points.
         */
        Straight : function() {
    	
    		var self = this;

            /**
             * Computes the new size and position of the canvas.
             * @param sourceAnchor Absolute position on screen of the source object's anchor.
             * @param targetAnchor Absolute position on screen of the target object's anchor.
             * @param positionMatrix  Indicates the relative positions of the left,top of the
             *  two plumbed objects.  so [0,0] indicates that the source is to the left of, and
             *  above, the target.  [1,0] means the source is to the right and above.  [0,1] means
             *  the source is to the left and below.  [1,1] means the source is to the right
             *  and below.  this is used to figure out which direction to draw the connector in.
             * @returns an array of positioning information.  the first two values are
             * the [left, top] absolute position the canvas should be placed on screen.  the
             * next two values are the [width,height] the canvas should be.  after that each
             * Connector can put whatever it likes into the array:it will be passed back in
             * to the paint call.  This particular function stores the origin and destination of
             * the line it is going to draw.  a more involved implementation, like a Bezier curve,
             * would store the control point info in this array too.
             */
            this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    			var w = Math.abs(sourcePos[0] - targetPos[0]);
                var h = Math.abs(sourcePos[1] - targetPos[1]);
                // these are padding to ensure the whole connector line appears
                var xo = 0.45 * w, yo = 0.45 * h;
                // these are padding to ensure the whole connector line appears
                w *= 1.9; h *=1.9;
                
                var x = Math.min(sourcePos[0], targetPos[0]) - xo;
                var y = Math.min(sourcePos[1], targetPos[1]) - yo;
                
                if (w < 2 * lineWidth) { 
            		// minimum size is 2 * line Width
            		w = 2 * lineWidth; 
            		// if we set this then we also have to place the canvas
            		x = sourcePos[0]  + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth;
            		xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;;
            	}
                if (h < 2 * lineWidth) { 
            		// minimum size is 2 * line Width
            		h = 2 * lineWidth; 
            		// if we set this then we also have to place the canvas
            		y = sourcePos[1]  + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth;
            		yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;;
            	}
                               
                var sx = sourcePos[0] < targetPos[0] ? w-xo : xo;
                var sy = sourcePos[1] < targetPos[1] ? h-yo : yo;
                var tx = sourcePos[0] < targetPos[0] ? xo : w-xo;
                var ty = sourcePos[1] < targetPos[1] ? yo : h-yo;
                var retVal = [ x, y, w, h, sx, sy, tx, ty ];
                                
                // return [canvasX, canvasY, canvasWidth, canvasHeight, 
                //         sourceX, sourceY, targetX, targetY] 
                return retVal;
            };

            this.paint = function(dimensions, ctx)
            {
                ctx.beginPath();
                ctx.moveTo(dimensions[4], dimensions[5]);
                ctx.lineTo(dimensions[6], dimensions[7]);
                ctx.stroke();
            };
        },
                
        /**
         * This Connector draws a Bezier curve with two control points.
         * @param curviness How 'curvy' you want the curve to be! This is a directive for the
         * placement of control points, not endpoints of the curve, so your curve does not 
         * actually touch the given point, but it has the tendency to lean towards it.  the larger
         * this value, the greater the curve is pulled from a straight line.
         * 
         * a future implementation of this could take the control points as arguments, rather
         * than fixing the curve to one basic shape.
         */
        Bezier : function(curviness) {
        	
        	var self = this;
        	this.majorAnchor = curviness || 150;
            this.minorAnchor = 10;
            
            this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) {
            	// determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
            	// points around if so (code could be tightened up)
            	var perpendicular = sourceAnchor.orientation[0] != targetAnchor.orientation[0] || sourceAnchor.orientation[1] == targetAnchor.orientation[1]; 
                var p = [];            
                var ma = self.majorAnchor, mi = self.minorAnchor;                
                if (!perpendicular) {
	                  if (sourceAnchor.orientation[0] == 0) // X
	                    p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
	                else p.push(point[0] - (ma * sourceAnchor.orientation[0]));
	                                 
	                 if (sourceAnchor.orientation[1] == 0) // Y
	                	p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
	                else p.push(point[1] + (ma * targetAnchor.orientation[1]));
                }
                 else {
	                if (targetAnchor.orientation[0] == 0) // X
	                	p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
	                else p.push(point[0] + (ma * targetAnchor.orientation[0]));
	                
	                if (targetAnchor.orientation[1] == 0) // Y
	                	p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
	                else p.push(point[1] + (ma * sourceAnchor.orientation[1]));
                 }

                return p;                
            };

            this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth)
            {
            	lineWidth = lineWidth || 0;
                var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
                var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
                var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2);
                var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2);
                var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
                var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor);                
                var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2);
                var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2);
                
                if (maxx > w) w = maxx;
                if (minx < 0) {
                    canvasX += minx; var ox = Math.abs(minx);
                    w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox;
                }                

                var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2);
                var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2);
                if (maxy > h) h = maxy;
                if (miny < 0) {
                    canvasY += miny; var oy = Math.abs(miny);
                    h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy;
                }

                // return [ canvasx, canvasy, canvasWidth, canvasHeight,
                //          sourceX, sourceY, targetX, targetY,
                //          controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y
                return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ];
            };

            this.paint = function(d, ctx) {
	            ctx.beginPath();
	            ctx.moveTo(d[4],d[5]);
	            ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);	            
	            ctx.stroke();
            }
        }
    },
    
    
    /**
     * Types of endpoint UIs.  we supply three - a circle of default radius 10px, a rectangle of
     * default size 20x20, and an image (with no default).  you can supply others of these if you want to - see the documentation
     * for a howto.
     */
    Endpoints : {	

    	/**
    	 * a round endpoint, with default radius 10 pixels.
    	 */
    	Dot : function(params) {
    	
    		params = params || { radius:10 };
    		var self = this;
    		this.radius = params.radius;
    		var defaultOffset = 0.5 * this.radius;
    		var defaultInnerRadius = this.radius / 3;
    		
    		var parseValue = function(value) {
    			try {
					return parseInt(value); 
				}
				catch(e) {
					if (value.substring(value.length - 1) == '%')
						return parseInt(value.substring(0, value - 1));
				}
    		}
    		
    		var calculateAdjustments = function(gradient) {
    			var offsetAdjustment = defaultOffset;
    			var innerRadius = defaultInnerRadius;
    			if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
            	if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
            	return [offsetAdjustment, innerRadius];
    		};
    		
	    	this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
    			var radius = endpointStyle.radius || self.radius;
    			var x = anchorPoint[0] - radius;
    			var y = anchorPoint[1] - radius;
    			jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2);
    			var ctx = canvas.getContext('2d');
    			var style = {};    			
    			applyPaintStyle(style, endpointStyle);
    			if (style.fillStyle ==  null) style.fillStyle = connectorPaintStyle.strokeStyle;
    			applyPaintStyle(ctx, style);
    			
    			var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
                if (endpointStyle.gradient && !ie) {
                	
                	var adjustments = calculateAdjustments(endpointStyle.gradient); 
                	var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0];
                	var xAdjust = orientation[0] == 1 ? adjustments[0] * -1:  adjustments[0];
                	var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]);
    	            for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
    	            	g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
    	            ctx.fillStyle = g;
                }

    			
    			ctx.beginPath();    			
    			ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
    			ctx.closePath();
    			ctx.fill();
	    	};
    	},
    	
    	/**
    	 * A Rectangular endpoint, with default size 20x20.
    	 */
    	Rectangle : function(params) {
        	
    		params = params || { width:20, height:20 };
    		var self = this;
    		this.width = params.width;
    		this.height = params.height;
    		
	    	this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {    		
    			var width = endpointStyle.width || self.width;
    			var height = endpointStyle.height || self.height;
    			var x = anchorPoint[0] - (width/2);
    			var y = anchorPoint[1] - (height/2);
    			jsPlumb.sizeCanvas(canvas, x, y, width, height);
    			var ctx = canvas.getContext('2d');
    			//todo: the fillStyle needs some thought. we want to support a few options:
    			// 1. nothing supplied; use the stroke color or the default if no stroke color.
    			// 2. a fill color supplied - use it
    			// 3. a gradient supplied - use it
    			// 4. setting the endpoint to the same color as the bg of the element it is attached to.
    			var style = {};    			
    			applyPaintStyle(style, endpointStyle);
    			if (style.fillStyle ==  null) style.fillStyle = connectorPaintStyle.strokeStyle;
    			applyPaintStyle(ctx, style);
    			
    			var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
                if (endpointStyle.gradient && !ie) {
                	// first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
                	var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0;
                	var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0;
                	var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0;
                	var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0;
    	            var g = ctx.createLinearGradient(x1,y1,x2,y2);
    	            for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
    	            	g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
    	            ctx.fillStyle = g;
                }
    			
    			ctx.beginPath();
    			ctx.rect(0, 0, width, height);
    			ctx.closePath();
    			ctx.fill();
	    	};
    	}, 
    	
    	Triangle : function(params) {
        	
    		params = params || { width:15, height:15 };
    		var self = this;
    		this.width = params.width;
    		this.height = params.height;
    		
	    	this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) 
			{    		
    			var width = endpointStyle.width || self.width;
    			var height = endpointStyle.height || self.height;
    			var x = anchorPoint[0] - width/2;
    			var y = anchorPoint[1] - height/2;
				
				jsPlumb.sizeCanvas(canvas, x, y, width, height);
				
    			var ctx = canvas.getContext('2d');
				var 
					offsetX = 0,
					offsetY = 0,
					angle = 0;
				
				if( orientation[0] == 1 )
				{
					offsetX = width;
					offsetY = height;
					angle = 180;
				}
				if( orientation[1] == -1 )
				{
					offsetX = width;
					angle = 90;
				}
				if( orientation[1] == 1 )
				{
					offsetY = height;
					angle = -90;
				}
				
				ctx.fillStyle = endpointStyle.fillStyle;
				
				ctx.translate(offsetX, offsetY);
				ctx.rotate(angle * Math.PI/180);

				ctx.beginPath();
				ctx.moveTo(0, 0);
				ctx.lineTo(width/2, height/2);
				ctx.lineTo(0, height);
				ctx.closePath();
				ctx.fill();
				
	    	};
    	}, 
    	
    	/**
    	 * Image endpoint - draws an image as the endpoint.  You must provide a 'url' property in the params object..
    	 */
    	Image : function(params) {
    		var self = this;
    		this.img = new Image();
    		var ready = false;
    		this.img.onload = function() {
    			self.ready = true;
    		};
    		this.img.src = params.url;
    		
    		var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
    			var width = self.img.width || endpointStyle.width;
    			var height = self.img.height || endpointStyle.height;
    			var x = anchorPoint[0] - (width/2);
    			var y = anchorPoint[1] - (height/2);
    			jsPlumb.sizeCanvas(canvas, x, y, width, height);
    			var ctx = canvas.getContext('2d');
    			ctx.drawImage(self.img,0,0);
    		};
    		
    		this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
    			if (self.ready) {
	    			actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
    			}
    			else 
    				window.setTimeout(function() {    					
    					self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
    				}, 200);
    		};    		
    	}
    },
    
    /**
     * establishes a connection between two elements.
     * @param params object containing setup for the connection.  see documentation.
     */
    connect : function(params) {
    	var jpc = new jsPlumbConnection(params);
    	var key = jpc.sourceId + "_" + jpc.targetId;
    	connections[key] = jpc;
    	var addToList = function(elId, jpc) {
    		var l = connections[elId];
    		if (l == null) {
    			l = [];
    			connections[elId] = l; 
    		}
    		l.push(jpc);
    	};
    	
    	// register this connection.
    	addToList(jpc.sourceId, jpc);
    	addToList(jpc.targetId, jpc);
    },           
    
    /**
     * Remove one connection to an element.
     * @param sourceId id of the first window in the connection
     * @param targetId id of the second window in the connection
     */
    detach : function(sourceId, targetId) {    	
    	var jpcs = connections[sourceId];
    	var idx = -1;
    	for (var i = 0; i < jpcs.length; i++) {
    		if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) {
    			_removeCanvas(jpcs[i].canvas);
    			if (jpcs[i].drawEndpoints) {
    				_removeCanvas(jpcs[i].targetEndpointCanvas);
    				_removeCanvas(jpcs[i].sourceEndpointCanvas);
    			}
    			idx = i;
    			break;
    		}
    	}
    	if (idx != -1)
    		jpcs.splice(idx, 1);
    	
    	// todo - dragging?  if no more connections for an object turn off dragging by default, but
    	// allow an override on it?
    },
    
    /**
     * remove all an element's connections.
     */
    detachAll : function(elId) {
    	var jpcs = connections[elId];
    	for (var i = 0; i < jpcs.length; i++) {
	    	_removeCanvas(jpcs[i].canvas);
			if (jpcs[i].drawEndpoints) {
				_removeCanvas(jpcs[i].targetEndpointCanvas);
				_removeCanvas(jpcs[i].sourceEndpointCanvas);
			}
    	}
    	delete connections[elId];
    	connections[elId] = [];
    },
    
    /**
     * remove all connections.
     */
    detachEverything : function() {
    	for (var elId in connections) {    		    
	    	var jpcs = connections[elId];
	    	if (jpcs.length) {
	    		try {
			    	for (var i = 0; i < jpcs.length; i++) {
			    		
				    	_removeCanvas(jpcs[i].canvas);
						if (jpcs[i].drawEndpoints) {
							_removeCanvas(jpcs[i].targetEndpointCanvas);
							_removeCanvas(jpcs[i].sourceEndpointCanvas);
						}
						
			    	}
	    		} catch (e) { }
	    	}
    	}
    	delete connections;
    	connections = [];
    },    
    
    getConnections : function(elId) {
    	return connections[elId];
    },
    
    /**
     * Set an element's connections to be hidden.
     */
    hide : function(elId) {
    	_setVisible(elId, "none");
    },
    
    /**
     * Creates an anchor with the given params.
     * x - the x location of the anchor as a percentage of the total width.  
	 * y - the y location of the anchor as a percentage of the total height.
	 * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
	 * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out.  optional. defaults to [0,0]. 
     */
    makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) {
    	// backwards compatibility here.  we used to require an object passed in but that makes the call very verbose.  easier to use
    	// by just passing in four values.  but for backwards compatibility if we are given only one value we assume it's a call in the old form.
    	var params = {};
    	if (arguments.length == 1) $.extend(params, x);
    	else {
    		params = {x:x, y:y};
    		if (arguments.length >= 4) {
    			params.orientation = [arguments[2], arguments[3]];
    		}
    		if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]];
    	}
    	return new Anchor(params);
    },
        
    
    /**
     * repaint element and its connections. element may be an id or the actual jQuery object.
     * this method gets new sizes for the elements before painting anything.
     */
    repaint : function(el) {
    	var _repaint = function(el, elId) {
	    	var jpcs = connections[elId];
	    	var idx = -1;
	    	var loc = {'absolutePosition': el.offset()};    	
	    	for (var i = 0; i < jpcs.length; i++) {
	    		jpcs[i].paint(elId, loc, true);
	    	}
    	};
    	
    	var _processElement = function(el) {
    		var ele = typeof(el)=='string' ? $("#" + el) : el;
	    	var eleId = ele.attr("id");
	    	_repaint(ele, eleId);
    	};
    	
    	// TODO: support a jQuery result object too!
    	
    	// support both lists...
    	if (typeof el =='object') {
    		for (var i = 0; i < el.length; i++)
    			_processElement(el[i]);
    	} // ...and single strings.
    	else _processElement(el);
    },       
    
    /**
     * repaint all connections.
     */
    repaintEverything : function() {
    	for (var elId in connections) {    		    
	    	var jpcs = connections[elId];
	    	if (jpcs.length) {
	    		try {
			    	for (var i = 0; i < jpcs.length; i++) {
			    		jpcs[i].repaint();
			    	}
	    		} catch (e) { }
	    	}
    	}
    },
    
    /**
     * sets/unsets automatic repaint on window resize.
     */
    setAutomaticRepaint : function(value) {
    	automaticRepaint = value;
    },
    
    /**
     * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so
     * one value is all that is required).  This is a hack for IE, because ExplorerCanvas seems
     * to need for a canvas to be larger than what you are going to draw on it at initialisation
     * time.  The default value of this is 1200 pixels, which is quite large, but if for some
     * reason you're drawing connectors that are bigger, you should adjust this value appropriately.
     */
    setDefaultNewCanvasSize : function(size) {
    	DEFAULT_NEW_CANVAS_SIZE = size;    	
    },
    
    /**
     * Sets whether or not a given element is draggable, regardless of what any plumb command
     * may request. 
     */
    setDraggable: function(element, draggable) {
    	_setDraggable(element, draggable);
    },
    
    /**
     * Sets whether or not elements are draggable by default.  Default for this is true.
     */
    setDraggableByDefault: function(draggable) {
    	_draggableByDefault = draggable;
    },
    
    /**
     * Sets the function to fire when the window size has changed and a repaint was fired.
     */
    setRepaintFunction : function(f) {
    	repaintFunction = f;
    },
        
    /**
     * Set an element's connections to be visible.
     */
    show : function(elId) {
    	_setVisible(elId, "block");
    },
    
    /**
     * helper to size a canvas.
     */
    sizeCanvas : function(canvas, x, y, w, h) {
        canvas.style.height = h + "px"; canvas.height = h;
        canvas.style.width = w + "px"; canvas.width = w; 
        canvas.style.left = x + "px"; canvas.style.top = y + "px";
    },
    
    /**
     * Toggles visibility of an element's connections.
     */
    toggle : function(elId) {
    	var jpcs = connections[elId];
    	if (jpcs.length > 0)
    		_setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none");
    }, 
    
    /**
     * Unloads jsPlumb, deleting all storage.  You should call this 
     */
    unload : function() {
    	delete connections;
		delete offsets;
		delete sizes;
		delete draggableStates;  
    }
};

// ************** connection
// ****************************************
/**
* allowed params:
* source:	source element (string or a jQuery element) (required)
* target:	target element (string or a jQuery element) (required)
* anchors: optional array of anchor placements. defaults to BottomCenter for source
*          and TopCenter for target.
*/
var jsPlumbConnection = function(params) {

// ************** get the source and target and register the connection. *******************
    var self = this;
    // get source and target as jQuery objects
    this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source;    
    this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target;
    this.sourceId = $(this.source).attr("id");
    this.targetId = $(this.target).attr("id");
    this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true;
    this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
    
 // get anchor
    this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter];
    // make connector
    this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier();
    this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE;
    // init endpoints
    this.endpoints = [];
    if(!params.endpoints) params.endpoints = [null,null];
    this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
    this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
    this.endpointStyles = [];
    if (!params.endpointStyles) params.endpointStyles = [null,null];
    this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
    this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
    
    offsets[this.sourceId] = this.source.offset(); 
    sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()]; 
    offsets[this.targetId] = this.target.offset();
    sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()];

// *************** create canvases on which the connection will be drawn ************
    var canvas = _newCanvas(jsPlumb.connectorClass);
    this.canvas = canvas;
    // create endpoint canvases
    if (this.drawEndpoints) {
	    this.sourceEndpointCanvas = _newCanvas(jsPlumb.endpointClass);	    
	    this.targetEndpointCanvas = _newCanvas(jsPlumb.endpointClass);
	    // sit them on top of the underlying element?
	    var setZIndex = function(canvas, source, above) {
    	   var adj = above ? 1 : -1;
    	   var zIndex = $(source).css("zIndex");
    	   $(canvas).css("zIndex", zIndex != "auto" ? zIndex + adj : "auto");
    	};

    	setZIndex(this.sourceEndpointCanvas, this.source, this.endpointsOnTop);
    	setZIndex(this.targetEndpointCanvas, this.target, this.endpointsOnTop);
    }
// ************** store the anchors     
  
    /**
     * paints the connection.
     * @param elId Id of the element that is in motion
     * @param ui jQuery's event system ui object (present if we came from a drag to get here)
     * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted.
     */
    this.paint = function(elId, ui, recalc) {    	
    	// if the moving object is not the source we must transpose the two references.
    	var swap = !(elId == this.sourceId);
    	var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
    	var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
    	
    	if (this.canvas.getContext) {
    		    		
    		if (recalc) {
	    		// get the current sizes of the two elements.
	    		var s = $("#" + elId);
	    		var t = $("#" + tId);
	    		sizes[elId] = [s.outerWidth(), s.outerHeight()];
	    		sizes[tId] = [t.outerWidth(), t.outerHeight()];
	    		offsets[elId] = s.offset();
	    		offsets[tId] = t.offset();
    		} else {
    			// faster to use the ui element if it was passed in.  offset is a fallback.
    			// fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with
    			// 1.8 and 1.7.
    			var pos = ui.absolutePosition || ui.offset;
        		var anOffset = ui != null ? pos : $("#" + elId).offset();
        		offsets[elId] = anOffset;
    		}
    		
    		var myOffset = offsets[elId]; 
    		var otherOffset = offsets[tId];
    		var myWH = sizes[elId];
            var otherWH = sizes[tId];
            
    		var ctx = canvas.getContext('2d');
            var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH);
            var sAnchorO = this.anchors[sIdx].orientation;
            var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH);
            var tAnchorO = this.anchors[tIdx].orientation;
            var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth);
            jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
            applyPaintStyle(ctx, this.paintStyle);
            
            var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
            if (this.paintStyle.gradient && !ie) { 
	            var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
	            for (var i = 0; i < this.paintStyle.gradient.stops.length; i++)
	            	g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]);
	            ctx.strokeStyle = g;
            }
            	            
            this.connector.paint(dim, ctx);
                            
            if (this.drawEndpoints) {
            	var style = this.endpointStyle || this.paintStyle;
            	var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas;
            	var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas;
            	this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle);
            	this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle);
            }
    	}
    };
    
    this.repaint = function() {
    	this.paint(this.sourceId, null, true);
    };

    // dragging
    var draggable = params.draggable == null ? _draggableByDefault : params.draggable;
    if (draggable && self.source.draggable) {    	
    	var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS; 
    	var dragCascade = dragOptions.drag || function(e,u) {};
    	var initDrag = function(element, dragFunc) {
    		var opts = $.extend({drag:dragFunc}, dragOptions);
    		var draggable = draggableStates[element.attr("id")];
    		opts.disabled = draggable == null ? false : !draggable;
        	element.draggable(opts);
    	};
    	initDrag(this.source, function(event, ui) {
    		_draw(self.source, ui);
    		dragCascade(event, ui);
    	});
    	initDrag(this.target, function(event, ui) {
    		_draw(self.target, ui);
    		dragCascade(event, ui);
    	});
    }
    
    // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not.
    if (this.source.resize) {
    	this.source.resize(function(e) {
    		jsPlumb.repaint(self.sourceId);
    	});
    }
    
    // finally, draw it.
    var o = this.source.offset();
    this.paint(this.sourceId, {'absolutePosition': this.source.offset()});
};

})();

// jQuery plugin code
(function($){
    $.fn.plumb = function(options) {
        var defaults = { };
        var options = $.extend(defaults, options);

        return this.each(function()
        {
            var obj = $(this);
            var params = {};
            params.source = obj;
            for (var i in options) {
                params[i] = options[i];
            }
            jsPlumb.connect(params);
        });
  };
  
  $.fn.detach = function(options) {
	  return this.each(function() 
	  {
		 var id = $(this).attr("id");
		 if (typeof options == 'string') options = [options];
		 for (var i = 0; i < options.length; i++)
			 jsPlumb.detach(id, options[i]);
	  });	  
  };
  
  $.fn.detachAll = function(options) {
	  return this.each(function() 
	  {
		 var id = $(this).attr("id");		 
		 jsPlumb.detachAll(id);
	  });	  
  };
})(jQuery);

