import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import IDUtil from "../../../../util/IDUtil";
import { getEventPath } from "../../../../util/EventPath";
import { MSAnnotationUtil } from "../../AnnotationClient";

const LEFT = "left";
const RIGHT = "right";
const BOTH = "both";

// A wrapper for editable section
class SectionEdit extends React.PureComponent {
    constructor(props) {
        super(props);
        this.direction = LEFT;
        this.lastX = 0;

        this.lastUserLayer = null;
        this.originalLayer = null;

        this.selection = null;
        this.dragging = false;

        this.state = {
            dragging: "",
        };

        this.ref = React.createRef();
    }

    componentWillUnmount() {
        this.stopDrag();
    }

    /**
     * Dragging section
     */
    startDragSection = (e) => {
        // shift key + left mouse button
        if (e.shiftKey && e.button == 0) {
            e.stopPropagation();
            this.direction = BOTH;
            this.startDrag(e.pageX, e.pageY);
        }
    };

    /**
     * Dragging timing left/right paddles to modify start/end position
     */

    startDragLeft = (e) => {
        if (!e.button == 0) {
            return;
        }
        e.stopPropagation();
        this.direction = e.shiftKey ? BOTH : LEFT;
        this.startDrag(e.pageX, e.pageY);
    };

    startDragRight = (e) => {
        if (!e.button == 0) {
            return;
        }
        e.stopPropagation();
        this.direction = e.shiftKey ? BOTH : RIGHT;
        this.startDrag(e.pageX, e.pageY);
    };

    loadSelection() {
        // get selection
        this.selection = MSAnnotationUtil.extractSelectionFromTarget(
            this.props.segment.target
        );

        if (!this.selection) {
            console.error(
                "Could not find selection for target",
                this.props.segment.target
            );
        }
    }

    onWindowLeave = (e) => {
        if (e.toElement == null && e.relatedTarget == null) {
            this.stopDrag(null);
        }
    };

    startDrag = (x, y) => {
        if (this.dragging) {
            return;
        }
        this.dragging = true;
        this.lastX = x;
        this.setState({ dragging: this.direction });

        this.props.annotationClient.setActiveAnnotation(this.props.segment);
        this.loadSelection();

        // for dragging between layers
        this.originalLayer =
            this.ref.current && this.ref.current.parentNode
                ? this.ref.current.parentNode.parentNode
                : null;
        this.lastUserLayer = this.originalLayer;

        document.addEventListener("mousemove", this.onDrag);
        document.addEventListener("mouseup", this.stopDrag);
        document.addEventListener("mouseleave", this.onWindowLeave);
    };

    stopDrag = (e) => {
        if (!this.dragging) {
            return;
        }
        this.dragging = false;

        //this.direction = "";
        this.setState({ dragging: "" });
        document.removeEventListener("mousemove", this.onDrag);
        document.removeEventListener("mouseup", this.stopDrag);
        document.removeEventListener("mouseleave", this.onWindowLeave);

        // Update section layer if the last hovered user layer differs from current user layer
        // The layer id is obtained directly from the layer attribute of the last user layer DOM element
        if (
            this.ref.current &&
            this.lastUserLayer &&
            this.lastUserLayer != this.ref.current.parentNode &&
            this.lastUserLayer.attributes.layer
        ) {
            this.props.segment.target.layerId = parseInt(
                this.lastUserLayer.attributes.layer.value
            );
            this.props.selectTimelineLayer(this.props.segment.target.layerId);
        }

        // Save changes
        this.props.updateSegment(this.props.segment, this.selection);
    };

