/**
 *
 */
Ext.define('Ext.event.gesture.Drag', {
    extend: 'Ext.event.gesture.SingleTouch',
 
    priority: 100,
 
    isStarted: false,
 
    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
            }
        };
    },
 
    onTouchStart: function(e) {
        if (this.callParent(arguments) === false) {
            if (this.isStarted && this.lastMoveEvent !== null) {
                this.lastMoveEvent.isStopped = false;
                this.onTouchEnd(this.lastMoveEvent);
            }
            return false;
        }
 
        this.startTime = e.time;
        this.startPoint = e.changedTouches[0].point;
    },
 
    tryDragStart: function(e) {
        var startPoint = this.startPoint,
            touch = e.changedTouches[0],
            point = touch.point,
            minDistance = this.getMinDistance(),
            info = this.info;
 
        if (Math.abs(point.getDistanceTo(startPoint)) >= minDistance) {
            this.isStarted = true;
 
            this.previousPoint = this.lastPoint = point;
 
            this.resetInfo('x', e, touch);
            this.resetInfo('y', e, touch);
 
            info.time = e.time;
 
            this.fire('dragstart', e, info);
        }
    },
 
    onTouchMove: function(e) {
        if (!this.startPoint) {
            return;
        }
 
        if (!this.isStarted) {
            this.tryDragStart(e);
        }
 
        if (!this.isStarted) {
            return;
        }
 
        var touch = e.changedTouches[0],
            point = touch.point;
 
        if (this.lastPoint) {
            this.previousPoint = this.lastPoint;
        }
 
        this.lastPoint = point;
        this.lastMoveEvent = e;
 
        this.updateInfo('x', e, touch);
        this.updateInfo('y', e, touch);
 
        this.info.time = e.time;
 
        this.fire('drag', e, this.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 value = this.lastPoint[axis],
            startValue = this.startPoint[axis],
            delta = value - startValue,
            capAxis = axis.toUpperCase(),
            info = this.info;
 
        info.touch = touch;
 
        info.delta[axis] = delta;
        info.absDelta[axis] = Math.abs(delta);
 
        info.previousTime[axis] = this.startTime;
        info.previous[axis] = startValue;
        info[axis] = value;
        info.direction[axis] = 0;
 
        info['start' + capAxis] = this.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 = this.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) {
        if (this.startPoint) {
            this.doEnd(e);
        }
    },
 
    onTouchCancel: function(e) {
        if (this.startPoint) {
            this.doEnd(e, true);
        }
        return false;
    },
 
    doEnd: function(e, isCancel) {
        var me = this,
            touch, point, info;
 
        if (!me.isStarted) {
            me.tryDragStart(e);
        }
 
        if (me.isStarted) {
            touch = e.changedTouches[0];
            point = touch.point;
            info = me.info;
 
            me.isStarted = false;
            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(isCancel ? 'dragcancel' : 'dragend', e, info);
 
            me.previousPoint = me.lastPoint = me.lastMoveEvent = null;
        }
 
        me.startPoint = null;
    },
 
    reset: function() {
        var me = this;
 
        me.isStarted = me.lastPoint =  me.startPoint = me.previousPoint = me.lastPoint =
            me.lastMoveEvent = null;
 
        me.initInfo();
        me.callParent();
    }
}, function(Drag) {
    var gestures = Ext.manifest.gestures;
    Drag.instance = new Drag(gestures && gestures.drag);
});