import H from '../parts/Globals.js';
import '../parts/Utilities.js';

/**
 * It provides methods for:
 * - adding and handling DOM events and a drag event,
 * - mapping a mouse move event to the distance between two following events.
 *   The units of the distance are specific to a transformation,
 *   e.g. for rotation they are radians, for scaling they are scale factors.
 *
 * @mixin
 * @memberOf Annotation
 */
var eventEmitterMixin = {
    /**
     * Add emitter events.
     */
    addEvents: function () {
        var emitter = this;

        H.addEvent(
            emitter.graphic.element,
            'mousedown',
            function (e) {
                emitter.onMouseDown(e);
            }
        );

        H.objectEach(emitter.options.events, function (event, type) {
            var eventHandler = function (e) {
                if (type !== 'click' || !emitter.cancelClick) {
                    event.call(
                        emitter,
                        emitter.chart.pointer.normalize(e),
                        emitter.target
                    );
                }
            };

            if (type !== 'drag') {
                emitter.graphic.on(type, eventHandler);
            } else {
                H.addEvent(emitter, 'drag', eventHandler);
            }
        });

        if (emitter.options.draggable) {
            H.addEvent(emitter, 'drag', emitter.onDrag);

            if (!emitter.graphic.renderer.styledMode) {
                emitter.graphic.css({
                    cursor: {
                        x: 'ew-resize',
                        y: 'ns-resize',
                        xy: 'move'
                    }[emitter.options.draggable]
                });
            }
        }
    },

    /**
     * Remove emitter document events.
     */
    removeDocEvents: function () {
        if (this.removeDrag) {
            this.removeDrag = this.removeDrag();
        }

        if (this.removeMouseUp) {
            this.removeMouseUp = this.removeMouseUp();
        }
    },

    /**
     * Mouse down handler.
     *
     * @param {Object} e event
     */
    onMouseDown: function (e) {
        var emitter = this,
            pointer = emitter.chart.pointer,
            prevChartX,
            prevChartY;

        // On right click, do nothing:
        if (e.button === 2) {
            return;
        }

        e.stopPropagation();

        e = pointer.normalize(e);
        prevChartX = e.chartX;
        prevChartY = e.chartY;

        emitter.cancelClick = false;

        emitter.removeDrag = H.addEvent(
            H.doc,
            'mousemove',
            function (e) {
                emitter.hasDragged = true;

                e = pointer.normalize(e);
                e.prevChartX = prevChartX;
                e.prevChartY = prevChartY;

                H.fireEvent(emitter, 'drag', e);

                prevChartX = e.chartX;
                prevChartY = e.chartY;
            }
        );

        emitter.removeMouseUp = H.addEvent(
            H.doc,
            'mouseup',
            function (e) {
                emitter.cancelClick = emitter.hasDragged;
                emitter.hasDragged = false;

                emitter.onMouseUp(e);
            }
        );
    },

    /**
     * Mouse up handler.
     *
     * @param {Object} e event
     */
    onMouseUp: function () {
        this.removeDocEvents();
    },

    /**
     * Drag and drop event. All basic annotations should share this
     * capability as well as the extended ones.
     *
     * @param {Object} e event
     */
    onDrag: function (e) {
        if (
            this.chart.isInsidePlot(
                e.chartX - this.chart.plotLeft,
                e.chartY - this.chart.plotTop
            )
        ) {
            var translation = this.mouseMoveToTranslation(e);

            if (this.options.draggable === 'x') {
                translation.y = 0;
            }

            if (this.options.draggable === 'y') {
                translation.x = 0;
            }

            if (this.points.length) {
                this.translate(translation.x, translation.y);
            } else {
                this.shapes.forEach(function (shape) {
                    shape.translate(translation.x, translation.y);
                });
                this.labels.forEach(function (label) {
                    label.translate(translation.x, translation.y);
                });
            }

            this.redraw(false);
        }
    },

    /**
     * Map mouse move event to the radians.
     *
     * @param {Object} e event
     * @param {number} cx center x
     * @param {number} cy center y
     */
    mouseMoveToRadians: function (e, cx, cy) {
        var prevDy = e.prevChartY - cy,
            prevDx = e.prevChartX - cx,
            dy = e.chartY - cy,
            dx = e.chartX - cx,
            temp;

        if (this.chart.inverted) {
            temp = prevDx;
            prevDx = prevDy;
            prevDy = temp;

            temp = dx;
            dx = dy;
            dy = temp;
        }

        return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
    },

    /**
     * Map mouse move event to the distance between two following events.
     *
     * @param {Object} e event
     */
    mouseMoveToTranslation: function (e) {
        var dx = e.chartX - e.prevChartX,
            dy = e.chartY - e.prevChartY,
            temp;

        if (this.chart.inverted) {
            temp = dy;
            dy = dx;
            dx = temp;
        }

        return {
            x: dx,
            y: dy
        };
    },

    /**
     * Map mouse move to the scale factors.
     *
     * @param {Object} e event
     * @param {number} cx center x
     * @param {number} cy center y
     **/
    mouseMoveToScale: function (e, cx, cy) {
        var prevDx = e.prevChartX - cx,
            prevDy = e.prevChartY - cy,
            dx = e.chartX - cx,
            dy = e.chartY - cy,
            sx = (dx || 1) / (prevDx || 1),
            sy = (dy || 1) / (prevDy || 1),
            temp;

        if (this.chart.inverted) {
            temp = sy;
            sy = sx;
            sx = temp;
        }

        return {
            x: sx,
            y: sy
        };
    },

    /**
     * Destroy the event emitter.
     */
    destroy: function () {
        this.removeDocEvents();

        H.removeEvent(this);

        this.hcEvents = null;
    }
};

export default eventEmitterMixin;