    onDrag = (e) => {
        e.stopPropagation();
        const dx = (e.pageX - this.lastX) / this.props.getPixelsPerSecond();

        const movePlayPos = e.ctrlKey || e.metaKey;
        switch (this.direction) {
            case BOTH:
                // use getEventPath to make it compatible to Chrome's e.path property
                const path = getEventPath(e);

                // Move segment between layers of type .tl-layer-header.user-segment
                // Here we directly manipulate the DOM, to show layer shift, but
                // prevent making changes to the data, which would result in the drag action to
                // be halted.
                if (this.ref.current && path) {
                    // check if we are hovering another user layer
                    const userLayer = path.find(
                        (p) =>
                            p.classList &&
                            p.classList.contains("bg__tl-layer") &&
                            p.classList.contains("user-segment")
                    );

                    // call may not be triggered by current ref
                    const isCurrentRef = path.some(
                        (p) => p == this.ref.current
                    );

                    // we hit a new user layer, so change section layer
                    if (
                        !isCurrentRef &&
                        userLayer &&
                        this.lastUserLayer !== userLayer
                    ) {
                        // snap newTop to new layer
                        const newTop =
                            userLayer.offsetTop - this.originalLayer.offsetTop;

                        this.ref.current.parentNode.style.marginTop =
                            newTop + "px";

                        this.lastUserLayer = userLayer;

                        // update active timeline layer to indicate drop target
                        userLayer.attributes.layer &&
                            this.props.selectTimelineLayer(
                                parseInt(userLayer.attributes.layer.value)
                            );
                    }
                }

                // move selection
                this.selection.start = this.props.withinRange(
                    this.selection.start + dx
                );
                this.selection.end = this.props.withinRange(
                    this.selection.end + dx
                );

                // Optionally, move the player position
                movePlayPos && this.props.updatePlayerPos(this.selection.start);
                break;
            case LEFT:
                this.selection.start = this.props.withinRange(
                    e.shiftKey
                        ? this.selection.start + dx
                        : this.snapToSegments(
                              this.selection.start + dx,
                              this.props.segment.target.layerId
                          )
                );

                // Prevent negative selection duration
                if (this.selection.start > this.selection.end) {
                    const end = this.selection.end;
                    this.selection.end = this.selection.start;
                    this.selection.start = end;
                    this.direction = RIGHT;
                    this.setState({ dragging: this.direction });
                }

                // Optionally, move the player position
                movePlayPos && this.props.updatePlayerPos(this.selection.start);
                break;
            case RIGHT:
                this.selection.end = this.props.withinRange(
                    e.shiftKey
                        ? this.selection.end + dx
                        : this.snapToSegments(
                              this.selection.end + dx,
                              this.props.segment.target.layerId
                          )
                );
                // Prevent negative selections duration
                if (this.selection.start > this.selection.end) {
                    const start = this.selection.start;
                    this.selection.start = this.selection.end;
                    this.selection.end = start;
                    this.direction = LEFT;
                    this.setState({ dragging: this.direction });
                }

                // Optionally, move the player position
                movePlayPos && this.props.updatePlayerPos(this.selection.end);
                break;
        }
        this.lastX = e.pageX;

        // Directly set the new values to the current selector object of the segment
        // Final values will be saved to the annotation API when the mouse is released
        this.props.segment.target.selector.refinedBy.start = this.selection.start;
        this.props.segment.target.selector.refinedBy.end = this.selection.end;

        // Force an update of the timeline layer
        this.props.updateAnnotationLayer(
            this.props.segment.target.layerId || 0
        );
    };

    snapToSegments(x, layerId = null) {
        return this.props.snapToSegments(
            x,
            null, // no segments, so snap to all
            this.props.segment,
            layerId
        );
    }

    render() {
        const { annotationClient, segment } = this.props;
        return (
            <div
                ref={this.ref}
                className={classNames(
                    IDUtil.cssClassName("tl-section-edit"),
                    {
                        dragging: this.state.dragging,
                        active:
                            annotationClient.activeAnnotation &&
                            annotationClient.activeAnnotation.id == segment.id,
                    },
                    this.state.dragging ? "dragging-" + this.state.dragging : ""
                )}
                onMouseDown={this.startDragSection}
            >
                <div className="left" onMouseDown={this.startDragLeft} />
                {this.props.children}
                <div className="right" onMouseDown={this.startDragRight} />
            </div>
        );
    }
}

SectionEdit.propTypes = {
    getPixelsPerSecond: PropTypes.func.isRequired,
    updateAnnotationLayer: PropTypes.func.isRequired,
    updatePlayerPos: PropTypes.func.isRequired,
    updateSegment: PropTypes.func.isRequired,
    snapToSegments: PropTypes.func.isRequired,
    withinRange: PropTypes.func.isRequired,
    selectTimelineLayer: PropTypes.func.isRequired,
    mediaObject: PropTypes.object.isRequired,
    segment: PropTypes.object.isRequired,
    annotationClient: PropTypes.object.isRequired,
};

export default SectionEdit;
