/*! Hammer.JS - v1.1.3 - 2014-05-22
 * http://eightmedia.github.io/hammer.js
 *
 * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
 * Licensed under the MIT license */

(function(window, undefined) {
    'use strict';

    /**
     * @main
     * @module hammer
     *
     * @class Hammer
     * @static
     */

    /**
     * Hammer, use this to create instances
     * ````
     * var hammertime = new Hammer(myElement);
     * ````
     *
     * @method Hammer
     * @param {HTMLElement} element
     * @param {Object} [options={}]
     * @return {Hammer.Instance}
     */
    var Hammer = function Hammer(element, options) {
        return new Hammer.Instance(element, options || {});
    };

    /**
     * version, as defined in package.json
     * the value will be set at each build
     * @property VERSION
     * @final
     * @type {String}
     */
    Hammer.VERSION = '1.1.3';

    /**
     * default settings.
     * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
     * by setting it's name (like `swipe`) to false.
     * You can set the defaults for all instances by changing this object before creating an instance.
     * @example
     * ````
     *  Hammer.defaults.drag = false;
     *  Hammer.defaults.behavior.touchAction = 'pan-y';
     *  delete Hammer.defaults.behavior.userSelect;
     * ````
     * @property defaults
     * @type {Object}
     */
    Hammer.defaults = {
        /**
         * this setting object adds styles and attributes to the element to prevent the browser from doing
         * its native behavior. The css properties are auto prefixed for the browsers when needed.
         * @property defaults.behavior
         * @type {Object}
         */
        behavior: {
            /**
             * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
             * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
             * @property defaults.behavior.userSelect
             * @type {String}
             * @default 'none'
             */
            userSelect: 'none',

            /**
             * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
             * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
             * @property defaults.behavior.touchAction
             * @type {String}
             * @default: 'pan-y'
             */
            touchAction: 'pan-y',

            /**
             * Disables the default callout shown when you touch and hold a touch target.
             * On iOS, when you touch and hold a touch target such as a link, Safari displays
             * a callout containing information about the link. This property allows you to disable that callout.
             * @property defaults.behavior.touchCallout
             * @type {String}
             * @default 'none'
             */
            touchCallout: 'none',

            /**
             * Specifies whether zooming is enabled. Used by IE10>
             * @property defaults.behavior.contentZooming
             * @type {String}
             * @default 'none'
             */
            contentZooming: 'none',

            /**
             * Specifies that an entire element should be draggable instead of its contents.
             * Mainly for desktop browsers.
             * @property defaults.behavior.userDrag
             * @type {String}
             * @default 'none'
             */
            userDrag: 'none',

            /**
             * Overrides the highlight color shown when the user taps a link or a JavaScript
             * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
             *
             * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
             * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
             * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
             * @property defaults.behavior.tapHighlightColor
             * @type {String}
             * @default 'rgba(0,0,0,0)'
             */
            tapHighlightColor: 'rgba(0,0,0,0)'
        }
    };

    /**
     * hammer document where the base events are added at
     * @property DOCUMENT
     * @type {HTMLElement}
     * @default window.document
     */
    Hammer.DOCUMENT = document;

    /**
     * detect support for pointer events
     * @property HAS_POINTEREVENTS
     * @type {Boolean}
     */
    Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;

    /**
     * detect support for touch events
     * @property HAS_TOUCHEVENTS
     * @type {Boolean}
     */
    Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);

    /**
     * detect mobile browsers
     * @property IS_MOBILE
     * @type {Boolean}
     */
    Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);

    /**
     * detect if we want to support mouseevents at all
     * @property NO_MOUSEEVENTS
     * @type {Boolean}
     */
    Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;

    /**
     * interval in which Hammer recalculates current velocity/direction/angle in ms
     * @property CALCULATE_INTERVAL
     * @type {Number}
     * @default 25
     */
    Hammer.CALCULATE_INTERVAL = 25;

    /**
     * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
     * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
     * @property EVENT_TYPES
     * @private
     * @writeOnce
     * @type {Object}
     */
    var EVENT_TYPES = {};

    /**
     * direction strings, for safe comparisons
     * @property DIRECTION_DOWN|LEFT|UP|RIGHT
     * @final
     * @type {String}
     * @default 'down' 'left' 'up' 'right'
     */
    var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
    var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
    var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
    var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';

    /**
     * pointertype strings, for safe comparisons
     * @property POINTER_MOUSE|TOUCH|PEN
     * @final
     * @type {String}
     * @default 'mouse' 'touch' 'pen'
     */
    var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
    var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
    var POINTER_PEN = Hammer.POINTER_PEN = 'pen';

    /**
     * eventtypes
     * @property EVENT_START|MOVE|END|RELEASE|TOUCH
     * @final
     * @type {String}
     * @default 'start' 'change' 'move' 'end' 'release' 'touch'
     */
    var EVENT_START = Hammer.EVENT_START = 'start';
    var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
    var EVENT_END = Hammer.EVENT_END = 'end';
    var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
    var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';

    /**
     * if the window events are set...
     * @property READY
     * @writeOnce
     * @type {Boolean}
     * @default false
     */
    Hammer.READY = false;

    /**
     * plugins namespace
     * @property plugins
     * @type {Object}
     */
    Hammer.plugins = Hammer.plugins || {};

    /**
     * gestures namespace
     * see `/gestures` for the definitions
     * @property gestures
     * @type {Object}
     */
    Hammer.gestures = Hammer.gestures || {};

    /**
     * setup events to detect gestures on the document
     * this function is called when creating an new instance
     * @private
     */
    function setup() {
        if(Hammer.READY) {
            return;
        }

        // find what eventtypes we add listeners to
        Event.determineEventTypes();

        // Register all gestures inside Hammer.gestures
        Utils.each(Hammer.gestures, function(gesture) {
            Detection.register(gesture);
        });

        // Add touch events on the document
        Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
        Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);

        // Hammer is ready...!
        Hammer.READY = true;
    }

    /**
     * @module hammer
     *
     * @class Utils
     * @static
     */
    var Utils = Hammer.utils = {
        /**
         * extend method, could also be used for cloning when `dest` is an empty object.
         * changes the dest object
         * @method extend
         * @param {Object} dest
         * @param {Object} src
         * @param {Boolean} [merge=false]  do a merge
         * @return {Object} dest
         */
        extend: function extend(dest, src, merge) {
            for(var key in src) {
                if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
                    continue;
                }
                dest[key] = src[key];
            }
            return dest;
        },

        /**
         * simple addEventListener wrapper
         * @method on
         * @param {HTMLElement} element
         * @param {String} type
         * @param {Function} handler
         */
        on: function on(element, type, handler) {
            element.addEventListener(type, handler, false);
        },

        /**
         * simple removeEventListener wrapper
         * @method off
         * @param {HTMLElement} element
         * @param {String} type
         * @param {Function} handler
         */
        off: function off(element, type, handler) {
            element.removeEventListener(type, handler, false);
        },

        /**
         * forEach over arrays and objects
         * @method each
         * @param {Object|Array} obj
         * @param {Function} iterator
         * @param {any} iterator.item
         * @param {Number} iterator.index
         * @param {Object|Array} iterator.obj the source object
         * @param {Object} context value to use as `this` in the iterator
         */
        each: function each(obj, iterator, context) {
            var i, len;

            // native forEach on arrays
            if('forEach' in obj) {
                obj.forEach(iterator, context);
                // arrays
            } else if(obj.length !== undefined) {
                for(i = 0, len = obj.length; i < len; i++) {
                    if(iterator.call(context, obj[i], i, obj) === false) {
                        return;
                    }
                }
                // objects
            } else {
                for(i in obj) {
                    if(obj.hasOwnProperty(i) &&
                        iterator.call(context, obj[i], i, obj) === false) {
                        return;
                    }
                }
            }
        },

        /**
         * find if a string contains the string using indexOf
         * @method inStr
         * @param {String} src
         * @param {String} find
         * @return {Boolean} found
         */
        inStr: function inStr(src, find) {
            return src.indexOf(find) > -1;
        },

        /**
         * find if a array contains the object using indexOf or a simple polyfill
         * @method inArray
         * @param {String} src
         * @param {String} find
         * @return {Boolean|Number} false when not found, or the index
         */
        inArray: function inArray(src, find) {
            if(src.indexOf) {
                var index = src.indexOf(find);
                return (index === -1) ? false : index;
            } else {
                for(var i = 0, len = src.length; i < len; i++) {
                    if(src[i] === find) {
                        return i;
                    }
                }
                return false;
            }
        },

        /**
         * convert an array-like object (`arguments`, `touchlist`) to an array
         * @method toArray
         * @param {Object} obj
         * @return {Array}
         */
        toArray: function toArray(obj) {
            return Array.prototype.slice.call(obj, 0);
        },

        /**
         * find if a node is in the given parent
         * @method hasParent
         * @param {HTMLElement} node
         * @param {HTMLElement} parent
         * @return {Boolean} found
         */
        hasParent: function hasParent(node, parent) {
            while(node) {
                if(node == parent) {
                    return true;
                }
                node = node.parentNode;
            }
            return false;
        },

        /**
         * get the center of all the touches
         * @method getCenter
         * @param {Array} touches
         * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
         */
        getCenter: function getCenter(touches) {
            var pageX = [],
                pageY = [],
                clientX = [],
                clientY = [],
                min = Math.min,
                max = Math.max;

            // no need to loop when only one touch
            if(touches.length === 1) {
                return {
                    pageX: touches[0].pageX,
                    pageY: touches[0].pageY,
                    clientX: touches[0].clientX,
                    clientY: touches[0].clientY
                };
            }

            Utils.each(touches, function(touch) {
                pageX.push(touch.pageX);
                pageY.push(touch.pageY);
                clientX.push(touch.clientX);
                clientY.push(touch.clientY);
            });

            return {
                pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
                pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
                clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
                clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
            };
        },

        /**
         * calculate the velocity between two points. unit is in px per ms.
         * @method getVelocity
         * @param {Number} deltaTime
         * @param {Number} deltaX
         * @param {Number} deltaY
         * @return {Object} velocity `x` and `y`
         */
        getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
            return {
                x: Math.abs(deltaX / deltaTime) || 0,
                y: Math.abs(deltaY / deltaTime) || 0
            };
        },

        /**
         * calculate the angle between two coordinates
         * @method getAngle
         * @param {Touch} touch1
         * @param {Touch} touch2
         * @return {Number} angle
         */
        getAngle: function getAngle(touch1, touch2) {
            var x = touch2.clientX - touch1.clientX,
                y = touch2.clientY - touch1.clientY;

            return Math.atan2(y, x) * 180 / Math.PI;
        },

        /**
         * do a small comparision to get the direction between two touches.
         * @method getDirection
         * @param {Touch} touch1
         * @param {Touch} touch2
         * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
         */
        getDirection: function getDirection(touch1, touch2) {
            var x = Math.abs(touch1.clientX - touch2.clientX),
                y = Math.abs(touch1.clientY - touch2.clientY);

            if(x >= y) {
                return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
            }
            return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
        },

        /**
         * calculate the distance between two touches
         * @method getDistance
         * @param {Touch}touch1
         * @param {Touch} touch2
         * @return {Number} distance
         */
        getDistance: function getDistance(touch1, touch2) {
            var x = touch2.clientX - touch1.clientX,
                y = touch2.clientY - touch1.clientY;

            return Math.sqrt((x * x) + (y * y));
        },

        /**
         * calculate the scale factor between two touchLists
         * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
         * @method getScale
         * @param {Array} start array of touches
         * @param {Array} end array of touches
         * @return {Number} scale
         */
        getScale: function getScale(start, end) {
            // need two fingers...
            if(start.length >= 2 && end.length >= 2) {
                return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
            }
            return 1;
        },

        /**
         * calculate the rotation degrees between two touchLists
         * @method getRotation
         * @param {Array} start array of touches
         * @param {Array} end array of touches
         * @return {Number} rotation
         */
        getRotation: function getRotation(start, end) {
            // need two fingers
            if(start.length >= 2 && end.length >= 2) {
                return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
            }
            return 0;
        },

        /**
         * find out if the direction is vertical   *
         * @method isVertical
         * @param {String} direction matches `DIRECTION_UP|DOWN`
         * @return {Boolean} is_vertical
         */
        isVertical: function isVertical(direction) {
            return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
        },

        /**
         * set css properties with their prefixes
         * @param {HTMLElement} element
         * @param {String} prop
         * @param {String} value
         * @param {Boolean} [toggle=true]
         * @return {Boolean}
         */
        setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
            var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
            prop = Utils.toCamelCase(prop);

            for(var i = 0; i < prefixes.length; i++) {
                var p = prop;
                // prefixes
                if(prefixes[i]) {
                    p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
                }

                // test the style
                if(p in element.style) {
                    element.style[p] = (toggle == null || toggle) && value || '';
                    break;
                }
            }
        },

        /**
         * toggle browser default behavior by setting css properties.
         * `userSelect='none'` also sets `element.onselectstart` to false
         * `userDrag='none'` also sets `element.ondragstart` to false
         *
         * @method toggleBehavior
         * @param {HtmlElement} element
         * @param {Object} props
         * @param {Boolean} [toggle=true]
         */
        toggleBehavior: function toggleBehavior(element, props, toggle) {
            if(!props || !element || !element.style) {
                return;
            }

            // set the css properties
            Utils.each(props, function(value, prop) {
                Utils.setPrefixedCss(element, prop, value, toggle);
            });

            var falseFn = toggle && function() {
                return false;
            };

            // also the disable onselectstart
            if(props.userSelect == 'none') {
                element.onselectstart = falseFn;
            }
            // and disable ondragstart
            if(props.userDrag == 'none') {
                element.ondragstart = falseFn;
            }
        },

        /**
         * convert a string with underscores to camelCase
         * so prevent_default becomes preventDefault
         * @param {String} str
         * @return {String} camelCaseStr
         */
        toCamelCase: function toCamelCase(str) {
            return str.replace(/[_-]([a-z])/g, function(s) {
                return s[1].toUpperCase();
            });
        }
    };


    /**
     * @module hammer
     */
    /**
     * @class Event
     * @static
     */
    var Event = Hammer.event = {
        /**
         * when touch events have been fired, this is true
         * this is used to stop mouse events
         * @property prevent_mouseevents
         * @private
         * @type {Boolean}
         */
        preventMouseEvents: false,

        /**
         * if EVENT_START has been fired
         * @property started
         * @private
         * @type {Boolean}
         */
        started: false,

        /**
         * when the mouse is hold down, this is true
         * @property should_detect
         * @private
         * @type {Boolean}
         */
        shouldDetect: false,

        /**
         * simple event binder with a hook and support for multiple types
         * @method on
         * @param {HTMLElement} element
         * @param {String} type
         * @param {Function} handler
         * @param {Function} [hook]
         * @param {Object} hook.type
         */
        on: function on(element, type, handler, hook) {
            var types = type.split(' ');
            Utils.each(types, function(type) {
                Utils.on(element, type, handler);
                hook && hook(type);
            });
        },

        /**
         * simple event unbinder with a hook and support for multiple types
         * @method off
         * @param {HTMLElement} element
         * @param {String} type
         * @param {Function} handler
         * @param {Function} [hook]
         * @param {Object} hook.type
         */
        off: function off(element, type, handler, hook) {
            var types = type.split(' ');
            Utils.each(types, function(type) {
                Utils.off(element, type, handler);
                hook && hook(type);
            });
        },

        /**
         * the core touch event handler.
         * this finds out if we should to detect gestures
         * @method onTouch
         * @param {HTMLElement} element
         * @param {String} eventType matches `EVENT_START|MOVE|END`
         * @param {Function} handler
         * @return onTouchHandler {Function} the core event handler
         */
        onTouch: function onTouch(element, eventType, handler) {
            var self = this;

            var onTouchHandler = function onTouchHandler(ev) {

                var srcType = ev.type.toLowerCase(),
                    isPointer = Hammer.HAS_POINTEREVENTS,
                    isMouse = Utils.inStr(srcType, 'mouse'),
                    triggerType;

                // if we are in a mouseevent, but there has been a touchevent triggered in this session
                // we want to do nothing. simply break out of the event.
                if(isMouse && self.preventMouseEvents) {
                    return;

                    // mousebutton must be down
                } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
                    self.preventMouseEvents = false;
                    self.shouldDetect = true;
                } else if(isPointer && eventType == EVENT_START) {
                    self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
                    // just a valid start event, but no mouse
                } else if(!isMouse && eventType == EVENT_START) {
                    self.preventMouseEvents = true;
                    self.shouldDetect = true;
                }

                // update the pointer event before entering the detection
                if(isPointer && eventType != EVENT_END) {
                    PointerEvent.updatePointer(eventType, ev);
                }

                // we are in a touch/down state, so allowed detection of gestures
                if(self.shouldDetect) {
                    triggerType = self.doDetect.call(self, ev, eventType, element, handler);
                }

                // ...and we are done with the detection
                // so reset everything to start each detection totally fresh
                if(triggerType == EVENT_END) {
                    self.preventMouseEvents = false;
                    self.shouldDetect = false;
                    PointerEvent.reset();
                    // update the pointerevent object after the detection
                }

                if(isPointer && eventType == EVENT_END) {
                    PointerEvent.updatePointer(eventType, ev);
                }
            };

            this.on(element, EVENT_TYPES[eventType], onTouchHandler);
            return onTouchHandler;
        },

        /**
         * the core detection method
         * this finds out what hammer-touch-events to trigger
         * @method doDetect
         * @param {Object} ev
         * @param {String} eventType matches `EVENT_START|MOVE|END`
         * @param {HTMLElement} element
         * @param {Function} handler
         * @return {String} triggerType matches `EVENT_START|MOVE|END`
         */
        doDetect: function doDetect(ev, eventType, element, handler) {
            var touchList = this.getTouchList(ev, eventType);
            var touchListLength = touchList.length;
            var triggerType = eventType;
            var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
            var changedLength = touchListLength;

            // at each touchstart-like event we want also want to trigger a TOUCH event...
            if(eventType == EVENT_START) {
                triggerChange = EVENT_TOUCH;
                // ...the same for a touchend-like event
            } else if(eventType == EVENT_END) {
                triggerChange = EVENT_RELEASE;

                // keep track of how many touches have been removed
                changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
            }

            // after there are still touches on the screen,
            // we just want to trigger a MOVE event. so change the START or END to a MOVE
            // but only after detection has been started, the first time we actualy want a START
            if(changedLength > 0 && this.started) {
                triggerType = EVENT_MOVE;
            }

            // detection has been started, we keep track of this, see above
            this.started = true;

            // generate some event data, some basic information
            var evData = this.collectEventData(element, triggerType, touchList, ev);

            // trigger the triggerType event before the change (TOUCH, RELEASE) events
            // but the END event should be at last
            if(eventType != EVENT_END) {
                handler.call(Detection, evData);
            }

            // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
            if(triggerChange) {
                evData.changedLength = changedLength;
                evData.eventType = triggerChange;

                handler.call(Detection, evData);

                evData.eventType = triggerType;
                delete evData.changedLength;
            }

            // trigger the END event
            if(triggerType == EVENT_END) {
                handler.call(Detection, evData);

                // ...and we are done with the detection
                // so reset everything to start each detection totally fresh
                this.started = false;
            }

            return triggerType;
        },

        /**
         * we have different events for each device/browser
         * determine what we need and set them in the EVENT_TYPES constant
         * the `onTouch` method is bind to these properties.
         * @method determineEventTypes
         * @return {Object} events
         */
        determineEventTypes: function determineEventTypes() {
            var types;
            if(Hammer.HAS_POINTEREVENTS) {
                if(window.PointerEvent) {
                    types = [
                        'pointerdown',
                        'pointermove',
                        'pointerup pointercancel lostpointercapture'
                    ];
                } else {
                    types = [
                        'MSPointerDown',
                        'MSPointerMove',
                        'MSPointerUp MSPointerCancel MSLostPointerCapture'
                    ];
                }
            } else if(Hammer.NO_MOUSEEVENTS) {
                types = [
                    'touchstart',
                    'touchmove',
                    'touchend touchcancel'
                ];
            } else {
                types = [
                    'touchstart mousedown',
                    'touchmove mousemove',
                    'touchend touchcancel mouseup'
                ];
            }

            EVENT_TYPES[EVENT_START] = types[0];
            EVENT_TYPES[EVENT_MOVE] = types[1];
            EVENT_TYPES[EVENT_END] = types[2];
            return EVENT_TYPES;
        },

        /**
         * create touchList depending on the event
         * @method getTouchList
         * @param {Object} ev
         * @param {String} eventType
         * @return {Array} touches
         */
        getTouchList: function getTouchList(ev, eventType) {
            // get the fake pointerEvent touchlist
            if(Hammer.HAS_POINTEREVENTS) {
                return PointerEvent.getTouchList();
            }

            // get the touchlist
            if(ev.touches) {
                if(eventType == EVENT_MOVE) {
                    return ev.touches;
                }

                var identifiers = [];
                var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
                var touchList = [];

                Utils.each(concat, function(touch) {
                    if(Utils.inArray(identifiers, touch.identifier) === false) {
                        touchList.push(touch);
                    }
                    identifiers.push(touch.identifier);
                });

                return touchList;
            }

            // make fake touchList from mouse position
            ev.identifier = 1;
            return [ev];
        },

        /**
         * collect basic event data
         * @method collectEventData
         * @param {HTMLElement} element
         * @param {String} eventType matches `EVENT_START|MOVE|END`
         * @param {Array} touches
         * @param {Object} ev
         * @return {Object} ev
         */
        collectEventData: function collectEventData(element, eventType, touches, ev) {
            // find out pointerType
            var pointerType = POINTER_TOUCH;
            if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
                pointerType = POINTER_MOUSE;
            } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
                pointerType = POINTER_PEN;
            }

            return {
                center: Utils.getCenter(touches),
                timeStamp: Date.now(),
                target: ev.target,
                touches: touches,
                eventType: eventType,
                pointerType: pointerType,
                srcEvent: ev,

                /**
                 * prevent the browser default actions
                 * mostly used to disable scrolling of the browser
                 */
                preventDefault: function() {
                    var srcEvent = this.srcEvent;
                    srcEvent.preventManipulation && srcEvent.preventManipulation();
                    srcEvent.preventDefault && srcEvent.preventDefault();
                },

                /**
                 * stop bubbling the event up to its parents
                 */
                stopPropagation: function() {
                    this.srcEvent.stopPropagation();
                },

                /**
                 * immediately stop gesture detection
                 * might be useful after a swipe was detected
                 * @return {*}
                 */
                stopDetect: function() {
                    return Detection.stopDetect();
                }
            };
        }
    };


    /**
     * @module hammer
     *
     * @class PointerEvent
     * @static
     */
    var PointerEvent = Hammer.PointerEvent = {
        /**
         * holds all pointers, by `identifier`
         * @property pointers
         * @type {Object}
         */
        pointers: {},

        /**
         * get the pointers as an array
         * @method getTouchList
         * @return {Array} touchlist
         */
        getTouchList: function getTouchList() {
            var touchlist = [];
            // we can use forEach since pointerEvents only is in IE10
            Utils.each(this.pointers, function(pointer) {
                touchlist.push(pointer);
            });

            return touchlist;
        },

        /**
         * update the position of a pointer
         * @method updatePointer
         * @param {String} eventType matches `EVENT_START|MOVE|END`
         * @param {Object} pointerEvent
         */
        updatePointer: function updatePointer(eventType, pointerEvent) {
            if(eventType == EVENT_END) {
                delete this.pointers[pointerEvent.pointerId];
            } else {
                pointerEvent.identifier = pointerEvent.pointerId;
                this.pointers[pointerEvent.pointerId] = pointerEvent;
            }
        },

        /**
         * check if ev matches pointertype
         * @method matchType
         * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
         * @param {PointerEvent} ev
         */
        matchType: function matchType(pointerType, ev) {
            if(!ev.pointerType) {
                return false;
            }

            var pt = ev.pointerType,
                types = {};

            types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
            types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
            types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
            return types[pointerType];
        },

        /**
         * reset the stored pointers
         * @method reset
         */
        reset: function resetList() {
            this.pointers = {};
        }
    };


    /**
     * @module hammer
     *
     * @class Detection
     * @static
     */
    var Detection = Hammer.detection = {
        // contains all registred Hammer.gestures in the correct order
        gestures: [],

        // data of the current Hammer.gesture detection session
        current: null,

        // the previous Hammer.gesture session data
        // is a full clone of the previous gesture.current object
        previous: null,

        // when this becomes true, no gestures are fired
        stopped: false,

        /**
         * start Hammer.gesture detection
         * @method startDetect
         * @param {Hammer.Instance} inst
         * @param {Object} eventData
         */
        startDetect: function startDetect(inst, eventData) {
            // already busy with a Hammer.gesture detection on an element
            if(this.current) {
                return;
            }

            this.stopped = false;

            // holds current session
            this.current = {
                inst: inst, // reference to HammerInstance we're working for
                startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
                lastEvent: false, // last eventData
                lastCalcEvent: false, // last eventData for calculations.
                futureCalcEvent: false, // last eventData for calculations.
                lastCalcData: {}, // last lastCalcData
                name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
            };

            this.detect(eventData);
        },

        /**
         * Hammer.gesture detection
         * @method detect
         * @param {Object} eventData
         * @return {any}
         */
        detect: function detect(eventData) {
            if(!this.current || this.stopped) {
                return;
            }

            // extend event data with calculations about scale, distance etc
            eventData = this.extendEventData(eventData);

            // hammer instance and instance options
            var inst = this.current.inst,
                instOptions = inst.options;

            // call Hammer.gesture handlers
            Utils.each(this.gestures, function triggerGesture(gesture) {
                // only when the instance options have enabled this gesture
                if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
                    gesture.handler.call(gesture, eventData, inst);
                }
            }, this);

            // store as previous event event
            if(this.current) {
                this.current.lastEvent = eventData;
            }

            if(eventData.eventType == EVENT_END) {
                this.stopDetect();
            }

            return eventData;
        },

        /**
         * clear the Hammer.gesture vars
         * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
         * to stop other Hammer.gestures from being fired
         * @method stopDetect
         */
        stopDetect: function stopDetect() {
            // clone current data to the store as the previous gesture
            // used for the double tap gesture, since this is an other gesture detect session
            this.previous = Utils.extend({}, this.current);

            // reset the current
            this.current = null;
            this.stopped = true;
        },

        /**
         * calculate velocity, angle and direction
         * @method getVelocityData
         * @param {Object} ev
         * @param {Object} center
         * @param {Number} deltaTime
         * @param {Number} deltaX
         * @param {Number} deltaY
         */
        getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
            var cur = this.current,
                recalc = false,
                calcEv = cur.lastCalcEvent,
                calcData = cur.lastCalcData;

            if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
                center = calcEv.center;
                deltaTime = ev.timeStamp - calcEv.timeStamp;
                deltaX = ev.center.clientX - calcEv.center.clientX;
                deltaY = ev.center.clientY - calcEv.center.clientY;
                recalc = true;
            }

            if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
                cur.futureCalcEvent = ev;
            }

            if(!cur.lastCalcEvent || recalc) {
                calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
                calcData.angle = Utils.getAngle(center, ev.center);
                calcData.direction = Utils.getDirection(center, ev.center);

                cur.lastCalcEvent = cur.futureCalcEvent || ev;
                cur.futureCalcEvent = ev;
            }

            ev.velocityX = calcData.velocity.x;
            ev.velocityY = calcData.velocity.y;
            ev.interimAngle = calcData.angle;
            ev.interimDirection = calcData.direction;
        },

        /**
         * extend eventData for Hammer.gestures
         * @method extendEventData
         * @param {Object} ev
         * @return {Object} ev
         */
        extendEventData: function extendEventData(ev) {
            var cur = this.current,
                startEv = cur.startEvent,
                lastEv = cur.lastEvent || startEv;

            // update the start touchlist to calculate the scale/rotation
            if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
                startEv.touches = [];
                Utils.each(ev.touches, function(touch) {
                    startEv.touches.push({
                        clientX: touch.clientX,
                        clientY: touch.clientY
                    });
                });
            }

            var deltaTime = ev.timeStamp - startEv.timeStamp,
                deltaX = ev.center.clientX - startEv.center.clientX,
                deltaY = ev.center.clientY - startEv.center.clientY;

            this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);

            Utils.extend(ev, {
                startEvent: startEv,

                deltaTime: deltaTime,
                deltaX: deltaX,
                deltaY: deltaY,

                distance: Utils.getDistance(startEv.center, ev.center),
                angle: Utils.getAngle(startEv.center, ev.center),
                direction: Utils.getDirection(startEv.center, ev.center),
                scale: Utils.getScale(startEv.touches, ev.touches),
                rotation: Utils.getRotation(startEv.touches, ev.touches)
            });

            return ev;
        },

        /**
         * register new gesture
         * @method register
         * @param {Object} gesture object, see `gestures/` for documentation
         * @return {Array} gestures
         */
        register: function register(gesture) {
            // add an enable gesture options if there is no given
            var options = gesture.defaults || {};
            if(options[gesture.name] === undefined) {
                options[gesture.name] = true;
            }

            // extend Hammer default options with the Hammer.gesture options
            Utils.extend(Hammer.defaults, options, true);

            // set its index
            gesture.index = gesture.index || 1000;

            // add Hammer.gesture to the list
            this.gestures.push(gesture);

            // sort the list by index
            this.gestures.sort(function(a, b) {
                if(a.index < b.index) {
                    return -1;
                }
                if(a.index > b.index) {
                    return 1;
                }
                return 0;
            });

            return this.gestures;
        }
    };


    /**
     * @module hammer
     */

    /**
     * create new hammer instance
     * all methods should return the instance itself, so it is chainable.
     *
     * @class Instance
     * @constructor
     * @param {HTMLElement} element
     * @param {Object} [options={}] options are merged with `Hammer.defaults`
     * @return {Hammer.Instance}
     */
    Hammer.Instance = function(element, options) {
        var self = this;

        // setup HammerJS window events and register all gestures
        // this also sets up the default options
        setup();

        /**
         * @property element
         * @type {HTMLElement}
         */
        this.element = element;

        /**
         * @property enabled
         * @type {Boolean}
         * @protected
         */
        this.enabled = true;

        /**
         * options, merged with the defaults
         * options with an _ are converted to camelCase
         * @property options
         * @type {Object}
         */
        Utils.each(options, function(value, name) {
            delete options[name];
            options[Utils.toCamelCase(name)] = value;
        });

        this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});

        // add some css to the element to prevent the browser from doing its native behavoir
        if(this.options.behavior) {
            Utils.toggleBehavior(this.element, this.options.behavior, true);
        }

        /**
         * event start handler on the element to start the detection
         * @property eventStartHandler
         * @type {Object}
         */
        this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
            if(self.enabled && ev.eventType == EVENT_START) {
                Detection.startDetect(self, ev);
            } else if(ev.eventType == EVENT_TOUCH) {
                Detection.detect(ev);
            }
        });

        /**
         * keep a list of user event handlers which needs to be removed when calling 'dispose'
         * @property eventHandlers
         * @type {Array}
         */
        this.eventHandlers = [];
    };

    Hammer.Instance.prototype = {
        /**
         * bind events to the instance
         * @method on
         * @chainable
         * @param {String} gestures multiple gestures by splitting with a space
         * @param {Function} handler
         * @param {Object} handler.ev event object
         */
        on: function onEvent(gestures, handler) {
            var self = this;
            Event.on(self.element, gestures, handler, function(type) {
                self.eventHandlers.push({ gesture: type, handler: handler });
            });
            return self;
        },

        /**
         * unbind events to the instance
         * @method off
         * @chainable
         * @param {String} gestures
         * @param {Function} handler
         */
        off: function offEvent(gestures, handler) {
            var self = this;

            Event.off(self.element, gestures, handler, function(type) {
                var index = Utils.inArray({ gesture: type, handler: handler });
                if(index !== false) {
                    self.eventHandlers.splice(index, 1);
                }
            });
            return self;
        },

        /**
         * trigger gesture event
         * @method trigger
         * @chainable
         * @param {String} gesture
         * @param {Object} [eventData]
         */
        trigger: function triggerEvent(gesture, eventData) {
            // optional
            if(!eventData) {
                eventData = {};
            }

            // create DOM event
            var event = Hammer.DOCUMENT.createEvent('Event');
            event.initEvent(gesture, true, true);
            event.gesture = eventData;

            // trigger on the target if it is in the instance element,
            // this is for event delegation tricks
            var element = this.element;
            if(Utils.hasParent(eventData.target, element)) {
                element = eventData.target;
            }

            if(navigator.isCocoonJS) {
                var me = this;
                me.eventHandlers.forEach(function(eventHandler) {
                    if(gesture === eventHandler.gesture) {
                        eventHandler.handler(event);
                    }
                });
            } else {
                element.dispatchEvent(event);
            }
            return this;
        },

        /**
         * enable of disable hammer.js detection
         * @method enable
         * @chainable
         * @param {Boolean} state
         */
        enable: function enable(state) {
            this.enabled = state;
            return this;
        },

        /**
         * dispose this hammer instance
         * @method dispose
         * @return {Null}
         */
        dispose: function dispose() {
            var i, eh;

            // undo all changes made by stop_browser_behavior
            Utils.toggleBehavior(this.element, this.options.behavior, false);

            // unbind all custom event handlers
            for(i = -1; (eh = this.eventHandlers[++i]);) {
                Utils.off(this.element, eh.gesture, eh.handler);
            }

            this.eventHandlers = [];

            // unbind the start event listener
            Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);

            return null;
        }
    };


    /**
     * @module gestures
     */
    /**
     * Move with x fingers (default 1) around on the page.
     * Preventing the default browser behavior is a good way to improve feel and working.
     * ````
     *  hammertime.on("drag", function(ev) {
 *    console.log(ev);
 *    ev.gesture.preventDefault();
 *  });
     * ````
     *
     * @class Drag
     * @static
     */
    /**
     * @event drag
     * @param {Object} ev
     */
    /**
     * @event dragstart
     * @param {Object} ev
     */
    /**
     * @event dragend
     * @param {Object} ev
     */
    /**
     * @event drapleft
     * @param {Object} ev
     */
    /**
     * @event dragright
     * @param {Object} ev
     */
    /**
     * @event dragup
     * @param {Object} ev
     */
    /**
     * @event dragdown
     * @param {Object} ev
     */

    /**
     * @param {String} name
     */
    (function(name) {
        var triggered = false;

        function dragGesture(ev, inst) {
            var cur = Detection.current;

            // max touches
            if(inst.options.dragMaxTouches > 0 &&
                ev.touches.length > inst.options.dragMaxTouches) {
                return;
            }

            switch(ev.eventType) {
                case EVENT_START:
                    triggered = false;
                    break;

                case EVENT_MOVE:
                    // when the distance we moved is too small we skip this gesture
                    // or we can be already in dragging
                    if(ev.distance < inst.options.dragMinDistance &&
                        cur.name != name) {
                        return;
                    }

                    var startCenter = cur.startEvent.center;

                    // we are dragging!
                    if(cur.name != name) {
                        cur.name = name;
                        if(inst.options.dragDistanceCorrection && ev.distance > 0) {
                            // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
                            // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
                            // It might be useful to save the original start point somewhere
                            var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
                            startCenter.pageX += ev.deltaX * factor;
                            startCenter.pageY += ev.deltaY * factor;
                            startCenter.clientX += ev.deltaX * factor;
                            startCenter.clientY += ev.deltaY * factor;

                            // recalculate event data using new start point
                            ev = Detection.extendEventData(ev);
                        }
                    }

                    // lock drag to axis?
                    if(cur.lastEvent.dragLockToAxis ||
                        ( inst.options.dragLockToAxis &&
                            inst.options.dragLockMinDistance <= ev.distance
                            )) {
                        ev.dragLockToAxis = true;
                    }

                    // keep direction on the axis that the drag gesture started on
                    var lastDirection = cur.lastEvent.direction;
                    if(ev.dragLockToAxis && lastDirection !== ev.direction) {
                        if(Utils.isVertical(lastDirection)) {
                            ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
                        } else {
                            ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
                        }
                    }

                    // first time, trigger dragstart event
                    if(!triggered) {
                        inst.trigger(name + 'start', ev);
                        triggered = true;
                    }

                    // trigger events
                    inst.trigger(name, ev);
                    inst.trigger(name + ev.direction, ev);

                    var isVertical = Utils.isVertical(ev.direction);

                    // block the browser events
                    if((inst.options.dragBlockVertical && isVertical) ||
                        (inst.options.dragBlockHorizontal && !isVertical)) {
                        ev.preventDefault();
                    }
                    break;

                case EVENT_RELEASE:
                    if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
                        inst.trigger(name + 'end', ev);
                        triggered = false;
                    }
                    break;

                case EVENT_END:
                    triggered = false;
                    break;
            }
        }

        Hammer.gestures.Drag = {
            name: name,
            index: 50,
            handler: dragGesture,
            defaults: {
                /**
                 * minimal movement that have to be made before the drag event gets triggered
                 * @property dragMinDistance
                 * @type {Number}
                 * @default 10
                 */
                dragMinDistance: 10,

                /**
                 * Set dragDistanceCorrection to true to make the starting point of the drag
                 * be calculated from where the drag was triggered, not from where the touch started.
                 * Useful to avoid a jerk-starting drag, which can make fine-adjustments
                 * through dragging difficult, and be visually unappealing.
                 * @property dragDistanceCorrection
                 * @type {Boolean}
                 * @default true
                 */
                dragDistanceCorrection: true,

                /**
                 * set 0 for unlimited, but this can conflict with transform
                 * @property dragMaxTouches
                 * @type {Number}
                 * @default 1
                 */
                dragMaxTouches: 1,

                /**
                 * prevent default browser behavior when dragging occurs
                 * be careful with it, it makes the element a blocking element
                 * when you are using the drag gesture, it is a good practice to set this true
                 * @property dragBlockHorizontal
                 * @type {Boolean}
                 * @default false
                 */
                dragBlockHorizontal: false,

                /**
                 * same as `dragBlockHorizontal`, but for vertical movement
                 * @property dragBlockVertical
                 * @type {Boolean}
                 * @default false
                 */
                dragBlockVertical: false,

                /**
                 * dragLockToAxis keeps the drag gesture on the axis that it started on,
                 * It disallows vertical directions if the initial direction was horizontal, and vice versa.
                 * @property dragLockToAxis
                 * @type {Boolean}
                 * @default false
                 */
                dragLockToAxis: false,

                /**
                 * drag lock only kicks in when distance > dragLockMinDistance
                 * This way, locking occurs only when the distance has become large enough to reliably determine the direction
                 * @property dragLockMinDistance
                 * @type {Number}
                 * @default 25
                 */
                dragLockMinDistance: 25
            }
        };
    })('drag');

    /**
     * @module gestures
     */
    /**
     * trigger a simple gesture event, so you can do anything in your handler.
     * only usable if you know what your doing...
     *
     * @class Gesture
     * @static
     */
    /**
     * @event gesture
     * @param {Object} ev
     */
    Hammer.gestures.Gesture = {
        name: 'gesture',
        index: 1337,
        handler: function releaseGesture(ev, inst) {
            inst.trigger(this.name, ev);
        }
    };

    /**
     * @module gestures
     */
    /**
     * Touch stays at the same place for x time
     *
     * @class Hold
     * @static
     */
    /**
     * @event hold
     * @param {Object} ev
     */

    /**
     * @param {String} name
     */
    (function(name) {
        var timer;

        function holdGesture(ev, inst) {
            var options = inst.options,
                current = Detection.current;

            switch(ev.eventType) {
                case EVENT_START:
                    clearTimeout(timer);

                    // set the gesture so we can check in the timeout if it still is
                    current.name = name;

                    // set timer and if after the timeout it still is hold,
                    // we trigger the hold event
                    timer = setTimeout(function() {
                        if(current && current.name == name) {
                            inst.trigger(name, ev);
                        }
                    }, options.holdTimeout);
                    break;

                case EVENT_MOVE:
                    if(ev.distance > options.holdThreshold) {
                        clearTimeout(timer);
                    }
                    break;

                case EVENT_RELEASE:
                    clearTimeout(timer);
                    break;
            }
        }

        Hammer.gestures.Hold = {
            name: name,
            index: 10,
            defaults: {
                /**
                 * @property holdTimeout
                 * @type {Number}
                 * @default 500
                 */
                holdTimeout: 500,

                /**
                 * movement allowed while holding
                 * @property holdThreshold
                 * @type {Number}
                 * @default 2
                 */
                holdThreshold: 2
            },
            handler: holdGesture
        };
    })('hold');

    /**
     * @module gestures
     */
    /**
     * when a touch is being released from the page
     *
     * @class Release
     * @static
     */
    /**
     * @event release
     * @param {Object} ev
     */
    Hammer.gestures.Release = {
        name: 'release',
        index: Infinity,
        handler: function releaseGesture(ev, inst) {
            if(ev.eventType == EVENT_RELEASE) {
                inst.trigger(this.name, ev);
            }
        }
    };

    /**
     * @module gestures
     */
    /**
     * triggers swipe events when the end velocity is above the threshold
     * for best usage, set `preventDefault` (on the drag gesture) to `true`
     * ````
     *  hammertime.on("dragleft swipeleft", function(ev) {
 *    console.log(ev);
 *    ev.gesture.preventDefault();
 *  });
     * ````
     *
     * @class Swipe
     * @static
     */
    /**
     * @event swipe
     * @param {Object} ev
     */
    /**
     * @event swipeleft
     * @param {Object} ev
     */
    /**
     * @event swiperight
     * @param {Object} ev
     */
    /**
     * @event swipeup
     * @param {Object} ev
     */
    /**
     * @event swipedown
     * @param {Object} ev
     */
    Hammer.gestures.Swipe = {
        name: 'swipe',
        index: 40,
        defaults: {
            /**
             * @property swipeMinTouches
             * @type {Number}
             * @default 1
             */
            swipeMinTouches: 1,

            /**
             * @property swipeMaxTouches
             * @type {Number}
             * @default 1
             */
            swipeMaxTouches: 1,

            /**
             * horizontal swipe velocity
             * @property swipeVelocityX
             * @type {Number}
             * @default 0.6
             */
            swipeVelocityX: 0.6,

            /**
             * vertical swipe velocity
             * @property swipeVelocityY
             * @type {Number}
             * @default 0.6
             */
            swipeVelocityY: 0.6
        },

        handler: function swipeGesture(ev, inst) {
            if(ev.eventType == EVENT_RELEASE) {
                var touches = ev.touches.length,
                    options = inst.options;

                // max touches
                if(touches < options.swipeMinTouches ||
                    touches > options.swipeMaxTouches) {
                    return;
                }

                // when the distance we moved is too small we skip this gesture
                // or we can be already in dragging
                if(ev.velocityX > options.swipeVelocityX ||
                    ev.velocityY > options.swipeVelocityY) {
                    // trigger swipe events
                    inst.trigger(this.name, ev);
                    inst.trigger(this.name + ev.direction, ev);
                }
            }
        }
    };

    /**
     * @module gestures
     */
    /**
     * Single tap and a double tap on a place
     *
     * @class Tap
     * @static
     */
    /**
     * @event tap
     * @param {Object} ev
     */
    /**
     * @event doubletap
     * @param {Object} ev
     */

    /**
     * @param {String} name
     */
    (function(name) {
        var hasMoved = false;

        function tapGesture(ev, inst) {
            var options = inst.options,
                current = Detection.current,
                prev = Detection.previous,
                sincePrev,
                didDoubleTap;

            switch(ev.eventType) {
                case EVENT_START:
                    hasMoved = false;
                    break;

                case EVENT_MOVE:
                    hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
                    break;

                case EVENT_END:
                    if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
                        // previous gesture, for the double tap since these are two different gesture detections
                        sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
                        didDoubleTap = false;

                        // check if double tap
                        if(prev && prev.name == name &&
                            (sincePrev && sincePrev < options.doubleTapInterval) &&
                            ev.distance < options.doubleTapDistance) {
                            inst.trigger('doubletap', ev);
                            didDoubleTap = true;
                        }

                        // do a single tap
                        if(!didDoubleTap || options.tapAlways) {
                            current.name = name;
                            inst.trigger(current.name, ev);
                        }
                    }
                    break;
            }
        }

        Hammer.gestures.Tap = {
            name: name,
            index: 100,
            handler: tapGesture,
            defaults: {
                /**
                 * max time of a tap, this is for the slow tappers
                 * @property tapMaxTime
                 * @type {Number}
                 * @default 250
                 */
                tapMaxTime: 250,

                /**
                 * max distance of movement of a tap, this is for the slow tappers
                 * @property tapMaxDistance
                 * @type {Number}
                 * @default 10
                 */
                tapMaxDistance: 10,

                /**
                 * always trigger the `tap` event, even while double-tapping
                 * @property tapAlways
                 * @type {Boolean}
                 * @default true
                 */
                tapAlways: true,

                /**
                 * max distance between two taps
                 * @property doubleTapDistance
                 * @type {Number}
                 * @default 20
                 */
                doubleTapDistance: 20,

                /**
                 * max time between two taps
                 * @property doubleTapInterval
                 * @type {Number}
                 * @default 300
                 */
                doubleTapInterval: 300
            }
        };
    })('tap');

    /**
     * @module gestures
     */
    /**
     * when a touch is being touched at the page
     *
     * @class Touch
     * @static
     */
    /**
     * @event touch
     * @param {Object} ev
     */
    Hammer.gestures.Touch = {
        name: 'touch',
        index: -Infinity,
        defaults: {
            /**
             * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
             * but it improves gestures like transforming and dragging.
             * be careful with using this, it can be very annoying for users to be stuck on the page
             * @property preventDefault
             * @type {Boolean}
             * @default false
             */
            preventDefault: false,

            /**
             * disable mouse events, so only touch (or pen!) input triggers events
             * @property preventMouse
             * @type {Boolean}
             * @default false
             */
            preventMouse: false
        },
        handler: function touchGesture(ev, inst) {
            if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
                ev.stopDetect();
                return;
            }

            if(inst.options.preventDefault) {
                ev.preventDefault();
            }

            if(ev.eventType == EVENT_TOUCH) {
                inst.trigger('touch', ev);
            }
        }
    };

    /**
     * @module gestures
     */
    /**
     * User want to scale or rotate with 2 fingers
     * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
     * `preventDefault` option.
     *
     * @class Transform
     * @static
     */
    /**
     * @event transform
     * @param {Object} ev
     */
    /**
     * @event transformstart
     * @param {Object} ev
     */
    /**
     * @event transformend
     * @param {Object} ev
     */
    /**
     * @event pinchin
     * @param {Object} ev
     */
    /**
     * @event pinchout
     * @param {Object} ev
     */
    /**
     * @event rotate
     * @param {Object} ev
     */

    /**
     * @param {String} name
     */
    (function(name) {
        var triggered = false;

        function transformGesture(ev, inst) {
            switch(ev.eventType) {
                case EVENT_START:
                    triggered = false;
                    break;

                case EVENT_MOVE:
                    // at least multitouch
                    if(ev.touches.length < 2) {
                        return;
                    }

                    var scaleThreshold = Math.abs(1 - ev.scale);
                    var rotationThreshold = Math.abs(ev.rotation);

                    // when the distance we moved is too small we skip this gesture
                    // or we can be already in dragging
                    if(scaleThreshold < inst.options.transformMinScale &&
                        rotationThreshold < inst.options.transformMinRotation) {
                        return;
                    }

                    // we are transforming!
                    Detection.current.name = name;

                    // first time, trigger dragstart event
                    if(!triggered) {
                        inst.trigger(name + 'start', ev);
                        triggered = true;
                    }

                    inst.trigger(name, ev); // basic transform event

                    // trigger rotate event
                    if(rotationThreshold > inst.options.transformMinRotation) {
                        inst.trigger('rotate', ev);
                    }

                    // trigger pinch event
                    if(scaleThreshold > inst.options.transformMinScale) {
                        inst.trigger('pinch', ev);
                        inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
                    }
                    break;

                case EVENT_RELEASE:
                    if(triggered && ev.changedLength < 2) {
                        inst.trigger(name + 'end', ev);
                        triggered = false;
                    }
                    break;
            }
        }

        Hammer.gestures.Transform = {
            name: name,
            index: 45,
            defaults: {
                /**
                 * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
                 * @property transformMinScale
                 * @type {Number}
                 * @default 0.01
                 */
                transformMinScale: 0.01,

                /**
                 * rotation in degrees
                 * @property transformMinRotation
                 * @type {Number}
                 * @default 1
                 */
                transformMinRotation: 1
            },

            handler: transformGesture
        };
    })('transform');

    /**
     * @module hammer
     */

// AMD export
    if(typeof define == 'function' && define.amd) {
        define(function() {
            return Hammer;
        });
// commonjs export
    } else if(typeof module !== 'undefined' && module.exports) {
        module.exports = Hammer;
// browser export
    } else {
        window.Hammer = Hammer;
    }

})(window);