// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkResliceCursorRepresentation.h"

#include "vtkActor.h"
#include "vtkActor2D.h"
#include "vtkBoundingBox.h"
#include "vtkCamera.h"
#include "vtkCoordinate.h"
#include "vtkCubeSource.h"
#include "vtkCutter.h"
#include "vtkHandleRepresentation.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageMapper3D.h"
#include "vtkImageReslice.h"
#include "vtkInteractorObserver.h"
#include "vtkLookupTable.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPlaneSource.h"
#include "vtkPointHandleRepresentation2D.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderer.h"
#include "vtkResliceCursor.h"
#include "vtkResliceCursorPolyDataAlgorithm.h"
#include "vtkScalarsToColors.h"
#include "vtkTextActor.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkTexture.h"
#include "vtkWindow.h"

#include <sstream>

VTK_ABI_NAMESPACE_BEGIN
vtkCxxSetObjectMacro(vtkResliceCursorRepresentation, ColorMap, vtkImageMapToColors);

//------------------------------------------------------------------------------
vtkResliceCursorRepresentation::vtkResliceCursorRepresentation()
{
  this->ManipulationMode = None;
  this->Modifier = 0;
  this->Tolerance = 5;
  this->ShowReslicedImage = 1;
  this->RestrictPlaneToVolume = 1;
  this->OriginalWindow = 1.0;
  this->OriginalLevel = 0.5;
  this->InitialWindow = 1.0;
  this->InitialLevel = 0.5;
  this->CurrentWindow = 1.0;
  this->CurrentLevel = 0.5;

  this->ThicknessTextProperty = vtkTextProperty::New();
  this->ThicknessTextProperty->SetBold(1);
  this->ThicknessTextProperty->SetItalic(1);
  this->ThicknessTextProperty->SetShadow(1);
  this->ThicknessTextProperty->SetFontFamilyToArial();
  this->ThicknessTextMapper = vtkTextMapper::New();
  this->ThicknessTextMapper->SetTextProperty(this->ThicknessTextProperty);
  this->ThicknessTextMapper->SetInput("0.0");
  this->ThicknessTextActor = vtkActor2D::New();
  this->ThicknessTextActor->SetMapper(this->ThicknessTextMapper);
  this->ThicknessTextActor->VisibilityOff();

  this->Reslice = nullptr;
  this->CreateDefaultResliceAlgorithm();
  this->PlaneSource = vtkPlaneSource::New();

  this->ThicknessLabelFormat = new char[6];
  snprintf(this->ThicknessLabelFormat, 6, "%s", "%0.3g");

  this->ResliceAxes = vtkMatrix4x4::New();
  this->NewResliceAxes = vtkMatrix4x4::New();
  this->LookupTable = nullptr;
  this->ColorMap = vtkImageMapToColors::New();
  this->Texture = vtkTexture::New();
  this->Texture->SetInputConnection(this->ColorMap->GetOutputPort());
  this->Texture->SetInterpolate(1);
  this->TexturePlaneActor = vtkActor::New();

  this->LookupTable = this->CreateDefaultLookupTable();

  this->ColorMap->SetLookupTable(this->LookupTable);
  this->ColorMap->SetOutputFormatToRGBA();
  this->ColorMap->PassAlphaToOutputOn();

  vtkPolyDataMapper* texturePlaneMapper = vtkPolyDataMapper::New();
  texturePlaneMapper->SetInputConnection(this->PlaneSource->GetOutputPort());
  vtkPolyDataMapper::SetResolveCoincidentTopologyToPolygonOffset();

  this->Texture->SetQualityTo32Bit();
  this->Texture->SetColorMode(VTK_COLOR_MODE_DEFAULT);
  this->Texture->SetInterpolate(1);
  this->Texture->RepeatOff();
  this->Texture->SetLookupTable(this->LookupTable);

  this->TexturePlaneActor->SetMapper(texturePlaneMapper);
  this->TexturePlaneActor->SetTexture(this->Texture);
  this->TexturePlaneActor->PickableOn();
  texturePlaneMapper->Delete();

  this->UseImageActor = false;
  this->ImageActor = vtkImageActor::New();
  this->ImageActor->GetMapper()->SetInputConnection(this->ColorMap->GetOutputPort());

  // Represent the text: annotation for cursor position and W/L

  this->DisplayText = 1;
  this->TextActor = vtkTextActor::New();
  this->GenerateText();
}

