// jQuery WipeTouch 1.2.0
// ------------------------------------------------------------------------
//
// Developed and maintained by Igor Ramadas
// http://aboutigor.com
// http://devv.com
//
// USAGE
// ------------------------------------------------------------------------
//
// $(selector).wipetouch(config);
//
// The wipe events should expect the result object with the following properties:
// speed - the wipe speed from 1 to 5
// x - how many pixels moved on the horizontal axis
// y - how many pixels moved on the vertical axis
// source - the element which triggered the wipe gesture
//
// EXAMPLE
//		$(document).wipetouch({
//			allowDiagonal: true,
//			wipeLeft: function(result) { alert("Left on speed " + result.speed) },
//			wipeTopLeft: function(result) { alert("Top left on speed " + result.speed) },
//			wipeBottomLeft: function(result) { alert("Bottom left on speed " + result.speed) }
//		});
//
//
// More details at http://wipetouch.codeplex.com/
//
// CHANGE LOG
// ------------------------------------------------------------------------
// 1.2.0
// - New: wipeMove event, triggered while moving the mouse/finger.
// - New: added "source" to the result object.
// - Bug fix: sometimes vertical wipe events would not trigger correctly.
// - Bug fix: improved tapToClick handler.
// - General code refactoring.
// - Windows Phone 7 is not supported, yet! Its behaviour is completely broken and would require some special tricks to make it work. Maybe in the future...
//
// 1.1.0
// - New: tapToClick, if true will identify taps and and trigger a click on the touched element. Default is false.
// - Changed: events wipeBottom*** and wipeTop*** renamed to wipeDown*** and wipeUp***.
// - Changed: better touch speed calculation (was always too fast before).
// - Changed: speed will be an integer now (instead of float).
// - Changed: better wipe detection (if Y movement is more than X, do a vertical wipe instead of horizontal).
// - Bug fix: added preventDefault to touchStart and touchEnd internal events (this was missing).
// - Other general tweaks to the code.
//
// The minified version of WipeTouch can be generated using Jasc: http://jasc.codeplex.com

