/**
 * 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;
});
