import React = require("react");
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { ControlPoint, InkData, PointData } from "../../fields/InkField";
import { listSpec } from "../../fields/Schema";
import { Cast } from "../../fields/Types";
import { setupMoveUpEvents, Utils } from "../../Utils";
import { Transform } from "../util/Transform";
import { UndoManager } from "../util/UndoManager";
import { Colors } from "./global/globalEnums";
import { InkStrokeProperties } from "./InkStrokeProperties";

export interface InkControlProps {
    inkDoc: Doc;
    inkCtrlPoints: InkData;
    screenCtrlPoints: InkData;
    format: number[];
    ScreenToLocalTransform: () => Transform;
    nearestScreenPt: () => PointData | undefined;
}

@observer
export class InkControls extends React.Component<InkControlProps> {

    @observable private _overControl = -1;
    @observable private _overAddPoint = -1;
    /**
     * Handles the movement of a selected control point when the user clicks and drags.
     * @param controlIndex The index of the currently selected control point.
     */
    @action
    onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
        if (InkStrokeProperties.Instance) {
            InkStrokeProperties.Instance.moveControl(0, 0, 1);
            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
            const screenScale = this.props.ScreenToLocalTransform().Scale;
            const order = controlIndex % 4;
            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
            setupMoveUpEvents(this, e,
                (e: PointerEvent, down: number[], delta: number[]) => {
                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
                    return false;
                },
                () => controlUndo?.end(),
                action((e: PointerEvent, doubleTap: boolean | undefined) => {
                    const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
                    if (doubleTap && brokenIndices?.includes(equivIndex)) {
                        InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
                    }
                    if (doubleTap && brokenIndices?.includes(controlIndex)) {
                        InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
                    }
                }));
        }
    }
    /**
     * Updates whether a user has hovered over a particular control point or point that could be added
     * on click.
     */
    @action onEnterControl = (i: number) => { this._overControl = i; };
    @action onLeaveControl = () => { this._overControl = -1; };
    @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
    @action onLeaveAddPoint = () => { this._overAddPoint = -1; };

    /**
     * Deletes the currently selected point.
     */
    @action
    onDelete = (e: KeyboardEvent) => {
        if (["-", "Backspace", "Delete"].includes(e.key)) {
            if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
        }
    }

    /**
     * Changes the current selected control point.
     */
    @action
    changeCurrPoint = (i: number) => {
        if (InkStrokeProperties.Instance) {
            InkStrokeProperties.Instance._currentPoint = i;
            document.addEventListener("keydown", this.onDelete, true);
        }
    }

    render() {
        const formatInstance = InkStrokeProperties.Instance;
        if (!formatInstance) return (null);


        // Accessing the current ink's data and extracting all control points.
        const scrData = this.props.screenCtrlPoints;
        const sreenCtrlPoints: ControlPoint[] = [];
        for (let i = 0; i <= scrData.length - 4; i += 4) {
            sreenCtrlPoints.push({ X: scrData[i].X, Y: scrData[i].Y, I: i });
            sreenCtrlPoints.push({ X: scrData[i + 3].X, Y: scrData[i + 3].Y, I: i + 3 });
        }

        const inkData = this.props.inkCtrlPoints;
        const inkCtrlPts: ControlPoint[] = [];
        for (let i = 0; i <= inkData.length - 4; i += 4) {
            inkCtrlPts.push({ X: inkData[i].X, Y: inkData[i].Y, I: i });
            inkCtrlPts.push({ X: inkData[i + 3].X, Y: inkData[i + 3].Y, I: i + 3 });
        }

        const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format;
        const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;

        const nearestScreenPt = this.props.nearestScreenPt();
        return (<svg>
            {/*   should really have just one circle here that represents the neqraest point on the stroke to the users hover point.
                   This points should be passed as a prop from InkingStroke's UI which should set it in its onPointerOver method  */}
            {!nearestScreenPt ? (null) :
                <circle key={"npt"}
                    cx={(nearestScreenPt.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                    cy={(nearestScreenPt.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                    r={screenSpaceLineWidth * 4}
                    fill={"#00007777"}
                    stroke={"#00007777"}
                    strokeWidth={0}
                    pointerEvents="none"
                    cursor="all-scroll"
                />
            }
            {sreenCtrlPoints.map((control, i) =>
                <rect key={i}
                    x={(control.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2 - rectHdlSize(i) / 2}
                    y={(control.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2 - rectHdlSize(i) / 2}
                    height={rectHdlSize(i)}
                    width={rectHdlSize(i)}
                    strokeWidth={screenSpaceLineWidth / 2}
                    stroke={Colors.MEDIUM_BLUE}
                    fill={formatInstance?._currentPoint === control.I ? Colors.MEDIUM_BLUE : Colors.WHITE}
                    onPointerDown={(e) => {
                        this.changeCurrPoint(control.I);
                        this.onControlDown(e, control.I);
                    }}
                    onMouseEnter={() => this.onEnterControl(i)}
                    onMouseLeave={this.onLeaveControl}
                    pointerEvents="all"
                    cursor="default"
                />
            )}
        </svg>
        );
    }
}