import { EPSILON, utilities as csUtils } from "@cornerstonejs/core"
import * as cornerstoneTools from "@cornerstonejs/tools"
const { triggerAnnotationModified } = cornerstoneTools.annotation.state

const { transformWorldToIndex } = csUtils
const { BaseTool, Enums, utilities } = cornerstoneTools
const { MeasurementType, ChangeTypes } = Enums
const { getCanvasCircleCorners } = utilities.math.circle
const { pointInEllipse } = utilities.math.ellipse
const { getCalibratedLengthUnitsAndScale } = utilities

class CircleROITool extends cornerstoneTools.CircleROITool {
  static toolName = "CircleROI"

  constructor(toolProps = {}, defaultToolProps) {
    super(toolProps, defaultToolProps)

    this._calculateCachedStats = (
      annotation,
      viewport,
      renderingEngine,
      enabledElement
    ) => {
      if (!this.configuration.calculateStats) return

      const { data } = annotation
      const { element } = viewport
      const wasInvalidated = annotation.invalidated
      const { points } = data.handles
      const canvasCoordinates = points.map((point) =>
        viewport.worldToCanvas(point)
      )
      const canvasCenter = canvasCoordinates[0]
      const canvasTop = canvasCoordinates[1]
      const [topLeftCanvas, bottomRightCanvas] = getCanvasCircleCorners([
        canvasCenter,
        canvasTop
      ])
      const topLeftWorld = viewport.canvasToWorld(topLeftCanvas)
      const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas)
      const { cachedStats } = data
      const targetIds = Object.keys(cachedStats)

      for (let i = 0; i < targetIds.length; i++) {
        const targetId = targetIds[i];
        const image = this.getTargetImageData(targetId)

        if (!image) {
          console.warn("image not found for stats:", targetId)
          delete cachedStats[targetId]
          continue
        }

        const { dimensions, imageData, metadata, voxelManager } = image
        const handles = points.map((point) => imageData.worldToIndex(point))
        const calibrate = getCalibratedLengthUnitsAndScale(image, handles)
        const radius = CircleROITool.calculateLengthInIndex(
          calibrate,
          handles.slice(0, 2)
        )
        const area = Math.PI * radius * radius
        const perimeter = 2 * Math.PI * radius
        const isEmptyArea = radius === 0
        const { unit, areaUnit } = calibrate
        const namedArea = {
          name: "area",
          value: area,
          unit: areaUnit,
          type: MeasurementType.Area,
        }
        const namedCircumference = {
          name: "circumference",
          value: perimeter,
          unit,
          type: MeasurementType.Linear,
        }
        const namedRadius = {
          name: "radius",
          value: radius,
          unit,
          type: MeasurementType.Linear,
        }
        const statsArray = [namedArea, namedRadius, namedCircumference]

        cachedStats[targetId] = {
          Modality: metadata.Modality,
          area,
          isEmptyArea,
          areaUnit,
          radius,
          radiusUnit: unit,
          perimeter,
          statsArray,
        }

        const pos1Index = transformWorldToIndex(imageData, topLeftWorld)
        const pos2Index = transformWorldToIndex(imageData, bottomRightWorld)

        this.isHandleOutsideImage = !BaseTool.isInsideVolume(dimensions, [
          pos1Index,
          pos2Index,
        ])
        if (!this.isHandleOutsideImage) {
          const iMin = Math.min(pos1Index[0], pos2Index[0])
          const iMax = Math.max(pos1Index[0], pos2Index[0])
          const jMin = Math.min(pos1Index[1], pos2Index[1])
          const jMax = Math.max(pos1Index[1], pos2Index[1])
          const kMin = Math.min(pos1Index[2], pos2Index[2])
          const kMax = Math.max(pos1Index[2], pos2Index[2])
          const boundsIJK = [
            [iMin, iMax],
            [jMin, jMax],
            [kMin, kMax],
          ]
          const center = points[0];
          const xRadius = Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2
          const yRadius = Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2
          const zRadius = Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2
          const ellipseObj = {
            center,
            xRadius: xRadius < EPSILON / 2 ? 0 : xRadius,
            yRadius: yRadius < EPSILON / 2 ? 0 : yRadius,
            zRadius: zRadius < EPSILON / 2 ? 0 : zRadius,
          }
          const pixelUnitsOptions = {
            isPreScaled: utilities.viewport.isViewportPreScaled(
              viewport,
              targetId
            ),
            isSuvScaled: this.isSuvScaled(
              viewport,
              targetId,
              annotation.metadata.referencedImageId
            ),
          }
          const modalityUnit = utilities.getPixelValueUnits(
            metadata.Modality,
            annotation.metadata.referencedImageId,
            pixelUnitsOptions
          )
          let pointsInShape
          if (voxelManager) {
            pointsInShape = voxelManager.forEach(
              this.configuration.statsCalculator.statsCallback,
              {
                isInObject: (pointLPS) =>
                  pointInEllipse(ellipseObj, pointLPS, { fast: true }),
                boundsIJK,
                imageData,
                returnPoints: this.configuration.storePointData,
              }
            )
          }
          const stats = this.configuration.statsCalculator.getStatistics()
          const mean = stats.mean?.value
          const count = stats.count?.value
          const total =
            Array.isArray(mean) && Number.isFinite(count)
              ? mean.map((value) => value * count)
              : Number.isFinite(mean) && Number.isFinite(count)
              ? mean * count
              : undefined;
          cachedStats[targetId] = {
            ...cachedStats[targetId],
            Modality: metadata.Modality,
            mean,
            max: stats.max?.value,
            min: stats.min?.value,
            total,
            count,
            pointsInShape,
            stdDev: stats.stdDev?.value,
            modalityUnit,
            statsArray: [...statsArray, ...stats.array],
          }
        }
      }

      annotation.invalidated = false
      if (wasInvalidated) {
        triggerAnnotationModified(
          annotation,
          element,
          ChangeTypes.StatsUpdated
        )
      }

      return cachedStats
    }

    this._throttledCalculateCachedStats = utilities.throttle(
      this._calculateCachedStats,
      100,
      { trailing: true }
    )
  }
}

export default CircleROITool
