Show:
/* Copyright © 2015-2016 David Valdman */

define(function(require, exports, module) {
    var TouchTracker = require('./_TouchTracker');
    var EventHandler = require('../events/EventHandler');
    var SimpleStream = require('../streams/SimpleStream');
    var OptionsManager = require('../core/_OptionsManager');

    var MINIMUM_TICK_TIME = 8;

    /**
     * Wrapper for DOM touch events. Converts
     *
     *      `touchstart` -> `start`
     *      `touchmove`  -> `update`
     *      `touchend`   -> `end`
     *
     * TouchInput emits these events with the following payload data:
     *
     *      `value`     - Displacement in pixels from `touchstart`
     *      `delta`     - Differential in pixels between successive mouse positions
     *      `velocity`  - Velocity of mouse movement in pixels per second
     *      `cumulate`  - Accumulated displacement over successive displacements
     *      `count`     - DOM event for number of simultaneous touches
     *      `touchId`   - DOM touch event identifier
     *      `event`     - Original DOM event
     *      `dt`        - Time since last update
     *
     * @example
     *
     *      var touchInput = new TouchInput({
     *          direction : TouchInput.DIRECTION.Y
     *      });
     *
     *      touchInput.subscribe(surface);
     *
     *      touchInput.on('start', function(payload){
     *          // fired on mouse down
     *          console.log('start', payload);
     *      });
     *
     *      touchInput.on('update', function(payload){
     *          // fired on mouse move
     *          console.log('update', payload);
     *      });
     *
     *      touchInput.on('end', function(payload){
     *          // fired on mouse up
     *          console.log('end', payload);
     *      });
     *
     * @class TouchInput
     * @constructor
     * @extends Streams.SimpleStream
     * @uses Inputs._TouchTracker
     * @uses Core._OptionsManager
     * @param [options] {Object}                Options
     * @param [options.scale=1] {Number}        Scale the response to the mouse
     * @param [options.track=1] {Number}        Max simultaneous touches to record
     * @param [options.limit=Infinity] {Number} Limit number of touches. If reached, no events are emitted
     * @param [options.direction] {Number}      Direction to project movement onto.
     *                                          Options found in TouchInput.DIRECTION.
     * @param [options.rails=false] {Boolean}   If a direction is unspecified, movement in the
     *                                          orthogonal to the principal direction is suppressed
     */
    function TouchInput(options) {
        this.options = OptionsManager.setOptions(this, options);

        this._eventOutput = new EventHandler();
        this._touchTracker = new TouchTracker(this.options);

        EventHandler.setOutputHandler(this, this._eventOutput);
        EventHandler.setInputHandler(this, this._touchTracker);

        this._touchTracker.on('trackstart', handleStart.bind(this));
        this._touchTracker.on('trackmove', handleMove.bind(this));
        this._touchTracker.on('trackend', handleEnd.bind(this));

        this._payload = {};
        this._cumulate = {};
        this._value = {};
    }

    TouchInput.prototype = Object.create(SimpleStream.prototype);
    TouchInput.prototype.constructor = TouchInput;

    TouchInput.DEFAULT_OPTIONS = {
        direction : undefined,
        scale : 1,
        rails : false,
        track : 1,
        limit : Infinity
    };

    /**
     * Constrain the input along a specific axis.
     *
     * @property DIRECTION {Object}
     * @property DIRECTION.X {Number}   x-axis
     * @property DIRECTION.Y {Number}   y-axis
     * @static
     */
    TouchInput.DIRECTION = {
        X : 0,
        Y : 1
    };

    function handleStart(data) {
        var touchId = data.touchId;
        var velocity;
        var delta;

        if (this.options.direction !== undefined) {
            if (!this._cumulate[touchId]) this._cumulate[touchId] = 0;
            this._value[touchId] = 0;
            velocity = 0;
            delta = 0;
        }
        else {
            if (!this._cumulate[touchId]) this._cumulate[touchId] = [0, 0];
            this._value[touchId] = [0, 0];
            velocity = [0, 0];
            delta = [0, 0];
        }

        var payload = {};
        this._payload[data.touchId] = payload;

        payload.delta = delta;
        payload.value = this._value[touchId];
        payload.cumulate = this._cumulate[touchId];
        payload.velocity = velocity;
        payload.count = data.count;
        payload.touchId = data.touchId;
        payload.event = data.event;
        payload.timestamp = data.timestamp;

        this._eventOutput.emit('start', payload);
    }

    function handleMove(data) {
        var direction = this.options.direction;
        var touchId = data.touchId;

        var scale = this.options.scale;
        var prevData = data.history[0];

        var prevTime = prevData.timestamp;
        var currTime = data.timestamp;

        var diffX = scale * (data.x - prevData.x);
        var diffY = scale * (data.y - prevData.y);

        if (this.options.rails){
            if ((direction === TouchInput.DIRECTION.X && Math.abs(diffY) > Math.abs(diffX)))
                diffY = 0;

            if (direction === TouchInput.DIRECTION.Y && Math.abs(diffX) > Math.abs(diffY))
                diffX = 0;
        }

        var dt = Math.max(currTime - prevTime, MINIMUM_TICK_TIME);
        var invDt = 1 / dt;

        var velX = diffX * invDt;
        var velY = diffY * invDt;

        var nextVel;
        var nextDelta;
        if (direction === TouchInput.DIRECTION.X) {
            nextDelta = diffX;
            nextVel = velX;
            this._value[touchId] += nextDelta;
            this._cumulate[touchId] += nextDelta;
        }
        else if (direction === TouchInput.DIRECTION.Y) {
            nextDelta = diffY;
            nextVel = velY;
            this._value[touchId] += nextDelta;
            this._cumulate[touchId] += nextDelta;
        }
        else {
            nextDelta = [diffX, diffY];
            nextVel = [velX, velY];
            this._value[touchId][0] += nextDelta[0];
            this._value[touchId][1] += nextDelta[1];
            this._cumulate[touchId][0] += nextDelta[0];
            this._cumulate[touchId][1] += nextDelta[1];
        }

        var payload = this._payload[data.touchId];
        payload.delta = nextDelta;
        payload.velocity = nextVel;
        payload.value = this._value[touchId];
        payload.cumulate = this._cumulate[touchId];
        payload.count = data.count;
        payload.touchId = data.touchId;
        payload.event = data.event;
        payload.timestamp = data.timestamp;
        payload.dt = dt;

        this._eventOutput.emit('update', payload);
    }

    function handleEnd(data) {
        var touchId = data.touchId;

        var payload = this._payload[touchId];
        payload.count = data.count;
        payload.event = data.event;
        payload.timestamp = data.timestamp;

        this._eventOutput.emit('end', payload);

        delete this._payload[touchId];
        delete this._value[touchId];
        delete this._cumulate[touchId];
    }

    module.exports = TouchInput;
});