import React = require("react");
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
import { SnappingManager } from "../util/SnappingManager";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
import { Colors } from "./global/globalEnums";
import { InkControlPtHandles } from "./InkControlPtHandles";
import "./InkStroke.scss";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { InkTangentHandles } from "./InkTangentHandles";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import Color = require("color");

type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);

@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
    static readonly MaskDim = 50000;
    public static IsClosed(inkData: InkData) {
        return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
    }
    @observable private _properties?: InkStrokeProperties;
    _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
    _selDisposer: IReactionDisposer | undefined;

    @observable _nearestT: number | undefined;
    @observable _nearestSeg: number | undefined;
    @observable _nearestScrPt: { X: number, Y: number } | undefined;
    @observable _inkSamplePts: { X: number, Y: number }[] | undefined;

    constructor(props: FieldViewProps & InkDocument) {
        super(props);

        this._properties = InkStrokeProperties.Instance;
    }

    componentDidMount() {
        this.props.setContentView?.(this);
        this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
            selected => !selected && this.toggleControlButton());
    }
    componentWillUnmount() {
        this._selDisposer?.();
    }

    analyzeStrokes() {
        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
    }

    public static toggleMask = action((inkDoc: Doc) => {
        inkDoc.isInkMask = !inkDoc.isInkMask;
        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
        inkDoc.color = "#9b9b9bff";
        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
    });
    /**
     * Handles the movement of the entire ink object when the user clicks and drags.
     */
    onPointerDown = (e: React.PointerEvent) => {
        this._handledClick = false;
        if (this.props.isSelected(true)) {
            setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                action((e: PointerEvent, doubleTap: boolean | undefined) => {
                    doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                    if (doubleTap && this._properties) {
                        this._properties._controlButton = true;
                        InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
                        this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                    } else if (this._properties?._controlButton) {
                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                    }
                }), this._properties?._controlButton, this._properties?._controlButton
            );
        }
    }

    /**
     * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
     */
    @action
    toggleControlButton = () => {
        if (!this.props.isSelected() && this._properties) {
            this._properties._controlButton = false;
        }
    }

    @action
    checkHighlighter = () => {
        if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) {
            // this._previousColor = ActiveInkColor();
            SetActiveInkColor("rgba(245, 230, 95, 0.75)");
        }
    }

    inkScaledData = () => {
        const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
        const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
        const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
        const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
        const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2;
        const inkRight = Math.max(...inkData.map(p => p.X)) + inkStrokeWidth / 2;
        const inkWidth = Math.max(1, inkRight - inkLeft);
        const inkHeight = Math.max(1, inkBottom - inkTop);
        return {
            inkData,
            inkStrokeWidth,
            inkTop,
            inkLeft,
            inkWidth,
            inkHeight,
            inkScaleX: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1),
            inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1)
        };
    }

    @action
    onPointerMove = (e: React.PointerEvent) => {
        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
        const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });

        this._nearestT = nearestT;
        this._nearestSeg = nearestSeg;
        this._nearestScrPt = nearestPt;
    }


    nearestScreenPt = () => this._nearestScrPt;
    componentUI = (boundsLeft: number, boundsTop: number) => {
        const inkDoc = this.props.Document;
        const screenSpaceCenterlineStrokeWidth = 3;  // the width of the blue line widget that shows the centerline of the ink stroke
        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();

        const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
        const screenHdlPts = screenPts;

        const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
        const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
        return SnappingManager.GetIsDragging() ? (null) : <div className="inkstroke-UI" style={{
            clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)`
        }} >
            {!this._properties?._controlButton ? (null) :
                <>
                    {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
                        StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
                        "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
                    <InkControlPtHandles
                        inkView={this.props.docViewPath().lastElement()}
                        inkDoc={inkDoc}
                        inkCtrlPoints={inkData}
                        screenCtrlPoints={screenHdlPts}
                        nearestScreenPt={this.nearestScreenPt}
                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                        ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                    <InkTangentHandles
                        inkView={this.props.docViewPath().lastElement()}
                        inkDoc={inkDoc}
                        screenCtrlPoints={screenHdlPts}
                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                        ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                </>}
        </div>;
    }

    render() {
        TraceMobx();
        const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();

        const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
        const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
        const closed = InkingStroke.IsClosed(inkData);
        const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
        const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);

        // Visually renders the polygonal line made by the user.
        const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth,
            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
            StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
        // Thin blue line indicating that the current ink stroke is selected.
        // const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
        //     StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 1.0, false);
        // Invisible polygonal line that enables the ink to be selected by the user.
        const highlightIndex = BoolCast(this.props.Document.isLinkButton) && Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
        const highlightColor = !highlightIndex ?
            StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
            ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];

        const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
            inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
            undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, false);
        // Set of points rendered upon the ink that can be added if a user clicks on one.

        return (
            <svg className="inkStroke"
                style={{
                    pointerEvents: "none",
                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                    overflow: "visible",
                    cursor: this.props.isSelected() ? "default" : undefined
                }}
                onPointerLeave={action(e => this._nearestScrPt = undefined)}
                onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
                onPointerDown={this.onPointerDown}
                onClick={e => this._handledClick && e.stopPropagation()}
                onContextMenu={() => {
                    const cm = ContextMenu.Instance;
                    !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                    cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
                    cm?.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
                }}
            >
                {clickableLine}
                {inkLine}
            </svg>
        );
    }
}


export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); }
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
