(function($){

	$.fn.lDrag = function(options) {
		this.each(function() {
			$.lDrag.create(this, options);
		});
	};

	$.fn.lDragBind = function(type, fn) {
		this.each(function() {
			$.lDrag.bind(this, type, fn);
		});
	};

	$.fn.lDragUnbind = function(type, fn) {
		this.each(function() {
			$.lDrag.unbind(this, type, fn);
		});
	};


	/* Define lDrag */

	$.lDrag = function(container, options) {
		/* OPTIONS
		 * handle (object): drag handle
		 * onStart (function): excute at start
		 * onMove (function): execute during dragging
		 * onComplete (function):  execute when completed
		 */
		$.lDrag.create(container, options)
	};

	$.lDrag.extendNativeFunctionObject({
		container : null,
		isDragging : false,
		scrollDirection: "",
		scrollTimer: 0,

		bind: function(container, type, fn) {
			if (container.dragSettings && type && fn) {
				if (type == "start") {
					container.dragSettings.onDragStart.push(fn);
				}
				else if (type == "move") {
					container.dragSettings.onDrag.push(fn);
				}
				else if (type == "complete") {
					container.dragSettings.onDragEnd.push(fn);
				}
			}
		},

		unbind: function(container, type, fn) {
			if (container.dragSettings && type && fn) {
				var settings = container.dragSettings;
				var prop = "";

				if (type == "start") {
					prop = "onDragStart";
				}
				else if (type == "move") {
					prop = "onDrag";
				}
				else if (type == "complete") {
					prop = "onDragEnd";
				}

				for (var i = 0; i < settings[prop].length; i++) {
					if (settings[prop][i] == fn) {
						settings[prop].splice(i,1);
					}
				}
			}
		},

		create: function(container, options) {
			options = options || {};
			options.container = container;

		    if (options.handle == null) {
	    		options.handle = container;
    		}
    		else {
    			if (typeof options.handle == "string") {
    				options.handle = $(options.handle)[0];
    			}
				options.handle.dragSettings = options;
    		}
			container.dragSettings = options;

			options.onDragStart = [];
			options.onDrag = [];
			options.onDragEnd = [];

			// Bind event listeners
			if (options.onStart) {
				$.lDrag.bind(container, "start", options.onStart);
			}
			if (options.onMove) {
				$.lDrag.bind(container, "move", options.onMove);
			}
			if (options.onComplete) {
				$.lDrag.bind(container, "complete", options.onComplete);
			}

			jQuery(options.handle).mousedown($.lDrag.onMouseDown);
		},

		scroll: function() {
			Liferay.Animate("layoutDragScroll", $.lDrag.scrollStart);
		},

	    scrollStart: function() {
	        var nwPosition;
	        var container = $.lDrag.container;
	        var jContainer = $($.lDrag.container);
	        var setTimer = false;
	        var scrollSpeed = 20;
	        var scrollTop = Viewport.scroll().y;

	        if ($.lDrag.scrollDirection == "down") {
	            nwPosition = jContainer.northwestPosition();
	            nwPosition.y += scrollSpeed;
	            nwPosition.reposition(container);
	            window.scrollTo(0, scrollTop + scrollSpeed);
	            setTimer = true;
	        }
	        else if ($.lDrag.scrollDirection == "up" && scrollTop > 0) {
	            nwPosition = jContainer.northwestPosition();
	            nwPosition.y -= scrollSpeed;
	            nwPosition.reposition(container);
	            window.scrollTo(0, scrollTop - scrollSpeed);
	            setTimer = true;
	        }
	        else {
	            setTimer = false;
	        }

	        if (!setTimer) {
	            $.lDrag.scrollDirection = "";
	            $.lDrag.scrollTimer = 0;
	            return false;
	        }
	    },

		onMouseDown: function(event) {
			mousePos.update(event);

			var settings = this.dragSettings;
			var container = settings.container;
			var jContainer = $(settings.container);

			if (!container._LFR_noDrag) {
				$.lDrag.container = container;
	
				var nwOffset = jContainer.northwestOffset(true);
				var seOffset = nwOffset.plus(jContainer.xySize());
	
				settings.originalZIndex = container.style.zIndex;
	
				// Offset of the mouse relative to the dragging container
				// This should remain constant.
				settings.mouseNwOffset = mousePos.minus(nwOffset);
				settings.mouseSeOffset = mousePos.minus(seOffset);
				settings.mouseStart = new Coordinate(mousePos.x, mousePos.y);
	
				$.lDrag._processListeners(settings, "start");
	
				$.lDrag._setConstraint(settings);

				jQuery(document).mousemove($.lDrag.onMouseMove);
				jQuery(document).mouseup($.lDrag.onMouseUp);
			
				return false;
			}
			else {
				return;
			}
		},

		onMouseMove: function(event) {
			mousePos.update(event);
			// Assigning "container" because event is associated with the document
			// and not the dragging obj.  This is for robustness during a drag
			var container = $.lDrag.container;
			var settings = container.dragSettings;

			if (!$.lDrag._isAboveThreshold(settings)) {
				return false;
			}
			else {
				$.lDrag.isDragging = true;
			}

			container = settings.clone ? $.lDrag._createClone(settings) : settings.container;

			var jContainer = $(container);
			var nwOffset = jContainer.northwestOffset(true);
			var nwPosition = jContainer.northwestPosition();
			var size = jContainer.xySize();
			var seOffset = nwOffset.plus(size);
			var sePosition = nwPosition.plus(size);

			$.lDrag._setScrolling(settings);

			// new-pos = cur-pos + (mouse-pos - mouse-offset - screen-offset)
			//
			//	 new-pos: where we want to position the element using styles
			//	 cur-pos: current styled position of container
			//	 mouse-pos: mouse position
			//	 mouse-offset: mouse position relative to the dragging container
			//	 screen-offset: screen position of the current element
			//
			nwPosition = nwPosition.plus(mousePos.minus(nwOffset).minus(settings.mouseNwOffset));

			var offsetBefore = settings.autoCorrect ? jContainer.northwestOffset(true) : null;

			if (!settings.noDrag) {
				nwPosition.reposition(container);
			}

			$.lDrag._processListeners(settings, "move");

			// once dragging has started, the position of the container
			// relative to the mouse should stay fixed.  They can get out
			// of sync if the DOM is manipulated while dragging, so we
			// correct the error here
			//
			// changed to be recursive/use absolute offset for corrections


			if (settings.autoCorrect) {
				var offsetAfter = jContainer.northwestOffset(true);

				if (!offsetBefore.equals(offsetAfter)) {
					// Position of the container has changed after the onDrag call.
					// Move element to the current mouse position
					var errorDelta = offsetBefore.minus(offsetAfter);
					nwPosition = jContainer.northwestPosition().plus(errorDelta);
					nwPosition.reposition(container);
				}
			}

			return false;
		},

		onMouseUp: function(event) {
			event = mousePos.update(event);
			var container = $.lDrag.container;
			var settings = container.dragSettings;

			if (settings.clone) $.lDrag._destroyClone(settings);

			jQuery(document).unbind("mousemove", $.lDrag.onMouseMove);
			jQuery(document).unbind("mouseup", $.lDrag.onMouseUp);

			$.lDrag._processListeners(settings, "complete");
			$.lDrag.container = null;
			$.lDrag.isDragging = false;
		},

		_createClone: function(settings) {
			if (!settings.clonedNode) {
				var jClone = $("<div></div>");
				var jContainer = $(settings.container);
				var nwPosition = jContainer.northwestOffset(true);

				jClone.css({
					height: jContainer.height() + "px",
					left: nwPosition.x + "px",
					position: "absolute",
					top: nwPosition.y + "px",
					width: jContainer.width() + "px",
					zIndex: Liferay.zIndex.DRAG_ITEM
				});

				if (settings.opacity) jClone.css("opacity", settings.opacity);
				if (settings.dragClass) jClone.addClass(settings.dragClass);

				jClone[0].dragSettings = settings;
				settings.clonedNode = jClone[0];

				$("body").append(jClone);
			}

			return settings.clonedNode;
		},

		_destroyClone: function(settings) {
			if (settings.clonedNode) {
				var jClone = $(settings.clonedNode);
				var jContainer = $(settings.container);
				var containerPos = jContainer.northwestPosition();
				var newPos = containerPos.plus(mousePos.minus(settings.mouseStart));

				newPos.reposition(settings.container);

				jClone.remove();
				settings.clonedNode = null;
			}
		},

		_isAboveThreshold: function(settings) {
			var rt = true;
			if (!$.lDrag.isDragging && settings.threshold) {
				var distance = settings.mouseStart.distance(mousePos);
				if (distance < settings.threshold) {
					rt = false;
				}
			}

			return rt;
		},

		_processListeners: function(settings, type) {
			var prop = "";

			if (type == "start") {
				prop = "onDragStart";
			}
			else if (type == "move") {
				prop = "onDrag";
			}
			else if (type == "complete") {
				prop = "onDragEnd";
			}

			for (var i = 0; i < settings[prop].length; i++) {
				settings[prop][i](settings);
			}
		},

		_setConstraint: function(settings) {
			// Constraint coordinates are translated to mouse constraint coordinates.
			// The algorithm below will looks at the bounds of the dragging container and
			// makes sure that no part of it extends outside the constraint bounds.

			var minMouseX;
			var minMouseY;
			var maxMouseX;
			var maxMouseY;

			if (settings.minX != null)
				minMouseX = settings.minX + settings.mouseNwOffset.x;
			if (settings.minY != null)
				minMouseY = settings.minY + settings.mouseNwOffset.y;
			if (settings.maxX != null)
				maxMouseX = settings.maxX + settings.mouseSeOffset.x;
			if (settings.maxY != null)
				maxMouseY = settings.maxY + settings.mouseSeOffset.y;

			if (minMouseX && maxMouseX && minMouseX > maxMouseX)
				maxMouseX = minMouseX;
			if (minMouseY && maxMouseY && minMouseY > maxMouseY)
				maxMouseY = minMouseY;

			settings.mouseMin = new Coordinate(minMouseX, minMouseY);
			settings.mouseMax = new Coordinate(maxMouseX, maxMouseY);
		},

		_setScrolling: function(settings) {
			// Automatically scroll the page it drags near the top or bottom
			if (settings.scroll) {
				var scrollZone = 30;
				var scrollSpeed = 5;
				var scrollTop = Viewport.scroll().y;
				var pageHeight = Viewport.page().y;
				var clientHeight = Viewport.frame().y;

				if ((scrollTop + clientHeight + 2 * scrollZone) < pageHeight
						&& mousePos.y > (scrollTop + clientHeight - scrollZone)) {

		            if ($.lDrag.scrollDirection != "down"){
		                $.lDrag.scrollDirection = "down";
		                $.lDrag.scroll();
		            }
				}
				else if (scrollTop > 0 && mousePos.y < (scrollTop + scrollZone)) {
		            if ($.lDrag.scrollDirection != "up"){
		                $.lDrag.scrollDirection = "up";
		                $.lDrag.scroll();
		            }
				}
				else {
		            $.lDrag.scrollDirection = "";
				}
			}
		}
	});
})(jQuery);