//------------------------------------------------------------------------------
vtkResliceCursorRepresentation::~vtkResliceCursorRepresentation()
{
  this->ThicknessTextProperty->Delete();
  this->ThicknessTextMapper->Delete();
  this->ThicknessTextActor->Delete();
  this->SetThicknessLabelFormat(nullptr);
  this->ImageActor->Delete();
  if (this->Reslice)
  {
    this->Reslice->Delete();
  }
  this->PlaneSource->Delete();
  this->ResliceAxes->Delete();
  this->NewResliceAxes->Delete();
  if (this->LookupTable)
  {
    this->LookupTable->UnRegister(this);
  }
  this->ColorMap->Delete();
  this->Texture->Delete();
  this->TexturePlaneActor->Delete();
  this->TextActor->Delete();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::SetLookupTable(vtkScalarsToColors* l)
{
  vtkSetObjectBodyMacro(LookupTable, vtkScalarsToColors, l);
  this->LookupTable = l;
  if (this->ColorMap)
  {
    this->ColorMap->SetLookupTable(this->LookupTable);
  }
}

//------------------------------------------------------------------------------
char* vtkResliceCursorRepresentation::GetThicknessLabelText()
{
  return this->ThicknessTextMapper->GetInput();
}

//------------------------------------------------------------------------------
double* vtkResliceCursorRepresentation::GetThicknessLabelPosition()
{
  return this->ThicknessTextActor->GetPosition();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::GetThicknessLabelPosition(double pos[3])
{
  this->ThicknessTextActor->GetPositionCoordinate()->GetValue(pos);
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::GetWorldThicknessLabelPosition(double pos[3])
{
  double viewportPos[3], worldPos[4];
  pos[0] = pos[1] = pos[2] = 0.0;
  if (!this->Renderer)
  {
    vtkErrorMacro("GetWorldLabelPosition: no renderer!");
    return;
  }

  this->ThicknessTextActor->GetPositionCoordinate()->GetValue(viewportPos);
  this->Renderer->ViewportToNormalizedViewport(viewportPos[0], viewportPos[1]);
  this->Renderer->NormalizedViewportToView(viewportPos[0], viewportPos[1], viewportPos[2]);
  this->Renderer->SetViewPoint(viewportPos);
  this->Renderer->ViewToWorld();
  this->Renderer->GetWorldPoint(worldPos);

  if (worldPos[3] != 0.0)
  {
    pos[0] = worldPos[0] / worldPos[3];
    pos[1] = worldPos[1] / worldPos[3];
    pos[2] = worldPos[2] / worldPos[3];
  }
  else
  {
    vtkErrorMacro("GetWorldLabelPosition: world position at index 3 is 0, not dividing by 0");
  }
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::SetManipulationMode(int m)
{
  this->ManipulationMode = m;
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::BuildRepresentation()
{
  this->Reslice->SetInputData(this->GetResliceCursor()->GetImage());

  this->TexturePlaneActor->SetVisibility(
    this->GetResliceCursor()->GetImage() ? (this->ShowReslicedImage && !this->UseImageActor) : 0);
  this->ImageActor->SetVisibility(
    this->GetResliceCursor()->GetImage() ? (this->ShowReslicedImage && this->UseImageActor) : 0);

  // Update the reslice plane if the plane is being manipulated
  if (this->GetManipulationMode() != WindowLevelling)
  {
    this->UpdateReslicePlane();
  }

  this->ImageActor->SetDisplayExtent(this->ColorMap->GetOutput()->GetExtent());

  // Update any text annotations
  this->ManageTextDisplay();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::InitializeReslicePlane()
{
  if (!this->GetResliceCursor()->GetImage())
  {
    return;
  }

  // this->GetResliceCursor()->GetImage()->UpdateInformation();

  // Initialize the reslice plane origins. Offset should be zero within
  // this function here.

  this->ComputeReslicePlaneOrigin();

  // Finally reset the camera to whatever orientation they were staring in

  this->ResetCamera();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::ResetCamera()
{

  // Reset the camera back to the default and the focal point to where
  // the cursor center is

  if (this->Renderer)
  {
    const int planeOrientation = this->GetCursorAlgorithm()->GetReslicePlaneNormal();
    double* normal = this->GetResliceCursor()->GetPlane(planeOrientation)->GetNormal();

    double center[3], focalPoint[3], cameraPosition[3];
    this->GetResliceCursor()->GetCenter(center);
    this->Renderer->GetActiveCamera()->GetFocalPoint(focalPoint);
    this->Renderer->GetActiveCamera()->GetPosition(cameraPosition);

    this->Renderer->SetWorldPoint(focalPoint[0], focalPoint[1], focalPoint[2], 1.0);
    this->Renderer->WorldToDisplay();
    double displayFocalPoint[3];
    this->Renderer->GetDisplayPoint(displayFocalPoint);

    this->Renderer->SetWorldPoint(center[0], center[1], center[2], 1.0);
    this->Renderer->WorldToDisplay();
    double displayResliceCenter[3];
    this->Renderer->GetDisplayPoint(displayResliceCenter);

    double centerOffset[3];
    vtkMath::Subtract(displayFocalPoint, displayResliceCenter, centerOffset);

    double distance = this->Renderer->GetActiveCamera()->GetDistance();

    double newCameraPosition[3] = { focalPoint[0] + normal[0] * distance,
      focalPoint[1] + normal[1] * distance, focalPoint[2] + normal[2] * distance };
    this->Renderer->GetActiveCamera()->SetPosition(newCameraPosition);

    double* viewUp = this->GetResliceCursor()->GetViewUp(planeOrientation);
    this->Renderer->GetActiveCamera()->SetViewUp(viewUp);

    this->Renderer->SetWorldPoint(center[0], center[1], center[2], 1.0);
    this->Renderer->WorldToDisplay();
    this->Renderer->GetDisplayPoint(displayResliceCenter);
    vtkMath::Add(displayResliceCenter, centerOffset, displayFocalPoint);

    this->Renderer->SetDisplayPoint(displayFocalPoint);
    this->Renderer->DisplayToWorld();
    double worldFocalPoint[4];
    this->Renderer->GetWorldPoint(worldFocalPoint);

    vtkNew<vtkPlane> plane;
    plane->SetNormal(normal);
    plane->SetOrigin(center);
    double t;
    double x[3];
    if (plane->IntersectWithLine(newCameraPosition, worldFocalPoint, t, x))
    {
      this->Renderer->GetActiveCamera()->SetFocalPoint(x);
    }
    else
    {
      this->Renderer->GetActiveCamera()->SetFocalPoint(worldFocalPoint);
    }

    this->Renderer->GetActiveCamera()->GetFocalPoint(focalPoint);
    this->Renderer->GetActiveCamera()->SetPosition(focalPoint[0] + normal[0] * distance,
      focalPoint[1] + normal[1] * distance, focalPoint[2] + normal[2] * distance);

    this->Renderer->ResetCameraClippingRange();
  }
}

//------------------------------------------------------------------------------
// This is the first axis of the reslice on the currently resliced plane
//
void vtkResliceCursorRepresentation::GetVector1(double v1[3])
{
  // From the initial view up vector, compute its cross product with the
  // current plane normal. This is Vector1. Then Vector2 is the cross product
  // of Vector1 and the normal.

  double v2[3];
  double* p2 = this->PlaneSource->GetPoint2();
  double* o = this->PlaneSource->GetOrigin();

  // Vector p2 -> o
  vtkMath::Subtract(p2, o, v2);

  const int planeOrientation = this->GetCursorAlgorithm()->GetReslicePlaneNormal();
  vtkPlane* plane = this->GetResliceCursor()->GetPlane(planeOrientation);

  double planeNormal[3];
  plane->GetNormal(planeNormal);

  vtkMath::Cross(v2, planeNormal, v1);
  vtkMath::Normalize(v1);
}

//------------------------------------------------------------------------------
// This is the second axis of the reslice on the currently resliced plane
// It is orthogonal to v1 and to the plane normal. Note that this is not the
// same as the reslice cursor's axes, which need not be orthogonal to each
// other. The goal of vector1 and vector2 is to compute the X and Y axes of
// the resliced plane.
//
void vtkResliceCursorRepresentation::GetVector2(double v2[3])
{
  const int planeOrientation = this->GetCursorAlgorithm()->GetReslicePlaneNormal();
  vtkPlane* plane = this->GetResliceCursor()->GetPlane(planeOrientation);
  double planeNormal[3];
  plane->GetNormal(planeNormal);

  double v1[3];
  this->GetVector1(v1);

  vtkMath::Cross(planeNormal, v1, v2);
  vtkMath::Normalize(v2);
}

//------------------------------------------------------------------------------
// Compute the origin of the reslice plane prior to transformations.
//
void vtkResliceCursorRepresentation::ComputeReslicePlaneOrigin()
{
  double bounds[6];
  this->GetResliceCursor()->GetImage()->GetBounds(bounds);

  double center[3], imageCenter[3], offset[3];
  this->GetResliceCursor()->GetCenter(center);
  this->GetResliceCursor()->GetImage()->GetCenter(imageCenter);

  // Offset based on the center of the image and how far from it the
  // reslice cursor is. This allows us to capture the whole image even
  // if we resliced in awkward places.

  for (int i = 0; i < 3; i++)
  {
    offset[i] = -fabs(center[i] - imageCenter[i]);
  }

  // Now resize the plane based on these offsets.

  const int planeOrientation = this->GetCursorAlgorithm()->GetReslicePlaneNormal();

  // Now set the size of the plane based on the location of the cursor so as to
  // at least completely cover the viewed region

  if (planeOrientation == 1)
  {
    this->PlaneSource->SetOrigin(bounds[0] + offset[0], center[1], bounds[4] + offset[2]);
    this->PlaneSource->SetPoint1(bounds[1] - offset[0], center[1], bounds[4] + offset[2]);
    this->PlaneSource->SetPoint2(bounds[0] + offset[0], center[1], bounds[5] - offset[2]);
  }
  else if (planeOrientation == 2)
  {
    this->PlaneSource->SetOrigin(bounds[0] + offset[0], bounds[2] + offset[1], center[2]);
    this->PlaneSource->SetPoint1(bounds[1] - offset[0], bounds[2] + offset[1], center[2]);
    this->PlaneSource->SetPoint2(bounds[0] + offset[0], bounds[3] - offset[1], center[2]);
  }
  else if (planeOrientation == 0)
  {
    this->PlaneSource->SetOrigin(center[0], bounds[2] + offset[1], bounds[4] + offset[2]);
    this->PlaneSource->SetPoint1(center[0], bounds[3] - offset[1], bounds[4] + offset[2]);
    this->PlaneSource->SetPoint2(center[0], bounds[2] + offset[1], bounds[5] - offset[2]);
  }
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::UpdateReslicePlane()
{
  if (!this->GetResliceCursor()->GetImage() || !this->TexturePlaneActor->GetVisibility())
  {
    return;
  }

  // Reinitialize the reslice plane.. We will recompute everything here.
  if (this->PlaneSource->GetPoint1()[0] == 0.5 && this->PlaneSource->GetOrigin()[0] == -0.5)
  {
    this->InitializeReslicePlane();
  }

  // Calculate appropriate pixel spacing for the reslicing
  //
  // this->GetResliceCursor()->UpdateInformation();
  double spacing[3];
  this->GetResliceCursor()->GetImage()->GetSpacing(spacing);
  double origin[3];
  this->GetResliceCursor()->GetImage()->GetOrigin(origin);
  int extent[6];
  this->GetResliceCursor()->GetImage()->GetExtent(extent);

  for (int i = 0; i < 3; i++)
  {
    if (extent[2 * i] > extent[2 * i + 1])
    {
      vtkErrorMacro("Invalid extent [" << extent[0] << ", " << extent[1] << ", " << extent[2]
                                       << ", " << extent[3] << ", " << extent[4] << ", "
                                       << extent[5] << "]."
                                       << " Perhaps the input data is empty?");
      break;
    }
  }

  const int planeOrientation = this->GetCursorAlgorithm()->GetReslicePlaneNormal();
  vtkPlane* plane = this->GetResliceCursor()->GetPlane(planeOrientation);
  double planeNormal[3];
  plane->GetNormal(planeNormal);

  // Compute the origin of the reslice plane prior to transformations.

  this->ComputeReslicePlaneOrigin();

  double* viewUp = this->GetResliceCursor()->GetViewUp(planeOrientation);
  double center[3];
  this->GetResliceCursor()->GetCenter(center);

  vtkResliceCursorRepresentation::TransformPlane(this->PlaneSource, center, planeNormal, viewUp);

  // Clip to bounds
  double* imageBounds = this->GetResliceCursor()->GetImage()->GetBounds();
  double boundedOrigin[3];
  double boundedP1[3];
  double boundedP2[3];
  this->PlaneSource->GetOrigin(boundedOrigin);
  this->PlaneSource->GetPoint1(boundedP1);
  this->PlaneSource->GetPoint2(boundedP2);
  int boundPlane =
    vtkResliceCursorRepresentation::BoundPlane(imageBounds, boundedOrigin, boundedP1, boundedP2);

  if (boundPlane == 1)
  {
    this->PlaneSource->SetOrigin(boundedOrigin);
    this->PlaneSource->SetPoint1(boundedP1);
    this->PlaneSource->SetPoint2(boundedP2);
  }

  double planeAxis1[3];
  double planeAxis2[3];

  vtkMath::Subtract(boundedP1, boundedOrigin, planeAxis1);
  vtkMath::Subtract(boundedP2, boundedOrigin, planeAxis2);

  // The x,y dimensions of the plane
  //
  const double planeSizeX = vtkMath::Normalize(planeAxis1);
  const double planeSizeY = vtkMath::Normalize(planeAxis2);

  double normal[3];
  this->PlaneSource->GetNormal(normal);

  this->NewResliceAxes->Identity();
  for (int i = 0; i < 3; i++)
  {
    this->NewResliceAxes->SetElement(0, i, planeAxis1[i]);
    this->NewResliceAxes->SetElement(1, i, planeAxis2[i]);
    this->NewResliceAxes->SetElement(2, i, normal[i]);
  }

  const double spacingX = fabs(planeAxis1[0] * spacing[0]) + fabs(planeAxis1[1] * spacing[1]) +
    fabs(planeAxis1[2] * spacing[2]);

  const double spacingY = fabs(planeAxis2[0] * spacing[0]) + fabs(planeAxis2[1] * spacing[1]) +
    fabs(planeAxis2[2] * spacing[2]);

  double planeOrigin[4];
  this->PlaneSource->GetOrigin(planeOrigin);

  planeOrigin[3] = 1.0;
  double originXYZW[4];
  double neworiginXYZW[4];

  this->NewResliceAxes->MultiplyPoint(planeOrigin, originXYZW);
  this->NewResliceAxes->Transpose();
  this->NewResliceAxes->MultiplyPoint(originXYZW, neworiginXYZW);

  this->NewResliceAxes->SetElement(0, 3, neworiginXYZW[0]);
  this->NewResliceAxes->SetElement(1, 3, neworiginXYZW[1]);
  this->NewResliceAxes->SetElement(2, 3, neworiginXYZW[2]);

  // Compute a new set of resliced extents
  int extentX, extentY;

  // Pad extent up to a power of two for efficient texture mapping

  // make sure we're working with valid values
  double realExtentX = (spacingX == 0) ? VTK_INT_MAX : planeSizeX / spacingX;

  // Sanity check the input data:
  // * if realExtentX is too large, extentX will wrap
  // * if spacingX is 0, things will blow up.
  if (realExtentX > (VTK_INT_MAX >> 1))
  {
    vtkErrorMacro(<< "Invalid X extent: " << realExtentX);
    extentX = 0;
  }
  else
  {
    extentX = 1;
    while (extentX < realExtentX)
    {
      extentX = extentX << 1;
    }
  }

  // make sure extentY doesn't wrap during padding
  double realExtentY = (spacingY == 0) ? VTK_INT_MAX : planeSizeY / spacingY;

  if (realExtentY > (VTK_INT_MAX >> 1))
  {
    vtkErrorMacro(<< "Invalid Y extent: " << realExtentY);
    extentY = 0;
  }
  else
  {
    extentY = 1;
    while (extentY < realExtentY)
    {
      extentY = extentY << 1;
    }
  }

  double outputSpacingX = (extentX == 0) ? 1.0 : planeSizeX / extentX;
  double outputSpacingY = (extentY == 0) ? 1.0 : planeSizeY / extentY;

  bool modify = false;
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 4; j++)
    {
      double d = this->NewResliceAxes->GetElement(i, j);
      if (d != this->ResliceAxes->GetElement(i, j))
      {
        this->ResliceAxes->SetElement(i, j, d);
        modify = true;
      }
    }
  }

  if (modify)
  {
    this->ResliceAxes->Modified();
  }

  this->SetResliceParameters(outputSpacingX, outputSpacingY, extentX, extentY);
  this->ResetCamera();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::ComputeOrigin(vtkMatrix4x4* m)
{
  double center[4] = { 0, 0, 0, 1 };
  double centerTransformed[4];

  this->GetResliceCursor()->GetCenter(center);
  m->MultiplyPoint(center, centerTransformed);

  for (int i = 0; i < 3; i++)
  {
    m->SetElement(i, 3, m->GetElement(i, 3) + center[i] - centerTransformed[i]);
  }
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation ::SetResliceParameters(
  double outputSpacingX, double outputSpacingY, int extentX, int extentY)
{
  vtkImageReslice* reslice = vtkImageReslice::SafeDownCast(this->Reslice);

  if (reslice)
  {
    // Set the default color the minimum scalar value
    double range[2];
    vtkImageData::SafeDownCast(reslice->GetInput())->GetScalarRange(range);
    reslice->SetBackgroundLevel(range[0]);

    this->ColorMap->SetInputConnection(reslice->GetOutputPort());
    reslice->TransformInputSamplingOff();
    reslice->AutoCropOutputOn();
    reslice->SetResliceAxes(this->ResliceAxes);
    reslice->SetOutputSpacing(outputSpacingX, outputSpacingY, 1);
    reslice->SetOutputOrigin(0.5 * outputSpacingX, 0.5 * outputSpacingY, 0);
    reslice->SetOutputExtent(0, extentX - 1, 0, extentY - 1, 0, 0);
  }
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation ::SetWindowLevel(double window, double level, int copy)
{
  if (copy)
  {
    this->CurrentWindow = window;
    this->CurrentLevel = level;
    return;
  }

  if (this->CurrentWindow == window && this->CurrentLevel == level)
  {
    return;
  }

  // if the new window is negative and the old window was positive invert table
  if (((window < 0 && this->CurrentWindow > 0) || (window > 0 && this->CurrentWindow < 0)))
  {
    this->InvertTable();
  }

  this->CurrentWindow = window;
  this->CurrentLevel = level;

  double rmin = this->CurrentLevel - 0.5 * fabs(this->CurrentWindow);
  double rmax = rmin + fabs(this->CurrentWindow);
  this->LookupTable->SetRange(rmin, rmax);

  this->Modified();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::GetWindowLevel(double wl[2])
{
  wl[0] = this->CurrentWindow;
  wl[1] = this->CurrentLevel;
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::WindowLevel(double X, double Y)
{
  if (!this->Renderer)
  {
    return;
  }

  const int* size = this->Renderer->GetSize();
  double window = this->InitialWindow;
  double level = this->InitialLevel;

  // Compute normalized delta

  double dx = 2.0 * (X - this->StartEventPosition[0]) / size[0];
  double dy = 2.0 * (this->StartEventPosition[1] - Y) / size[1];

  // Scale by current values

  if (fabs(window) > 0.01)
  {
    dx = dx * window;
  }
  else
  {
    dx = dx * (window < 0 ? -0.01 : 0.01);
  }
  if (fabs(level) > 0.01)
  {
    dy = dy * level;
  }
  else
  {
    dy = dy * (level < 0 ? -0.01 : 0.01);
  }

  // Abs so that direction does not flip

  if (window < 0.0)
  {
    dx = -1 * dx;
  }
  if (level < 0.0)
  {
    dy = -1 * dy;
  }

  // Compute new window level

  double newWindow = dx + window;
  double newLevel = level - dy;

  if (fabs(newWindow) < 0.01)
  {
    newWindow = 0.01 * (newWindow < 0 ? -1 : 1);
  }
  if (fabs(newLevel) < 0.01)
  {
    newLevel = 0.01 * (newLevel < 0 ? -1 : 1);
  }

  if ((newWindow < 0 && this->CurrentWindow > 0) || (newWindow > 0 && this->CurrentWindow < 0))
  {
    this->InvertTable();
  }

  double rmin = newLevel - 0.5 * fabs(newWindow);
  double rmax = rmin + fabs(newWindow);
  this->LookupTable->SetRange(rmin, rmax);

  if (this->DisplayText && (this->CurrentWindow != newWindow || this->CurrentLevel != newLevel))
  {
    this->CurrentWindow = newWindow;
    this->CurrentLevel = newLevel;
  }
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::InvertTable()
{
  vtkLookupTable* lut = vtkLookupTable::SafeDownCast(this->LookupTable);

  if (lut)
  {
    int index = lut->GetNumberOfTableValues();
    unsigned char swap[4];
    size_t num = 4 * sizeof(unsigned char);
    vtkUnsignedCharArray* table = lut->GetTable();
    for (int count = 0; count < --index; count++)
    {
      unsigned char* rgba1 = table->GetPointer(4 * count);
      unsigned char* rgba2 = table->GetPointer(4 * index);
      memcpy(swap, rgba1, num);
      memcpy(rgba1, rgba2, num);
      memcpy(rgba2, swap, num);
    }

    // force the lookuptable to update its InsertTime to avoid
    // rebuilding the array
    double temp[4];
    lut->GetTableValue(0, temp);
    lut->SetTableValue(0, temp);
  }
}

//------------------------------------------------------------------------------
int vtkResliceCursorRepresentation::BoundPlane(
  double bounds[6], double origin[3], double p1[3], double p2[3])
{
  double v1[3];
  vtkMath::Subtract(p1, origin, v1);
  vtkMath::Normalize(v1);

  double v2[3];
  vtkMath::Subtract(p2, origin, v2);
  vtkMath::Normalize(v2);

  double n[3] = { 0, 0, 1 };
  vtkMath::Cross(v1, v2, n);
  vtkMath::Normalize(n);

  vtkNew<vtkPlane> plane;
  plane->SetOrigin(origin);
  plane->SetNormal(n);

  vtkNew<vtkCubeSource> cubeSource;
  cubeSource->SetBounds(bounds);

  vtkNew<vtkCutter> cutter;
  cutter->SetCutFunction(plane.Get());
  cutter->SetInputConnection(cubeSource->GetOutputPort());
  cutter->Update();

  vtkPolyData* cutBounds = cutter->GetOutput();
  if (cutBounds->GetNumberOfPoints() == 0)
  {
    return 0;
  }

  double localBounds[6];
  vtkBoundingBox::ComputeLocalBounds(cutBounds->GetPoints(), v1, v2, n, localBounds);

  for (int i = 0; i < 3; i++)
  {
    origin[i] = localBounds[0] * v1[i] + localBounds[2] * v2[i] + localBounds[4] * n[i];
    p1[i] = localBounds[1] * v1[i] + localBounds[2] * v2[i] + localBounds[4] * n[i];
    p2[i] = localBounds[0] * v1[i] + localBounds[3] * v2[i] + localBounds[4] * n[i];
  }
  return 1;
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::TransformPlane(vtkPlaneSource* planeToTransform,
  double targetCenter[3], double targetNormal[3], double targetViewUp[3])
{
  planeToTransform->SetNormal(targetNormal);
  double currentViewUp[3];
  vtkMath::Subtract(planeToTransform->GetPoint2(), planeToTransform->GetOrigin(), currentViewUp);
  double angle = vtkMath::SignedAngleBetweenVectors(currentViewUp, targetViewUp, targetNormal);
  planeToTransform->Rotate(vtkMath::DegreesFromRadians(angle), targetNormal);
  planeToTransform->SetCenter(targetCenter);
}

//----------------------------------------------------------------------------
void vtkResliceCursorRepresentation::CreateDefaultResliceAlgorithm()
{
  // Allows users to optionally use their own reslice filters or other
  // algorithms here.
  if (!this->Reslice)
  {
    this->Reslice = vtkImageReslice::New();
  }
}

//------------------------------------------------------------------------------
vtkScalarsToColors* vtkResliceCursorRepresentation::CreateDefaultLookupTable()
{
  vtkLookupTable* lut = vtkLookupTable::New();
  lut->Register(this);
  lut->Delete();
  lut->SetNumberOfColors(256);
  lut->SetHueRange(0, 0);
  lut->SetSaturationRange(0, 0);
  lut->SetValueRange(0, 1);
  lut->SetAlphaRange(1, 1);
  lut->Build();
  return lut;
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::ActivateText(int i)
{
  this->TextActor->SetVisibility(this->Renderer && this->GetVisibility() && i && this->DisplayText);
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::ManageTextDisplay()
{
  if (!this->DisplayText)
  {
    return;
  }

  if (this->ManipulationMode == vtkResliceCursorRepresentation::WindowLevelling)
  {
    snprintf(this->TextBuff, VTK_RESLICE_CURSOR_REPRESENTATION_MAX_TEXTBUFF,
      "Window, Level: ( %g, %g )", this->CurrentWindow, this->CurrentLevel);
  }
  else if (this->ManipulationMode == vtkResliceCursorRepresentation::ResizeThickness)
  {
    // For now all the thickness' are the same anyway.
    snprintf(this->TextBuff, VTK_RESLICE_CURSOR_REPRESENTATION_MAX_TEXTBUFF,
      "Reslice Thickness: %g mm", this->GetResliceCursor()->GetThickness()[0]);
  }

  this->TextActor->SetInput(this->TextBuff);
  this->TextActor->Modified();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::SetTextProperty(vtkTextProperty* tprop)
{
  this->TextActor->SetTextProperty(tprop);
}

//------------------------------------------------------------------------------
vtkTextProperty* vtkResliceCursorRepresentation::GetTextProperty()
{
  return this->TextActor->GetTextProperty();
}

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::GenerateText()
{
  snprintf(this->TextBuff, VTK_RESLICE_CURSOR_REPRESENTATION_MAX_TEXTBUFF, "NA");
  this->TextActor->SetInput(this->TextBuff);
  this->TextActor->SetTextScaleModeToNone();

  vtkTextProperty* textprop = this->TextActor->GetTextProperty();
  textprop->SetColor(1, 1, 1);
  textprop->SetFontFamilyToArial();
  textprop->SetFontSize(18);
  textprop->BoldOff();
  textprop->ItalicOff();
  textprop->ShadowOff();
  textprop->SetJustificationToLeft();
  textprop->SetVerticalJustificationToBottom();

  vtkCoordinate* coord = this->TextActor->GetPositionCoordinate();
  coord->SetCoordinateSystemToNormalizedViewport();
  coord->SetValue(.01, .01);

  this->TextActor->VisibilityOff();
}

//------------------------------------------------------------------------------
// Prints an object if it exists.
#define vtkPrintMemberObjectMacro(obj, os, indent)                                                 \
  do                                                                                               \
  {                                                                                                \
    os << indent << #obj << ": ";                                                                  \
    if (this->obj)                                                                                 \
    {                                                                                              \
      os << this->obj << "\n";                                                                     \
    }                                                                                              \
    else                                                                                           \
    {                                                                                              \
      os << "(null)\n";                                                                            \
    }                                                                                              \
  } while (false)

//------------------------------------------------------------------------------
void vtkResliceCursorRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Tolerance: " << this->Tolerance << "\n";
  os << indent << "Thickness Label Text: " << this->GetThicknessLabelText() << "\n";
  os << indent << "PlaneSource: " << this->PlaneSource << "\n";
  if (this->PlaneSource)
  {
    this->PlaneSource->PrintSelf(os, indent.GetNextIndent());
  }
  os << indent << "PlaneSource: " << this->PlaneSource << "\n";
  vtkPrintMemberObjectMacro(ThicknessLabelFormat, os, indent);
  vtkPrintMemberObjectMacro(Reslice, os, indent);
  vtkPrintMemberObjectMacro(ThicknessTextProperty, os, indent);
  vtkPrintMemberObjectMacro(ThicknessTextMapper, os, indent);
  vtkPrintMemberObjectMacro(ThicknessTextActor, os, indent);
  vtkPrintMemberObjectMacro(ResliceAxes, os, indent);
  vtkPrintMemberObjectMacro(NewResliceAxes, os, indent);
  vtkPrintMemberObjectMacro(ColorMap, os, indent);
  vtkPrintMemberObjectMacro(TexturePlaneActor, os, indent);
  vtkPrintMemberObjectMacro(Texture, os, indent);
  vtkPrintMemberObjectMacro(LookupTable, os, indent);
  vtkPrintMemberObjectMacro(ImageActor, os, indent);
  vtkPrintMemberObjectMacro(TextActor, os, indent);
  os << indent << "RestrictPlaneToVolume: " << this->RestrictPlaneToVolume << "\n";
  os << indent << "ShowReslicedImage: " << this->ShowReslicedImage << "\n";
  os << indent << "OriginalWindow: " << this->OriginalWindow << "\n";
  os << indent << "OriginalLevel: " << this->OriginalLevel << "\n";
  os << indent << "CurrentWindow: " << this->CurrentWindow << "\n";
  os << indent << "CurrentLevel: " << this->CurrentLevel << "\n";
  os << indent << "InitialWindow: " << this->InitialWindow << "\n";
  os << indent << "InitialLevel: " << this->InitialLevel << "\n";
  os << indent << "UseImageActor: " << this->UseImageActor << "\n";
  os << indent << "DisplayText: " << this->DisplayText << "\n";
  // this->ManipulationMode
  // this->LastEventPosition[2]
  // this->TextBuff[128];
  // this->ResliceAxes;
  // this->LookupTable;
  // this->Reslice;
  // this->ThicknessLabelFormat;
  // this->ColorMap;
  // this->ImageActor;
  // this->ThicknessTextProperty;
  // this->ThicknessTextMapper;
  // this->ThicknessTextActor;
  // this->NewResliceAxes;
  // this->TexturePlaneActor;
  // this->Texture;
  // this->TextActor;
}
VTK_ABI_NAMESPACE_END
