import { quat, vec3 } from 'gl-matrix';
import { utilities as csUtils, cache, volumeLoader } from '@cornerstonejs/core';
import { run } from './runGrowCut';
import { getSphereBoundsInfo } from '../../getSphereBoundsInfo';
const { transformWorldToIndex } = csUtils;
const POSITIVE_SEED_VALUE = 254;
const NEGATIVE_SEED_VALUE = 255;
const POSITIVE_SEED_VARIANCE = 0.1;
const NEGATIVE_SEED_VARIANCE = 0.8;
function _getGrowCutSphereBoundsInfo(referencedVolume, sphereBoundsInfo) {
    const { topLeftWorld, bottomRightWorld } = sphereBoundsInfo;
    const topLeftIJK = transformWorldToIndex(referencedVolume.imageData, topLeftWorld);
    const bottomRightIJK = transformWorldToIndex(referencedVolume.imageData, bottomRightWorld);
    return {
        ...sphereBoundsInfo,
        topLeftIJK,
        bottomRightIJK,
    };
}
function _getSphereBoundsInfo(referencedVolume, sphereInfo) {
    const direction = referencedVolume.imageData.getDirection();
    const vecColumn = vec3.fromValues(direction[3], direction[4], direction[5]);
    const { center: sphereCenterPoint, radius: sphereRadius } = sphereInfo;
    const refVolImageData = referencedVolume.imageData;
    const topCirclePoint = vec3.scaleAndAdd(vec3.create(), sphereCenterPoint, vecColumn, -sphereRadius);
    const bottomCirclePoint = vec3.scaleAndAdd(vec3.create(), sphereCenterPoint, vecColumn, sphereRadius);
    const sphereBoundsInfo = getSphereBoundsInfo([bottomCirclePoint, topCirclePoint], refVolImageData);
    return _getGrowCutSphereBoundsInfo(referencedVolume, sphereBoundsInfo);
}
function _createSubVolumeFromSphere(referencedVolume, sphereInfo, viewport) {
    const refVolImageData = referencedVolume.imageData;
    const camera = viewport.getCamera();
    const { ijkVecRowDir, ijkVecColDir } = csUtils.getVolumeDirectionVectors(refVolImageData, camera);
    const obliqueView = [ijkVecRowDir, ijkVecColDir].some((vec) => !csUtils.isEqual(Math.abs(vec[0]), 1) &&
        !csUtils.isEqual(Math.abs(vec[1]), 1) &&
        !csUtils.isEqual(Math.abs(vec[2]), 1));
    if (obliqueView) {
        console.warn('Oblique view is not supported!');
        return;
    }
    const { boundsIJK: sphereBoundsIJK } = _getSphereBoundsInfo(referencedVolume, sphereInfo);
    const subVolumeBoundsIJK = {
        minX: sphereBoundsIJK[0][0],
        maxX: sphereBoundsIJK[0][1] + 1,
        minY: sphereBoundsIJK[1][0],
        maxY: sphereBoundsIJK[1][1] + 1,
        minZ: sphereBoundsIJK[2][0],
        maxZ: sphereBoundsIJK[2][1] + 1,
    };
    return csUtils.createSubVolume(referencedVolume.volumeId, subVolumeBoundsIJK, {
        targetBuffer: {
            type: 'Float32Array',
        },
    });
}
function _setPositiveSeedValues(referencedVolume, labelmap, sphereInfo, options) {
    const refVolumePixelData = referencedVolume.voxelManager.getCompleteScalarDataArray();
    const worldStartPos = sphereInfo.center;
    const [width, height, numSlices] = referencedVolume.dimensions;
    const numPixelsPerSlice = width * height;
    const ijkStartPosition = transformWorldToIndex(referencedVolume.imageData, worldStartPos);
    const referencePixelValue = refVolumePixelData[ijkStartPosition[2] * numPixelsPerSlice +
        ijkStartPosition[1] * width +
        ijkStartPosition[0]];
    const positiveSeedValue = options.positiveSeedValue ?? POSITIVE_SEED_VALUE;
    const positiveSeedVariance = options.positiveSeedVariance ?? POSITIVE_SEED_VARIANCE;
    const positiveSeedVarianceValue = Math.abs(referencePixelValue * positiveSeedVariance);
    const minPositivePixelValue = referencePixelValue - positiveSeedVarianceValue;
    const maxPositivePixelValue = referencePixelValue + positiveSeedVarianceValue;
    const neighborsCoordDelta = [
        [-1, 0, 0],
        [1, 0, 0],
        [0, -1, 0],
        [0, 1, 0],
        [0, 0, -1],
        [0, 0, 1],
    ];
    const startVoxelIndex = ijkStartPosition[2] * numPixelsPerSlice +
        ijkStartPosition[1] * width +
        ijkStartPosition[0];
    labelmap.voxelManager.setAtIndex(startVoxelIndex, positiveSeedValue);
    const queue = [ijkStartPosition];
    while (queue.length) {
        const ijkVoxel = queue.shift();
        const [x, y, z] = ijkVoxel;
        for (let i = 0, len = neighborsCoordDelta.length; i < len; i++) {
            const neighborCoordDelta = neighborsCoordDelta[i];
            const nx = x + neighborCoordDelta[0];
            const ny = y + neighborCoordDelta[1];
            const nz = z + neighborCoordDelta[2];
            if (nx < 0 ||
                nx >= width ||
                ny < 0 ||
                ny >= height ||
                nz < 0 ||
                nz >= numSlices) {
                continue;
            }
            const neighborVoxelIndex = nz * numPixelsPerSlice + ny * width + nx;
            const neighborPixelValue = refVolumePixelData[neighborVoxelIndex];
            const neighborLabelmapValue = labelmap.voxelManager.getAtIndex(neighborVoxelIndex);
            if (neighborLabelmapValue === positiveSeedValue ||
                neighborPixelValue < minPositivePixelValue ||
                neighborPixelValue > maxPositivePixelValue) {
                continue;
            }
            labelmap.voxelManager.setAtIndex(neighborVoxelIndex, positiveSeedValue);
            queue.push([nx, ny, nz]);
        }
    }
}
function _setNegativeSeedValues(subVolume, labelmap, sphereInfo, viewport, options) {
    const subVolPixelData = subVolume.voxelManager.getCompleteScalarDataArray();
    const [columns, rows, numSlices] = labelmap.dimensions;
    const numPixelsPerSlice = columns * rows;
    const { worldVecRowDir, worldVecSliceDir } = csUtils.getVolumeDirectionVectors(labelmap.imageData, viewport.getCamera());
    const ijkSphereCenter = transformWorldToIndex(subVolume.imageData, sphereInfo.center);
    const referencePixelValue = subVolPixelData[ijkSphereCenter[2] * columns * rows +
        ijkSphereCenter[1] * columns +
        ijkSphereCenter[0]];
    const negativeSeedVariance = options.negativeSeedVariance ?? NEGATIVE_SEED_VARIANCE;
    const negativeSeedValue = options?.negativeSeedValue ?? NEGATIVE_SEED_VALUE;
    const negativeSeedVarianceValue = Math.abs(referencePixelValue * negativeSeedVariance);
    const minNegativePixelValue = referencePixelValue - negativeSeedVarianceValue;
    const maxNegativePixelValue = referencePixelValue + negativeSeedVarianceValue;
    const numCirclePoints = 360;
    const rotationAngle = (2 * Math.PI) / numCirclePoints;
    const worldQuat = quat.setAxisAngle(quat.create(), worldVecSliceDir, rotationAngle);
    const vecRotation = vec3.clone(worldVecRowDir);
    for (let i = 0; i < numCirclePoints; i++) {
        const worldCircleBorderPoint = vec3.scaleAndAdd(vec3.create(), sphereInfo.center, vecRotation, sphereInfo.radius);
        const ijkCircleBorderPoint = transformWorldToIndex(labelmap.imageData, worldCircleBorderPoint);
        const [x, y, z] = ijkCircleBorderPoint;
        vec3.transformQuat(vecRotation, vecRotation, worldQuat);
        if (x < 0 ||
            x >= columns ||
            y < 0 ||
            y >= rows ||
            z < 0 ||
            z >= numSlices) {
            continue;
        }
        const offset = x + y * columns + z * numPixelsPerSlice;
        const pixelValue = subVolPixelData[offset];
        if (pixelValue < minNegativePixelValue ||
            pixelValue > maxNegativePixelValue) {
            labelmap.voxelManager.setAtIndex(offset, negativeSeedValue);
        }
    }
}
async function _createAndCacheSegmentationSubVolumeForSphere(subVolume, sphereInfo, viewport, options) {
    const labelmap = await volumeLoader.createAndCacheDerivedLabelmapVolume(subVolume.volumeId);
    _setPositiveSeedValues(subVolume, labelmap, sphereInfo, options);
    _setNegativeSeedValues(subVolume, labelmap, sphereInfo, viewport, options);
    return labelmap;
}
async function runGrowCutForSphere(referencedVolumeId, sphereInfo, viewport, options) {
    const referencedVolume = cache.getVolume(referencedVolumeId);
    const subVolume = _createSubVolumeFromSphere(referencedVolume, sphereInfo, viewport);
    const labelmap = await _createAndCacheSegmentationSubVolumeForSphere(subVolume, sphereInfo, viewport, options);
    await run(subVolume.volumeId, labelmap.volumeId);
    return labelmap;
}
export { runGrowCutForSphere as default, runGrowCutForSphere };
