import {
  eventTarget,
  triggerEvent,
  VolumeViewport,
  utilities as csUtils,
  getEnabledElement
} from '@cornerstonejs/core'

const { transformWorldToIndex } = csUtils
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
  Enums,
  utilities,
  annotation,
  drawing,
  cursors

} = cornerstoneTools
// const { getWorldWidthAndHeightFromTwoPoints } = utilities.planar
// const { roundNumber } = utilities
const { hideElementCursor } = cursors.elementCursor
const { getAnnotations, addAnnotation } = annotation.state
const { isAnnotationVisible } = annotation.visibility
const { isAnnotationLocked } = annotation.locking
const drawHandlesSvg = drawing.drawHandles
const drawCircleSvg = drawing.drawCircle
const drawLinkedTextBoxSvg = drawing.drawLinkedTextBox
const { triggerAnnotationRenderForViewportIds, } = utilities
const { getTextBoxCoordsCanvas } = utilities.drawing
const { getViewportIdsWithToolToRender } = utilities.viewportFilters
import {
  getCalibratedAspect, getCalibratedLengthUnitsAndScale
} from './../js/getCalibratedUnits'
import { vec3 } from 'gl-matrix'
class CircleROITool extends cornerstoneTools.CircleROITool {
  static toolName;
  // editData= {
  //   annotation: null,
  //   viewportIdsToRender: [],
  //   handleIndex: null,
  //   movingTextBox: null,
  //   newAnnotation: null,
  //   hasMoved: null
  // }
  // touchDragCallback: any;
  // mouseDragCallback: any;
  // _throttledCalculateCachedStats: any;
  // editData: {
  //   annotation: any;
  //   viewportIdsToRender: Array<string>;
  //   handleIndex?: number;
  //   movingTextBox?: boolean;
  //   newAnnotation?: boolean;
  //   hasMoved?: boolean;
  // } | null;
  // isDrawing: boolean;
  // isHandleOutsideImage = false;

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

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

