import React = require('react');
import { action } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { HandleLine, HandlePoint, InkData } from '../../fields/InkField';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
import { emptyFunction, setupMoveUpEvents } from '../../Utils';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
export interface InkHandlesProps {
    inkDoc: Doc;
    inkView: InkingStroke;
    screenCtrlPoints: InkData;
    screenSpaceLineWidth: number;
    ScreenToLocalTransform: () => Transform;
}

@observer
export class InkTangentHandles extends React.Component<InkHandlesProps> {
    get docView() {
        return this.props.inkView.props.docViewPath().lastElement();
    }
    /**
     * Handles the movement of a selected handle point when the user clicks and drags.
     * @param handleNum The index of the currently selected handle point.
     */
    onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
        const order = handleIndex % 4;
        const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
        const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
        const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
        setupMoveUpEvents(
            this,
            e,
            action((e: PointerEvent, down: number[], delta: number[]) => {
                if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent');
                if (e.altKey) this.onBreakTangent(controlIndex);
                const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
                const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
                InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
                return false;
            }),
            action(() => {
                this.props.inkView.controlUndo?.end();
                this.props.inkView.controlUndo = undefined;
                UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
            }),
            emptyFunction
        );
    };

    /**
     * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and
     * its matching (opposite) handle to a list of broken handle indices.
     * @param handleNum The index of the currently selected handle point.
     */
    @action
    onBreakTangent = (controlIndex: number) => {
        const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints);
        const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
        if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
            if (brokenIndices) brokenIndices.push(controlIndex);
            else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
        }
    };

    render() {
        // Accessing the current ink's data and extracting all handle points and handle lines.
        const data = this.props.screenCtrlPoints;
        const tangentHandles: HandlePoint[] = [];
        const tangentLines: HandleLine[] = [];
        const closed = InkingStroke.IsClosed(data);
        if (data.length >= 4) {
            for (let i = 0; i <= data.length - 4; i += 4) {
                tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
                tangentHandles.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
            }
            // Adding first and last (single) handle lines.
            if (closed) {
                tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
            } else {
                tangentLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
                tangentLines.push({
                    X1: data[data.length - 2].X,
                    Y1: data[data.length - 2].Y,
                    X2: data[data.length - 1].X,
                    Y2: data[data.length - 1].Y,
                    X3: data[data.length - 1].X,
                    Y3: data[data.length - 1].Y,
                    dot1: data.length - 1,
                    dot2: data.length - 1,
                });
            }
            for (let i = 2; i < data.length - 4; i += 4) {
                tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
            }
        }
        const screenSpaceLineWidth = this.props.screenSpaceLineWidth;

        return (
            <>
                {tangentHandles.map((pts, i) => (
                    <svg height="10" width="10" key={`hdl${i}`}>
                        <circle
                            cx={pts.X}
                            cy={pts.Y}
                            r={screenSpaceLineWidth * 2}
                            fill={Colors.MEDIUM_BLUE}
                            strokeWidth={1}
                            stroke={Colors.BLACK}
                            onPointerDown={e => this.onHandleDown(e, pts.I)}
                            pointerEvents="all"
                            cursor="default"
                            display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
                        />
                    </svg>
                ))}
                {tangentLines.map((pts, i) => {
                    const tangentLine = (x1: number, y1: number, x2: number, y2: number) => (
                        <line
                            x1={x1}
                            y1={y1}
                            x2={x2}
                            y2={y2}
                            stroke={Colors.MEDIUM_BLUE}
                            strokeDasharray={'1 1'}
                            strokeWidth={1}
                            display={pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint ? 'inherit' : 'none'}
                        />
                    );
                    return (
                        <svg height="100" width="100" key={`line${i}`}>
                            {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
                            {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
                        </svg>
                    );
                })}
            </>
        );
    }
}
