(function(window, undefined) {

	var SvgConnecterIP2,
		CreateConnection,
		CreateConnectionSlave,
		CreateSvgConnection,
		ERROR;

	ERROR = {
		NO_PAPER: 'Raphael paper was not created because supplied body returned NULL',
		NO_CANVAS: 'Paper can not be supplied because Raphael was not instanciated'
	};

	/**
	 * @class SvgConnecter
	 *
	 *
	 */
	SvgConnecterIP2 = function(options) {
		return new SvgConnecterIP2.init(options);
	};

	$.extend(SvgConnecterIP2, {
		version: '0.1',
		author: 'Igor Szyporyn',
		getHelp: function() {
			return 'here is some help will be whatever cool I can think of';
		}
	});

	SvgConnecterIP2.prototype = {
		constructor: SvgConnecterIP2
	};
	SvgConnecterIP2.init = function(options) {

		var me = this,
			defaults,
			settings,
			checkedElement,
			raphael;

		// Trim options
		if (getType(options) === 'string') {
			options = {
				body: options
			};
		}

		defaults = {
			body: 'body',
			offset: {
				x: 0,
				y: 0
			},
			defaults: {
				color: '#000000',
				width: 2
			}
		};

		settings = $.extend(true,
			defaults,
			getType(options) === 'object' ? options : {}
		);

		me.defaults = settings.defaults;

		// Create the body property to use for SVG paper
		// Use elementChecker which will check for jQuery, Ext, Sizzle string, DOM node
		checkedElement = elementChecker(settings.body);

		me.jquery = checkedElement.jquery;

		me.component = checkedElement.component;

		me.paper = me.jquery ? buildRaphael(me) : ERROR.NO_PAPER;

		// Curtesy property containing the Raphael paper (SVG element) as a jQuery instance
		me.canvas = me.paper !== ERROR.NO_PAPER ? $(me.paper.canvas) : ERROR.NO_CANVAS;

		me.canvas.prependTo(checkedElement.jquery);

		me.connections = [];
	};
	SvgConnecterIP2.init.prototype = {
		add: function(options) {

			var me;
			var settings;
			var connection;
			var master;
			var controller;
			var checkedElement;
			var i; 
			var len;
			var parent = options.parent;

			me = this;

			settings = $.extend({}, me.defaults, options);

			checkedElement = elementChecker(settings.master);

			master = checkedElement.jquery;

			if (master) {

				connection = CreateConnection(me, checkedElement);

				parent.on('afterDrag', function() {
					placeGhost(connection);
				});

				if (connection) {

					i = 0;
					len = settings.slaves.length;

					for (; i < len; i++) {

						(function() {

							var slave = CreateConnectionSlave(me, settings.slaves[i], settings.occurrence[i], connection);

							if (slave.jquery) {
								connection.slaves.push(slave);
							}
						})();
					}

					me.connections.push(connection);
				}
			}

			return me;
		},
		remove: function() {
		},
		get: function(master) {
			var me,
				i, len,
				toReturn;

			me = this;

			master = elementChecker(master);

			toReturn = [];

			i = 0;
			len = me.connections.length;

			for (; i < len; i++) {
				if (me.connections[i].master.jquery.get(0) === master.jquery.get(0)) {
					toReturn.push(me.connections[i]);
				}
			}

			if (toReturn.length === 1) {
				toReturn = toReturn[0];
			}

			return toReturn;
		}
	};
	SvgConnecterIP2.init.prototype.addConnection = SvgConnecterIP2.init.prototype.add;

	/**
	 * @class CreateConnection
	 * Creates a connections
	 * @private
	 */
	CreateConnection = function(root, options) {
		return new CreateConnection.init(root, options);
	};
	CreateConnection.prototype = {
		constructor: CreateConnection
	};
	CreateConnection.init = function(root, options) {

		this.root = root;

		this.master = {
			jquery: options.jquery,
			component: options.component
		};

		this.controller = CreateController(root, this, options);

		this.slaves = [];
	};
	CreateConnection.init.prototype = {
		add: function() {
		},
		get: function(slave) {
			var me,
				i, len,
				toReturn,
				checkedElement;

			me = this;

			checkedElement = elementChecker(slave);

			slave = checkedElement.jquery;

			toReturn = [];

			i = 0;
			len = me.slaves.length;

			for (; i < len; i++) {
				if (me.slaves[i].jquery.get(0) === slave.get(0)) {
					toReturn.push(me.slaves[i]);
				}
			}

			if (toReturn.length === 1) {
				toReturn = toReturn[0];
			}

			return toReturn;
		},
		remove: function() {
		},
		hide: function() {
			var i = 0, len = this.slaves.length;

			for (; i < len; i++) {
				this.slaves[i].path.remove();
			}
			return this;
		},
		show: function() {
			var i = 0, len = this.slaves.length;

			for (; i < len; i++) {
				// Create the connection
				var slave = this.slaves[i];

				// SYMAND: We need to count the slaves that have allready been added and points to the same DOMElement,
				//     So we can push the line alittle to see multiply relations point to the same element:
				var count = Ext.Array.filter(this.slaves.slice(), function(s) {
					return s.jquery.get(0) == slave.jquery.get(0);
				}).length;

				CreateSvgConnection(this.root, slave, this, count * 10);
				this.slaves[i].path.show();
			}
			return this;
		},
		render: function() {
		}
	};

	/**
	 * @class CreateConnectionSlave
	 * Creates a slave for the connection
	 * @private
	 */
	CreateConnectionSlave = function(owner, options, occurrence, connection) {
		return new CreateConnectionSlave.init(owner, options, occurrence, connection);
	};
	CreateConnectionSlave.prototype = {
		constructor: CreateConnectionSlave
	};
	CreateConnectionSlave.init = function(owner, options, occurrence, connection) {

		var me,
			jquery,
			type,
			color,
			slave,
			checkedElement,
			width;

		me = this;

		color = owner.defaults.color;
		width = owner.defaults.width;
		type = getType(options);

		if (type === 'string') {
			checkedElement = elementChecker(options);
			jquery = checkedElement.jquery;
			component = checkedElement.component;
		}

		if (type === 'object') {
			slave = options.slave ? options.slave : options;
			checkedElement = elementChecker(slave);
			jquery = checkedElement.jquery;
			component = checkedElement.component;
			color = options.color ? options.color : color;
			width = options.width ? options.width : width;
		}

		if (type === 'array') {
			checkedElement = elementChecker(options[0]);
			jquery = checkedElement.jquery;
			component = checkedElement.component;
			color = options[1] ? options[1] : color;
			width = options[2] ? options[2] : width;
		}

		me.jquery = jquery;

		me.occurrence = occurrence;

		me.component = component;

		me.color = color;

		me.width = width;

		if (me.jquery) {
			me.connection = CreateSvgConnection(owner, me, connection);
		}
	};
	CreateConnectionSlave.init.prototype = {
	};

	/**
	 * @class CreateSvgConnection
	 * Creates a SVG connection between master and slave
	 * @private
	 */
	CreateSvgConnection = function(root, owner, connection, negative) {
		return new CreateSvgConnection.init(root, owner, connection, negative);
	};
	CreateSvgConnection.prototype = {
		constructor: CreateSvgConnection
	};
	CreateSvgConnection.init = function(root, owner, connection, negative) {

		var master, masterVertices,
			slave, slaveVertices,
			paper,
			offset;

		paper = root.paper;

		master = connection.master.jquery;

		var i_masterComponent = connection.master.component;
		var i_master = i_masterComponent.up("panel");

		var i_saveComponent = owner.component;
		var i_slave = i_saveComponent.up("panel");

		if (i_master) {
			if (i_master.isDragging) {
				var i_ghostExpand = i_master.ghostPanel.header.query("[type=expand]")[0];
				master = $(i_ghostExpand.el.dom);
			}
		}

		masterVertices = master.vertices();

		slave = owner.jquery;

		if (i_slave) {
			if (i_slave.isDragging) {
				var i_ghostExpand = i_slave.ghostPanel.header.query("[type=expand]")[0];
				slave = $(i_ghostExpand.el.dom);
			}
		}

		slaveVertices = slave.vertices();

		offset = root.jquery.offset();

		offset.startX = masterVertices.c.x - offset.left;
		offset.startY = masterVertices.c.y - offset.top;
		offset.endX = slaveVertices.c.x - offset.left;
		offset.endY = slaveVertices.c.y - offset.top;
		if (negative) {
			var direction = getLinearDirection(offset);
			if (direction == 'down') {
				offset.endX -= negative;
				offset.endY -= negative;
			}
			else {
				offset.endX += negative;
				offset.endY += negative;
			}
		}

		// gradient config
		if (root.defaults.gradient) {
			if (offset.startX == offset.endX) {
				// if vertical line and start -& end position are equal, add 1 to end position show to gradient
				offset.startX--;
			}
			else if (offset.startY == offset.endY) {
				// if horizontal line and start -& end position are equal, add 1 to end position show to gradient
				offset.startY--;
			}
		}

		var start = 'M' + offset.startX + ' ' + offset.startY;
		var end = 'L' + offset.endX + ' ' + offset.endY;
		var coordinates = start + end;

		var path = paper.path(coordinates);

		if (root.defaults.gradient) {
			path.attr({
				  'stroke-width': owner.width
			});
			var line = defineLinearGradient(owner, offset, root.defaults.gradient);
			path.strokeLinearGradient(line.defId, line.defCfg, line.width, line.stops);
		}
		else {
			  path.attr({
				  'stroke': owner.color,
				  'stroke-width': owner.width
			  });
		}

		path.hide();

		owner.path = path;
	};
	CreateSvgConnection.init.prototype = {};

	CreateController = function(root, owner) {
		return new CreateController.init(root, owner);
	};
	CreateController.prototype = {
		constructor: CreateController
	};
	CreateController.init = function(root, owner) {

		var paper,
			master,
			vertices,
			offset,
			x, y,
			width, height;

		paper = root.paper;
		master = owner.master.jquery;
		vertices = master.vertices();
		offset = root.jquery.offset();

		x = vertices.nw.x;
		y = vertices.nw.y;
		width = vertices.ne.x - x;
		height = vertices.sw.y - y;

		x -= offset.left;
		y -= offset.top;

		owner.ghost = paper.rect(x, y, width, height);
		owner.ghost.attr({
			opacity: 0,
			fill: '#000000'
		});

		owner.master.component.on('click', function() {

			swapCanvasOn(root, owner);

			$(document).bind('click.SvgConnecterIP2', function() {

				var i_masterComponent = owner.master.component;
				var i_master = i_masterComponent.up("panel");

				var b_dragging = false;

				if (i_master) {
					b_dragging = i_master.wasDragging;
					if (!b_dragging) {

						Ext.Array.each(owner.slaves, function(i_slave) {
							var i_component = i_slave.component;
							var i_panel = i_component.up("panel");
							b_dragging = i_panel.wasDragging;
							if (b_dragging) {
								return false;
							}
						});
					}
				}
				if (!b_dragging) {
					swapCanvasOff(root, owner);

					$(this).unbind('click.SvgConnecterIP2');
				}
			});
		});
	};
	CreateController.init.prototype = {};

	function placeGhost(owner) {
		var root,
			master,
			vertices,
			offset,
			x, y,
			width, height;

		root = owner.root;
		master = owner.master.jquery;
		vertices = master.vertices();
		offset = root.jquery.offset();

		x = vertices.nw.x;
		y = vertices.nw.y;
		width = vertices.ne.x - x;
		height = vertices.sw.y - y;

		x -= offset.left;
		y -= offset.top;

		owner.ghost.attr({
			x: x,
			y: y
		});
	}

	function swapCanvasOn(root, owner) {
		root.canvas.css('z-index', '9999');
		owner.ghost.show();
		owner.show();
		owner.ghost.toFront();
	}
	function swapCanvasOff(root, owner) {
		owner.hide();
		owner.ghost.hide();
		owner.ghost.toBack();
		root.canvas.css('z-index', '-1');
	}

	window.SvgConnecterIP2 = (function() {
		var error = checkRequired();
		return !error ? SvgConnecterIP2 : error;
	})();

	function isJquery(el) {
		return el instanceof jQuery;
	}

	function buildRaphael(me) {

		var x, y,
			width, height,
			offset;

		// Create container variable - check for jQuery, Ext, Sizzle selector string and regular DOM node
		// Convert to jQuery instance of DOM node
		container = elementChecker(me.jquery).jquery;

		offset = container.offset();
		x = offset.left;
		y = offset.top;
		width = container.outerWidth();
		height = container.outerHeight();

		return Raphael(0, 0, '100%', '100%');
	}

	// Check element for Ext, jQuery, IA, DOM node or Sizzle string selector
	// Return jQuery instance of derived DOM node
	// TODO: All of it - cheat for now
	function elementChecker(element) {

		var $element = null,
			component = null;

		// Is it a string
		if (getType(element) === 'string') {
			$element = $(element) instanceof jQuery && $(element).length > 0 ? $(element) : null;
			component = $element;
		}

		// Is it a object (jQuery, ExtJS, IA or DOM)
		if (getType(element) === 'object') {

			// Is it a jQuery element?
			if (isJquery(element)) {
				$element = element;
				component = $element;
			}

			// Is it a IA component?
			if (element.getEl && isJquery($(element.getEl()))) {
				$element = $(element.getEl());
				component = element;
			}

			// Is it a ExtJS component?
			if (element.el && element.el.dom && isJquery($(element.el.dom))) {
				$element = $(element.el.dom);
				component = element;
			}
		}

		return {
			jquery: $element,
			component: component
		};
	}

	function getType(s, l) {
		if (s === null) {
			s = "Null";
		}
		else if (s === undefined) {
			s = "Undefined";
		}
		else {
			s = (({}).toString.call(s)).slice(8, -1);
		}

		return l ? s : s.toLowerCase();
	}

	function checkRequired() {

		var errorMessages = [],
			error = false;

		// Is Raphael loaded?
		if (getType(window.Raphael) !== 'function') {
			errorMessages.push('Raphael not loaded');
			error = true;
		}

		// Is jQuery loaded?
		if (getType(window.jQuery) !== 'function') {
			errorMessages.push('jQuery not loaded');
			error = true;
		}

		// jQuery is loaded - how about jQuery plugins etc..
		else {

			// Is jQuery.fn.vertices loaded?
			if (getType(window.jQuery.fn.vertices) !== 'function') {
				errorMessages.push('jQuery vertices plugin not loaded');
				error = true;
			}
		}

		return error ? errorMessages : error;
	}

	var defCnt = 0;
	function defineLinearGradient(owner, offset, gradient) {
		var line = {};
		defCnt++;
		line.defId = 'gradient' + defCnt;
		line.width = owner.width;

		line.defCfg = (Math.abs(offset.startX - offset.endX) < 10 && Math.abs(offset.startY - offset.endY) > 10) ? [{ 'x1': '0%' },{ 'x2': '0%' },{ 'y1': '0%' },{ 'y2': '100%' }] : [{ 'x1': '0%' },{ 'x2': '100%' },{ 'y1': '0%' },{ 'y2': '0%' }];

		var direction = getLinearDirection(offset);

		var stops;
		if (owner.occurrence == 'one2one') {
			stops = ['master','master'];
		}
		else if (owner.occurrence == 'one2many') {
			if (direction == 'left' || direction == 'up') {
				stops = ['slave','master'];
			}
			else {
				stops = ['master','slave'];
			}
		}
		else if (owner.occurrence == 'many2one') {
			if (direction == 'right' || direction == 'down') {
				stops = ['slave','master'];
			}
			else {
				stops = ['master','slave'];
			}
		}
		else {
			stops = ['master','slave', 'master'];
		}

		line.stops = [];
		var offset2 = (stops.length == 3) ? ['20%', '50%', '80%'] : ['20%', '80%'];
		for (var i = 0; i < stops.length; i++) {
			line.stops.push({
				'offset': offset2[i],
				'stop-color': (stops[i] == 'master') ? gradient.master.color : gradient.slave.color
			});
		}

		return line;
	}
	function getLinearDirection(offset) {
		if (offset.endX - offset.startX > 5) {
			return 'right';
		}
		else if (offset.endX - offset.startX < -5) {
			return 'left';
		}
		else if (offset.endY - offset.startY > 5) {
			return 'down';
		}
		else if (offset.endY - offset.startY < -5) {
			return 'up';
		}
		else {
			return 'right';
		}
	}
})(window);

