// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

import { numberArrayToPoints, pointsToNumberArray, Point } from '../math';

export interface IntelligentScissorsParams {
    shape: {
        shapeType: 'polygon' | 'polyline';
    };
    canvas: {
        shapeType: 'points';
        enableThreshold: boolean;
        enableSliding: boolean;
        allowRemoveOnlyLast: boolean;
        minPosVertices: number;
        onChangeToolsBlockerState: (event:string)=>void;
    };
}

export interface IntelligentScissors {
    reset(): void;
    run(points: number[], image: ImageData, offsetX: number, offsetY: number): number[];
    params: IntelligentScissorsParams;
    switchBlockMode(mode?:boolean):void;
}

function applyOffset(points: Point[], offsetX: number, offsetY: number): Point[] {
    return points.map(
        (point: Point): Point => ({
            x: point.x - offsetX,
            y: point.y - offsetY,
        }),
    );
}

export default class IntelligentScissorsImplementation implements IntelligentScissors {
    private cv: any;
    private onChangeToolsBlockerState: (event:string)=>void;
    private scissors: {
        tool: any;
        state: {
            path: number[];
            anchors: Record<
            number,
            {
                point: Point;
                start: number;
            }
            >; // point index : start index in path
            image: any | null;
            blocked: boolean;
        };
    };

    public constructor(cv: any, onChangeToolsBlockerState:(event:string)=>void) {
        this.cv = cv;
        this.onChangeToolsBlockerState = onChangeToolsBlockerState;
        this.reset();
    }

    public switchBlockMode(mode:boolean): void {
        this.scissors.state.blocked = mode;
    }

    public reset(): void {
        if (this.scissors && this.scissors.tool) {
            this.scissors.tool.delete();
        }

        this.scissors = {
            // eslint-disable-next-line new-cap
            tool: new this.cv.segmentation_IntelligentScissorsMB(),
            state: {
                path: [],
                anchors: {},
                image: null,
                blocked: false,
            },
        };

        this.scissors.tool.setEdgeFeatureCannyParameters(32, 100);
        this.scissors.tool.setGradientMagnitudeMaxLimit(200);
    }

    public run(coordinates: number[], image: ImageData, offsetX: number, offsetY: number): number[] {
        if (!Array.isArray(coordinates)) {
            throw new Error('Coordinates is expected to be an array');
        }
        if (!coordinates.length) {
            throw new Error('At least one point is expected');
        }
        if (!(image instanceof ImageData)) {
            throw new Error('Image is expected to be an instance of ImageData');
        }

        const { cv, scissors } = this;
        const { tool, state } = scissors;

        const points = applyOffset(numberArrayToPoints(coordinates), offsetX, offsetY);
        if (points.length > 1) {
            let matImage = null;
            const contour = new cv.Mat();

            try {
                const [prev, cur] = points.slice(-2);
                const { x: prevX, y: prevY } = prev;
                const { x: curX, y: curY } = cur;

                const latestPointRemoved = points.length < Object.keys(state.anchors).length;
                const latestPointReplaced = points.length === Object.keys(state.anchors).length;

                if (latestPointRemoved) {
                    for (const i of Object.keys(state.anchors).sort((a, b) => +b - +a)) {
                        if (+i >= points.length) {
                            state.path = state.path.slice(0, state.anchors[points.length].start);
                            delete state.anchors[+i];
                        }
                    }
                    return [...state.path];
                }

                matImage = cv.matFromImageData(image);

                if (latestPointReplaced) {
                    state.path = state.path.slice(0, state.anchors[points.length - 1].start);
                    delete state.anchors[points.length - 1];
                }
                const pathSegment = [];
                if (!state.blocked) {
                    tool.applyImage(matImage);
                    tool.buildMap(new cv.Point(prevX, prevY));
                    tool.getContour(new cv.Point(curX, curY), contour);

                    for (let row = 0; row < contour.rows; row++) {
                        pathSegment.push(contour.intAt(row, 0) + offsetX, contour.intAt(row, 1) + offsetY);
                    }
                } else {
                    pathSegment.push(curX + offsetX, curY + offsetY);
                }
                state.anchors[points.length - 1] = {
                    point: cur,
                    start: state.path.length,
                };
                state.path.push(...pathSegment);
            } finally {
                if (matImage) {
                    matImage.delete();
                }

                contour.delete();
            }
        } else {
            state.path = [];
            state.path.push(...pointsToNumberArray(applyOffset(points.slice(-1), -offsetX, -offsetY)));
            state.anchors[0] = {
                point: points[0],
                start: 0,
            };
        }
        return [...state.path];
    }

    // eslint-disable-next-line class-methods-use-this
    public get type(): string {
        return 'opencv_intelligent_scissors';
    }

    // eslint-disable-next-line class-methods-use-this
    public get params(): IntelligentScissorsParams {
        return {
            shape: {
                shapeType: 'polygon',
            },
            canvas: {
                shapeType: 'points',
                enableThreshold: true,
                enableSliding: true,
                allowRemoveOnlyLast: true,
                minPosVertices: 1,
                onChangeToolsBlockerState: this.onChangeToolsBlockerState,
            },
        };
    }
}