(function ($)
{
	$.fn.wipetouch = function (settings)
	{
		// ------------------------------------------------------------------------
		// PLUGIN SETTINGS
		// ------------------------------------------------------------------------

		var config = {

			// Variables and options
			moveX: 40, 	// minimum amount of horizontal pixels to trigger a wipe event
			moveY: 40, 	// minimum amount of vertical pixels to trigger a wipe event
			tapToClick: false, // if user taps the screen it will fire a click event on the touched element
			preventDefault: true, // if true, prevents default events (click for example)
			allowDiagonal: false, // if false, will trigger horizontal and vertical movements so wipeUpLeft, wipeDownLeft, wipeUpRight, wipeDownRight are ignored

			// Wipe events
			wipeLeft: false, // called on wipe left gesture
			wipeRight: false, // called on wipe right gesture
			wipeUp: false, // called on wipe up gesture
			wipeDown: false, // called on wipe down gesture
			wipeUpLeft: false, // called on wipe top and left gesture
			wipeDownLeft: false, // called on wipe bottom and left gesture
			wipeUpRight: false, // called on wipe top and right gesture
			wipeDownRight: false, // called on wipe bottom and right gesture
			wipeMove: false, // triggered whenever touchMove acts

			// DEPRECATED EVENTS
			wipeTopLeft: false, // USE WIPEUPLEFT
			wipeBottomLeft: false, // USE WIPEDOWNLEFT
			wipeTopRight: false, // USE WIPEUPRIGHT
			wipeBottomRight: false	// USE WIPEDOWNRIGHT
		};

		if (settings)
		{
			$.extend(config, settings);
		}

		this.each(function ()
		{
			// ------------------------------------------------------------------------
			// INTERNAL VARIABLES
			// ------------------------------------------------------------------------

			var startX; 					// where touch has started, left
			var startY; 					// where touch has started, top
			var startDate = false; 			// used to calculate timing and aprox. acceleration
			var curX; 						// keeps touch X position while moving on the screen
			var curY; 						// keeps touch Y position while moving on the screen
			var isMoving = false; 			// is user touching and moving?
			var touchedElement = false; 	// element which user has touched

			// These are for non-touch devices!
			var useMouseEvents = false; 	// force using the mouse events to simulate touch
			var clickEvent = false; 		// holds the click event of the target, when used hasn't clicked

			// ------------------------------------------------------------------------
			// TOUCH EVENTS
			// ------------------------------------------------------------------------

			// Called when user touches the screen.
			function onTouchStart(e)
			{
				var start = useMouseEvents || (e.originalEvent.touches && e.originalEvent.touches.length > 0);

				if (!isMoving && start)
				{
					if (config.preventDefault)
					{
						e.preventDefault();
					}

					// Temporary fix for deprecated events, these will be removed on next version!
					if (config.allowDiagonal)
					{
						if (!config.wipeDownLeft)
						{
							config.wipeDownLeft = config.wipeBottomLeft;
						}

						if (!config.wipeDownRight)
						{
							config.wipeDownRight = config.wipeBottomRight;
						}

						if (!config.wipeUpLeft)
						{
							config.wipeUpLeft = config.wipeTopLeft;
						}

						if (!config.wipeUpRight)
						{
							config.wipeUpRight = config.wipeTopRight;
						}
					}

					// When touch events are not present, use mouse events.
					if (useMouseEvents)
					{
						startX = e.pageX;
						startY = e.pageY;

						$(this).bind("mousemove", onTouchMove);
						$(this).one("mouseup", onTouchEnd);
					}
					else
					{
						startX = e.originalEvent.touches[0].pageX;
						startY = e.originalEvent.touches[0].pageY;

						$(this).bind("touchmove", onTouchMove);
					}

					// Set the start date and current X/Y.
					startDate = new Date().getTime();
					curX = startX;
					curY = startY;
					isMoving = true;

					touchedElement = $(e.target);
				}
			}

			// Called when user untouches the screen.
			function onTouchEnd(e)
			{
				if (config.preventDefault)
				{
					e.preventDefault();
				}

				// When touch events are not present, use mouse events.
				if (useMouseEvents)
				{
					$(this).unbind("mousemove", onTouchMove);
				}
				else
				{
					$(this).unbind("touchmove", onTouchMove);
				}

				// If is moving then calculate the touch results, otherwise reset it.
				if (isMoving)
				{
					touchCalculate(e);
				}
				else
				{
					resetTouch();
				}
			}

			// Called when user is touching and moving on the screen.
			function onTouchMove(e)
			{
				if (config.preventDefault)
				{
					e.preventDefault();
				}

				if (useMouseEvents && !isMoving)
				{
					onTouchStart(e);
				}

				if (isMoving)
				{
					if (useMouseEvents)
					{
						curX = e.pageX;
						curY = e.pageY;
					}
					else
					{
						curX = e.originalEvent.touches[0].pageX;
						curY = e.originalEvent.touches[0].pageY;
					}

					// If there's a wipeMove event, call it passing
					// current X and Y position (curX and curY).
					if (config.wipeMove)
					{
						triggerEvent(config.wipeMove, {
							curX: curX,
							curY: curY
						});
					}
				}
			}

			// ------------------------------------------------------------------------
			// CALCULATE TOUCH AND TRIGGER
			// ------------------------------------------------------------------------

			function touchCalculate(e)
			{
				var endDate = new Date().getTime(); 	// current date to calculate timing
				var ms = startDate - endDate; 			// duration of touch in milliseconds

				var x = curX; 							// current left position
				var y = curY; 							// current top position
				var dx = x - startX; 					// diff of current left to starting left
				var dy = y - startY; 					// diff of current top to starting top
				var ax = Math.abs(dx); 					// amount of horizontal movement
				var ay = Math.abs(dy); 					// amount of vertical movement

				// If moved less than 15 pixels, touch duration is less than 100ms,
				// and tapToClick is true then trigger a click event and stop processing.
				if (ax < 15 && ay < 15 && ms < 100)
				{
					clickEvent = false;

					if (config.preventDefault)
					{
						resetTouch();

						touchedElement.trigger("click");
						return;
					}
				}
				// When touch events are not present, use mouse events.
				else if (useMouseEvents)
				{
					var evts = touchedElement.data("events");

					if (evts)
					{
						// Save click event to the temp clickEvent variable.
						var clicks = evts.click;

						if (clicks && clicks.length > 0)
						{
							$.each(clicks, function (i, f)
							{
								clickEvent = f;
								return;
							});

							touchedElement.unbind("click");
						}
					}
				}

				// Is it moving to the right or left, top or bottom?
				var toright = dx > 0;
				var tobottom = dy > 0;

				// Calculate speed from 1 to 5, 1 being slower and 5 faster.
				var s = ((ax + ay) * 60) / ((ms) / 6 * (ms));

				if (s < 1) s = 1;
				if (s > 5) s = 5;

				var result = {
					speed: parseInt(s),
					x: ax,
					y: ay,
					source: touchedElement
				};

				if (ax >= config.moveX)
				{
					// Check if it's allowed and trigger diagonal wipe events.
					if (config.allowDiagonal && ay >= config.moveY)
					{
						if (toright && tobottom)
						{
							triggerEvent(config.wipeDownRight, result);
						}
						else if (toright && !tobottom)
						{
							triggerEvent(config.wipeUpRight, result);
						}
						else if (!toright && tobottom)
						{
							triggerEvent(config.wipeDownLeft, result);
						}
						else
						{
							triggerEvent(config.wipeUpLeft, result);
						}
					}
					// Otherwise trigger horizontal events if X > Y.
					else if (ax >= ay)
					{
						if (toright)
						{
							triggerEvent(config.wipeRight, result);
						}
						else
						{
							triggerEvent(config.wipeLeft, result);
						}
					}
				}
				// If Y > X and no diagonal, trigger vertical events.
				else if (ay >= config.moveY && ay > ax)
				{
					if (tobottom)
					{
						triggerEvent(config.wipeDown, result);
					}
					else
					{
						triggerEvent(config.wipeUp, result);
					}
				}

				resetTouch();
			}

			// Resets the cached variables.
			function resetTouch()
			{
				startX = false;
				startY = false;
				startDate = false;
				isMoving = false;

				// If there's a click event, bind after a few miliseconds.
				if (clickEvent)
				{
					window.setTimeout(function ()
					{
						touchedElement.bind("click", clickEvent);
						clickEvent = false;
					}, 50);
				}
			}

			// Trigger a wipe event passing a result object with
			// speed from 1 to 5, x / y movement amount in pixels,
			// and the source element.
			function triggerEvent(wipeEvent, result)
			{
				if (wipeEvent)
				{
					wipeEvent(result);
				}
			}

			// ------------------------------------------------------------------------
			// ADD TOUCHSTART AND TOUCHEND EVENT LISTENERS
			// ------------------------------------------------------------------------

			if ("ontouchstart" in document.documentElement)
			{
				$(this).bind("touchstart", onTouchStart);
				$(this).bind("touchend", onTouchEnd);
			}
			else
			{
				useMouseEvents = true;

				$(this).bind("mousedown", onTouchStart);
				$(this).bind("mouseout", onTouchEnd);
			}
		});

		return this;
	};
})(jQuery);