import React from 'react';
import PropTypes from 'prop-types';

import IDUtil from '../../../../util/IDUtil';

const MODE_MOVE = 0; // move/drag the timeline
const MODE_SET = 1; // set the current position/cursor

// Handle zoom, drag, and set cursor position all in a single box that can wrap the dynamic timeline components
class ZoomDragBox extends React.Component {
    constructor(props) {
        super(props);

        // refs
        this.ref = React.createRef();

        // default values
        this.dragCursorOffset = this.props.dragCursorOffset
            ? this.props.dragCursorOffset
            : 10;
        this.zoomSensitivity = this.props.zoomSensitivity
            ? this.props.zoomSensitivity
            : 500;
        this.doubleClickTime = this.props.doubleClickTime
            ? this.props.doubleClickTime
            : 200;
        this.doubleClickOffset = this.props.doubleClickOffset
            ? this.props.doubleClickOffset
            : 5;
        this.boundingBox = { x: 0, y: 0, width: 0, height: 0 };
    }

    componentDidMount() {
        this.ref.current.addEventListener('wheel', this.onWheel, {
            passive: false
        });
    }

    componentWillUnmount() {
        this.clearEventListeners();
        this.ref.current.removeEventListener('wheel', this.onWheel);
    }

    onMouseDown = e => {
        const leftMouseButton = e.button === 0;
        const time = new Date().getTime();
        let forceModeSet = false;

        // check for ctrlKey, which enables MODE_SET
        if (e.ctrlKey) {
            forceModeSet = true;
        }

        // check for double click, which enables MODE_SET
        if (
            leftMouseButton &&
            this.interactionVars &&
            time - this.interactionVars.lastDown < this.doubleClickTime &&
            e.pageX - this.interactionVars.downX < this.doubleClickOffset
        ) {
            forceModeSet = true;
        }

        // initialize interaction variables
        this.interactionVars = {
            downX: e.pageX,
            offsetX: 0,
            mode: forceModeSet ? MODE_SET : MODE_MOVE,
            lastDown: leftMouseButton ? time : 0
        };

        // If click is nearby the cursor, enable SET mode
        const cursorPos =
            this.props.start +
            (e.pageX - this.props.boundingBox.x) / this.props.pixelsPerSecond;
        if (
            // near cursor
            Math.abs(cursorPos - this.props.position) <
                this.dragCursorOffset / this.props.pixelsPerSecond ||
            // label region
            (this.props.position < cursorPos &&
                e.clientY - this.props.boundingBox.y < 25 &&
                cursorPos - this.props.position <
                    60 / this.props.pixelsPerSecond)
        ) {
            this.interactionVars.mode = MODE_SET;
            this.interactionVars.offsetX = cursorPos - this.props.position;
        }

        // add event listeners
        document.addEventListener('mousemove', this.onMouseMove);
        document.addEventListener('mouseup', this.onMouseUp);

        // trigger mousemove
        this.onMouseMove(e);
    };

    onMouseMove = e => {
        e.preventDefault();
        const cursorPos =
            (e.pageX - this.props.boundingBox.x) / this.props.pixelsPerSecond;

        switch (this.interactionVars.mode) {
            // set cursor
            case MODE_SET:
                // set position
                this.props.setPosition(
                    this.props.start + cursorPos - this.interactionVars.offsetX,
                    true
                );
                return;

            case MODE_MOVE:
                // drag timeline
                const posDiff =
                    (this.interactionVars.downX - e.pageX) /
                    this.props.pixelsPerSecond;
                this.interactionVars.downX = e.pageX;
                this.props.onMove(posDiff, !e.shiftKey);
                return;
            default:
                console.error('Unknown mode:', this.interactionVars.mode);
        }
    };

    onMouseUp = e => {
        this.clearEventListeners();
    };

    clearEventListeners = () => {
        document.removeEventListener('mousemove', this.onMouseMove);
        document.removeEventListener('mouseup', this.onMouseUp);
    };

    // scroll wheel
    onWheel = e => {
        const perc =
            (e.pageX - this.props.boundingBox.x) / this.props.boundingBox.width;
        this.props.onZoom(1 - e.deltaY / this.zoomSensitivity, perc);

        // prevent scrolling
        e.preventDefault();
        e.stopPropagation();
    };

    render() {
        return (
            <div
                ref={this.ref}
                className={IDUtil.cssClassName('tl-zoom-drag-box')}
                onMouseDown={this.onMouseDown}
            >
                {this.props.children}
            </div>
        );
    }
}

ZoomDragBox.propTypes = {
    start: PropTypes.number.isRequired,
    position: PropTypes.number.isRequired,

    setPosition: PropTypes.func.isRequired,
    onMove: PropTypes.func.isRequired,
    onZoom: PropTypes.func.isRequired,

    // optional
    dragCursorOffset: PropTypes.number,
    zoomSensitivity: PropTypes.number,
    doubleClickTime: PropTypes.number,
    doubleClickOffset: PropTypes.number
};

export default ZoomDragBox;