    // this._throttledCalculateCachedStats = utilities.throttle(
    //   this._calculateCachedStats,
    //   100,
    //   { trailing: true }
    // )
    // this._getTextLines = this.getTextLines
  }
  addNewAnnotation = (evt) => {
    const eventDetail = evt.detail
    const { currentPoints, element } = eventDetail
    const worldPos = currentPoints.world
    // const canvasPos = currentPoints.canvas;

    const enabledElement = getEnabledElement(element)
    const { viewport, renderingEngine } = enabledElement

    this.isDrawing = true

    const camera = viewport.getCamera()
    const { viewPlaneNormal, viewUp } = camera

    const referencedImageId = this.getReferencedImageId(
      viewport,
      worldPos,
      viewPlaneNormal,
      viewUp
    )

    const FrameOfReferenceUID = viewport.getFrameOfReferenceUID()

    const annotation = {
      highlighted: true,
      invalidated: true,
      metadata: {
        toolName: this.getToolName(),
        viewPlaneNormal: [...viewPlaneNormal],
        viewUp: [...viewUp],
        FrameOfReferenceUID,
        referencedImageId,
        ...viewport.getViewReference({ points: [worldPos] }),
      },
      data: {
        label: '',
        handles: {
          textBox: {
            hasMoved: false,
            worldPosition: [0, 0, 0],
            worldBoundingBox: {
              topLeft: [0, 0, 0],
              topRight: [0, 0, 0],
              bottomLeft: [0, 0, 0],
              bottomRight: [0, 0, 0],
            },
          },
          points: [[...worldPos], [...worldPos]],
          activeHandleIndex: null,
        },
        cachedStats: {},
      },
    };
    addAnnotation(annotation, element)

    const viewportIdsToRender = getViewportIdsWithToolToRender(
      element,
      this.getToolName()
    )

    this.editData = {
      annotation,
      viewportIdsToRender,
      newAnnotation: true,
      hasMoved: false
    }
    this._activateDraw(element)

    hideElementCursor(element)

    evt.preventDefault()

    triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender)

    return annotation
  };
  renderAnnotation = (enabledElement, svgDrawingHelper) => {
    let renderStatus = false
    const { viewport } = enabledElement
    const { element } = viewport

    let annotations = getAnnotations(this.getToolName(), element)

    if (!annotations || (annotations && annotations.length === 0)) {
      return renderStatus
    }

    annotations = this.filterInteractableAnnotationsForElement(
      element,
      annotations
    )

    if (!annotations || (annotations && annotations.length === 0)) {
      return renderStatus
    }

    const targetId = this.getTargetId(viewport)

    const renderingEngine = viewport.getRenderingEngine()

    const styleSpecifier = {
      toolGroupId: this.toolGroupId,
      toolName: this.getToolName(),
      viewportId: enabledElement.viewport.id
    }

    for (let i = 0; i < annotations.length; i++) {
      const annotation = annotations[i]
      const { annotationUID, data } = annotation
      const { handles } = data
      // const { points, activeHandleIndex } = handles
      const { points, activeHandleIndex } = handles
      // var activeHandleIndex = null
      // if (annotation.highlighted && !annotation.isLocked && annotation.isVisible) {
      //   activeHandleIndex = 1
      // }

      styleSpecifier.annotationUID = annotationUID
      const { color, lineWidth, lineDash } = this.getAnnotationStyle({
        annotation,
        styleSpecifier,
      });

      // const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation)
      // const lineDash = this.getStyle('lineDash', styleSpecifier, annotation)
      // const color = this.getStyle('color', styleSpecifier, annotation)

      const canvasCoordinates = points.map((p) =>
        viewport.worldToCanvas(p)
      )
      const center = canvasCoordinates[0]
      const radius = getCanvasCircleRadius(canvasCoordinates)
      const canvasCorners = getCanvasCircleCorners(canvasCoordinates)

      const { centerPointRadius } = this.configuration

      // const modalityUnitOptions = {
      //   isPreScaled: utilities.viewport.isViewportPreScaled(viewport, targetId),

      //   isSuvScaled: this.isSuvScaled(
      //     viewport,
      //     targetId,
      //     annotation.metadata.referencedImageId
      //   )
      // }

      // If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
      // force to recalculate the stats from the points
      if (
        !data.cachedStats[targetId] ||
        data.cachedStats[targetId].areaUnit === undefined
      ) {
        data.cachedStats[targetId] = {
          Modality: null,
          area: null,
          max: null,
          mean: null,
          stdDev: null,
          areaUnit: null,
          radius: null,
          radiusUnit: null,
          perimeter: null
        }
        this._calculateCachedStats(
          annotation,
          viewport,
          renderingEngine,
          enabledElement,
          // modalityUnitOptions
        )
      } else if (annotation.invalidated) {
        this._throttledCalculateCachedStats(
          annotation,
          viewport,
          renderingEngine,
          enabledElement,
          // modalityUnitOptions
        )

        // If the invalidated data is as a result of volumeViewport manipulation
        // of the tools, we need to invalidate the related viewports data, so that
        // when scrolling to the related slice in which the tool were manipulated
        // we re-render the correct tool position. This is due to stackViewport
        // which doesn't have the full volume at each time, and we are only working
        // on one slice at a time.
        if (viewport instanceof VolumeViewport) {
          const { referencedImageId } = annotation.metadata

          // invalidate all the relevant stackViewports if they are not
          // at the referencedImageId
          for (const targetId in data.cachedStats) {
            if (targetId.startsWith('imageId')) {
              const viewports = renderingEngine.getStackViewports()

              const invalidatedStack = viewports.find((vp) => {
                // The stack viewport that contains the imageId but is not
                // showing it currently
                const referencedImageURI =
                  csUtils.imageIdToURI(referencedImageId)
                const hasImageURI = vp.hasImageURI(referencedImageURI)
                const currentImageURI = csUtils.imageIdToURI(
                  vp.getCurrentImageId()
                )
                return hasImageURI && currentImageURI !== referencedImageURI
              })

              if (invalidatedStack) {
                delete data.cachedStats[targetId]
              }
            }
          }
        }
      }

      // If rendering engine has been destroyed while rendering
      if (!viewport.getRenderingEngine()) {
        console.warn('Rendering Engine has been destroyed')
        return renderStatus
      }

      let activeHandleCanvasCoords

      if (!isAnnotationVisible(annotationUID)) {
        continue
      }

      if (
        !isAnnotationLocked(annotation) &&
        !this.editData &&
        activeHandleIndex !== null
      ) {
        // Not locked or creating and hovering over handle, so render handle.
        activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]]
      }

      if (activeHandleCanvasCoords) {
        const handleGroupUID = '0'
        drawHandlesSvg(
          svgDrawingHelper,
          annotationUID,
          handleGroupUID,
          activeHandleCanvasCoords,
          {
            color
          }
        )
      }

      const dataId = `${annotationUID}-circle`
      const circleUID = '0'
      drawCircleSvg(
        svgDrawingHelper,
        annotationUID,
        circleUID,
        center,
        radius,
        {
          color,
          lineDash,
          lineWidth
        },
        dataId
      )

      // draw center point, if "centerPointRadius" configuration is valid.
      if (centerPointRadius > 0) {
        if (radius > 3 * centerPointRadius) {
          drawCircleSvg(
            svgDrawingHelper,
            annotationUID,
            `${circleUID}-center`,
            center,
            centerPointRadius,
            {
              color,
              lineDash,
              lineWidth
            }
          )
        }
      }

      renderStatus = true

      const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation)
      if (!options.visibility) {
        data.handles.textBox = {
          hasMoved: false,
          worldPosition: [0, 0, 0],
          worldBoundingBox: {
            topLeft: [0, 0, 0],
            topRight: [0, 0, 0],
            bottomLeft: [0, 0, 0],
            bottomRight: [0, 0, 0]
          }
        }
        continue
      }

      const textLines = this.configuration.getTextLines(data, targetId)
      if (!textLines || textLines.length === 0) {
        continue
      }

      // Poor man's cached?
      let canvasTextBoxCoords

      if (!data.handles.textBox.hasMoved) {
        canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCorners)

        data.handles.textBox.worldPosition =
          viewport.canvasToWorld(canvasTextBoxCoords)
      }

      const textBoxPosition = viewport.worldToCanvas(
        data.handles.textBox.worldPosition
      )

      const textBoxUID = '1'
      const boundingBox = drawLinkedTextBoxSvg(
        svgDrawingHelper,
        annotationUID,
        textBoxUID,
        textLines,
        textBoxPosition,
        canvasCoordinates,
        {},
        options
      )

      const { x: left, y: top, width, height } = boundingBox

      data.handles.textBox.worldBoundingBox = {
        topLeft: viewport.canvasToWorld([left, top]),
        topRight: viewport.canvasToWorld([left + width, top]),
        bottomLeft: viewport.canvasToWorld([left, top + height]),
        bottomRight: viewport.canvasToWorld([left + width, top + height])
      }
    }

    return renderStatus
  };
  _calculateCachedStats = (
    annotation,
    viewport,
    renderingEngine,
    enabledElement
  ) => {
    const data = annotation.data;
    const { element } = viewport;

    const { points } = data.handles;

    const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
    const { viewPlaneNormal, viewUp } = viewport.getCamera();

    const [topLeftCanvas, bottomRightCanvas] = (
      getCanvasCircleCorners(canvasCoordinates)
    );

    const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
    const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
    const { cachedStats } = data;

    const targetIds = Object.keys(cachedStats);
    const worldPos1 = topLeftWorld;
    const worldPos2 = bottomRightWorld;

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

      const image = this.getTargetImageData(targetId);

      // If image does not exists for the targetId, skip. This can be due
      // to various reasons such as if the target was a volumeViewport, and
      // the volumeViewport has been decached in the meantime.
      if (!image) {
        continue;
      }

      const { dimensions, imageData, metadata, voxelManager } = image;

      const pos1Index = transformWorldToIndex(imageData, worldPos1);

      pos1Index[0] = Math.floor(pos1Index[0]);
      pos1Index[1] = Math.floor(pos1Index[1]);
      pos1Index[2] = Math.floor(pos1Index[2]);

      const pos2Index = transformWorldToIndex(imageData, worldPos2);

      pos2Index[0] = Math.floor(pos2Index[0]);
      pos2Index[1] = Math.floor(pos2Index[1]);
      pos2Index[2] = Math.floor(pos2Index[2]);

      // Check if one of the indexes are inside the volume, this then gives us
      // Some area to do stats over.

      if (this._isInsideVolume(pos1Index, pos2Index, dimensions)) {
        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 = [
          (topLeftWorld[0] + bottomRightWorld[0]) / 2,
          (topLeftWorld[1] + bottomRightWorld[1]) / 2,
          (topLeftWorld[2] + bottomRightWorld[2]) / 2,
        ];

        const ellipseObj = {
          center,
          xRadius: Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2,
          yRadius: Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2,
          zRadius: Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2,
        };

        const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(
          viewPlaneNormal,
          viewUp,
          worldPos1,
          worldPos2
        );
        const isEmptyArea = worldWidth === 0 && worldHeight === 0;
        const handles = [pos1Index, pos2Index];
        const { scale, unit, areaUnit } = getCalibratedLengthUnitsAndScale(
          image,
          handles
        );
        const aspect = getCalibratedAspect(image);
        const area = Math.abs(
          Math.PI *
            (worldWidth / scale / 2) *
            (worldHeight / aspect / scale / 2)
        );

        const pixelUnitsOptions = {
          isPreScaled: isViewportPreScaled(viewport, targetId),
          isSuvScaled: this.isSuvScaled(
            viewport,
            targetId,
            annotation.metadata.referencedImageId
          ),
        };

        const modalityUnit = getPixelValueUnits(
          metadata.Modality,
          annotation.metadata.referencedImageId,
          pixelUnitsOptions
        );

        const 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();

        cachedStats[targetId] = {
          Modality: metadata.Modality,
          area,
          mean: stats.mean?.value,
          max: stats.max?.value,
          pointsInShape,
          stdDev: stats.stdDev?.value,
          statsArray: stats.array,
          isEmptyArea,
          areaUnit,
          radius: worldWidth / 2 / scale,
          radiusUnit: unit,
          perimeter: (2 * Math.PI * (worldWidth / 2)) / scale,
          modalityUnit,
        };
      } else {
        this.isHandleOutsideImage = true;

        cachedStats[targetId] = {
          Modality: metadata.Modality,
        };
      }
    }

    annotation.invalidated = false;

    // Dispatching annotation modified
    triggerAnnotationModified(annotation, element);

    return cachedStats;
  };
}

