/**
 * @ignore
 * base handle for touch gesture, mouse and touch normalization
 * @author yiminghe@gmail.com
 */
KISSY.add('event/dom/touch/handle', function (S, Dom, eventHandleMap, DomEvent) {
    var key = S.guid('touch-handle'),
        Features = S.Features,
        gestureStartEvent,
        gestureMoveEvent,
        gestureEndEvent;

    function isTouchEvent(type) {
        return S.startsWith(type, 'touch');
    }

    function isMouseEvent(type) {
        return S.startsWith(type, 'mouse');
    }

    function isMSPointerEvent(type) {
        return S.startsWith(type, 'MSPointer');
    }

    // This should be long enough to ignore compatible mouse events made by touch
    var DUP_TIMEOUT = 2500;
    // radius around touchend that swallows mouse events
    var DUP_DIST = 25;

    if (Features.isTouchEventSupported()) {
        gestureEndEvent = 'touchend touchcancel mouseup';
        // allow touch and mouse both!
        gestureStartEvent = 'touchstart mousedown';
        gestureMoveEvent = 'touchmove mousemove';
        if (S.UA.ios) {
            // ios mousedown is buggy
            gestureEndEvent = 'touchend touchcancel';
            gestureStartEvent = 'touchstart';
            gestureMoveEvent = 'touchmove';
        }
    } else if (Features.isMsPointerSupported()) {
        gestureStartEvent = 'MSPointerDown';
        gestureMoveEvent = 'MSPointerMove';
        gestureEndEvent = 'MSPointerUp MSPointerCancel';
    } else {
        gestureStartEvent = 'mousedown';
        gestureMoveEvent = 'mousemove';
        gestureEndEvent = 'mouseup';
    }

    function DocumentHandler(doc) {
        var self = this;
        self.doc = doc;
        self.eventHandle = {};
        self.init();
        // normalize pointer event to touch event
        self.touches = [];
        // touches length of touch event
        self.inTouch = 0;
    }

    DocumentHandler.prototype = {
        constructor: DocumentHandler,

        lastTouches: [],

        firstTouch: null,

        init: function () {
            var self = this,
                doc = self.doc;
            DomEvent.on(doc, gestureStartEvent, self.onTouchStart, self);
            DomEvent.on(doc, gestureMoveEvent, self.onTouchMove, self);
            DomEvent.on(doc, gestureEndEvent, self.onTouchEnd, self);
        },

        addTouch: function (originalEvent) {
            originalEvent.identifier = originalEvent['pointerId'];
            this.touches.push(originalEvent);
        },

        removeTouch: function (originalEvent) {
            var i = 0,
                touch,
                pointerId = originalEvent['pointerId'],
                touches = this.touches,
                l = touches.length;
            for (; i < l; i++) {
                touch = touches[i];
                if (touch['pointerId'] === pointerId) {
                    touches.splice(i, 1);
                    break;
                }
            }
        },

        updateTouch: function (originalEvent) {
            var i = 0,
                touch,
                pointerId = originalEvent['pointerId'],
                touches = this.touches,
                l = touches.length;
            for (; i < l; i++) {
                touch = touches[i];
                if (touch['pointerId'] === pointerId) {
                    touches[i] = originalEvent;
                }
            }
        },

        isPrimaryTouch: function (inTouch) {
            return this.firstTouch === inTouch.identifier;
        },

        setPrimaryTouch: function (inTouch) {
            if (this.firstTouch === null) {
                this.firstTouch = inTouch.identifier;
            }
        },

        removePrimaryTouch: function (inTouch) {
            if (this.isPrimaryTouch(inTouch)) {
                this.firstTouch = null;
            }
        },

        // prevent mouse events from creating pointer events
        dupMouse: function (inEvent) {
            var lts = this.lastTouches;
            var t = inEvent.changedTouches[0];
            // only the primary finger will dup mouse events
            if (this.isPrimaryTouch(t)) {
                // remember x/y of last touch
                var lt = {x: t.clientX, y: t.clientY};
                lts.push(lt);
                setTimeout(function () {
                    var i = lts.indexOf(lt);
                    if (i > -1) {
                        lts.splice(i, 1);
                    }
                }, DUP_TIMEOUT);
            }
        },

        // collide with the touch event
        isEventSimulatedFromTouch: function (inEvent) {
            var lts = this.lastTouches;
            var x = inEvent.clientX,
                y = inEvent.clientY;
            for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
                // simulated mouse events will be swallowed near a primary touchend
                var dx = Math.abs(x - t.x),
                    dy = Math.abs(y - t.y);
                if (dx <= DUP_DIST && dy <= DUP_DIST) {
                    return true;
                }
            }
            return 0;
        },

        normalize: function (e) {
            var type = e.type,
                notUp,
                touchList;
            if (isTouchEvent(type)) {
                touchList = (type == 'touchend' || type == 'touchcancel') ?
                    e.changedTouches :
                    e.touches;
                if (touchList.length == 1) {
                    e.which = 1;
                    e.pageX = touchList[0].pageX;
                    e.pageY = touchList[0].pageY;
                }
                return e;
            } else {
                touchList = this.touches;
            }
            notUp = !type.match(/(up|cancel)$/i);
            e.touches = notUp ? touchList : [];
            e.targetTouches = notUp ? touchList : [];
            e.changedTouches = touchList;
            return e;
        },

        onTouchStart: function (event) {
            var e, h,
                self = this,
                type = event.type,
                eventHandle = self.eventHandle;
            if (isTouchEvent(type)) {
                self.setPrimaryTouch(event.changedTouches[0]);
                self.dupMouse(event);
            } else if (isMouseEvent(type)) {
                if (self.isEventSimulatedFromTouch(event)) {
                    return;
                }
                self.touches = [event.originalEvent];
            } else if (isMSPointerEvent(type)) {
                self.addTouch(event.originalEvent);
            } else {
                throw new Error('unrecognized touch event: ' + event.type);
            }


            for (e in eventHandle) {
                h = eventHandle[e].handle;
                h.isActive = 1;
            }
            // if preventDefault, will not trigger click event
            self.callEventHandle('onTouchStart', event);

        },

        onTouchMove: function (event) {
            var self = this,
                type = event.type;
            if (isTouchEvent(type)) {
            } else if (isMouseEvent(type)) {
                if (self.isEventSimulatedFromTouch(type)) {
                    return;
                }
                self.touches = [event.originalEvent];
            } else if (isMSPointerEvent(type)) {
                self.updateTouch(event.originalEvent);
            } else {
                throw new Error('unrecognized touch event: ' + event.type);
            }
            // no throttle! to allow preventDefault
            self.callEventHandle('onTouchMove', event);
        },

        onTouchEnd: function (event) {
            var self = this,
                type = event.type;
            if (isMouseEvent(type)) {
                if (self.isEventSimulatedFromTouch(event)) {
                    return;
                }
            }
            self.callEventHandle('onTouchEnd', event);
            if (isTouchEvent(type)) {
                self.dupMouse(event);
                S.makeArray(event.changedTouches).forEach(function (touch) {
                    self.removePrimaryTouch(touch);
                });
            } else if (isMouseEvent(type)) {
                self.touches = [];
            } else if (isMSPointerEvent(type)) {
                self.removeTouch(event.originalEvent);
            }
        },

        callEventHandle: function (method, event) {
            var self = this,
                eventHandle = self.eventHandle,
                e,
                h;
            event = self.normalize(event);
            for (e in eventHandle) {
                // event processor shared by multiple events
                h = eventHandle[e].handle;
                if (h.processed) {
                    continue;
                }
                h.processed = 1;
                //type=event.type;
                if (h.isActive && h[method] && h[method](event) === false) {
                    h.isActive = 0;
                }
                //event.type=type;
            }
            for (e in eventHandle) {
                h = eventHandle[e].handle;
                h.processed = 0;
            }
        },

        addEventHandle: function (event) {
            var self = this,
                eventHandle = self.eventHandle,
                handle = eventHandleMap[event].handle;
            if (eventHandle[event]) {
                eventHandle[event].count++;
            } else {
                eventHandle[event] = {
                    count: 1,
                    handle: handle
                };
            }
        },

        'removeEventHandle': function (event) {
            var eventHandle = this.eventHandle;
            if (eventHandle[event]) {
                eventHandle[event].count--;
                if (!eventHandle[event].count) {
                    delete eventHandle[event];
                }
            }
        },

        destroy: function () {
            var self = this,
                doc = self.doc;
            DomEvent.detach(doc, gestureStartEvent, self.onTouchStart, self);
            DomEvent.detach(doc, gestureMoveEvent, self.onTouchMove, self);
            DomEvent.detach(doc, gestureEndEvent, self.onTouchEnd, self);
        }
    };

    return {
        addDocumentHandle: function (el, event) {
            var doc = Dom.getDocument(el),
                handle = Dom.data(doc, key);
            if (!handle) {
                Dom.data(doc, key, handle = new DocumentHandler(doc));
            }
            if (event) {
                handle.addEventHandle(event);
            }
        },

        removeDocumentHandle: function (el, event) {
            var doc = Dom.getDocument(el),
                handle = Dom.data(doc, key);
            if (handle) {
                if (event) {
                    handle.removeEventHandle(event);
                }
                if (S.isEmptyObject(handle.eventHandle)) {
                    handle.destroy();
                    Dom.removeData(doc, key);
                }
            }
        }
    };
}, {
    requires: [
        'dom',
        './handle-map',
        'event/dom/base',
        './tap',
        './swipe',
        './double-tap',
        './pinch',
        './tap-hold',
        './rotate'
    ]
});
/*
 2013-08-29 yiminghe@gmail.com
 - ios bug
 create new element on touchend handler
 then a mousedown event will be fired on the new element
 - refer: https://github.com/Polymer/PointerEvents/

 2013-08-28 yiminghe@gmail.com
 - chrome android bug: first series touchstart is not fired!
 - chrome android bug when bind mousedown and touch together to ordinary div
 chrome pc :
 touchstart mousedown touchend
 chrome android :
 touchstart touchend mousedown
 safari no mousedown
 - https://code.google.com/p/chromium/issues/detail?id=280516
 - https://code.google.com/p/chromium/issues/detail?id=280507

 2013-07-23 yiminghe@gmail.com
 - bind both mouse and touch for start
 - but bind mousemove or touchmove for move

 2012 yiminghe@gmail.com
 in order to make tap/doubleTap bubbling same with native event.
 register event on document and then bubble
 */