/*jslint undef: true, browser: true, continue: true, eqeq: true, vars: true, forin: true, white: true, newcap: false, nomen: true, plusplus: true, maxerr: 50, indent: 4 */

/**
 * jGestures: a jQuery plugin for gesture events
 * Copyright 2010-2011 Neue Digitale / Razorfish GmbH
 * Copyright 2011-2012, Razorfish GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @fileOverview
 * Razorfish GmbH javascript library: add touch events such as 'pinch',
 * 'rotate', 'swipe', 'tap' and 'orientationchange' on capable user agents.
 * For incapable devices there's a basic event substitution: a "tapone" event
 * can be triggered by "clicking", a "swipeone" by performing a swipe-ish
 * gesture using the mouse (buttondown - mousemove - buttonup).
 *
 * This is still a beta version, bugfixes and improvements appreciated.
 *
 * @author martin.krause@razorfish.de
 * @version 0.90-shake
 *
 * @requires
 * jQuery JavaScript Library v1.4.2 - http://jquery.com/
 *	Copyright 2010, John Resig
 *	Dual licensed under the MIT or GPL Version 2 licenses.
 *	http://jquery.org/license
 *
 * @example	jQuery('#swipe').bind('swipeone',eventHandler);
 *
 * Notification on native events:
 * On every native touchstart, touchend, gesturestart and gestureend-event,
 * jgestures triggers a corresponding custom event
 * ('jGestures.touchstart', 'jGestures.touchend;start', 'jGestures.touchend;processed',
 * 'jGestures.gesturestart', 'jGestures.gestureend;start', 'jGestures.gestureend;processed') on the event-element.
 * The  eventhandler's second argument represents the original touch event (yes: including all touchpoints).
 * Use this if you need very detailed control e.g. kinetic scrolling or implementing additional gestures.
 *
 * Every jGesture-eventhandler receives a custom object as second argument
 * containing the original event (originalEvent property) and processed
 * information (such as delta values and timesptamp).
 * Example:{
 *				type: eventtype e.g. "swipe","pinch",
 *				originalEvent: {DOM-Event},
 *				// default: just one entry on the delta-array - the first touchpoint
 *				// the first touchpoint is the reference point for every gesture,
 *				// because moving touchpoints in various directions would result in
 *				// a gesture.
 *				// delta and direction details are just provided for touch not for gesture / motion events
 *				delta : [
 *					{
 *						lastX:{Number} , // x-axis: relative to the last touchevent (e.g. touchmove!)
 *						lastY:{Number}, // y-axis: relative to the last touchevent (e.g. touchmove!)
 *						moved: {Number},  // distance: relative to the original touchpoint
 *						startX: {Number} , // relative to the original touchpoint
 *						startY: {Number} ,// relative to the original touchpoint
 *					} ],
 *				// based on the first touchpoint
 *				direction : { // relative to the last touchevent (e.g. touchmove!)
 *					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
 *					orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
 *					lastX : {Number}, // -1,0,+1 || null (orientationchange) // relative to the last touchevent (e.g. touchmove!)
 *					lastY : {Number}, // -1,0,+1 || null (orientationchange)// relative to the last touchevent (e.g. touchmove!)
 *					startX: {Number} , // relative to the original touchpoint
 *					startY: {Number} ,// relative to the original touchpoint
 *				},
 *				rotation: {Number} || null, // gestureonly: amount of rotation relative to the current position NOT the original
 *				scale: {Number} || null, // gestureonly: amount of scaling relative to the current position NOT the original
 *				duration: {Number}, // ms: relative to the original touchpoint
 *				description : {String} // details as String: {TYPE *}:{TOUCHES 1|2|3|4}:{X-AXIS 'right'|'left'|'steady'}:{Y-AXIS 'down'|'up'|'steady'} e.g. "swipe:1:left:steady" relative to the last touchpoint
 *			};
 *
 * Available jGesture-events can be grouped into:
 *
 *
 * Device events:
 *	The jGesture-Events in this group are triggered by the device.
 *
 * @event 'orientationchange'
 *		The device is turned clockwise or counterclockwise. This event is triggered
 *		by the device and might use an internal gyroscope.
 *	obj.description:
 *		orientationchange:landscape:clockwise:-90
 *		orientationchange:portrait:default:0
 *		orientationchange:landscape:counterclockwise|portrait:90
 *		orientationchange:portrait:upsidedown:180
 *
 *
 * Move events:
 *	The jGesture-Events in this group are triggered during the touch/gesture
 *	execution whenever a touchpoint changes.
 *	In contrast to touchend/gestureend-events which are triggered after
 *	the touch/gesture has completed.
 *
 * @event 'pinch'
 *		Is triggered during a pinch gesture (two fingers moving away from or
 *		towards each other).
 *	obj.description:
 *		pinch:-1:close
 *		pinch:+1:open
 *
 * @event 'rotate'
 *		Is triggered during a rotation gesture (two fingers rotating clockwise
 *		or counterclockwise).
 *	obj.description:
 *		rotate:-1:counterclockwise
 *		rotate:+1:+clockwise
 *
 * @event 'swipemove'
 *		Is triggered during a swipe move gesture (finger(s) being moved around
 *		the device, e.g. dragging)
 *	obj.description:
 *		swipemove:1:left:down
 *		swipemove:1:left:up
 *		swipemove:1:left:steady
 *		swipemove:1:right:down
 *		swipemove:1:right:up
 *		swipemove:1:right:steady
 *		swipemove:2:left:down
 *		swipemove:2:left:up
 *		swipemove:2:left:steady
 *		swipemove:2:right:down
 *		swipemove:2:right:up
 *		swipemove:2:right:steady
 *		swipemove:2:left:down
 *		swipemove:3:left:up
 *		swipemove:3:left:steady
 *		swipemove:3:right:down
 *		swipemove:3:right:up
 *		swipemove:3:right:steady
 *		swipemove:3:left:down
 *		swipemove:4:left:up
 *		swipemove:4:left:steady
 *		swipemove:4:right:down
 *		swipemove:4:right:up
 *		swipemove:4:right:steady
 *
 *
 * Toucheend events:
 *	The jGesture-Events in this group are triggered after the touch/gesture
 *	has completed.
 *	In contrast to touchmove-events which are triggered during the touch/gesture
 *	execution whenever a touchpoint changes.
 *
 * @event 'swipeone'
 *		Is triggered after a swipe move gesture with one touchpoint (one finger
 *		was moved around the device)
 *	obj.description:
 *		swipeone:1:left:down
 *		swipeone:1:left:up
 *		swipeone:1:left:steady
 *		swipeone:1:right:down
 *		swipeone:1:right:up
 *		swipeone:1:right:steady
 *
 * @event 'swipetwo'
 *		Is triggered after a swipe move gesture with two touchpoints (two fingers
 *		were moved around the device)
 *	obj.description:
 *		swipetwo:2:left:down
 *		swipetwo:2:left:up
 *		swipetwo:2:left:steady
 *		swipetwo:2:right:down
 *		swipetwo:2:right:up
 *		swipetwo:2:right:steady
 *
 * @event 'swipethree'
 *		Is triggered after a swipe move gesture with three touchpoints (three
 *		fingers were moved around the device)
 *	obj.description:
 *		swipethree:3:left:down
 *		swipethree:3:left:up
 *		swipethree:3:left:steady
 *		swipethree:3:right:down
 *		swipethree:3:right:up
 *		swipethree:3:right:steady
 *
 * @event 'swipefour'
 *		Is triggered after a swipe move gesture with four touchpoints (four
 *		fingers were moved around the device)
 *	obj.description:
 *		swipefour:4:left:down
 *		swipefour:4:left:up
 *		swipefour:4:left:steady
 *		swipefour:4:right:down
 *		swipefour:4:right:up
 *		swipefour:4:right:steady
 *
 *
 * @event 'swipeup'
 *		Is triggered after an  strict upwards swipe move gesture
 *	obj.description:
 *		swipe:1:steady:up
 *		swipe:2:steady:up
 *		swipe:3:steady:up
 *		swipe:4:steady:up
 *
 * @event 'swiperightup'
 *		Is triggered after a rightwards and upwards swipe move gesture
 *	obj.description:
 *		swipe:1:right:up
 *		swipe:2:right:up
 *		swipe:3:right:up
 *		swipe:4:right:up
 *
 * @event 'swiperight'
 *		Is triggered after a  strict rightwards swipe move gesture
 *	obj.description:
 *		swipe:1:right:steady
 *		swipe:2:right:steady
 *		swipe:3:right:steady
 *		swipe:4:right:steady
 *
 * @event 'swiperightdown'
 *		Is triggered after a rightwards and downwards swipe move gesture
 *	obj.description:
 *		swipe:1:right:down
 *		swipe:2:right:down
 *		swipe:3:right:down
 *		swipe:4:right:down
 *
 * @event 'swipedown'
 *		Is triggered after a  strict downwards swipe move gesture
 *	obj.description:
 *		swipe:1:steady:down
 *		swipe:2:steady:down
 *		swipe:3:steady:down
 *		swipe:4:steady:down
 *
 * @event 'swipeleftdown'
 *		Is triggered after a leftwards and downwards swipe move gesture
 *	obj.description:
 *		swipe:1:left:down
 *		swipe:2:left:down
 *		swipe:3:left:down
 *		swipe:4:left:down
 *
 * @event 'swipeleft'
 *		Is triggered after a strict leftwards swipe move gesture
 *	obj.description:
 *		swipe:1:left:steady
 *		swipe:2:left:steady
 *		swipe:3:left:steady
 *		swipe:4:left:steady
 *
 * @event 'swipeleftup'
 *		Is triggered after a leftwards and upwards swipe move gesture
 *	obj.description:
 *		swipe:1:left:up
 *		swipe:2:left:up
 *		swipe:3:left:up
 *		swipe:4:left:up
 *
 * @event 'tapone'
 *		Is triggered after a single (one finger) tap gesture
 *	obj.description:
 *		tapone
 *
 * @event 'taptwo'
 *		Is triggered after a double (two finger) tap gesture
 *	obj.description:
 *		taptwo
 * *
 * @event 'tapthree'
 *		Is triggered after a tripple (three finger) tap gesture
 *	obj.description:
 *		tapthree
 *
 *
 * Gestureend events:
 *	A gesture is an interpretation of different touchpoints.
 *	The jGesture-Events in this group are triggered when a gesture has finished
 *	and the touchpoints are removed from the device.
 *
 * @event 'pinchopen'
 *		Is triggered when a pinchopen gesture (two fingers moving away from each
 *		other) occured and the touchpoints (fingers) are removed the device.
 *	obj.description:
 *		pinch:+1:open
 *
 * @event 'pinchclose'
 *		Is triggered when a pinchclose gesture (two fingers moving towards each
 *		other) occured and the touchpoints (fingers) are removed the device.
 *	obj.description:
 *		pinch:-1:close
 *
 * @event 'rotatecw'
 *		Is triggered when a clockwise rotation gesture (two fingers rotating
 *		clockwise) occured and the touchpoints (fingers) are removed the device.
 *	obj.description:
 *		rotate:+1:+clockwise
 *
 * @event 'rotateccw'
 *		Is triggered when a counterclockwise rotation gesture (two fingers
 *		rotating counterclockwise) occured and the touchpoints (fingers) are
 *		removed the device.
 *	obj.description:
 *		rotate:-1:+counterclockwise
 *
 *
 * Motion events:
 *  A "motion event" is an interpretation of changes in space, e.g. a "shaking motion"
 *  consists of a specified number of acceleration changes in a given interval.
 * For understanding "directions", place your mobile device on a table with the bottom
 * (home button) close to you:
 *  - x-axis: horizontal left / right
 *  - y-axis: horizontal front / back (through the home button)
 *  - z-axis: vertical through your device
 *
 *  Note: Devicemotion / deviceorientation don't send custom event (such as: jGestures.touchstart).
 *  Note: Devicemotion should be bound on the "window-element" - because the whole device moves
 *
 * @event 'shake'
 *		Is triggered when a shaking motion is detected
 *	obj.description:
 *		shake:leftright:x-axisfrontback:y-axis:updown:z-axis
 *
 * @event 'shakefrontback'
 *		Is triggered when a shaking motion is detected and the gesture can be interpreted as a mainly front-back movement.
  *	obj.description:
 *		shakefrontback:shakefrontback:y-axis
 *
 * @event 'shakeleftright'
 *		Is triggered when a shaking motion is detected and the gesture can be interpreted as a mainly left-right movement.
 *		Additional major movements are mentioned in the obj.description.
 *	obj.description:
 *		shakeleftright:leftright:x-axis
 *
 * @event 'shakeupdown'
 *		Is triggered when a shaking motion is detected and the gesture can be interpreted as a mainly up-down movement.
 *		Additional major movements are mentioned in the obj.description.
 *	obj.description:
 *		shake:shakeupdown:updown:z-axis
 *
 * @example
 *		.bind( eventType, [ eventData ], handler(eventObject) )
 * jQuery('body').bind('tapone',function(){alert(arguments[1].description);})
 *
 */

 (function($) {

	/**
	* General thresholds.
	*/
	// @TODO: move to $...defaults
	// @TODO: shake to defaults freeze etc
	// change of x deg in y ms


	$.jGestures = {};
	$.jGestures.defaults = {};
	$.jGestures.defaults.thresholdShake =  {
		requiredShakes : 10,
		freezeShakes: 100,
		frontback : {
			sensitivity: 10
		 },
		leftright : {
			sensitivity: 10
		},
		updown : {
			sensitivity: 10
		}
	};

	$.jGestures.defaults.thresholdPinchopen = 0.05;
	$.jGestures.defaults.thresholdPinchmove = 0.05;
	$.jGestures.defaults.thresholdPinch = 0.05;
	$.jGestures.defaults.thresholdPinchclose = 0.05;
	$.jGestures.defaults.thresholdRotatecw = 5; //deg
	$.jGestures.defaults.thresholdRotateccw = 5; // deg
	// a tap becomes a swipe if x/y values changes are above this threshold
	$.jGestures.defaults.thresholdMove = 20;
	$.jGestures.defaults.thresholdSwipe = 100;
	// get capable user agents
	$.jGestures.data = {};
	$.jGestures.data.capableDevicesInUserAgentString = ['iPad','iPhone','iPod','Mobile Safari']; // basic functionality such as swipe, pinch, rotate, tap should work on every mobile safari, e.g. GalaxyTab
	$.jGestures.data.hasGestures = (function () { var _i; for(_i = 0; _i < $.jGestures.data.capableDevicesInUserAgentString.length; _i++ ) {  if (navigator.userAgent.indexOf($.jGestures.data.capableDevicesInUserAgentString[_i]) !== -1 ) {return true;} } return false; } )();
	$.hasGestures = $.jGestures.data.hasGestures;
	$.jGestures.events = {
		touchstart : 'jGestures.touchstart',
		touchendStart: 'jGestures.touchend;start',
		touchendProcessed: 'jGestures.touchend;processed',
		gesturestart: 'jGestures.gesturestart',
		gestureendStart: 'jGestures.gestureend;start',
		gestureendProcessed: 'jGestures.gestureend;processed'
	};

	jQuery
		.each({
			// "first domevent necessary"_"touch event+counter" : "exposed as"
			// event: orientationchange
			orientationchange_orientationchange01: "orientationchange",
			// event: gestures
			gestureend_pinchopen01: "pinchopen",
			gestureend_pinchclose01: "pinchclose",
			gestureend_rotatecw01 : 'rotatecw',
			gestureend_rotateccw01 : 'rotateccw',
			// move events
			gesturechange_pinch01: 'pinch',
			gesturechange_rotate01: 'rotate',
			touchstart_swipe13: 'swipemove',
			// event: touches
			touchstart_swipe01: "swipeone",
			touchstart_swipe02: "swipetwo",
			touchstart_swipe03: "swipethree",
			touchstart_swipe04: "swipefour",
			touchstart_swipe05: 'swipeup',
			touchstart_swipe06: 'swiperightup',
			touchstart_swipe07: 'swiperight',
			touchstart_swipe08: 'swiperightdown',
			touchstart_swipe09: 'swipedown',
			touchstart_swipe10: 'swipeleftdown',
			touchstart_swipe11: 'swipeleft',
			touchstart_swipe12: 'swipeleftup',
			touchstart_tap01: 'tapone',
			touchstart_tap02: 'taptwo',
			touchstart_tap03: 'tapthree',
			touchstart_tap04: 'tapfour',

			devicemotion_shake01: 'shake',
			devicemotion_shake02: 'shakefrontback',
			devicemotion_shake03: 'shakeleftright',
			devicemotion_shake04: 'shakeupdown'

		},

		/**
		* Add gesture events inside the jQuery.event.special namespace
		*/
		function( sInternal_, sPublicFN_ ) {

			// add as funciton to jQuery.event.special.sPublicFN_
			jQuery.event.special[ sPublicFN_ ] = {

				/**
				* When the first event handler is bound, jQuery executes the setup function.
				* This plugin just uses one eventhandler per element, regardless of the number of bound events.
				* All Events are stored internally as properties on the dom-element using the $.data api.
				* The setup-function adds the eventlistener, acting as a proxy function for the internal events.
				* $.data.ojQueryGestures[_sDOMEvent ('tap') ] = {Boolean}
				* @return {Void}
				*/
				setup: function () {
					// split the arguments to necessary controll arguements
					var _aSplit = sInternal_.split('_');
					var _sDOMEvent = _aSplit[0]; //
					// get the associated gesture event and strip the counter: necessary for distinguisihng similliar events such as tapone-tapfour
					var _sGestureEvent = _aSplit[1].slice(0,_aSplit[1].length-2);
					var _$element = jQuery(this);
					var _oDatajQueryGestures ;
					var oObj;
					// bind the event handler on the first $.bind() for a gestureend-event, set marker
					if (!_$element.data('ojQueryGestures') || !_$element.data('ojQueryGestures')[_sDOMEvent])  {
						// setup pseudo event
						_oDatajQueryGestures = _$element.data('ojQueryGestures') || {};
						oObj = {};
						// marker for:  domEvent being set on this element
						// e.g.: $.data.oGestureInternals['touchstart'] = true;
						// since they're grouped, i'm just marking the first one being added
						oObj[_sDOMEvent] = true;
						$.extend(true,_oDatajQueryGestures,oObj);
						_$element.data('ojQueryGestures' ,_oDatajQueryGestures);
						// add gesture events
						if($.hasGestures) {
							switch(_sGestureEvent) {

								// event: orientationchange
								case 'orientationchange':
									_$element.get(0).addEventListener('orientationchange', _onOrientationchange, false);
								break;

								// event:
								// - shake
								// - tilt
								case 'shake':
								case 'shakefrontback':
								case 'shakeleftright':
								case 'shakeupdown':
								case 'tilt':
									//$.hasGyroscope = true //!window.DeviceOrientationEvent;
									//_$element.get(0).addEventListener('devicemotion', _onDevicemotion, false);
									//_$element.get(0).addEventListener('deviceorientation', _onDeviceorientation, false);
									_$element.get(0).addEventListener('devicemotion', _onDevicemotion, false);
								break;

								// event:
								// - touchstart
								// - touchmove
								// - touchend
								case 'tap':
								case 'swipe':
								case 'swipeup':
								case 'swiperightup':
								case 'swiperight':
								case 'swiperightdown':
								case 'swipedown':
								case 'swipeleftdown':
								case 'swipeleft':
									_$element.get(0).addEventListener('touchstart', _onTouchstart, false);
								break;

								// event: gestureend
								case 'pinchopen':
								case 'pinchclose' :
								case 'rotatecw' :
								case 'rotateccw' :
									_$element.get(0).addEventListener('gesturestart', _onGesturestart, false);
									_$element.get(0).addEventListener('gestureend', _onGestureend, false);
								break;

								// event: gesturechange
								case 'pinch':
								case 'rotate':
									_$element.get(0).addEventListener('gesturestart', _onGesturestart, false);
									_$element.get(0).addEventListener('gesturechange', _onGesturechange, false);
								break;
							}
						}
						// create substitute for gesture events
						else {
							switch(_sGestureEvent) {
								// event substitutes:
								// - touchstart: mousedown
								// - touchmove: none
								// - touchend: mouseup
								case 'tap':
								case 'swipe':
									// _$element.get(0).addEventListener('mousedown', _onTouchstart, false);
									 _$element.bind('mousedown', _onTouchstart);
								break;

								// no substitution
								case 'orientationchange':
								case 'pinchopen':
								case 'pinchclose' :
								case 'rotatecw' :
								case 'rotateccw' :
								case 'pinch':
								case 'rotate':
								case 'shake':
								case 'tilt':

								break;
							}
						}

					}
					return false;
				},

				/**
				* For every $.bind(GESTURE) the add-function will be called.
				* Instead of binding an actual eventlister, the event is stored as $.data on the element.
				* The handler will be triggered using $.triggerHandler(GESTURE) if the internal
				* eventhandler (proxy being bound on setup()) detects a GESTURE event
				* @param {Object} event_ jQuery-Event-Object being passed by $.bind()
				* @return {Void}
				*/
				add : function(event_) {
					// add pseudo event: properties on $.data
					var _$element = jQuery(this);
					var _oDatajQueryGestures = _$element.data('ojQueryGestures');
//					_oDatajQueryGestures[event_.type] = { 'originalType' : event_.type , 'threshold' : event_.data.threshold, 'preventDefault' : event_.data.preventDefault } ;
					_oDatajQueryGestures[event_.type] = { 'originalType' : event_.type } ;
					return false;
				},

				/**
				* For every $.unbind(GESTURE) the remove-function will be called.
				* Instead of removing the actual eventlister, the event is removed from $.data on the element.
				* @param {Object} event_ jQuery-Event-Object being passed by $.bind()
				* @return {Void}
				*/
				remove : function(event_) {
					// remove pseudo event: properties on $.data
					var _$element = jQuery(this);
					var _oDatajQueryGestures = _$element.data('ojQueryGestures');
					_oDatajQueryGestures[event_.type] = false;
					_$element.data('ojQueryGestures' ,_oDatajQueryGestures );
					return false;
				},

				/**
				* The last $.unbind()-call on the domElement triggers the teardown function
				* removing the eventlistener
				* @return {Void}
				*/
				// @TODO: maybe rework teardown to work with event type?!
				teardown : function() {
					// split the arguments to necessary controll arguements
					var _aSplit = sInternal_.split('_');
					var _sDOMEvent = _aSplit[0]; //
					// get the associated gesture event and strip the counter: necessary for distinguisihng similliar events such as tapone-tapfour
					var _sGestureEvent = _aSplit[1].slice(0,_aSplit[1].length-2);
					var _$element = jQuery(this);
					var _oDatajQueryGestures;
					var oObj;
					// bind the event handler on the first $.bind() for a gestureend-event, set marker
					if (!_$element.data('ojQueryGestures') || !_$element.data('ojQueryGestures')[_sDOMEvent])  {
						// setup pseudo event
						_oDatajQueryGestures = _$element.data('ojQueryGestures') || {};
						oObj = {};
						// remove marker for:  domEvent being set on this element
						oObj[_sDOMEvent] = false;
						$.extend(true,_oDatajQueryGestures,oObj);
						_$element.data('ojQueryGestures' ,_oDatajQueryGestures);

						// remove gesture events
						if($.hasGestures) {
							switch(_sGestureEvent) {

								// event: orientationchange
								case 'orientationchange':
									_$element.get(0).removeEventListener('orientationchange', _onOrientationchange, false);
								break;

								case 'shake':
								case 'shakefrontback':
								case 'shakeleftright':
								case 'shakeupdown':
								case 'tilt':
									_$element.get(0).removeEventListener('devicemotion', _onDevicemotion, false);
								break;

								// event :
								// - touchstart
								// - touchmove
								// - touchend
								case 'tap':
								case 'swipe':
								case 'swipeup':
								case 'swiperightup':
								case 'swiperight':
								case 'swiperightdown':
								case 'swipedown':
								case 'swipeleftdown':
								case 'swipeleft':
								case 'swipeleftup':
									_$element.get(0).removeEventListener('touchstart', _onTouchstart, false);
									_$element.get(0).removeEventListener('touchmove', _onTouchmove, false);
									_$element.get(0).removeEventListener('touchend', _onTouchend, false);
								break;

								// event: gestureend
								case 'pinchopen':
								case 'pinchclose' :
								case 'rotatecw' :
								case 'rotateccw' :
									_$element.get(0).removeEventListener('gesturestart', _onGesturestart, false);
									_$element.get(0).removeEventListener('gestureend', _onGestureend, false);
								break;

								// event: gesturechange
								case 'pinch':
								case 'rotate':
									_$element.get(0).removeEventListener('gesturestart', _onGesturestart, false);
									_$element.get(0).removeEventListener('gesturechange', _onGesturechange, false);
								break;
							}
						}
						// remove substitute for gesture events
						else {
							switch(_sGestureEvent) {
								// event substitutes:
								// - touchstart: mousedown
								// - touchmove: none
								// - touchend: mouseup
								case 'tap':
								case 'swipe':
//									_$element.get(0).removeEventListener('mousedown', _onTouchstart, false);
//									_$element.get(0).removeEventListener('mousemove', _onTouchmove, false);
//									_$element.get(0).removeEventListener('mouseup', _onTouchend, false);
									_$element.unbind('mousedown', _onTouchstart);
									_$element.unbind('mousemove', _onTouchmove);
									_$element.unbind('mouseup', _onTouchend);
								break;

								// no substitution
								case 'orientationchange':
								case 'pinchopen':
								case 'pinchclose' :
								case 'rotatecw' :
								case 'rotateccw' :
								case 'pinch':
								case 'rotate':
								case 'shake':
								case 'tilt':

								break;
							}
						}

					}
				return false;
				}

			};
		});

	/**
	* Creates the object that ist passed as second argument to the $element.triggerHandler function.
	* This object contains detailed informations about the gesture event.
	* @param {Object} oOptions_  {type: {String}, touches: {String}, deltaY: {String},deltaX : {String}, startMove: {Object}, event:{DOM-Event}, timestamp:{String},vector: {Number}}
	* @example _createOptions (
	*				{
	*					type: 'swipemove',
	*					touches: '1',
	*					deltaY: _iDeltaY,
	*					deltaX : _iDeltaX,
	*					startMove: _oDatajQueryGestures.oStartTouch,
	*					event:event_,
	*					timestamp:_oEventData.timestamp,
	*					vector: -1
	*				}
	*			);
	* @returns {Object}
	*			{
	*				type: eventtype e.g. "swipe","pinch",
	*				originalEvent: {DOM-Event},
	*				// default: just one entry on the delta-array - the first touchpoint
	*				// the first touchpoint is the reference point for every gesture,
	*				// because moving touchpoints in various directions would result in
	*				// a gesture.
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : [
	*					{
	*						lastX:{Number} , // x-axis: relative to the last touchevent (e.g. touchmove!)
	*						lastY:{Number}, // y-axis: relative to the last touchevent (e.g. touchmove!)
	*						moved: {Number},  // distance: relative to the original touchpoint
	*						startX: {Number} , // relative to the original touchpoint
	*						startY: {Number} ,// relative to the original touchpoint
	*					} ],
	*				// based on the first touchpoint
	*				direction : { // relative to the last touchevent (e.g. touchmove!)
	*					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
	*					orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
	*					lastX : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					lastY : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					startX: {Number} , //-1,0,+1 relative to the original touchpoint
	*					startY: {Number} ,// -1,0,+1 relative to the original touchpoint
	*				},
	*				rotation: {Number} || null, // gestureonly: amount of rotation relative to the current position NOT the original
	*				scale: {Number} || null, // gestureonly: amount of scaling relative to the current position NOT the original
	*				duration: {Number}, // ms: relative to the original touchpoint
	*				description : {String} // details as String: {TYPE *}:{TOUCHES 1|2|3|4}:{X-AXIS 'right'|'left'|'steady'}:{Y-AXIS 'down'|'up'|'steady'} e.g. "swipe:1:left:steady" relative to the last touchpoint
	*			};
	*/
	function _createOptions(oOptions_) {
		// force properties
		oOptions_.startMove = (oOptions_.startMove) ? oOptions_.startMove : {startX: null,startY:null,timestamp:null}  ;
		var _iNow = new Date().getTime();
		var _oDirection;
		var _oDelta;
		// calculate touch differences
		if (oOptions_.touches) {
			// store delta values
			_oDelta = [
				{
					lastX: oOptions_.deltaX ,
					lastY: oOptions_.deltaY,
					moved: null,
					startX:  oOptions_.screenX - oOptions_.startMove.screenX ,
					startY: oOptions_.screenY - oOptions_.startMove.screenY
				}
			];

			_oDirection =  {
				vector: oOptions_.vector || null,
				orientation : window.orientation || null,
				lastX : ((_oDelta[0].lastX > 0) ? +1 : ( (_oDelta[0].lastX < 0) ? -1 : 0 ) ),
				lastY : ((_oDelta[0].lastY > 0) ? +1 : ( (_oDelta[0].lastY < 0) ? -1 : 0 ) ),
				startX : ((_oDelta[0].startX > 0) ? +1 : ( (_oDelta[0].startX < 0) ? -1 : 0 ) ),
				startY : ((_oDelta[0].startY > 0) ? +1 : ( (_oDelta[0].startY < 0) ? -1 : 0 ) )
			};

			// calculate distance traveled using the pythagorean theorem
			_oDelta[0].moved =  Math.sqrt(Math.pow(Math.abs(_oDelta[0].startX), 2) + Math.pow(Math.abs(_oDelta[0].startY), 2));

		}
		return {
			type: oOptions_.type || null,
			originalEvent: oOptions_.event || null,
			delta : _oDelta  || null,
			direction : _oDirection || { orientation : window.orientation || null, vector: oOptions_.vector || null},
			duration: (oOptions_.duration) ? oOptions_.duration : ( oOptions_.startMove.timestamp ) ? _iNow - oOptions_.timestamp : null,
			rotation: oOptions_.rotation || null,
			scale: oOptions_.scale || null,
			description : oOptions_.description || [
				oOptions_.type,
				':',
				oOptions_.touches,
				':',
				((_oDelta[0].lastX != 0) ? ((_oDelta[0].lastX > 0) ? 'right' : 'left') : 'steady'),
				':',
				((_oDelta[0].lastY != 0) ? ( (_oDelta[0].lastY > 0) ? 'down' : 'up') :'steady')
				].join('')
		};

	}



	/**
	* DOM-event handlers
	*/

	/**
	* Handler: orientationchange
	* Triggers the bound orientationchange handler on the window element
	* The "orientationchange" handler will receive an object with additional information
	* about the event.
	*  {
	*	direction : {
	*		orientation: {-90|0|90|180}
	*	},
	*	description : [
	*		'orientationchange:{landscape:clockwise:|portrait:default|landscape:counterclockwise|portrait:upsidedown}:{-90|0|90|180}' // e.g. 'orientation:landscape:clockwise:-90
	*	}
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onOrientationchange(event_) {

		// window.orientation: -90,0,90,180
		var _aDict = ['landscape:clockwise:','portrait:default:','landscape:counterclockwise:','portrait:upsidedown:'];

		$(window).triggerHandler('orientationchange',
			{
				direction : {orientation: window.orientation},
				description : [
					'orientationchange:',
					_aDict[( (window.orientation / 90) +1)],
					window.orientation
					].join('')
			});
	}


	/**
	* Handler: devicemotion
	* Calculates "motion events" such as shake, tilt, wiggle by observing "changes in space"
	* For understanding "directions", place your mobile device on a table with the bottom
	* (home button) close to you:
	*  - x-axis: horizontal left / right
	*  - y-axis: horizontal front / back (through the home button)
	*  - z-axis: vertical through your device
	* @param {DOM-Event} event_
	* @returns {Object}
	*			{
	*				type: eventtype e.g. "shake",
	*				originalEvent: {DOM-Event},
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : null,
	*				direction :{
	*					vector: null,
	*					orientation: -90,0,90,180 || null (window.orienntation)
	*				}
	*				rotation: {Number} , //  amount of rotation relative to the current position NOT the original
	*				scale: {Number} , // amount of scaling relative to the current position NOT the original
	*				duration: {Number}, // ms: duration of the motion
	*				description : {String} // details as String: pinch:{'close'|'open'} e.g. "pinch:-1:close" ||  rotate:{'counterclockwise'|'clockwise'} e.g. "rotate:-1:counterclockwise"
	*			};
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onDevicemotion(event_) {

		var _sType;
		var _$element = jQuery(window);
		//var _bHasGyroscope = $.hasGyroscope;

		// skip custom notification: devicemotion is triggered every 0.05s regardlesse of any gesture

		// get options
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		var _oThreshold = $.jGestures.defaults.thresholdShake;

		// get last position or set initital values
		var _oLastDevicePosition = _oDatajQueryGestures.oDeviceMotionLastDevicePosition || {
			accelerationIncludingGravity : {
				x: 0,
				y: 0,
				z: 0
			},
			shake : {
				eventCount: 0,
				intervalsPassed: 0,
				intervalsFreeze: 0
			},
			shakeleftright : {
				eventCount: 0,
				intervalsPassed: 0,
				intervalsFreeze: 0
			},
			shakefrontback : {
				eventCount: 0,
				intervalsPassed: 0,
				intervalsFreeze: 0
			},
			shakeupdown : {
				eventCount: 0,
				intervalsPassed: 0,
				intervalsFreeze: 0
			}
		};

		// cache current values
		var _oCurrentDevicePosition = {
			accelerationIncludingGravity : {
				x: event_.accelerationIncludingGravity.x,
				y: event_.accelerationIncludingGravity.y,
				z: event_.accelerationIncludingGravity.z
			},
			shake: {
				eventCount: _oLastDevicePosition.shake.eventCount,
				intervalsPassed: _oLastDevicePosition.shake.intervalsPassed,
				intervalsFreeze: _oLastDevicePosition.shake.intervalsFreeze
			 },
			 shakeleftright: {
				eventCount: _oLastDevicePosition.shakeleftright.eventCount,
				intervalsPassed: _oLastDevicePosition.shakeleftright.intervalsPassed,
				intervalsFreeze: _oLastDevicePosition.shakeleftright.intervalsFreeze
			 },
			 shakefrontback: {
				eventCount: _oLastDevicePosition.shakefrontback.eventCount,
				intervalsPassed: _oLastDevicePosition.shakefrontback.intervalsPassed,
				intervalsFreeze: _oLastDevicePosition.shakefrontback.intervalsFreeze
			 },
			 shakeupdown: {
				eventCount: _oLastDevicePosition.shakeupdown.eventCount,
				intervalsPassed: _oLastDevicePosition.shakeupdown.intervalsPassed,
				intervalsFreeze: _oLastDevicePosition.shakeupdown.intervalsFreeze
			 }

		};


		// options
		var _aType;
		var _aDescription;
		var _oObj;


		// trigger events for all bound pseudo events on this element
		for (_sType in _oDatajQueryGestures) {
			// get current pseudo event


			// trigger bound events on this element
			switch(_sType) {

				case 'shake':
				case 'shakeleftright':
				case 'shakefrontback':
				case 'shakeupdown':

					// options
					_aType = [];
					_aDescription = [];

					_aType.push(_sType);

					// freeze shake - prevent multiple shake events on one  shaking motion (user won't stop shaking immediately)
					if (++_oCurrentDevicePosition[_sType].intervalsFreeze > _oThreshold.freezeShakes && _oCurrentDevicePosition[_sType].intervalsFreeze < (2*_oThreshold.freezeShakes) ) { break;	}

					// set control values
					_oCurrentDevicePosition[_sType].intervalsFreeze  = 0;
					_oCurrentDevicePosition[_sType].intervalsPassed++;

					// check for shaking motions: massive acceleration changes in every direction
					if ( ( _sType === 'shake' ||_sType === 'shakeleftright' ) && ( _oCurrentDevicePosition.accelerationIncludingGravity.x > _oThreshold.leftright.sensitivity  || _oCurrentDevicePosition.accelerationIncludingGravity.x < (-1* _oThreshold.leftright.sensitivity) ) ) {
						_aType.push('leftright');
						_aType.push('x-axis');
					}

					if ( ( _sType === 'shake' ||_sType === 'shakefrontback' ) && (_oCurrentDevicePosition.accelerationIncludingGravity.y > _oThreshold.frontback.sensitivity  || _oCurrentDevicePosition.accelerationIncludingGravity.y < (-1 * _oThreshold.frontback.sensitivity) ) ) {
						_aType.push('frontback');
						_aType.push('y-axis');
					}

					if ( ( _sType === 'shake' ||_sType === 'shakeupdown' ) && ( _oCurrentDevicePosition.accelerationIncludingGravity.z+9.81 > _oThreshold.updown.sensitivity  || _oCurrentDevicePosition.accelerationIncludingGravity.z+9.81 < (-1 * _oThreshold.updown.sensitivity) ) ) {
						_aType.push('updown');
						_aType.push('z-axis');
					}

					// at least one successful shaking event
					if (_aType.length > 1) {
						// minimum number of shaking motions during  the defined "time" (messured by events - device event interval: 0.05s)
						if (++_oCurrentDevicePosition[_sType].eventCount == _oThreshold.requiredShakes && (_oCurrentDevicePosition[_sType].intervalsPassed) < _oThreshold.freezeShakes ) {
							// send event
							_$element.triggerHandler(_sType, _createOptions ({type: _sType, description: _aType.join(':'), event:event_,duration:_oCurrentDevicePosition[_sType].intervalsPassed*5 }) );
							// reset
							_oCurrentDevicePosition[_sType].eventCount = 0;
							_oCurrentDevicePosition[_sType].intervalsPassed = 0;
							// freeze shake
							_oCurrentDevicePosition[_sType].intervalsFreeze = _oThreshold.freezeShakes+1;
						}
						// too slow, reset
						else if (_oCurrentDevicePosition[_sType].eventCount == _oThreshold.requiredShakes && (_oCurrentDevicePosition[_sType].intervalsPassed) > _oThreshold.freezeShakes ) {
							_oCurrentDevicePosition[_sType].eventCount = 0 ;
							_oCurrentDevicePosition[_sType].intervalsPassed = 0;
						}
					}
				break;

			}

			// refresh pseudo events
			_oObj = {};
			_oObj.oDeviceMotionLastDevicePosition = _oCurrentDevicePosition;
			_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));

		}
	}


	/**
	* Handler: touchstart or mousedown
	* Setup pseudo-event by storing initial values such as :
	*	screenX : {Number}
	*	screenY : {Number}
	*	timestamp: {Number}
	*  on the pseudo gesture event and
	*  sets up additional eventlisteners for handling touchmove events.
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onTouchstart(event_) {

		// ignore bubbled handlers
		// if ( event_.currentTarget !== event_.target ) { return; }

		var _$element = jQuery(event_.currentTarget);
		// var _$element = jQuery(event_.target);

		// trigger custom notification
		_$element.triggerHandler($.jGestures.events.touchstart,event_);


		// set the necessary touch events
		if($.hasGestures) {
			event_.currentTarget.addEventListener('touchmove', _onTouchmove, false);
			event_.currentTarget.addEventListener('touchend', _onTouchend, false);
		}
		// event substitution
		else {
//			event_.currentTarget.addEventListener('mousemove', _onTouchmove, false);
//			event_.currentTarget.addEventListener('mouseup', _onTouchend, false);
			_$element.bind('mousemove', _onTouchmove);
			_$element.bind('mouseup', _onTouchend);
		}

		// get stored pseudo event
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		// var _oEventData = _oDatajQueryGestures[_sType];
		var _eventBase = (event_.touches) ? event_.touches[0] : event_;
		// store current values for calculating relative values (changes between touchmoveevents)
		var _oObj = {};
		_oObj.oLastSwipemove = { screenX : _eventBase.screenX, screenY : _eventBase.screenY, timestamp:new Date().getTime()};
		_oObj.oStartTouch = { screenX : _eventBase.screenX, screenY : _eventBase.screenY, timestamp:new Date().getTime()};

		_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));
	}


	/**
	* Handler: touchmove or mousemove
	* Calculates the x/y changes since the last event,
	* compares it to $.jGestures.defaults.thresholdMove and triggers
	* an swipemove event if the distance exceed the
	* threshold.
	* Custom-event argument object:
	* {Object}
	*			{
	*				type: e.g. 'swipemove',
	*				≈: {DOM-Event},
	*				// default: just one entry on the delta-array - the first touchpoint
	*				// the first touchpoint is the reference point for every gesture,
	*				// because moving touchpoints in various directions would result in
	*				// a gesture.
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : [
	*					{
	*						lastX:{Number} , // x-axis: relative to the last touchevent (e.g. touchmove!)
	*						lastY:{Number}, // y-axis: relative to the last touchevent (e.g. touchmove!)
	*						moved: {Number},  // distance: relative to the original touchpoint
	*						startX: {Number} , // relative to the original touchpoint
	*						startY: {Number} ,// relative to the original touchpoint
	*					} ],
	*				// based on the first touchpoint
	*				direction : { // relative to the last touchevent (e.g. touchmove!)
	*					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
	*					orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
	*					lastX : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					lastY : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					startX: {Number} , //-1,0,+1 relative to the original touchpoint
	*					startY: {Number} ,// -1,0,+1 relative to the original touchpoint
	*				},
	*				rotation: null, // gestureonly: amount of rotation relative to the current position NOT the original
	*				scale: null, // gestureonly: amount of scaling relative to the current position NOT the original
	*				duration: {Number}, // ms: relative to the original touchpoint
	*				description : {String} // details as String: {TYPE *}:{TOUCHES 1|2|3|4}:{X-AXIS 'right'|'left'|'steady'}:{Y-AXIS 'down'|'up'|'steady'} e.g. "swipe:1:left:steady" relative to the last touchpoint
	*			};
	*
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onTouchmove(event_) {

		var _$element = jQuery(event_.currentTarget);
		// var _$element = jQuery(event_.target);

		// get stored pseudo event
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		var _bHasTouches = !!event_.touches;
		var _iScreenX = (_bHasTouches) ? event_.changedTouches[0].screenX : event_.screenX;
		var _iScreenY = (_bHasTouches) ? event_.changedTouches[0].screenY : event_.screenY;

		//relative to the last event
		var _oEventData = _oDatajQueryGestures.oLastSwipemove;
		var _iDeltaX = _iScreenX - _oEventData.screenX   ;
		var _iDeltaY = _iScreenY - _oEventData.screenY;

		var _oDetails;

			// there's a swipemove set (not the first occurance), trigger event
		if (!!_oDatajQueryGestures.oLastSwipemove) {
			// check
			_oDetails = _createOptions({type: 'swipemove', touches: (_bHasTouches) ? event_.touches.length: '1', screenY: _iScreenY,screenX:_iScreenX ,deltaY: _iDeltaY,deltaX : _iDeltaX, startMove:_oEventData, event:event_, timestamp:_oEventData.timestamp});
			_$element.triggerHandler(_oDetails.type,_oDetails);
		}
		// store the new values
		var _oObj = {};
		var _eventBase = (event_.touches) ? event_.touches[0] : event_;
		_oObj.oLastSwipemove = { screenX : _eventBase.screenX, screenY : _eventBase.screenY, timestamp:new Date().getTime()};
		_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));
	}


	/**
	* Handler: touchend or mouseup
	* Removes the additional handlers (move/end)
	* Calculates the x/y changes since the touchstart event
	* not in relation to the last move event.
	* Triggers the
	*	swipeone|swipetwo|swipethree|swipefour|
	*	swipeup|swiperightup|swiperight|swiperightdown|swipedown|
	*	swipeleftdown|swipeleft|swipeleftup|
	*	tapone|taptwo|tapthree|tapfour
	* event.
	*		{Object}
	*			{
	*				type: eventtype e.g. "swipeone","swipeleftdown",
	*				originalEvent: {DOM-Event},
	*				// default: just one entry on the delta-array - the first touchpoint
	*				// the first touchpoint is the reference point for every gesture,
	*				// because moving touchpoints in various directions would result in
	*				// a gesture.
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : [
	*					{
	*						lastX:{Number} , // x-axis: relative to the last touchevent (e.g. touchmove!)
	*						lastY:{Number}, // y-axis: relative to the last touchevent (e.g. touchmove!)
	*						moved: {Number},  // distance: relative to the original touchpoint
	*						startX: {Number} , // relative to the original touchpoint
	*						startY: {Number} ,// relative to the original touchpoint
	*					} ],
	*				// based on the first touchpoint
	*				direction : { // relative to the last touchevent (e.g. touchmove!)
	*					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
	*					orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
	*					lastX : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					lastY : {Number}, // -1,0,+1 relative to the last touchevent (e.g. touchmove!)
	*					startX: {Number} , //-1,0,+1 relative to the original touchpoint
	*					startY: {Number} ,// -1,0,+1 relative to the original touchpoint
	*				},
	*				rotation: null,
	*				scale: null ,
	*				duration: {Number}, // ms: relative to the original touchpoint
	*				description : {String} // details as String: {TYPE *}:{TOUCHES 1|2|3|4}:{X-AXIS 'right'|'left'|'steady'}:{Y-AXIS 'down'|'up'|'steady'} e.g. "swipe:1:left:steady" relative to the last touchpoint
	*			};
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onTouchend(event_) {

		// ignore bubbled handlers
		// if ( event_.currentTarget !== event_.target ) { return; }

		var _$element = jQuery(event_.currentTarget);
		var _bHasTouches = !!event_.changedTouches;
		var _iTouches = (_bHasTouches) ? event_.changedTouches.length : '1';
		var _iScreenX = (_bHasTouches) ? event_.changedTouches[0].screenX : event_.screenX;
		var _iScreenY = (_bHasTouches) ? event_.changedTouches[0].screenY : event_.screenY;

		// trigger custom notification
		_$element.triggerHandler($.jGestures.events.touchendStart,event_);

		// var _$element = jQuery(event_.target);
		// remove events
		if($.hasGestures) {
			event_.currentTarget.removeEventListener('touchmove', _onTouchmove, false);
			event_.currentTarget.removeEventListener('touchend', _onTouchend, false);
		}
		// event substitution
		else {
//			event_.currentTarget.removeEventListener('mousemove', _onTouchmove, false);
//			event_.currentTarget.removeEventListener('mouseup', _onTouchend, false);
			_$element.unbind('mousemove', _onTouchmove);
			_$element.unbind('mouseup', _onTouchend);
		}
		// get all bound pseudo events
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		// if the current change on the x/y position is above the defined threshold for moving an element set the moved flag
		// to distinguish between a moving gesture and a shaking finger trying to tap
		var _bHasMoved = (
			Math.abs(_oDatajQueryGestures.oStartTouch.screenX - _iScreenX) > $.jGestures.defaults.thresholdMove ||
			Math.abs(_oDatajQueryGestures.oStartTouch.screenY - _iScreenY) > $.jGestures.defaults.thresholdMove
		) ? true : false;

		// if the current change on the x/y position is above the defined threshold for swiping set the moved flag
		// to indicate we're dealing with a swipe gesture
		var _bHasSwipeGesture = (
			Math.abs(_oDatajQueryGestures.oStartTouch.screenX - _iScreenX) > $.jGestures.defaults.thresholdSwipe ||
			Math.abs(_oDatajQueryGestures.oStartTouch.screenY - _iScreenY) > $.jGestures.defaults.thresholdSwipe
		) ? true : false;


		var _sType;
		var _oEventData ;

		var _oDelta;

		// calculate distances in relation to the touchstart position not the last touchmove event!
		var _iDeltaX;
		var _iDeltaY;
		var _oDetails;

		var _aDict = ['zero','one','two','three','four'];

		// swipe marker
		var _bIsSwipe;


		// trigger events for all bound pseudo events on this element
		for (_sType in _oDatajQueryGestures) {

			// get current pseudo event
			_oEventData = _oDatajQueryGestures.oStartTouch;

			_oDelta = {};
			_iScreenX = (_bHasTouches) ? event_.changedTouches[0].screenX : event_.screenX;
			_iScreenY = (_bHasTouches) ? event_.changedTouches[0].screenY : event_.screenY;
			// calculate distances in relation to the touchstart position not the last touchmove event!
			_iDeltaX = _iScreenX - _oEventData.screenX ;
			_iDeltaY = _iScreenY - _oEventData.screenY;
			_oDetails = _createOptions({type: 'swipe', touches: _iTouches, screenY: _iScreenY,screenX:_iScreenX ,deltaY: _iDeltaY,deltaX : _iDeltaX, startMove:_oEventData, event:event_, timestamp:  _oEventData.timestamp });


			// swipe marker
			_bIsSwipe = false;

			// trigger bound events on this element
			switch(_sType) {
				case 'swipeone':

					if( _bHasTouches === false && _iTouches == 1 && _bHasMoved === false){
						// trigger tapone!
						break;
					}
					if (_bHasTouches===false || ( _iTouches == 1  && _bHasMoved === true && _bHasSwipeGesture===true)) {
						_bIsSwipe = true;

						_oDetails.type = ['swipe',_aDict[_iTouches]].join('');
						_$element.triggerHandler(_oDetails.type,_oDetails);
					}
				break;

				case 'swipetwo':
					if (( _bHasTouches && _iTouches== 2 && _bHasMoved === true && _bHasSwipeGesture===true)) {
						_bIsSwipe = true;
						_oDetails.type = ['swipe',_aDict[_iTouches]].join('');
						_$element.triggerHandler(_oDetails.type,_oDetails);
					}
				break;

				case 'swipethree':
					if ( ( _bHasTouches && _iTouches == 3 && _bHasMoved === true && _bHasSwipeGesture===true)) {
						_bIsSwipe = true;
						_oDetails.type = ['swipe',_aDict[_iTouches]].join('');
						_$element.triggerHandler(_oDetails.type,_oDetails);
					}
				break;

				case 'swipefour':
					if ( ( _bHasTouches && _iTouches == 4 && _bHasMoved === true && _bHasSwipeGesture===true)) {
						_bIsSwipe = true;
						_oDetails.type = ['swipe',_aDict[_iTouches]].join('');
						_$element.triggerHandler(_oDetails.type,_oDetails);
					}
				break;

				case 'swipeup':
				case 'swiperightup':
				case 'swiperight':
				case 'swiperightdown':
				case 'swipedown':
				case 'swipeleftdown':
				case 'swipeleft':
				case 'swipeleftup':
					if ( _bHasTouches && _bHasMoved === true && _bHasSwipeGesture===true) {
						_bIsSwipe = true;
						_oDetails.type = [
									'swipe',
								((_oDetails.delta[0].lastX != 0) ? ((_oDetails.delta[0].lastX > 0) ? 'right' : 'left') : ''),
								((_oDetails.delta[0].lastY != 0) ? ((_oDetails.delta[0].lastY > 0) ? 'down' : 'up') :'')
									].join('');
						_$element.triggerHandler(_oDetails.type, _oDetails);
					}
				break;

				case 'tapone':
				case 'taptwo':
				case 'tapthree':
				case 'tapfour':
					if (( /* _bHasTouches && */ _bHasMoved !== true && _bIsSwipe !==true) && (_aDict[_iTouches] ==_sType.slice(3)) ) {
						_oDetails.description = ['tap',_aDict[_iTouches]].join('');
						_oDetails.type = ['tap',_aDict[_iTouches]].join('');
						_$element.triggerHandler(_oDetails.type,_oDetails);
						}
					break;

			}

			// refresh pseudo events
			var _oObj = {};
