/**
 * Swipe gestures
 *
 * @module Ink.UI.Swipe_1
 * @version 1
 */
Ink.createModule(
	'Ink.UI.Swipe',
	'1',
	['Ink.Dom.Event_1', 'Ink.Dom.Element_1', 'Ink.UI.Common_1'],
	function(InkEvent, InkElement, Common) {
	'use strict';

	/**
	 * Subscribe swipe gestures.
	 *
	 * Supports filtering swipes be any combination of the criteria supported in the options.
	 *
	 * -----
	 *
	 * Arguments received by the callbacks
	 * -----------------------------------
	 *
	 * The `onStart`, `onMove`, and `onEnd` options receive as argument an object containing:
	 *
	 *   - `event`: the DOMEvent object
	 *   - `element`: the target element
	 *   - `Instance`: the `Ink.UI.Swipe_1` instance
	 *   - `position`: `Array` with `[x, y]` coordinates of current position
	 *   - `dt`: Time passed between now and the first event (onMove only)
	 *   - `gesture`: an Array containing [x,y] coordinates of every touchmove event received (only if options.storeGesture is enabled) (onEnd only)
	 *   - `time`: an Array containing all the `dt` values for every touchmove event (onEnd only)
	 *   - `overallMovement`: X and Y distance traveled by the touch movement (`[x, y]`) (onEnd only)
	 *   - `overallTime`: total time passed (onEnd only)
	 *
	 * @class Ink.UI.Swipe
	 * @constructor
	 * @param {String|Element}      el                      Element or Selector
	 * @param {Object}              options                 Options Object
	 * @param {Function}            [options.onEnd]         Callback function for the `touchend` event. Gets all the gesture information, and is filtered by min/max Dist and Duration options (see below)
	 * @param {Function}            [options.onStart]       Callback function for `touchstart` event.
	 * @param {Function}            [options.onMove]        Callback function for every `touchmove` event. Gets current gesture information.
	 * @param {Number}              [options.minDist]       Minimum allowed distance, in pixels.
	 * @param {Number}              [options.maxDist]       Maximum allowed distance, in pixels.
	 * @param {Number}              [options.minDuration]   Minimum allowed duration, in seconds.
	 * @param {Number}              [options.maxDuration]   Maximum allowed duration, in seconds.
	 * @param {String}              [options.axis]          If either 'x' or 'y' is passed, only swipes where the dominant axis is the given one trigger the callback
	 * @param {String}              [options.storeGesture]  If to store gesture information and provide it to the callback. Defaults to true.
	 * @param {String}              [options.stopEvents]    Flag to stop (default and propagation) of the received events. Defaults to true.
	 *
	 * @sample Ink_UI_Swipe_1.html
	 */
	function Swipe() {
			if (typeof arguments[1] === 'function') {
				arguments[1] = { onEnd: arguments[1] };
				}

			Common.BaseUIComponent.apply( this, arguments );
	}

	Swipe._name = 'Swipe_1';

	Swipe._optionDefinition = {
			onEnd:          ['Function', undefined],
			onStart:        ['Function', undefined],
			onMove:         ['Function', undefined],
			minDist:        ['Number',   undefined],      // in pixels
			maxDist:        ['Number',   undefined],
			minDuration:    ['Number',   undefined],      // in seconds
			maxDuration:    ['Number',   undefined],
			axis:           ['String',   undefined],       // x | y
			storeGesture:   ['Boolean',  false],
			stopEvents:     ['Boolean',  true]
	};

	Swipe.prototype = {
			_supported: ('ontouchstart' in document.documentElement),

			_init: function() {
				this._handlers = {
					down: Ink.bindEvent( this._onDown, this ),
					move: Ink.bindEvent( this._onMove, this ),
					up:   Ink.bindEvent( this._onUp, this )
				};

				var db = document.body;
				InkEvent.observe( db, 'touchstart', this._handlers.down );
				if (this._options.storeGesture || this._options.onMove) {
					InkEvent.observe( db, 'touchmove', this._handlers.move );
				}
				InkEvent.observe( db, 'touchend', this._handlers.up );
				this._isOn = false;
				},

		_isMeOrParent: function(el, parentEl) {
			if ( ! el) {
return;}
			do {
				if (el === parentEl) {
return true; }
				el = el.parentNode;
			} while (el);
			return false;
			},

		_pushGesture: function (coords, dt) {
			if (this._options.storeGesture) {
				this._gesture.push( coords );
				this._time.push( dt );
			}
			},

		_onDown: function(event) {
			if (event.changedTouches.length !== 1) {
return; }
			if ( ! this._isMeOrParent( event.target, this._element )) {
return; }

			if ( this._options.stopEvents === true ) {
				InkEvent.stop( event );
			}
			event        = event.changedTouches[0];
			this._isOn   = true;
			this._target = event.target;

			this._t0 = +new Date();
			this._p0 = [event.pageX, event.pageY];

			if (this._options.storeGesture) {
				this._gesture = [];
				this._time    = [];
			}

			this._pushGesture( this._p0, 0 );

			if (this._options.onStart) {
				this._options.onStart(
					{
					event: event,
					element: this._element,
					instance: this,
					position: this._p0,
					dt: 0
				}
					);
			}
			},

		_onMove: function(event) {
			if ( ! this._isOn || event.changedTouches.length !== 1) {
return; }
			if ( this._options.stopEvents === true ) {
				InkEvent.stop( event );
			}

			event  = event.changedTouches[0];
			var t1 = +new Date();
			var dt = (t1 - this._t0);

			var gesture = [event.pageX, event.pageY];

			this._pushGesture( gesture, dt );

			if (this._options.onMove) {
				this._options.onMove(
					{
					event: event,
					element: this._element,
					instance: this,
					position: gesture,
					dt: dt
				}
					);
			}
			},

		_onUp: function(event) {
			if ( ! this._isOn || event.changedTouches.length !== 1) {
return; }

			if ( this._options.stopEvents === true ) {
				InkEvent.stop( event );
			}
			event      = event.changedTouches[0];   // TODO SHOULD CHECK IT IS THE SAME TOUCH
			this._isOn = false;

			var t1   = +new Date();
			var p1   = [event.pageX, event.pageY];
			var dt   = (t1 - this._t0);
			var dr   = [
				p1[0] - this._p0[0],
				p1[1] - this._p0[1]
			];
			var dist = Math.sqrt( dr[0] * dr[0] + dr[1] * dr[1] );
			var axis = Math.abs( dr[0] ) > Math.abs( dr[1] ) ? 'x' : 'y';

			var o = this._options;
			if (o.minDist && dist < o.minDist) {
return; }
			if (o.maxDist && dist > o.maxDist) {
return; }
			if (o.minDuration && dt < o.minDuration) {
return; }
			if (o.maxDuration && dt > o.maxDuration) {
return; }
			if (o.axis && axis !== o.axis) {
return; }

			if (this._options.onEnd) {
				this._options.onEnd(
					{
					event: event,
					element: this._element,
					instance: this,
					gesture: this._gesture,
					time: this._time,
					axis: axis,
					overallMovement: dr,
					overallTime: dt
				}
					);
			}
			}
	};

	Common.createUIComponent( Swipe );

	return Swipe;
}
	);
