# Source: composer/press-composer.js

    /*global require, exports*/

    /**
     * @module montage/composer/press-composer
     * @requires montage/core/core
     * @requires montage/composer/composer
     * @requires montage/core/event/mutable-event
     */
    var Montage = require("../core/core").Montage,
        Composer = require("./composer").Composer,
        MutableEvent = require("../core/event/mutable-event").MutableEvent;

    /*
     * @class PressEvent
     * @inherits MutableEvent
     * @classdesc The event dispatched by the `PressComposer`, providing access to
     * the raw DOM event and proxying its properties.
     */
    var PressEvent = (function (){

        function eventPropDescriptor(prop) {
            return {
                get: function () {
                    return this._event[prop];
                }
            };
        }

        function typePropDescriptor(prop) {
            return {
                get: function () {
                    return (this._touch) ? this._touch[prop] : this._event[prop];
                }
            };
        }

        var PressEvent = MutableEvent.specialize({
            type: {
                value: "press"
            },
            _event: {
                enumerable: false,
                value: null
            },
            event: {
                get: function () {
                    return this._event;
                },
                set: function (value) {
                    this._event = value;
                }
            },
            _touch: {
                enumerable: false,
                value: null
            },
            touch: {
                get: function () {
                    return this._touch;
                },
                set: function (value) {
                    this._touch = value;
                }
            }
        });

        // These properties are available directly on the event
        [
            "altKey", "ctrlKey", "metaKey", "shiftKey",
            "cancelBubble", "currentTarget", "defaultPrevented",
            "eventPhase", "timeStamp", "preventDefault",
            "stopImmediatePropagation", "stopPropagation"
        ].forEach(function (eventProp) {
            Montage.defineProperty(PressEvent, eventProp, eventPropDescriptor(eventProp));
        });

        // These properties are available on the event in the case of mouse, and
        // on the _touch in the case of touch
        ["clientX", "clientY", "pageX", "pageY", "screenX", "screenY", "target"].forEach(function (typeProp) {
            Montage.defineProperty(PressEvent, typeProp, typePropDescriptor(typeProp));
        });

        return PressEvent;
    }());

    /**
     * @class PressComposer
     * @classdesc The `PressComposer` abstracts away handling mouse and touch
     * events that represent presses, allowing generic detection of presses, long
     * presses, and cancelled presses.
     *
     * @extends Composer
     * @fires pressStart
     * @fires press
     * @fires longPress
     * @fires pressCancel
     */
    var PressComposer = exports.PressComposer = Composer.specialize(/** @lends PressComposer.prototype # */ {

        /**
         * Dispatched when a press begins. It is ended by either a {@link press} or
         * {@link pressCancel} event.
         *
         * @event pressStart
         * @memberof PressComposer
         * @param {PressEvent} event
         */

        /**
         * Dispatched when a press is complete.
         *
         * @event press
         * @memberof PressComposer
         * @param {PressEvent} event
         */

        /**
         * Dispatched when a press lasts for longer than (@link longPressThreshold}
         * On a long press, the sequence of events will be:
         * - pressStart: as soon as the composer recognizes it is a press.
         * - longPress: `longPressThreshold` after the pressStart, if the press has
         *   not yet ended.
         * - press: when the press ends, if it isn't cancelled.
         *
         * Handlers of the `longPress` event can call `cancelPress` to prevent
         * `press` being triggered.
         *
         * @event longPress
         * @memberof PressComposer
         * @param {PressEvent} event
         */

        /**
         * Dispatched when a press is canceled. This could be because the pointer
         * left the element, was claimed by another component or maybe a phone call
         * came in.
         *
         * @event pressCancel
         * @memberof PressComposer
         * @param {PressEvent} event
         */

        // Load/unload

        load: {
            value: function () {
                if (window.PointerEvent) {
                    this._element.addEventListener("pointerdown", this, true);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    this._element.addEventListener("MSPointerDown", this, true);

                } else {
                    this._element.addEventListener("touchstart", this, true);
                    this._element.addEventListener("mousedown", this, true);
                }
            }
        },

        unload: {
            value: function () {
                if (window.PointerEvent) {
                    this._element.removeEventListener("pointerdown", this, true);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    this._element.removeEventListener("MSPointerDown", this, true);

                } else {
                    this._element.removeEventListener("touchstart", this, true);
                    this._element.removeEventListener("mousedown", this, true);
                }
            }
        },

        /**
         * Delegate that implements `surrenderPointer`. See Component for
         * explanation of what this method should do.
         *
         * @type {Object}
         * @default null
         */
        delegate: {
            value: null
        },

        /**
         * Cancel the current press.
         *
         * Can be used in a "longPress" event handler to prevent the "press" event
         * being fired.
         * @returns boolean true if a press was canceled, false if the composer was
         * already in a unpressed or canceled state.
         */
        cancelPress: {
            value: function () {
                if (this._state === PressComposer.PRESSED) {
                    this._cancelPress();
                    return true;
                }
                return false;
            }
        },

        _cancelPress: {
            value: function (event) {
                this._dispatchPressCancel(event);
                this._endInteraction();
            }
        },

        // Optimisation so that we don't set a timeout if we do not need to
        addEventListener: {
            value: function (type, listener, useCapture) {
                Composer.prototype.addEventListener.call(this, type, listener, useCapture);
                if (type === "longPress") {
                    this._shouldDispatchLongPress = true;
                }
            }
        },

        _state: {
            value: 0
        },
        state: {
            get: function () {
                return this._state;
            }
        },

        _shouldDispatchLongPress: {
            value: false
        },

        _longPressThreshold: {
            value: 1000
        },

        /**
         * How long a press has to last (in milliseconds) for a longPress event to
         * be dispatched
         * @type number
         */
        longPressThreshold: {
            get: function () {
                return this._longPressThreshold;
            },
            set: function (value) {
                if (this._longPressThreshold !== value) {
                    this._longPressThreshold = value;
                }
            }
        },

        _needDispatchSafePress: {
            value: false
        },

        _longPressTimeout: {
            value: null
        },

        // Magic

        _observedPointer: {
            value: null
        },

        _initialCenterPositionX: {
            value : 0
        },

        _initialCenterPositionY: {
            value: 0
        },

        _shouldSaveInitialCenterPosition: {
            value: false
        },

        _targetElementOnLastTouchMove: {
            value: null
        },

        /**
         * Remove event listeners after an interaction has finished.
         * @private
         */
        _endInteraction: {
            value: function () {
                if (this._element) {
                    this._removeEventListeners();

                    if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                        this.component.eventManager.forfeitPointer(this._observedPointer, this);
                    }

                    this._observedPointer = null;
                    this._targetElementOnLastTouchMove = null;
                    this._state = PressComposer.UNPRESSED;
                    this._initialCenterPositionX = 0;
                    this._initialCenterPositionY = 0;
                    this._shouldSaveInitialCenterPosition = false;
                }
            }
        },

        /**
         * Checks if we are observing one of the changed touches. Returns the index
         * of the changed touch if one matches, otherwise returns false. Make sure
         * to check against `!== false` or `=== false` as the
         * matching index might be 0.
         *
         * @function
         * @returns {number|boolean} The index of the matching touch, or false
         * @private
         */
        _changedTouchisObserved: {
            value: function (changedTouches) {
                if (this._observedPointer === null) {
                    return false;
                }

                var i = 0, changedTouchCount = changedTouches.length;

                for (; i < changedTouchCount; i++) {
                    if (changedTouches[i].identifier === this._observedPointer) {
                        return i;
                    }
                }
                return false;
            }
        },

        // Surrender pointer

        surrenderPointer: {
            value: function (pointer, component) {
                var shouldSurrender = this.callDelegateMethod(
                    "shouldComposerSurrenderPointerToComponent",
                    this,
                    pointer,
                    component
                );

                if (typeof shouldSurrender !== "undefined" && shouldSurrender === false) {
                    return false;
                }

                this.cancelPress();

                return true;
            }
        },

        _shouldPerformPress: {
            value: function () {
                return !(this.component.disabled || this._observedPointer !== null);
            }
        },

        // Handlers

        capturePointerdown: {
            value: function (event) {
                if (event.pointerType === "touch" || (window.MSPointerEvent && event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH)) {
                    this.captureTouchstart(event);

                } else if (event.pointerType === "mouse" || (window.MSPointerEvent && event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_MOUSE)) {
                    this.captureMousedown(event);
                }
            }
        },

        handlePointerup: {
            value: function (event) {
                if (event.pointerType === "touch" || (window.MSPointerEvent && event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH)) {
                    this.handleTouchend(event);

                } else if (event.pointerType === "mouse" || (window.MSPointerEvent && event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_MOUSE)) {
                    this.handleMouseup(event);
                }
            }
        },

        handlePointercancel: {
            value: function (event) {
                if (event.pointerType === "touch" || (window.MSPointerEvent && event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH)) {
                    this.handleTouchcancel(event);
                }
            }
        },

        captureTouchstart: {
            value: function (event) {
                if (this._shouldPerformPress()) {
                    if (event.pointerId !== void 0) { // -> pointer events support.
                        this._observedPointer = event.pointerId;

                    } else if (event.changedTouches && event.changedTouches.length === 1) {
                        this._observedPointer = event.changedTouches[0].identifier;
                    }

                    if (this._observedPointer !== null && this.component.eventManager.claimPointer(this._observedPointer, this)) {
                        this._addPointerDownListener();

                    } else {
                        this._observedPointer = null;
                    }
                }
            }
        },

        handleTouchend: {
            value: function (event) {
                if (this._observedPointer === null) {
                    this._endInteraction(event);
                    return;
                }

                var target;

                if (event.pointerId === this._observedPointer)  {
                    target = event.target;

                } else if (this._changedTouchisObserved(event.changedTouches) !== false) {
                    // We need to keep the last element reached by a touchmove because we can't just rely on the position
                    // given by the touchEnd because in some cases, hiding the android keyboard for example, could result
                    // to change the press composer's element position between a touchStart and a touchEnd event. Therefore,
                    // getting the "wrong" target.
                    target = this._targetElementOnLastTouchMove || event.target;
                }

                if (target && this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                    if (this.element === target || this.element.contains(target)) {
                        this._dispatchPress(event, typeof event.pointerId === "undefined" ? target : null);

                    } else {
                        this._dispatchPressCancel(event);
                    }

                    this._endInteraction(event);
                }
            }
        },

        // The PressComposer saves the initial center position after the first move or the first wheel event,
        // in order to wait for a possible css transform (translate, scale...) appeared on its element
        // after that the PressStart event has been raised.
        _saveInitialCenterPositionIfNeeded: {
            value: function () {
                if (this._shouldSaveInitialCenterPosition) {
                    this._saveInitialCenterPosition();
                    this._shouldSaveInitialCenterPosition = false;
                }
            }
        },

        _cancelPressIfCenterPositionChange: {
            value: function (event) {
                this._saveInitialCenterPositionIfNeeded();

                if (this._isPositionChanged(event)) {
                    this._cancelPress(event);

                    return true;
                }

                return false;
            }
        },

        _handleMove: {
            value: function (event) {
                if (this._observedPointer === null) {
                    this._endInteraction(event);
                    return;
                }

                this._needDispatchSafePress = false;

                if ((this._observedPointer === "mouse" || event.pointerId === this._observedPointer ||
                    (event.changedTouches && this._changedTouchisObserved(event.changedTouches) !== false)) &&
                    this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {

                    if (!this._cancelPressIfCenterPositionChange(event) && event.type === "touchmove") {
                        var touch = event.changedTouches[0];
                        this._targetElementOnLastTouchMove = document.elementFromPoint(touch.clientX, touch.clientY);
                    }
                }
            }
        },

        captureWheel: {
            value: function (event) {
                if (this._observedPointer === null) {
                    this._endInteraction(event);
                    return;
                }

                if ((event.target === this.element || event.target === window ||
                    (typeof event.target.contains === "function" && event.target.contains(this.element)) || this.element.contains(event.target))) {

                    this._cancelPressIfCenterPositionChange(event);
                }
            }
        },

        handleTouchcancel: {
            value: function (event) {
                if (this._observedPointer === null || event.pointerId === this._observedPointer || this._changedTouchisObserved(event.changedTouches) !== false) {
                    if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                        this._dispatchPressCancel(event);
                    }

                    this._endInteraction(event);
                }
            }
        },

        captureMousedown: {
            value: function (event) {
                if (event.button === 0 && this._shouldPerformPress()) {
                    this._observedPointer = "mouse";
                    this.component.eventManager.claimPointer(this._observedPointer, this);

                    if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                        this._addPointerDownListener();

                    } else{
                        this._observedPointer = null;
                    }
                }
            }
        },

        /**
         * Handle every kind of interactions (touch, mouse or pointer),
         * in order to check if a press composer still claims the observed pointer in the bubbling phase.
         * @private
         */
        _handlePointerDown: {
            value: function (event) {
                this._removePointerDownListener();

                if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                    this._needDispatchSafePress = false;

                    if (event.type === "touchstart") {
                        var self = this;

                        window.nativeAddEventListener("touchstart", function _touchStartDefaultPrevented (touchStartEvent) {
                            window.nativeRemoveEventListener("touchstart", _touchStartDefaultPrevented, false);
                            self._needDispatchSafePress = !touchStartEvent.defaultPrevented;
                        }, false);
                    }

                    this._shouldSaveInitialCenterPosition = true;
                    this._addEventListeners();
                    this._dispatchPressStart(event);

                } else {
                    this._observedPointer = null;
                }
            }
        },

        captureScroll: {
            value: function (event) {
                if (event.target === this.element || event.target === window ||
                    (typeof event.target.contains === "function" && event.target.contains(this.element)) ||
                    this.element.contains(event.target)) {

                    this._cancelPressIfCenterPositionChange(event);
                }
            }
        },

        handleMouseup: {
            value: function (event) {
                if (this._observedPointer === null) {
                    this._endInteraction(event);
                    return;
                }

                if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
                    var target = event.target;

                    while (target !== this._element && target && target.parentNode) {
                        target = target.parentNode;
                    }

                    if (target === this._element) {
                        this._dispatchPress(event);
                        this._endInteraction(event);
                        return;
                    }
                }

                this._cancelPress(event);
            }
        },

        handleDragstart: {
            value: function (event) {
                this._cancelPress(event);
            }
        },

        _saveInitialCenterPosition: {
            value: function () {
                // Makes sure we compute the position of a PressComposer’s element only on HTMLElement.
                // Indeed a press composer can be used on other objects such as document, window…
                if (this.element instanceof HTMLElement) {
                    var boundingClientRect = this.element.getBoundingClientRect();

                    this._initialCenterPositionX = boundingClientRect.left + (boundingClientRect.width / 2);
                    this._initialCenterPositionY = boundingClientRect.top + (boundingClientRect.height / 2);
                }
            }
        },

        _isPositionChanged: {
            value: function (event) {
                if (this.element instanceof HTMLElement) {
                    var boundingClientRect = this.element.getBoundingClientRect(),
                        newCenterPositionX = boundingClientRect.left + (boundingClientRect.width / 2),
                        newCenterPositionY = boundingClientRect.top + (boundingClientRect.height / 2);

                    if (this._initialCenterPositionX !== newCenterPositionX || this._initialCenterPositionY !== newCenterPositionY) {
                        var type = event.type,
                            deltaX = Math.abs(this._initialCenterPositionX - newCenterPositionX),
                            deltaY = Math.abs(this._initialCenterPositionY - newCenterPositionY),
                            radius = this._observedPointer === "mouse" || type === "wheel" || type === "mousewheel" || type === "scroll" ?
                                this._mouseRadiusThreshold : this._touchRadiusThreshold;

                        return Composer.isCoordinateOutsideRadius(deltaX, deltaY, radius);
                    }
                }

                return false;
            }
        },

        _mouseRadiusThreshold: {
            value: 2 //px
        },

        _touchRadiusThreshold: {
            value: 4 //px
        },

        _addPointerDownListener: {
            value: function () {
                if (window.PointerEvent) {
                    this._element.addEventListener("pointerdown", this, false);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    this._element.addEventListener("MSPointerDown", this, false);

                } else {
                    if (this._observedPointer === "mouse") {
                        this._element.addEventListener("mousedown", this, false);

                    } else {
                        this._element.addEventListener("touchstart", this, false);
                    }
                }
            }
        },

        _removePointerDownListener: {
            value: function () {
                if (window.PointerEvent) {
                    this._element.removeEventListener("pointerdown", this, false);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    this._element.removeEventListener("MSPointerDown", this, false);

                } else {
                    if (this._observedPointer === "mouse") {
                        this._element.removeEventListener("mousedown", this, false);

                    } else {
                        this._element.removeEventListener("touchstart", this, false);
                    }
                }
            }
        },

        _addEventListeners: {
            value: function () {
                if (window.PointerEvent) {
                    if (this.shawdowRoot) {
                        this.shawdowRoot.addEventListener("pointerup", this, false);
                        this.shawdowRoot.addEventListener("pointermove", this, false);
                        this.shawdowRoot.addEventListener("pointercancel", this, false);
                    }

                    document.addEventListener("pointerup", this, false);
                    document.addEventListener("pointermove", this, false);
                    document.addEventListener("pointercancel", this, false);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    if (this.shawdowRoot) {
                        this.shawdowRoot.addEventListener("MSPointerUp", this, false);
                        this.shawdowRoot.addEventListener("MSPointerMove", this, false);
                        this.shawdowRoot.addEventListener("MSPointerCancel", this, false);
                    }

                    document.addEventListener("MSPointerUp", this, false);
                    document.addEventListener("MSPointerMove", this, false);
                    document.addEventListener("MSPointerCancel", this, false);

                } else {
                    if (this._observedPointer === "mouse") {
                        if (this.shawdowRoot) {
                            this.shawdowRoot.addEventListener("mouseup", this, false);
                            this.shawdowRoot.addEventListener("mousemove", this, false);
                            this.shawdowRoot.addEventListener("dragstart", this, false);
                        }

                        document.addEventListener("mouseup", this, false);
                        document.addEventListener("mousemove", this, false);

                        // Needed to cancel the press because once a drag is started
                        // no mouse events are fired
                        // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#initiate-the-drag-and-drop-operation
                        this._element.addEventListener("dragstart", this, false);

                    } else {
                        if (this.shawdowRoot) {
                            this.shawdowRoot.addEventListener("touchend", this, false);
                            this.shawdowRoot.addEventListener("touchcancel", this, false);
                            this.shawdowRoot.addEventListener("touchmove", this, false);
                        }

                        document.addEventListener("touchend", this, false);
                        document.addEventListener("touchcancel", this, false);
                        document.addEventListener("touchmove", this, false);
                    }
                }

                var wheelEventName = typeof window.onwheel !== "undefined" || typeof window.WheelEvent !== "undefined" ?
                    "wheel" : "mousewheel";

                if (this.shawdowRoot) {
                    this.shawdowRoot.addEventListener(wheelEventName, this, true);
                    this.shawdowRoot.addEventListener("scroll", this, true);
                }

                document.addEventListener(wheelEventName, this, true);
                document.addEventListener("scroll", this, true);
            }
        },

        _removeEventListeners: {
            value: function () {
                if (window.PointerEvent) {
                    if (this.shawdowRoot) {
                        this.shawdowRoot.removeEventListener("pointerup", this, false);
                        this.shawdowRoot.removeEventListener("pointermove", this, false);
                        this.shawdowRoot.removeEventListener("pointercancel", this, false);
                    }

                    document.removeEventListener("pointerup", this, false);
                    document.removeEventListener("pointermove", this, false);
                    document.removeEventListener("pointercancel", this, false);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    if (this.shawdowRoot) {
                        this.shawdowRoot.removeEventListener("MSPointerUp", this, false);
                        this.shawdowRoot.removeEventListener("MSPointerMove", this, false);
                        this.shawdowRoot.removeEventListener("MSPointerCancel", this, false);
                    }

                    document.removeEventListener("MSPointerUp", this, false);
                    document.removeEventListener("MSPointerMove", this, false);
                    document.removeEventListener("MSPointerCancel", this, false);

                } else {
                    if (this._observedPointer === "mouse") {
                        if (this.shawdowRoot) {
                            this.shawdowRoot.removeEventListener("mouseup", this, false);
                            this.shawdowRoot.removeEventListener("mousemove", this, false);
                            this.shawdowRoot.removeEventListener("dragstart", this, false);
                        }

                        document.removeEventListener("mouseup", this, false);
                        document.removeEventListener("mousemove", this, false);

                        // Needed to cancel the press because once a drag is started
                        // no mouse events are fired
                        // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#initiate-the-drag-and-drop-operation
                        this._element.removeEventListener("dragstart", this, false);

                    } else {
                        if (this.shawdowRoot) {
                            this.shawdowRoot.removeEventListener("touchend", this, false);
                            this.shawdowRoot.removeEventListener("touchcancel", this, false);
                            this.shawdowRoot.removeEventListener("touchmove", this, false);
                        }

                        document.removeEventListener("touchend", this, false);
                        document.removeEventListener("touchcancel", this, false);
                        document.removeEventListener("touchmove", this, false);
                    }
                }

                var wheelEventName = typeof window.onwheel !== "undefined" || typeof window.WheelEvent !== "undefined" ?
                    "wheel" : "mousewheel";

                if (this.shawdowRoot) {
                    this.shawdowRoot.removeEventListener(wheelEventName, this, true);
                    this.shawdowRoot.removeEventListener("scroll", this, true);
                }

                document.removeEventListener(wheelEventName, this, true);
                document.removeEventListener("scroll", this, true);
            }
        },

        // Event dispatch

        _createPressEvent: {
            enumerable: false,
            value: function (name, event) {
                var contactPoint = event,
                    pressEvent, index;

                if (!event) {
                    event = document.createEvent("CustomEvent");
                    event.initCustomEvent(name, true, true, null);
                }

                pressEvent = new PressEvent();
                pressEvent.event = event;
                pressEvent.type = name;
                pressEvent.pointer = this._observedPointer;
                pressEvent.targetElement = event.target;

                if (event.changedTouches && (index = this._changedTouchisObserved(event.changedTouches)) !== false) {
                    contactPoint = pressEvent.touch = event.changedTouches[index];
                }

                if (contactPoint) { // a PressCancel event can be dispatched programtically, so with no event.
                    pressEvent.clientX = contactPoint.clientX;
                    pressEvent.clientY = contactPoint.clientY;
                    pressEvent.pageX = contactPoint.pageX;
                    pressEvent.pageY = contactPoint.pageY;
                }

                return pressEvent;
            }
        },

        _dispatchPressStart: {
            enumerable: false,
            value: function (event) {
                this._state = PressComposer.PRESSED;
                this.dispatchEvent(this._createPressEvent("pressStart", event));

                if (this._shouldDispatchLongPress) {
                    var self = this;

                    this._longPressTimeout = setTimeout(function () {
                        self._dispatchLongPress(event.target);
                    }, this._longPressThreshold);
                }
            }
        },

        _dispatchPress: {
            enumerable: false,
            value: function (event, touchEndTargetElement) {
                if (this._shouldDispatchLongPress) {
                    clearTimeout(this._longPressTimeout);
                    this._longPressTimeout = null;
                }

                var pressEvent = this._createPressEvent("press", event);

                // The target of touchend events is actually the same element on which the touch point started.
                // So, we override the property targetElement of the PressEvent with the element on which the touch point has been released.
                if (touchEndTargetElement) {
                    pressEvent.targetElement = touchEndTargetElement;

                    if (this._needDispatchSafePress || event.defaultPrevented) {// no simulated events when a touchMove has been raised
                        var self = this,
                            eventManager = document.defaultView.defaultEventManager;

                        // Raise a Press event after the simulated mousedown event has been raised,
                        // in order to avoid elements that are not on the same layer  to get the focus.
                        // @example: When a button is pressed in order to hide a overlay that it belong to.
                        // Knowing that, the simulated mouse events are not dispatched by the event manager, but they are still
                        // walking the dom. An issue could happen here when the positioning of elements is changing (z-index)
                        // after the press event has been raised, which could result to giving the focus to a wrong element.
                        // @example: @see press-composer.info
                        //@todo: should be deprecated when browsers will support PointerEvents.
                        var dispatchSafePressCallBack = function (mouseDownEvent) {
                            if (touchEndTargetElement === mouseDownEvent.target ||
                                eventManager._couldEmulatedEventHaveWrongTarget(
                                    event.changedTouches[0],
                                    mouseDownEvent,
                                    eventManager._emulatedEventRadiusThreshold,
                                    eventManager._emulatedEventTimestampThreshold
                                )
                            ) {
                                self._dispatchSafePress(pressEvent, dispatchSafePressCallBack);
                            }
                        };

                        window.nativeAddEventListener("mousedown", dispatchSafePressCallBack, true);

                        // -> long press fallBack: no simulated events with long touch press.
                        dispatchSafePressCallBack.timeoutID = setTimeout(function () {
                            self._dispatchSafePress(pressEvent, dispatchSafePressCallBack);
                        }, 300);

                        return void 0;
                    }
                }

                this.dispatchEvent(pressEvent);
                this._state = PressComposer.UNPRESSED;
            }
        },

        _dispatchSafePress: {
            value: function (pressEvent, dispatchSafePressCallBack) {
                clearTimeout(dispatchSafePressCallBack.timeoutID);
                dispatchSafePressCallBack.timeoutID = null;
                window.nativeRemoveEventListener("mousedown", dispatchSafePressCallBack, true);
                this.dispatchEvent(pressEvent);
                this._state = PressComposer.UNPRESSED;
            }
        },

        _dispatchLongPress: {
            enumerable: false,
            value: function (targetElement) {
                var event, target;
                if (this._shouldDispatchLongPress) {
                    event = this._createPressEvent("longPress");
                    event.targetElement = targetElement;
                    this.dispatchEvent(event);
                    this._longPressTimeout = null;
                }
            }
        },

        _dispatchPressCancel: {
            enumerable: false,
            value: function (event) {
                if (this._shouldDispatchLongPress) {
                    clearTimeout(this._longPressTimeout);
                    this._longPressTimeout = null;
                }

                this._state = PressComposer.CANCELLED;
                this.dispatchEvent(this._createPressEvent("pressCancel", event));
            }
        }

    } , {
        UNPRESSED: {
            value: 0
        },
        PRESSED: {
            value: 1
        },
        CANCELLED: {
            value: 2
        }
    });

    PressComposer.prototype.captureMSPointerDown = PressComposer.prototype.capturePointerdown;
    PressComposer.prototype.handleMSPointerUp = PressComposer.prototype.handlePointerup;
    PressComposer.prototype.handleMSPointerCancel = PressComposer.prototype.handlePointercancel;
    PressComposer.prototype.handleMSPointerMove = PressComposer.prototype._handleMove;
    PressComposer.prototype.handlePointermove = PressComposer.prototype._handleMove;
    PressComposer.prototype.handleTouchmove = PressComposer.prototype._handleMove;
    PressComposer.prototype.handleMousemove = PressComposer.prototype._handleMove;
    PressComposer.prototype.handleMousewheel = PressComposer.prototype.handleWheel;
    PressComposer.prototype.handleMSPointerDown = PressComposer.prototype._handlePointerDown;
    PressComposer.prototype.handlePointerdown = PressComposer.prototype._handlePointerDown;
    PressComposer.prototype.handleMousedown = PressComposer.prototype._handlePointerDown;
    PressComposer.prototype.handleTouchstart = PressComposer.prototype._handlePointerDown;