/*!
 * jquery.event.drop - v 2.2
 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
 * Open Source MIT License - http://threedubmedia.com/code/license
 */
// Created: 2008-06-04
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x, event.drag 2.2

;(function($) { // secure $ jQuery alias
	// Events: drop, dropstart, dropend

	// add the jquery instance method
	$.fn.drop = function(str, arg, opts) {
		// figure out the event type
		var type = typeof str == "string" ? str : "",
			// figure out the event handler...
			fn = $.isFunction(str) ? str : $.isFunction(arg) ? arg : null;
		// fix the event type
		if (type.indexOf("drop") !== 0) {
			type = "drop"+ type;
		}
		// were options passed
		opts = (str == fn ? arg : opts) || {};
		// trigger or bind event handler
		return fn ? this.bind(type, opts, fn) : this.trigger(type);
	};

	// DROP MANAGEMENT UTILITY
	// returns filtered drop target elements, caches their positions
	$.drop = function(opts) {
		opts = opts || {};
		// safely set new options...
		drop.multi = opts.multi === true ? Infinity :
			opts.multi === false ? 1 : !isNaN(opts.multi) ? opts.multi : drop.multi;
		drop.delay = opts.delay || drop.delay;
		drop.tolerance = $.isFunction(opts.tolerance) ? opts.tolerance :
			opts.tolerance === null ? null : drop.tolerance;
		drop.mode = opts.mode || drop.mode || 'intersect';
	};

	// local refs (increase compression)
	var $event = $.event,
		$special = $event.special,
		// configure the drop special event
		drop = $.event.special.drop = {

		// these are the default settings
		multi: 1, // allow multiple drop winners per dragged element
		delay: 20, // async timeout delay
		mode: 'overlap', // drop tolerance mode

		// internal cache
		targets: [],

		// the key name for stored drop data
		datakey: "dropdata",

		// prevent bubbling for better performance
		noBubble: true,

		// count bound related events
		add: function(obj) {
			// read the interaction data
			var data = $.data(this, drop.datakey);
			// count another realted event
			data.related += 1;
		},

		// forget unbound related events
		remove: function() {
			$.data(this, drop.datakey).related -= 1;
		},

		// configure the interactions
		setup: function() {
			// check for related events
			if ($.data(this, drop.datakey)) {
				return;
			}
			// initialize the drop element data
			var data = {
				related: 0,
				active: [],
				anyactive: 0,
				winner: 0,
				location: {}
			};
			// store the drop data on the element
			$.data(this, drop.datakey, data);
			// store the drop target in internal cache
			drop.targets.push(this);
		},

		// destroy the configure interaction
		teardown: function() {
			var data = $.data(this, drop.datakey) || {};
			// check for related events
			if (data.related) {
				return;
			}
			// remove the stored data
			$.removeData(this, drop.datakey);
			// reference the targeted element
			var element = this;
			// remove from the internal cache
			drop.targets = $.grep(drop.targets, function(target) {
				return (target !== element);
			});
		},

		// shared event handler
		handler: function(event, dd) {
			// local vars
			var results, $targets;
			// make sure the right data is available
			if (!dd) {
				return;
			}
			// handle various events
			switch (event.type) {
				// draginit, from $.event.special.drag
				case 'mousedown': // DROPINIT >>
				case 'touchstart': // DROPINIT >>
					// collect and assign the drop targets
					$targets = $(drop.targets);
					if (typeof dd.drop == "string") {
						$targets = $targets.filter(dd.drop);
					}
					// reset drop data winner properties
					$targets.each(function() {
						var data = $.data(this, drop.datakey);
						data.active = [];
						data.anyactive = 0;
						data.winner = 0;
					});
					// set available target elements
					dd.droppable = $targets;
					// activate drop targets for the initial element being dragged
					$special.drag.hijack(event, "dropinit", dd);
					break;
				// drag, from $.event.special.drag
				case 'mousemove': // TOLERATE >>
				case 'touchmove': // TOLERATE >>
					drop.event = event; // store the mousemove event
					if (!drop.timer) {
						// monitor drop targets
						drop.tolerate(dd);
					}
					break;
				// dragend, from $.event.special.drag
				case 'mouseup': // DROP >> DROPEND >>
				case 'touchend': // DROP >> DROPEND >>
					drop.timer = clearTimeout(drop.timer); // delete timer
					if (dd.propagates) {
						$special.drag.hijack(event, "drop", dd);
						$special.drag.hijack(event, "dropend", dd);
					}
					break;
			}
		},

		// returns the location positions of an element
		locate: function(elem, index) {
			var data = $.data(elem, drop.datakey),
			$elem = $(elem),
			posi = $elem.offset() || {},
			height = $elem.outerHeight(),
			width = $elem.outerWidth(),
			location = {
				elem: elem,
				width: width,
				height: height,
				top: posi.top,
				left: posi.left,
				right: posi.left + width,
				bottom: posi.top + height
			};
			// drag elements might not have dropdata
			if (data) {
				data.location = location;
				data.index = index;
				data.elem = elem;
			}
			return location;
		},

		// test the location positions of an element against another OR an X,Y coord
		contains: function(target, test) { // target { location } contains test [x,y] or { location }
			return ((test[0] || test.left) >= target.left && (test[0] || test.right) <= target.right
				&& (test[1] || test.top) >= target.top && (test[1] || test.bottom) <= target.bottom);
		},

		// stored tolerance modes
		modes: { // fn scope: "$.event.special.drop" object
			// target with mouse wins, else target with most overlap wins
			'intersect': function(event, proxy, target) {
				return this.contains(target, [event.pageX, event.pageY]) ? // check cursor
					1e9 : this.modes.overlap.apply(this, arguments); // check overlap
			},
			// target with most overlap wins
			'overlap': function(event, proxy, target) {
				// calculate the area of overlap...
				return Math.max(0, Math.min(target.bottom, proxy.bottom) - Math.max(target.top, proxy.top))
					* Math.max(0, Math.min(target.right, proxy.right) - Math.max(target.left, proxy.left));
			},
			// proxy is completely contained within target bounds
			'fit': function(event, proxy, target) {
				return this.contains(target, proxy) ? 1 : 0;
			},
			// center of the proxy is contained within target bounds
			'middle': function(event, proxy, target) {
				return this.contains(target, [proxy.left + proxy.width * .5, proxy.top + proxy.height * .5]) ? 1 : 0;
			}
		},

		// sort drop target cache by by winner (dsc), then index (asc)
		sort: function(a, b) {
			return (b.winner - a.winner) || (a.index - b.index);
		},

		// async, recursive tolerance execution
		tolerate: function(dd) {
			// declare local refs
			var i, drp, drg, data, arr, len, elem,
			// interaction iteration variables
			x = 0, ia, end = dd.interactions.length,
			// determine the mouse coords
			xy = [drop.event.pageX, drop.event.pageY],
			// custom or stored tolerance fn
			tolerance = drop.tolerance || drop.modes[drop.mode];
			// go through each passed interaction...
			do if (ia = dd.interactions[x]) {
				// check valid interaction
				if (!ia) {
					return;
				}
				// initialize or clear the drop data
				ia.drop = [];
				// holds the drop elements
				arr = [];
				len = ia.droppable.length;
				// determine the proxy location, if needed
				if (tolerance) {
					drg = drop.locate(ia.proxy);
				}
				// reset the loop
				i = 0;
				// loop each stored drop target
				do if (elem = ia.droppable[i]) {
					data = $.data(elem, drop.datakey);
					drp = data.location;
					if (!drp) {
						continue;
					}
					// find a winner: tolerance function is defined, call it
					data.winner = tolerance ? tolerance.call(drop, drop.event, drg, drp)
						// mouse position is always the fallback
						: drop.contains(drp, xy) ? 1 : 0;
					arr.push(data);
				} while (++i < len); // loop
				// sort the drop targets
				arr.sort(drop.sort);
				// reset the loop
				i = 0;
				// loop through all of the targets again
				do if (data = arr[i]) {
					// winners...
					if (data.winner && ia.drop.length < drop.multi) {
						// new winner... dropstart
						if (!data.active[x] && !data.anyactive) {
							// check to make sure that this is not prevented
							if ($special.drag.hijack(drop.event, "dropstart", dd, x, data.elem)[0] !== false) {
								data.active[x] = 1;
								data.anyactive += 1;
							}
							// if false, it is not a winner
							else {
								data.winner = 0;
							}
						}
						// if it is still a winner
						if (data.winner) {
							ia.drop.push(data.elem);
						}
					}
					// losers...
					else if (data.active[x] && data.anyactive == 1) {
						// former winner... dropend
						$special.drag.hijack(drop.event, "dropend", dd, x, data.elem);
						data.active[x] = 0;
						data.anyactive -= 1;
					}
				} while (++i < len); // loop
			} while (++x < end) // loop
			// check if the mouse is still moving or is idle
			if (drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY) {
				delete drop.timer; // idle, don't recurse
			} else {// recurse
				drop.timer = setTimeout(function() {
					drop.tolerate(dd);
				}, drop.delay);
			}
			// remember event, to compare idleness
			drop.last = drop.event;
		}

	};

	// share the same special event configuration with related events...
	$special.dropinit = $special.dropstart = $special.dropend = drop;
})(jQuery); // confine scope