/**
 *
 */
Ext.define('Ext.event.gesture.Drag', {
    extend: 'Ext.event.gesture.SingleTouch',
 
    priority: 100,
 
    startPoint: null,
 
    previousPoint: null,
 
    lastPoint: null,
 
    handledEvents: ['dragstart', 'drag', 'dragend', 'dragcancel'],
 
    config: {
        /**
         * @cfg {Number} minDistance 
         * The minimum distance of pixels before a touch event becomes a drag event.
         */
        minDistance: 8
    },
 
    constructor: function() {
        this.callParent(arguments);
 
        this.initInfo();
    },
 
    initInfo: function() {
        this.info = {
            touch: null,
            previous: {
                x: 0,
                y: 0
            },
            x: 0,
            y: 0,
            delta: {
                x: 0,
                y: 0
            },
            absDelta: {
                x: 0,
                y: 0
            },
            flick: {
                velocity: {
                    x: 0,
                    y: 0
                }
            },
            direction: {
                x: 0,
                y: 0
            },
            time: 0,
            previousTime: {
                x: 0,
                y: 0
            },
            longpress: false
        };
    },
 
    onTouchStart: function(e) {
        var me = this,
            ret = me.callParent([e]);
 
        if (ret !== false) {
            me.startTime = e.time;
            me.startPoint = e.changedTouches[0].point;
        }
 
        return ret;
    },
 
    tryDragStart: function(e) {
        var me = this,
            point = e.changedTouches[0].point,
            minDistance = me.getMinDistance(),
            scale = Ext.Element.getViewportScale(),
            // account for scale so that move distance is actual screen pixels, not page pixels 
            distance = Math.round(Math.abs(point.getDistanceTo(me.startPoint) * scale));
 
        if (distance >= minDistance) {
            me.doDragStart(e);
        }
    },
 
    doDragStart: function(e, isLongPress) {
        var me = this,
            touch = e.changedTouches[0],
            point = touch.point,
            info = me.info,
            time;
 
        if (isLongPress) {
            time = Ext.now();
            me.startTime = time;
            me.startPoint = point;
            info.longpress = true;
        } else {
            time = e.time;
        }
 
        me.isStarted = true;
 
        me.previousPoint = me.lastPoint = point;
 
        me.resetInfo('x', e, touch);
        me.resetInfo('y', e, touch);
 
        info.time = time;
 
        me.fire('dragstart', e, info);
    },
 
    onTouchMove: function(e) {
        var me = this,
            touch, point;
 
        if (!me.startPoint) {
            return;
        }
 
        if (!me.isStarted) {
            me.tryDragStart(e);
        }
 
        if (!me.isStarted) {
            return;
        }
 
        touch = e.changedTouches[0];
        point = touch.point;
 
        if (me.lastPoint) {
            me.previousPoint = me.lastPoint;
        }
 
        me.lastPoint = point;
        me.lastMoveEvent = e;
 
        me.updateInfo('x', e, touch);
        me.updateInfo('y', e, touch);
 
        me.info.time = e.time;
 
        me.fire('drag', e, me.info);
    },
 
    onAxisDragEnd: function(axis, info) {
        var duration = info.time - info.previousTime[axis];
 
        if (duration > 0) {
            info.flick.velocity[axis] = (info[axis] - info.previous[axis]) / duration;
        }
    },
 
    resetInfo: function(axis, e, touch) {
        var me = this,
            value = me.lastPoint[axis],
            startValue = me.startPoint[axis],
            delta = value - startValue,
            capAxis = axis.toUpperCase(),
            info = me.info;
 
        info.touch = touch;
 
        info.delta[axis] = delta;
        info.absDelta[axis] = Math.abs(delta);
 
        info.previousTime[axis] = me.startTime;
        info.previous[axis] = startValue;
        info[axis] = value;
        info.direction[axis] = 0;
 
        info['start' + capAxis] = me.startPoint[axis];
        info['previous' + capAxis] = info.previous[axis];
        info['page' + capAxis] = info[axis];
        info['delta' + capAxis] = info.delta[axis];
        info['absDelta' + capAxis] = info.absDelta[axis];
        info['previousDelta' + capAxis] = 0;
        info.startTime = me.startTime;
    },
 
    updateInfo: function(axis, e, touch) {
        var me = this,
            value = me.lastPoint[axis],
            previousValue = me.previousPoint[axis],
            startValue = me.startPoint[axis],
            delta = value - startValue,
            info = me.info,
            direction = info.direction,
            capAxis = axis.toUpperCase(),
            previousFlick = info.previous[axis];
 
        info.touch = touch;
        info.delta[axis] = delta;
        info.absDelta[axis] = Math.abs(delta);
 
        if (value !== previousFlick && value !== info[axis]) {
            info.previous[axis] = info[axis];
            info.previousTime[axis] = info.time;
        }
 
        info[axis] = value;
 
        if (value > previousValue) {
            direction[axis] = 1;
        }
        else if (value < previousValue) {
            direction[axis] = -1;
        }
 
        info['start' + capAxis] = startValue;
        info['previous' + capAxis] = info.previous[axis];
        info['page' + capAxis] = info[axis];
        info['delta' + capAxis] = info.delta[axis];
        info['absDelta' + capAxis] = info.absDelta[axis];
        info['previousDelta' + capAxis] = info.previous[axis] - startValue;
        info.startTime = me.startTime;
    },
 
    onTouchEnd: function(e) {
        var me = this,
            touch, point, info;
 
        if (me.isStarted) {
            touch = e.changedTouches[0];
            point = touch.point;
            info = me.info;
 
            me.lastPoint = point;
 
            me.updateInfo('x', e, touch);
            me.updateInfo('y', e, touch);
 
            info.time = e.time;
 
            me.onAxisDragEnd('x', info);
            me.onAxisDragEnd('y', info);
 
            me.fire('dragend', e, info);
        }
 
        return this.callParent([e]);
    },
 
    onCancel: function(e) {
        var me = this,
            touch = e.changedTouches[0],
            info = me.info;
 
        // if "e" is a true cancellation event (touchcancel, pointercancel) e.touches.length 
        // will be 0.  If length is anything else we can safely assume that this was called 
        // because an additional touch was added (see SingleTouch#onTouchStart).  If that 
        // is the case we do not want to update the lastPoint because the coordinates should 
        // be those of the last single-touch drag, not the new touch. 
        if (!e.touches.length) {
            me.lastPoint = touch.point;
        }
 
        me.updateInfo('x', e, touch);
        me.updateInfo('y', e, touch);
 
        info.time = e.time;
 
        me.fire('dragcancel', e, info, true);
    },
 
    reset: function() {
        var me = this;
 
        me.lastPoint =  me.startPoint = me.previousPoint = me.lastPoint = me.lastMoveEvent = null;
 
        me.initInfo();
 
        return me.callParent();
    }
}, function(Drag) {
    var gestures = Ext.manifest.gestures;
    Drag.instance = new Drag(gestures && gestures.drag);
});