// @flow

import DOM from '../../util/dom';
import type Point from '@mapbox/point-geometry';

const LEFT_BUTTON = 0;
const RIGHT_BUTTON = 2;

// the values for each button in MouseEvent.buttons
const BUTTONS_FLAGS = {
    [LEFT_BUTTON]: 1,
    [RIGHT_BUTTON]: 2
};

function buttonStillPressed(e: MouseEvent, button: number) {
    const flag = BUTTONS_FLAGS[button];
    return e.buttons === undefined || (e.buttons & flag) !== flag;
}

class MouseHandler {

    _enabled: boolean;
    _active: boolean;
    _lastPoint: Point;
    _eventButton: number;
    _moved: boolean;
    _clickTolerance: number;

    constructor(options: { clickTolerance: number }) {
        this.reset();
        this._clickTolerance = options.clickTolerance || 1;
    }

    blur() {
        this.reset();
    }

    reset() {
        this._active = false;
        this._moved = false;
        delete this._lastPoint;
        delete this._eventButton;
    }

    _correctButton(e: MouseEvent, button: number) {  //eslint-disable-line
        return false; // implemented by child
    }

    _move(lastPoint: Point, point: Point) {  //eslint-disable-line
        return {}; // implemented by child
    }

    mousedown(e: MouseEvent, point: Point) {
        if (this._lastPoint) return;

        const eventButton = DOM.mouseButton(e);
        if (!this._correctButton(e, eventButton)) return;

        this._lastPoint = point;
        this._eventButton = eventButton;
    }

    mousemoveWindow(e: MouseEvent, point: Point) {
        const lastPoint = this._lastPoint;
        if (!lastPoint) return;
        e.preventDefault();

        if (buttonStillPressed(e, this._eventButton)) {
            // Some browsers don't fire a `mouseup` when the mouseup occurs outside
            // the window or iframe:
            // https://github.com/mapbox/mapbox-gl-js/issues/4622
            //
            // If the button is no longer pressed during this `mousemove` it may have
            // been released outside of the window or iframe.
            this.reset();
            return;
        }

        if (!this._moved && point.dist(lastPoint) < this._clickTolerance) return;
        this._moved = true;
        this._lastPoint = point;

        // implemented by child class
        return this._move(lastPoint, point);
    }

    mouseupWindow(e: MouseEvent) {
        if (!this._lastPoint) return;
        const eventButton = DOM.mouseButton(e);
        if (eventButton !== this._eventButton) return;
        if (this._moved) DOM.suppressClick();
        this.reset();
    }

    enable() {
        this._enabled = true;
    }

    disable() {
        this._enabled = false;
        this.reset();
    }

    isEnabled() {
        return this._enabled;
    }

    isActive() {
        return this._active;
    }
}

export class MousePanHandler extends MouseHandler {

    mousedown(e: MouseEvent, point: Point) {
        super.mousedown(e, point);
        if (this._lastPoint) this._active = true;
    }
    _correctButton(e: MouseEvent, button: number) {
        return button === LEFT_BUTTON && !e.ctrlKey;
    }

    _move(lastPoint: Point, point: Point) {
        return {
            around: point,
            panDelta: point.sub(lastPoint)
        };
    }
}

export class MouseRotateHandler extends MouseHandler {
    _correctButton(e: MouseEvent, button: number) {
        return (button === LEFT_BUTTON && e.ctrlKey) || (button === RIGHT_BUTTON);
    }

    _move(lastPoint: Point, point: Point) {
        const degreesPerPixelMoved = 0.8;
        const bearingDelta = (point.x - lastPoint.x) * degreesPerPixelMoved;
        if (bearingDelta) {
            this._active = true;
            return {bearingDelta};
        }
    }

    contextmenu(e: MouseEvent) {
        // prevent browser context menu when necessary; we don't allow it with rotation
        // because we can't discern rotation gesture start from contextmenu on Mac
        e.preventDefault();
    }
}

export class MousePitchHandler extends MouseHandler {
    _correctButton(e: MouseEvent, button: number) {
        return (button === LEFT_BUTTON && e.ctrlKey) || (button === RIGHT_BUTTON);
    }

    _move(lastPoint: Point, point: Point) {
        const degreesPerPixelMoved = -0.5;
        const pitchDelta = (point.y - lastPoint.y) * degreesPerPixelMoved;
        if (pitchDelta) {
            this._active = true;
            return {pitchDelta};
        }
    }

    contextmenu(e: MouseEvent) {
        // prevent browser context menu when necessary; we don't allow it with rotation
        // because we can't discern rotation gesture start from contextmenu on Mac
        e.preventDefault();
    }
}