function getCanvasCircleCorners(
  circleCanvasPoints
) {
  const [center, end] = circleCanvasPoints
  const radius = utilities.math.point.distanceToPoint(center, end)

  const topLeft = [center[0] - radius, center[1] - radius]
  const bottomRight = [center[0] + radius, center[1] + radius]

  return [topLeft, bottomRight]
}
function getCanvasCircleRadius(
  circleCanvasPoints
) {
  const [center, end] = circleCanvasPoints
  return utilities.math.point.distanceToPoint(center, end)
}
function getWorldWidthAndHeightFromTwoPoints(
  viewPlaneNormal,
  viewUp,
  worldPos1,
  worldPos2
) {
  const viewRight = vec3.create()

  vec3.cross(viewRight, viewUp, viewPlaneNormal)

  const pos1 = vec3.fromValues(...worldPos1)
  const pos2 = vec3.fromValues(...worldPos2)

  const diagonal = vec3.create()
  vec3.subtract(diagonal, pos1, pos2)

  const diagonalLength = vec3.length(diagonal)

  // When the two points are very close to each other return width as 0
  // to avoid NaN the cosTheta formula calculation
  if (diagonalLength < 0.0001) {
    return { worldWidth: 0, worldHeight: 0 }
  }

  const cosTheta =
    vec3.dot(diagonal, viewRight) / (diagonalLength * vec3.length(viewRight))

  const sinTheta = Math.sqrt(1 - cosTheta * cosTheta)

  const worldWidth = sinTheta * diagonalLength
  const worldHeight = cosTheta * diagonalLength

  return { worldWidth, worldHeight }
}
function pointInEllipse(
  ellipse,
  pointLPS
) {
  const { center: circleCenterWorld, xRadius, yRadius, zRadius } = ellipse
  const [x, y, z] = pointLPS
  const [x0, y0, z0] = circleCenterWorld

  let inside = 0
  if (xRadius !== 0) {
    inside += ((x - x0) * (x - x0)) / (xRadius * xRadius)
  }

  if (yRadius !== 0) {
    inside += ((y - y0) * (y - y0)) / (yRadius * yRadius)
  }

  if (zRadius !== 0) {
    inside += ((z - z0) * (z - z0)) / (zRadius * zRadius)
  }

  return inside <= 1
}
// function defaultGetTextLines(data, targetId) {
//   const cachedVolumeStats = data.cachedStats[targetId]
//   const {
//     radius,
//     radiusUnit,
//     area,
//     mean,
//     stdDev,
//     max,
//     isEmptyArea,
//     areaUnit,
//     modalityUnit
//   } = cachedVolumeStats

//   const textLines = []

//   if (radius) {
//     const radiusLine = isEmptyArea
//       ? `Radius: Oblique not supported`
//       : `Radius: ${roundNumber(radius)} ${radiusUnit}`
//     textLines.push(radiusLine)
//   }

//   if (area) {
//     const areaLine = isEmptyArea
//       ? `Area: Oblique not supported`
//       : `Area: ${roundNumber(area)} ${areaUnit}`
//     textLines.push(areaLine)
//   }

//   if (mean) {
//     textLines.push(`Mean: ${roundNumber(mean)} ${modalityUnit}`)
//   }

//   if (max) {
//     textLines.push(`Max: ${roundNumber(max)} ${modalityUnit}`)
//   }

//   if (stdDev) {
//     textLines.push(`Std Dev: ${roundNumber(stdDev)} ${modalityUnit}`)
//   }

//   return textLines
// }

CircleROITool.toolName = 'CircleROI'
export default CircleROITool

