/**
 * @param {MouseEvent|TouchEvent} evt
 * @param {HTMLElement} relativeTo
 * @returns {{x: number, y: number}}
 */
export const getPointFromPointerEvent = (evt, relativeTo) => {
    let x, y;
    const touches =
        (evt.changedTouches && evt.changedTouches.length > 0 && evt.changedTouches) || evt.touches;
    if (touches && touches.length > 0) {
        x = touches[0].clientX;
        y = touches[0].clientY;
    } else {
        x = evt.clientX;
        y = evt.clientY;
    }
    if (relativeTo) {
        const { top, left } = relativeTo.getBoundingClientRect();
        x -= left;
        y -= top;
    }
    return { x, y };
};

/**
 * Creates and stores the print of the event position for later comparison.
 * It can be used to create a position change checker function, in order to solve the problem
 * of Chrome's automatically triggered mousemove event when clicking without moving the mouse.
 * @param evt
 * @returns {function(*=): boolean}
 */
export const createPositionChangeChecker = evt => {
    const getPositionPrint = evt => '' + evt.pageX + '-' + evt.pageY;
    const initialPrint = getPositionPrint(evt);
    return evt => initialPrint !== getPositionPrint(evt);
};

/**
 * Creates a "hit" event listener.
 * It detects a single tap (or click) - if the pointer was released without moving.
 * @param element
 * @param {function} fn The event handler function
 * @returns {function()} A function to be called to remove the event listeners
 */
export const createPointerHitListener = (element, fn) => {
    let moved = false;
    let checkPositionChange = false;

    // preventing the emulated mouse events after touchend
    // see https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent
    const preventEmulatedEvents = cb => evt => {
        if (!moved && evt.type === 'touchend') {
            evt.preventDefault();
        }
        cb(evt);
    };

    const startHandler = evt => {
        moved = false;
        const isTouch = (evt.changedTouches || evt.touches || []).length > 0;
        if (isTouch) {
            // in case of touch events we can skip the check, the events are fired correctly
            checkPositionChange = false;
            element.addEventListener('touchmove', moveHandler, true);
            element.addEventListener('touchend', endHandler);
        } else {
            checkPositionChange = createPositionChangeChecker(evt);
            element.addEventListener('mousemove', moveHandler);
            element.addEventListener('mouseup', endHandler);
        }
    };
    const moveHandler = evt => {
        if (checkPositionChange) {
            moved = checkPositionChange(evt);
        } else {
            moved = true;
        }
    };
    const endHandler = preventEmulatedEvents((...args) => {
        !moved && fn(...args);
        element.removeEventListener('touchmove', moveHandler, true);
        element.removeEventListener('mousemove', moveHandler);
        element.removeEventListener('touchend', endHandler);
        element.removeEventListener('mouseup', endHandler);
    });

    element.addEventListener('mousedown', startHandler);
    element.addEventListener('touchstart', startHandler);
    return () => {
        element.removeEventListener('mousedown', startHandler);
        element.removeEventListener('touchstart', startHandler);
    };
};

/**
 * Creates a general pointer event listener (for mouse and touch events)
 * @param element
 * @param {string} name The name of the event phase ('start'|'move'|'end')
 * @param {function} fn The event handler function
 * @returns {function()} A function to be called to remove the event listeners
 */
export const createPointerEventListener = (element, name, fn) => {
    let touchName, mouseName;
    switch (name) {
        case 'start': {
            touchName = 'touchstart';
            mouseName = 'mousedown';
            break;
        }
        case 'move': {
            touchName = 'touchmove';
            mouseName = 'mousemove';
            break;
        }
        case 'end': {
            touchName = 'touchend';
            mouseName = 'mouseup';
            break;
        }
    }
    const tolerance = 1; // ms
    let timeStamp = 0;
    const touchHandler = (evt, ...args) => {
        // prevent mouse events if it's a touch event
        //evt.type.indexOf('touch') === 0 && evt.preventDefault();
        timeStamp = evt.timeStamp;
        fn(evt, ...args);
    };
    const mouseHandler = (evt, ...args) => {
        if (evt.timeStamp - timeStamp < tolerance) {
            // eslint-disable-next-line no-console
            console.log('Skipped double event handling.');
            return;
        }
        fn(evt, ...args);
    };
    element.addEventListener(touchName, touchHandler);
    element.addEventListener(mouseName, mouseHandler);
    return () => {
        element.removeEventListener(touchName, touchHandler);
        element.removeEventListener(mouseName, mouseHandler);
    };
};