//			_oObj[_sType] = false;
//			_oObj.hasTouchmoved = false;
			_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));
			_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));

		}
		_$element.triggerHandler($.jGestures.events.touchendProcessed,event_);
	}


	/**
	* Handler: gesturestart
	* Setup pseudo-event by storing initial values such as :
	*	timestamp: {Number}
	*  on the pseudo gesture event
	* Since the gesture-event doesn't supply event.touches no tuchpoints will be calculated
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onGesturestart(event_) {

		// ignore bubbled handlers
		// if ( event_.currentTarget !== event_.target ) { return; }

		var _$element = jQuery(event_.currentTarget);
		// var _$element = jQuery(event_.target);

		// trigger custom notification
		_$element.triggerHandler($.jGestures.events.gesturestart,event_);


		// get stored pseudo event
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		// var _oEventData = _oDatajQueryGestures[_sType];
		// store current values for calculating relative values (changes between touchmoveevents)
		var _oObj = {};
		_oObj.oStartTouch = {timestamp:new Date().getTime()};
		_$element.data('ojQueryGestures',$.extend(true,_oDatajQueryGestures,_oObj));
	}

	/**
	* Handler: gesturechange
	* Read the event_.scale / event_.rotate values,
	* an triggers a pinch|rotate event if necessary.
	* Since the gesture-event doesn't supply event.touches no tuchpoints will be calculated
	* @returns {Object}
	*			{
	*				type: eventtype e.g. "pinch","rotate",
	*				originalEvent: {DOM-Event},
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : null,
	*				direction : {
	*					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
	*					 orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
	*				 },
	*				rotation: {Number} , //  amount of rotation relative to the current position NOT the original
	*				scale: {Number} , // amount of scaling relative to the current position NOT the original
	*				duration: {Number}, // ms: relative to the original touchpoint
	*				description : {String} // details as String: pinch:{'close'|'open'} e.g. "pinch:-1:close" ||  rotate:{'counterclockwise'|'clockwise'} e.g. "rotate:-1:counterclockwise"
	*			};
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onGesturechange(event_) {

		// ignore bubbled handlers
		// if ( event_.currentTarget !== event_.target ) { return; }

		var _$element = jQuery(event_.currentTarget);
		// var _$element = jQuery(event_.target);
		var _iDelta,_iDirection,_sDesc,_oDetails;
		// get all pseudo events
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		// trigger events for all bound pseudo events on this element
		var _sType;
		for (_sType in _oDatajQueryGestures) {

			// trigger a specific bound event
			switch(_sType) {

				case 'pinch':
					_iDelta = event_.scale;
					if ( ( ( _iDelta < 1 ) && (_iDelta % 1) < (1 - $.jGestures.defaults.thresholdPinchclose) ) || ( ( _iDelta > 1 ) && (_iDelta % 1) > ($.jGestures.defaults.thresholdPinchopen) ) ) {
						_iDirection = (_iDelta < 1 ) ? -1 : +1 ;
						_oDetails = _createOptions({type: 'pinch', scale: _iDelta, touches: null,startMove:_oDatajQueryGestures.oStartTouch, event:event_, timestamp: _oDatajQueryGestures.oStartTouch.timestamp, vector:_iDirection, description: ['pinch:',_iDirection,':' , ( (_iDelta < 1 ) ? 'close' : 'open' )].join('') });
						_$element.triggerHandler(_oDetails.type, _oDetails);
					}
				break;

				case 'rotate':
					_iDelta = event_.rotation;
					if ( ( ( _iDelta < 1 ) &&  ( -1*(_iDelta) > $.jGestures.defaults.thresholdRotateccw ) ) || ( ( _iDelta > 1 ) && (_iDelta  > $.jGestures.defaults.thresholdRotatecw) ) ) {
						_iDirection = (_iDelta < 1 ) ? -1 : +1 ;
						_oDetails = _createOptions({type: 'rotate', rotation: _iDelta, touches: null, startMove:_oDatajQueryGestures.oStartTouch, event:event_, timestamp: _oDatajQueryGestures.oStartTouch.timestamp, vector:_iDirection, description: ['rotate:',_iDirection,':' , ( (_iDelta < 1 ) ? 'counterclockwise' : 'clockwise' )].join('') });
						_$element.triggerHandler(_oDetails.type, _oDetails);
					}
				break;

			}
		}

	}


	/**
	* Handler: gestureend
	* Read the event_.scale / event_.rotate values,
	* compares it to $.jGestures.defaults.threshold* and triggers
	* a pinchclose|pinchclose|rotatecw|rotateccw event if the distance exceed the
	* Since the gesture-event doesn't supply event.touches no tuchpoints will be calculated
	* * Custom-event argument object:
	* @returns {Object}
	*			{
	*				type: eventtype e.g. "pinchclose","pinchopen", "rotatecw", "rotateccw",
	*				originalEvent: {DOM-Event},
	*				// delta and direction details are just provided for touch not for gesture / motion events
	*				delta : null,
	*				// based on the first touchpoint
	*				direction : {
	*					vector: {Number}, // -1|+1, indicates the direction if necessary(pinch/rotate)
	*					orientation: {Number} // window.orientation: -90,0,90,180 || null (window.orienntation)
	*				},
	*				rotation: {Number} , //  amount of rotation relative to the current position NOT the original
	*				scale: {Number} , // amount of scaling relative to the current position NOT the original
	*				duration: {Number}, // ms: relative to the original touchpoint
	*				description : {String} // details as String: pinch:{'close'|'open'} e.g. "pinch:-1:close" ||  rotate:{'counterclockwise'|'clockwise'} e.g. "rotate:-1:counterclockwise"
	*			};
	* @param {DOM-Event} event_
	* @return {Void}
	*/
	function _onGestureend(event_) {
		// ignore bubbled handlers
		// if ( event_.currentTarget !== event_.target ) { return; }

		var _$element = jQuery(event_.currentTarget);
		// var _$element = jQuery(event_.target);

		// trigger custom notification
		_$element.triggerHandler($.jGestures.events.gestureendStart,event_);

		var _iDelta;
		var _oDatajQueryGestures = _$element.data('ojQueryGestures');

		// trigger handler for every bound event
		var _sType;
		for (_sType in _oDatajQueryGestures) {

			switch(_sType) {

				case 'pinchclose':
					_iDelta = event_.scale;
					if (( _iDelta < 1 ) && (_iDelta % 1) < (1 - $.jGestures.defaults.thresholdPinchclose)) {
						_$element.triggerHandler('pinchclose', _createOptions ({type: 'pinchclose', scale:_iDelta, vector: -1, touches: null, startMove: _oDatajQueryGestures.oStartTouch, event:event_, timestamp:_oDatajQueryGestures.oStartTouch.timestamp,description: 'pinch:-1:close' }) );
					}
				break;

				case 'pinchopen':
					_iDelta = event_.scale;
					if ( ( _iDelta > 1 ) && (_iDelta % 1) > ($.jGestures.defaults.thresholdPinchopen) ) {
						_$element.triggerHandler('pinchopen', _createOptions ({type: 'pinchopen', scale:_iDelta, vector: +1, touches: null, startMove: _oDatajQueryGestures.oStartTouch, event:event_, timestamp:_oDatajQueryGestures.oStartTouch.timestamp,description: 'pinch:+1:open'}) );
					}
				break;

				case 'rotatecw':
					_iDelta = event_.rotation;
					if ( ( _iDelta > 1 ) && (_iDelta  > $.jGestures.defaults.thresholdRotatecw) ) {
						_$element.triggerHandler('rotatecw', _createOptions ({type: 'rotatecw', rotation:_iDelta, vector: +1, touches: null, startMove: _oDatajQueryGestures.oStartTouch, event:event_, timestamp:_oDatajQueryGestures.oStartTouch.timestamp,description: 'rotate:+1:clockwise'}) );
					}
				break;

				case 'rotateccw':
					_iDelta = event_.rotation;
					if ( ( _iDelta < 1 ) &&  ( -1*(_iDelta) > $.jGestures.defaults.thresholdRotateccw ) ) {
							_$element.triggerHandler('rotateccw', _createOptions ({type: 'rotateccw', rotation:_iDelta, vector: -1, touches: null, startMove: _oDatajQueryGestures.oStartTouch, event:event_, timestamp:_oDatajQueryGestures.oStartTouch.timestamp,description: 'rotate:-1:counterclockwise'}) );
						}
				break;

				}
			}
			_$element.triggerHandler($.jGestures.events.gestureendProcessed,event_);
		}
	}
)(jQuery);