/* Plugin to Raphael   */
/* global Raphael:true */
(function() {
	if (Raphael.vml) {
		Raphael.el.strokeLinearGradient = function() {
			// not supporting VML yet
			return this; // maintain chainability
		};
	}
	else {
		var setAttr = function(el, attr) {
			var key;
			if (attr) {
				for (key in attr) {
					if (attr.hasOwnProperty(key)) {
						el.setAttribute(key, attr[key]);
					}
				}
			}
			else {
				return document.createElementNS("http://www.w3.org/2000/svg", el);
			}

			return null;
		};

		var defLinearGrad = function(defId, defCfg, stops) {
			var def = setAttr("linearGradient");
			var i, l;
			def.id = defId;

			for (i = 0, l = defCfg.length; i < l; i += 1) {
				setAttr(def, defCfg[i]);
			}

			for (i = 0, l = stops.length; i < l; i += 1) {
				var stopEle = setAttr("stop");
				var stop = stops[i];
				setAttr(stopEle, stop);

				def.appendChild(stopEle);
			}

			return def;
		};

		Raphael.el.strokeLinearGradient = function(defId, defCfg, width, stops) {

			if (stops) {
				this.paper.defs.appendChild(defLinearGrad(defId, defCfg, stops));
			}

			setAttr(this.node, {
				"stroke": "url(#" + defId + ")",
				"stroke-width": width
			});

			return this; // maintain chainability
		};

		Raphael.st.strokeLinearGradient = function(defId, defCfg, width, stops) {
			return this.forEach(function(el) {
				el.strokeLinearGradient(defId, defCfg, width, stops);
			});
		};

		Raphael.fn.defineLinearGradient = function(defId, defCfg, stops) {

			this.defs.appendChild(defLinearGrad(defId, defCfg, stops));
		};
	}
}());
