import { vec3 } from 'gl-matrix';
import { getEnabledElement, utilities as csUtils, cache, getRenderingEngine, BaseVolumeViewport, } from '@cornerstonejs/core';
import { drawPolyline as drawPolylineSvg } from '../../drawingSvg';
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
import { Events } from '../../enums';
import triggerAnnotationRenderForViewportUIDs from '../../utilities/triggerAnnotationRenderForViewportIds';
import { growCut } from '../../utilities/segmentation';
import GrowCutBaseTool from '../base/GrowCutBaseTool';
const NEGATIVE_PIXEL_RANGE = [-Infinity, -995];
const POSITIVE_PIXEL_RANGE = [0, 1900];
const ISLAND_PIXEL_RANGE = [1000, 1900];
const { transformWorldToIndex, transformIndexToWorld } = csUtils;
class WholeBodySegmentTool extends GrowCutBaseTool {
    constructor(toolProps = {}, defaultToolProps = {
        supportedInteractionTypes: ['Mouse', 'Touch'],
        configuration: {
            isPartialVolume: true,
            positivePixelRange: POSITIVE_PIXEL_RANGE,
            negativePixelRange: NEGATIVE_PIXEL_RANGE,
            islandRemoval: {
                enabled: true,
                islandPixelRange: ISLAND_PIXEL_RANGE,
            },
        },
    }) {
        super(toolProps, defaultToolProps);
        this._dragCallback = (evt) => {
            const eventData = evt.detail;
            const { element, currentPoints } = eventData;
            const { world: currentWorldPoint } = currentPoints;
            const enabledElement = getEnabledElement(element);
            const { viewport } = enabledElement;
            const linePoints = this._getHorizontalLineWorldPoints(enabledElement, currentWorldPoint);
            this.growCutData.horizontalLines[1] = linePoints;
            triggerAnnotationRenderForViewportUIDs([viewport.id]);
        };
        this._endCallback = async (evt) => {
            const eventData = evt.detail;
            const { element } = eventData;
            const enabledElement = getEnabledElement(element);
            const { viewport } = enabledElement;
            await this.runGrowCut();
            this._deactivateDraw(element);
            this.growCutData = null;
            resetElementCursor(element);
            triggerAnnotationRenderForViewportUIDs([viewport.id]);
        };
        this._deactivateDraw = (element) => {
            element.removeEventListener(Events.MOUSE_UP, this._endCallback);
            element.removeEventListener(Events.MOUSE_DRAG, this._dragCallback);
            element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
        };
    }
    async preMouseDownCallback(evt) {
        const eventData = evt.detail;
        const { element, currentPoints } = eventData;
        const { world: worldPoint } = currentPoints;
        const enabledElement = getEnabledElement(element);
        const { viewport, renderingEngine } = enabledElement;
        const linePoints = this._getHorizontalLineWorldPoints(enabledElement, worldPoint);
        await super.preMouseDownCallback(evt);
        this.growCutData.horizontalLines = [linePoints, linePoints];
        this._activateDraw(element);
        hideElementCursor(element);
        triggerAnnotationRenderForViewportUIDs([viewport.id]);
        return true;
    }
    renderAnnotation(enabledElement, svgDrawingHelper) {
        if (!this.growCutData) {
            return;
        }
        const { segmentation: segmentationData, horizontalLines } = this.growCutData;
        if (horizontalLines.length !== 2) {
            return;
        }
        const { viewport } = enabledElement;
        const { segmentationId, segmentIndex } = segmentationData;
        const [line1, line2] = horizontalLines;
        const [worldLine1P1, worldLine1P2] = line1;
        const [worldLine2P1, worldLine2P2] = line2;
        const canvasPoints = [
            worldLine1P1,
            worldLine1P2,
            worldLine2P2,
            worldLine2P1,
        ].map((worldPoint) => viewport.worldToCanvas(worldPoint));
        const annotationUID = 'growCutRect';
        const squareGroupUID = '0';
        const { color, fillColor, lineWidth, fillOpacity, lineDash } = this.getSegmentStyle({
            segmentationId,
            segmentIndex,
            viewportId: viewport.id,
        });
        drawPolylineSvg(svgDrawingHelper, annotationUID, squareGroupUID, canvasPoints, {
            color,
            fillColor,
            fillOpacity,
            lineWidth,
            lineDash,
            closePath: true,
        });
    }
    async getGrowCutLabelmap(growCutData) {
        const { segmentation: { segmentIndex, referencedVolumeId }, renderingEngineId, viewportId, horizontalLines, } = growCutData;
        const renderingEngine = getRenderingEngine(renderingEngineId);
        const viewport = renderingEngine.getViewport(viewportId);
        const [line1, line2] = horizontalLines;
        const worldSquarePoints = [line1[0], line1[1], line2[1], line2[0]];
        const referencedVolume = cache.getVolume(referencedVolumeId);
        const { topLeft: worldTopLeft, bottomRight: worldBottomRight } = this._getWorldBoundingBoxFromProjectedSquare(viewport, worldSquarePoints);
        const ijkTopLeft = transformWorldToIndex(referencedVolume.imageData, worldTopLeft);
        const ijkBottomRight = transformWorldToIndex(referencedVolume.imageData, worldBottomRight);
        const boundingBoxInfo = {
            boundingBox: {
                ijkTopLeft,
                ijkBottomRight,
            },
        };
        const config = this.configuration;
        const options = {
            positiveSeedValue: segmentIndex,
            negativeSeedValue: 255,
            negativePixelRange: config.negativePixelRange,
            positivePixelRange: config.positivePixelRange,
        };
        return growCut.runGrowCutForBoundingBox(referencedVolumeId, boundingBoxInfo, options);
    }
    getRemoveIslandData() {
        const { segmentation: { segmentIndex, referencedVolumeId, labelmapVolumeId }, } = this.growCutData;
        const referencedVolume = cache.getVolume(referencedVolumeId);
        const labelmapVolume = cache.getVolume(labelmapVolumeId);
        const referencedVolumeData = referencedVolume.voxelManager.getCompleteScalarDataArray();
        const labelmapData = labelmapVolume.voxelManager.getCompleteScalarDataArray();
        const { islandPixelRange } = this.configuration.islandRemoval;
        const islandPointIndexes = [];
        for (let i = 0, len = labelmapData.length; i < len; i++) {
            if (labelmapData[i] !== segmentIndex) {
                continue;
            }
            const pixelValue = referencedVolumeData[i];
            if (pixelValue >= islandPixelRange[0] &&
                pixelValue <= islandPixelRange[1]) {
                islandPointIndexes.push(i);
            }
        }
        return {
            islandPointIndexes,
        };
    }
    _activateDraw(element) {
        element.addEventListener(Events.MOUSE_UP, this._endCallback);
        element.addEventListener(Events.MOUSE_DRAG, this._dragCallback);
        element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
    }
    _projectWorldPointAcrossSlices(viewport, worldEdgePoint, vecDirection) {
        const volume = this._getViewportVolume(viewport);
        const { dimensions } = volume;
        const ijkPoint = transformWorldToIndex(volume.imageData, worldEdgePoint);
        const axis = vecDirection.findIndex((n) => csUtils.isEqual(Math.abs(n), 1));
        if (axis === -1) {
            throw new Error('Non-orthogonal direction vector');
        }
        const ijkLineP1 = [...ijkPoint];
        const ijkLineP2 = [...ijkPoint];
        ijkLineP1[axis] = 0;
        ijkLineP2[axis] = dimensions[axis] - 1;
        return [ijkLineP1, ijkLineP2];
    }
    _getCuboidIJKEdgePointsFromProjectedWorldPoint(viewport, worldEdgePoint) {
        const { viewPlaneNormal } = viewport.getCamera();
        return this._projectWorldPointAcrossSlices(viewport, worldEdgePoint, viewPlaneNormal);
    }
    _getWorldCuboidCornerPoints(viewport, worldSquarePoints) {
        const cuboidPoints = [];
        const volume = this._getViewportVolume(viewport);
        worldSquarePoints.forEach((worldSquarePoint) => {
            const ijkEdgePoints = this._getCuboidIJKEdgePointsFromProjectedWorldPoint(viewport, worldSquarePoint);
            const worldEdgePoints = ijkEdgePoints.map((ijkPoint) => transformIndexToWorld(volume.imageData, ijkPoint));
            cuboidPoints.push(...worldEdgePoints);
        });
        return cuboidPoints;
    }
    _getWorldBoundingBoxFromProjectedSquare(viewport, worldSquarePoints) {
        const worldCuboidPoints = this._getWorldCuboidCornerPoints(viewport, worldSquarePoints);
        const topLeft = [...worldCuboidPoints[0]];
        const bottomRight = [...worldCuboidPoints[0]];
        worldCuboidPoints.forEach((worldPoint) => {
            vec3.min(topLeft, topLeft, worldPoint);
            vec3.max(bottomRight, bottomRight, worldPoint);
        });
        return { topLeft, bottomRight };
    }
    _getViewportVolume(viewport) {
        if (!(viewport instanceof BaseVolumeViewport)) {
            throw new Error('Viewport is not a BaseVolumeViewport');
        }
        const volumeId = viewport.getAllVolumeIds()[0];
        return cache.getVolume(volumeId);
    }
    _getHorizontalLineIJKPoints(enabledElement, worldPoint) {
        const { viewport } = enabledElement;
        const volume = this._getViewportVolume(viewport);
        const { dimensions } = volume;
        const ijkPoint = transformWorldToIndex(volume.imageData, worldPoint);
        const { viewUp, viewPlaneNormal } = viewport.getCamera();
        const vecRow = vec3.cross(vec3.create(), viewUp, viewPlaneNormal);
        const axis = vecRow.findIndex((n) => csUtils.isEqual(Math.abs(n), 1));
        const ijkLineP1 = [...ijkPoint];
        const ijkLineP2 = [...ijkPoint];
        ijkLineP1[axis] = 0;
        ijkLineP2[axis] = dimensions[axis] - 1;
        return [ijkLineP1, ijkLineP2];
    }
    _getHorizontalLineWorldPoints(enabledElement, worldPoint) {
        const { viewport } = enabledElement;
        const volume = this._getViewportVolume(viewport);
        const [ijkPoint1, ijkPoint2] = this._getHorizontalLineIJKPoints(enabledElement, worldPoint);
        const worldPoint1 = transformIndexToWorld(volume.imageData, ijkPoint1);
        const worldPoint2 = transformIndexToWorld(volume.imageData, ijkPoint2);
        return [worldPoint1, worldPoint2];
    }
}
WholeBodySegmentTool.toolName = 'WholeBodySegment';
export default WholeBodySegmentTool;
