/*!
 * jQuery UI Touch Punch 1.0.9 as modified by RWAP Software
 * based on original touchpunch v0.2.3 which has not been updated since 2014
 *
 * Updates by RWAP Software to take account of various suggested changes on the original code issues
 *
 * Original: https://github.com/furf/jquery-ui-touch-punch
 * Copyright 2011–2014, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Fork: https://github.com/RWAP/jquery-ui-touch-punch
 *
 * Depends:
 * jquery.ui.widget.js
 * jquery.ui.mouse.js
 */

(function( factory ) {
    if ( typeof define === "function" && define.amd ) {

        // AMD. Register as an anonymous module.
        define([ "jquery", "jquery-ui" ], factory );
    } else {

        // Browser globals
        factory( jQuery );
    }
}(function ($) {

    // Detect touch support - Windows Surface devices and other touch devices
    $.mspointer = window.navigator.msPointerEnabled;
    $.touch = ( 'ontouchstart' in document
        || 'ontouchstart' in window
        || window.TouchEvent
        || (window.DocumentTouch && document instanceof DocumentTouch)
        || navigator.maxTouchPoints > 0
        || navigator.msMaxTouchPoints > 0
    );

    // Ignore browsers without touch or mouse support
    if ((!$.touch && !$.mspointer) || !$.ui.mouse) {
        return;
    }

    let mouseProto = $.ui.mouse.prototype,
        _mouseInit = mouseProto._mouseInit,
        _mouseDestroy = mouseProto._mouseDestroy,
        touchHandled;

    /**
     * Get the x,y position of a touch event
     * @param {Object} event A touch event
     */
    function getTouchCoords (event) {
        return {
            x: event.originalEvent.changedTouches[0].pageX,
            y: event.originalEvent.changedTouches[0].pageY
        };
    }

    /**
     * Simulate a mouse event based on a corresponding touch event
     * @param {Object} event A touch event
     * @param {String} simulatedType The corresponding mouse event
     */
    function simulateMouseEvent (event, simulatedType) {

        // Ignore multi-touch events
        if (event.originalEvent.touches.length > 1) {
            return;
        }

        //Ignore input or textarea elements so user can still enter text
        if ($(event.target).is("input") || $(event.target).is("textarea")) {
            return;
        }

        // Prevent "Ignored attempt to cancel a touchmove event with cancelable=false" errors
        if (event.cancelable) {
            event.preventDefault();
        }

        let touch = event.originalEvent.changedTouches[0],
            simulatedEvent = document.createEvent('MouseEvents');

        // Initialize the simulated mouse event using the touch event's coordinates
        simulatedEvent.initMouseEvent(
            simulatedType,    // type
            true,             // bubbles
            true,             // cancelable
            window,           // view
            1,                // detail
            touch.screenX,    // screenX
            touch.screenY,    // screenY
            touch.clientX,    // clientX
            touch.clientY,    // clientY
            false,            // ctrlKey
            false,            // altKey
            false,            // shiftKey
            false,            // metaKey
            0,                // button
            null              // relatedTarget
        );

        // Dispatch the simulated event to the target element
        event.target.dispatchEvent(simulatedEvent);
    }

    /**
     * Handle the jQuery UI widget's touchstart events
     * @param {Object} event The widget element's touchstart event
     */
    mouseProto._touchStart = function (event) {

        let self = this;

        // Interaction time
        this._startedMove = event.timeStamp;

        // Track movement to determine if interaction was a click
        self._startPos = getTouchCoords(event);

        // Ignore the event if another widget is already being handled
        if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
            return;
        }

        // Set the flag to prevent other widgets from inheriting the touch event
        touchHandled = true;

        // Track movement to determine if interaction was a click
        self._touchMoved = false;

        // Simulate the mouseover event
        simulateMouseEvent(event, 'mouseover');

        // Simulate the mousemove event
        simulateMouseEvent(event, 'mousemove');

        // Simulate the mousedown event
        simulateMouseEvent(event, 'mousedown');
    };

    /**
     * Handle the jQuery UI widget's touchmove events
     * @param {Object} event The document's touchmove event
     */
    mouseProto._touchMove = function (event) {

        // Ignore event if not handled
        if (!touchHandled) {
            return;
        }

        // Interaction was moved
        this._touchMoved = true;

        // Simulate the mousemove event
        simulateMouseEvent(event, 'mousemove');
    };

    /**
     * Handle the jQuery UI widget's touchend events
     * @param {Object} event The document's touchend event
     */
    mouseProto._touchEnd = function (event) {

        // Ignore event if not handled
        if (!touchHandled) {
            return;
        }

        // Simulate the mouseup event
        simulateMouseEvent(event, 'mouseup');

        // Simulate the mouseout event
        simulateMouseEvent(event, 'mouseout');

        // If the touch interaction did not move, it should trigger a click
        // Check for this in two ways - length of time of simulation and distance moved
        // Allow for Apple Stylus to be used also
        let timeMoving = event.timeStamp - this._startedMove;
        if (!this._touchMoved || timeMoving < 500) {
            // Simulate the click event
            simulateMouseEvent(event, 'click');
        } else {
            let endPos = getTouchCoords(event);
            if ((Math.abs(endPos.x - this._startPos.x) < 10) && (Math.abs(endPos.y - this._startPos.y) < 10)) {

                // If the touch interaction did not move, it should trigger a click
                if (!this._touchMoved || event.originalEvent.changedTouches[0].touchType === 'stylus') {
                    // Simulate the click event
                    simulateMouseEvent(event, 'click');
                }
            }
        }

        // Unset the flag to determine the touch movement stopped
        this._touchMoved = false;

        // Unset the flag to allow other widgets to inherit the touch event
        touchHandled = false;
    };

    /**
     * A duck punch of the $.ui.mouse _mouseInit method to support touch events.
     * This method extends the widget with bound touch event handlers that
     * translate touch events to mouse events and pass them to the widget's
     * original mouse event handling methods.
     */
    mouseProto._mouseInit = function () {

        let self = this;

        // Microsoft Surface Support = remove original touch Action
        if ($.support.mspointer) {
            self.element[0].style.msTouchAction = 'none';
        }

        // Delegate the touch handlers to the widget's element
        self.element.on({
            touchstart: $.proxy(self, '_touchStart'),
            touchmove: $.proxy(self, '_touchMove'),
            touchend: $.proxy(self, '_touchEnd')
        });

        // Call the original $.ui.mouse init method
        _mouseInit.call(self);
    };

    /**
     * Remove the touch event handlers
     */
    mouseProto._mouseDestroy = function () {

        let self = this;

        // Delegate the touch handlers to the widget's element
        self.element.off({
            touchstart: $.proxy(self, '_touchStart'),
            touchmove: $.proxy(self, '_touchMove'),
            touchend: $.proxy(self, '_touchEnd')
        });

        // Call the original $.ui.mouse destroy method
        _mouseDestroy.call(self);
    };

}));