/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkVolumeInspectionRepresentation.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkVolumeInspectionRepresentation.h"

#include "smtk/common/UUID.h"
#include "smtk/extension/vtk/source/vtkResourceMultiBlockSource.h"

#include "vtkActor.h"
#include "vtkAssemblyPath.h"
#include "vtkBox.h"
#include "vtkDataObjectTreeIterator.h"
#include "vtkEventData.h"
#include "vtkImageData.h"
#include "vtkInteractorObserver.h"
#include "vtkMath.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkOutlineFilter.h"
#include "vtkPVImageSliceMapper2.h"
#include "vtkPVLODActor.h"
#include "vtkPickingManager.h"
#include "vtkPolyDataMapper.h"
#include "vtkPropPicker.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkSphereSource.h"

#include <cfloat> //for FLT_EPSILON

vtkStandardNewMacro(vtkVolumeInspectionRepresentation);

//----------------------------------------------------------------------------
vtkVolumeInspectionRepresentation::vtkVolumeInspectionRepresentation()
  : InBuildRepresentation(false)
  , LookupTable(nullptr)
{
  // this->CropPlaneToBoundingBox = true;

  // Handle size is in pixels for this widget
  this->HandleSize = 5.0;

  // Build the representation of the widget

  // Create the origin handle
  int elemId = Origin;
  this->OriginSphere->SetOutputPointsPrecision(vtkAlgorithm::DOUBLE_PRECISION);
  this->OriginSphere->SetThetaResolution(16);
  this->OriginSphere->SetPhiResolution(8);
  this->Elements[elemId].Mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  this->Elements[elemId].Mapper->SetInputConnection(this->OriginSphere->GetOutputPort());
  this->Elements[elemId].Actor = vtkSmartPointer<vtkActor>::New();
  this->Elements[elemId].Actor->SetMapper(this->Elements[elemId].Mapper);

  // Create outline box
  elemId = Outline;
  this->Box->SetDimensions(2, 2, 2);
  this->OutlineBox->SetOutputPointsPrecision(vtkAlgorithm::DOUBLE_PRECISION);
  this->OutlineBox->SetInputData(this->Box);
  this->Elements[elemId].Mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  this->Elements[elemId].Mapper->SetInputConnection(this->OutlineBox->GetOutputPort());
  this->Elements[elemId].Actor = vtkSmartPointer<vtkActor>::New();
  this->Elements[elemId].Actor->SetMapper(this->Elements[elemId].Mapper);
  this->OutsideBounds = 0;
  this->ConstrainToWidgetBounds = 1;
  this->DrawOutline = 1;

  // Use a blank image input initially. This avoids warnings when there is no input.
  vtkNew<vtkImageData> blank;
  for (int ii = 0; ii < 3; ++ii)
  {
    elemId = ii + PlaneX;
    this->Elements[elemId].Mapper = vtkSmartPointer<vtkPVImageSliceMapper2>::New();
    this->Elements[elemId].Mapper->SetInputDataObject(0, blank);
    this->Elements[elemId].Actor = vtkSmartPointer<vtkPVLODActor>::New();
    this->Elements[elemId].Actor->SetMapper(this->Elements[elemId].Mapper);
    this->Elements[elemId].Mapper->SetLookupTable(this->LookupTable);
    auto* sliceMapper = vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX + ii].Mapper);
    sliceMapper->SetSliceMode(vtkPVImageSliceMapper2::YZ_PLANE + ii);
    this->planePerm[ii] = ii;
  }

  this->Image = nullptr;
  this->ImageID = nullptr;
  this->DrawPlane = 1;

  // Manage the picking stuff. vtkCellPicker seems to use infinite planes, so use vtkPropPicker.
  for (int ii = Origin; ii < Outline; ++ii)
  {
    this->Picker->AddPickList(this->Elements[ii].Actor);
  }
  this->Picker->PickFromListOn();

  // Set up the initial properties
  this->CreateDefaultProperties();

  // Pass the initial properties to the actors.
  this->Elements[Origin].Actor->SetProperty(this->SphereProperty);
  this->Elements[PlaneX].Actor->SetProperty(this->PlaneProperty);
  this->Elements[PlaneY].Actor->SetProperty(this->PlaneProperty);
  this->Elements[PlaneZ].Actor->SetProperty(this->PlaneProperty);
  this->Elements[Outline].Actor->SetProperty(this->OutlineProperty);

  this->RepresentationState = vtkVolumeInspectionRepresentation::Outside;
}

//----------------------------------------------------------------------------
vtkVolumeInspectionRepresentation::~vtkVolumeInspectionRepresentation()
{
  if (this->Image)
  {
    this->Image->Delete();
    this->Image = nullptr;
  }
  if (this->ImageID)
  {
    delete[] this->ImageID;
    this->ImageID = nullptr;
  }
  if (this->LookupTable)
  {
    this->LookupTable->Delete();
    this->LookupTable = nullptr;
  }
}

void vtkVolumeInspectionRepresentation::SetInputData(vtkImageData* in)
{
  // Don't allow a null input dataset into the mapper.
  static vtkNew<vtkImageData> dummy;
  if (!in)
  {
    in = dummy;
  }
  for (int ii = 0; ii < 3; ++ii)
  {
    auto* sliceMapper = vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX + ii].Mapper);
    if (sliceMapper)
    {
      sliceMapper->SetInputData(in);
    }
  }

  // figure out the permutations of slice
  double eps = 1e-8;
  for (int ii = 0; ii < 3; ++ii) // loop over 3 directions
  {
    int ijk0[3] = { 0, 0, 0 };
    int ijk1[3] = { 0, 0, 0 };
    ijk1[ii] = 1;
    double xyz0[3];
    double xyz1[3];
    in->TransformIndexToPhysicalPoint(ijk0, xyz0);
    in->TransformIndexToPhysicalPoint(ijk1, xyz1);
    for (int jj = 0; jj < 3; ++jj)
    {
      if (fabs(xyz1[jj] - xyz0[jj]) > eps)
      {
        this->planePerm[ii] = jj;
        break;
      }
    }
  }

  this->PlaceWidget(in->GetBounds());
}

//----------------------------------------------------------------------------
int vtkVolumeInspectionRepresentation::ComputeInteractionState(int X, int Y, int modify)
{
  (void)modify;

  // See if anything has been selected
  vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->Picker);

  if (path == nullptr) // Not picking this widget
  {
    this->SetRepresentationState(vtkVolumeInspectionRepresentation::Outside);
    this->InteractionState = vtkVolumeInspectionRepresentation::Outside;
    return this->InteractionState;
  }

  // Something picked, continue
  this->ValidPick = 1;
  // this->InteractionState = vtkVolumeInspectionRepresentation::Moving;
  // this->Picker->GetPickPosition(this->LastPickPosition);

  // Depending on the interaction state (set by the widget) we modify
  // this state based on what is picked.
  if (this->InteractionState == vtkVolumeInspectionRepresentation::Moving)
  {
    vtkProp* prop = path->GetFirstNode()->GetViewProp();
    if (prop == this->Elements[PlaneX].Actor)
    {
      this->InteractionState = vtkVolumeInspectionRepresentation::PushingX;
      this->SetRepresentationState(vtkVolumeInspectionRepresentation::PushingX);
    }
    else if (prop == this->Elements[PlaneY].Actor)
    {
      this->InteractionState = vtkVolumeInspectionRepresentation::PushingY;
      this->SetRepresentationState(vtkVolumeInspectionRepresentation::PushingY);
    }
    else if (prop == this->Elements[PlaneZ].Actor)
    {
      this->InteractionState = vtkVolumeInspectionRepresentation::PushingZ;
      this->SetRepresentationState(vtkVolumeInspectionRepresentation::PushingZ);
    }
    else if (prop == this->Elements[Origin].Actor)
    {
      this->InteractionState = vtkVolumeInspectionRepresentation::MovingOrigin;
      this->SetRepresentationState(vtkVolumeInspectionRepresentation::MovingOrigin);
    }
    else
    {
      this->InteractionState = vtkVolumeInspectionRepresentation::Outside;
      this->SetRepresentationState(vtkVolumeInspectionRepresentation::Outside);
    }
  }
  else
  {
    this->InteractionState = vtkVolumeInspectionRepresentation::Outside;
  }

  return this->InteractionState;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::SetRepresentationState(int state)
{
  if (this->RepresentationState == state)
  {
    return;
  }

  // Clamp the state
  state = (state < vtkVolumeInspectionRepresentation::Outside
      ? vtkVolumeInspectionRepresentation::Outside
      : (state > vtkVolumeInspectionRepresentation::PushingZ
            ? vtkVolumeInspectionRepresentation::PushingZ
            : state));

  this->RepresentationState = state;
  this->Modified();

  if (state == vtkVolumeInspectionRepresentation::PushingX)
  {
    this->HighlightPlane(PlaneX, 1);
  }
  else if (state == vtkVolumeInspectionRepresentation::PushingY)
  {
    this->HighlightPlane(PlaneY, 1);
  }
  else if (state == vtkVolumeInspectionRepresentation::PushingZ)
  {
    this->HighlightPlane(PlaneZ, 1);
  }
  else if (state == vtkVolumeInspectionRepresentation::MovingOrigin)
  {
    this->HighlightOrigin(1);
  }
  else
  {
    this->HighlightPlane(PlaneX, 0);
    this->HighlightPlane(PlaneY, 0);
    this->HighlightPlane(PlaneZ, 0);
    this->HighlightOrigin(0);
    this->HighlightOutline(0);
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::StartWidgetInteraction(double eventPos[2])
{
  this->StartEventPosition[0] = eventPos[0];
  this->StartEventPosition[1] = eventPos[1];
  this->StartEventPosition[2] = 0.0;

  this->LastEventPosition[0] = eventPos[0];
  this->LastEventPosition[1] = eventPos[1];
  this->LastEventPosition[2] = 0.0;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::WidgetInteraction(double newEventPos[2])
{
  // Do different things depending on state
  // Calculations everybody does
  double focalPoint[4];
  double pickPoint[4];
  double prevPickPoint[4];
  double z;

  vtkCamera* camera = this->Renderer->GetActiveCamera();
  if (!camera)
  {
    return;
  }

  // Compute the two points defining the motion vector
  double pos[3];
  this->Picker->GetPickPosition(pos);
  vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, pos[0], pos[1], pos[2], focalPoint);
  z = focalPoint[2];
  vtkInteractorObserver::ComputeDisplayToWorld(
    this->Renderer, this->LastEventPosition[0], this->LastEventPosition[1], z, prevPickPoint);
  vtkInteractorObserver::ComputeDisplayToWorld(
    this->Renderer, newEventPos[0], newEventPos[1], z, pickPoint);

  if (this->InteractionState == vtkVolumeInspectionRepresentation::MovingOrigin)
  {
    this->TranslateOrigin(prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkVolumeInspectionRepresentation::PushingX)
  {
    this->PushSlice(0, prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkVolumeInspectionRepresentation::PushingY)
  {
    this->PushSlice(1, prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkVolumeInspectionRepresentation::PushingZ)
  {
    this->PushSlice(2, prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkVolumeInspectionRepresentation::Outside)
  {
    // do nothing
  }

  this->LastEventPosition[0] = newEventPos[0];
  this->LastEventPosition[1] = newEventPos[1];
  this->LastEventPosition[2] = 0.0;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::EndWidgetInteraction(double newEventPos[2])
{
  (void)newEventPos;
  this->SetRepresentationState(vtkVolumeInspectionRepresentation::Outside);
}

//----------------------------------------------------------------------------
double* vtkVolumeInspectionRepresentation::GetPlaneColor()
{
  static std::array<double, 4> result;
  this->PlaneProperty->GetAmbientColor(result.data());
  result[3] = this->PlaneProperty->GetOpacity();
  return result.data();
}

void vtkVolumeInspectionRepresentation::GetPlaneColor(double& red,
  double& green,
  double& blue,
  double& alpha)
{
  this->PlaneProperty->GetAmbientColor(red, green, blue);
  alpha = this->PlaneProperty->GetOpacity();
}

void vtkVolumeInspectionRepresentation::SetPlaneColor(const double* rgba)
{
  this->PlaneProperty->SetAmbientColor(rgba);
  this->PlaneProperty->SetOpacity(rgba[3]);
}

double* vtkVolumeInspectionRepresentation::GetSelectedPlaneColor()
{
  static std::array<double, 4> result;
  this->SelectedPlaneProperty->GetAmbientColor(result.data());
  result[3] = this->SelectedPlaneProperty->GetOpacity();
  return result.data();
}

void vtkVolumeInspectionRepresentation::GetSelectedPlaneColor(double& red,
  double& green,
  double& blue,
  double& alpha)
{
  this->SelectedPlaneProperty->GetAmbientColor(red, green, blue);
  alpha = this->SelectedPlaneProperty->GetOpacity();
}

void vtkVolumeInspectionRepresentation::SetSelectedPlaneColor(const double* rgba)
{
  this->SelectedPlaneProperty->SetAmbientColor(rgba);
  this->SelectedPlaneProperty->SetOpacity(rgba[3]);
}

//----------------------------------------------------------------------
double* vtkVolumeInspectionRepresentation::GetBounds()
{
  this->BuildRepresentation();
  this->BoundingBox->SetBounds(this->Elements[Outline].Actor->GetBounds());
  this->BoundingBox->AddBounds(this->Elements[PlaneX].Actor->GetBounds());
  this->BoundingBox->AddBounds(this->Elements[PlaneY].Actor->GetBounds());
  this->BoundingBox->AddBounds(this->Elements[PlaneZ].Actor->GetBounds());
  this->BoundingBox->AddBounds(this->Elements[Origin].Actor->GetBounds());

  return this->BoundingBox->GetBounds();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::GetActors(vtkPropCollection* pc)
{
  for (int ii = 0; ii < NumberOfElements; ++ii)
  {
    this->Elements[ii].Actor->GetActors(pc);
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::ReleaseGraphicsResources(vtkWindow* w)
{
  for (int ii = 0; ii < NumberOfElements; ++ii)
  {
    this->Elements[ii].Actor->ReleaseGraphicsResources(w);
  }
}

//----------------------------------------------------------------------------
int vtkVolumeInspectionRepresentation::RenderOpaqueGeometry(vtkViewport* v)
{
  int count = 0;
  this->BuildRepresentation();
  if (this->DrawOutline)
  {
    count += this->Elements[Outline].Actor->RenderOpaqueGeometry(v);
  }

  count += this->Elements[Origin].Actor->RenderOpaqueGeometry(v);

  if (this->DrawPlane)
  {
    count += this->Elements[PlaneX].Actor->RenderOpaqueGeometry(v);
    count += this->Elements[PlaneY].Actor->RenderOpaqueGeometry(v);
    count += this->Elements[PlaneZ].Actor->RenderOpaqueGeometry(v);
  }

  if (this->DrawOrigin)
  {
    count += this->Elements[Origin].Actor->RenderOpaqueGeometry(v);
  }

  return count;
}

//-----------------------------------------------------------------------------
int vtkVolumeInspectionRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* v)
{
  int count = 0;
  this->BuildRepresentation();
  if (this->DrawOutline)
  {
    count += this->Elements[Outline].Actor->RenderTranslucentPolygonalGeometry(v);
  }
  count += this->Elements[Origin].Actor->RenderTranslucentPolygonalGeometry(v);
  if (this->DrawPlane)
  {
    count += this->Elements[PlaneX].Actor->RenderTranslucentPolygonalGeometry(v);
    count += this->Elements[PlaneY].Actor->RenderTranslucentPolygonalGeometry(v);
    count += this->Elements[PlaneZ].Actor->RenderTranslucentPolygonalGeometry(v);
  }
  if (this->DrawOrigin)
  {
    count += this->Elements[Origin].Actor->RenderTranslucentPolygonalGeometry(v);
  }

  return count;
}

//-----------------------------------------------------------------------------
vtkTypeBool vtkVolumeInspectionRepresentation::HasTranslucentPolygonalGeometry()
{
  int result = 0;
  if (this->DrawOutline)
  {
    result |= this->Elements[Outline].Actor->HasTranslucentPolygonalGeometry();
  }
  result |= this->Elements[Origin].Actor->HasTranslucentPolygonalGeometry();
  if (this->DrawPlane)
  {
    result |= this->Elements[PlaneX].Actor->HasTranslucentPolygonalGeometry();
    result |= this->Elements[PlaneY].Actor->HasTranslucentPolygonalGeometry();
    result |= this->Elements[PlaneZ].Actor->HasTranslucentPolygonalGeometry();
  }

  if (this->DrawOrigin)
  {
    result |= this->Elements[Origin].Actor->HasTranslucentPolygonalGeometry();
  }

  return result;
}

std::string vtkVolumeInspectionRepresentation::InteractionStateToString(int state)
{
  std::string str;
  switch (state)
  {
    case Outside:
      str = "Outside";
      break;
    case Moving:
      str = "Moving";
      break;
    // case OnPlanes: str = "OnPlanes"; break;
    // case MovingOutline: str = "MovingOutline"; break;
    case MovingOrigin:
      str = "MovingOrigin";
      break;
    case PushingX:
      str = "PushingX";
      break;
    case PushingY:
      str = "PushingY";
      break;
    case PushingZ:
      str = "PushingZ";
      break;
    default:
      str = "Unknown";
      break;
  }
  return str;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  if (this->PlaneProperty)
  {
    os << indent << "Plane Property: " << this->PlaneProperty << "\n";
  }
  else
  {
    os << indent << "Plane Property: (none)\n";
  }

  if (this->SelectedPlaneProperty)
  {
    os << indent << "Selected Plane Property: " << this->SelectedPlaneProperty << "\n";
  }
  else
  {
    os << indent << "Selected Plane Property: (none)\n";
  }

  if (this->OutlineProperty)
  {
    os << indent << "Outline Property: " << this->OutlineProperty << "\n";
  }
  else
  {
    os << indent << "Outline Property: (none)\n";
  }
  if (this->SelectedOutlineProperty)
  {
    os << indent << "Selected Outline Property: " << this->SelectedOutlineProperty << "\n";
  }
  else
  {
    os << indent << "Selected Outline Property: (none)\n";
  }

  os << indent << "Widget Bounds: " << this->WidgetBounds[0] << ", " << this->WidgetBounds[1]
     << ", " << this->WidgetBounds[2] << ", " << this->WidgetBounds[3] << ", "
     << this->WidgetBounds[4] << ", " << this->WidgetBounds[5] << "\n";

  os << indent << "Outside Bounds: " << (this->OutsideBounds ? "On" : "Off") << "\n";
  os << indent << "Constrain to Widget Bounds: " << (this->ConstrainToWidgetBounds ? "On" : "Off")
     << "\n";
  os << indent << "Draw Outline: " << (this->DrawOutline ? "On" : "Off") << "\n";
  os << indent << "Draw Plane: " << (this->DrawPlane ? "On" : "Off") << "\n";

  os << indent << "Representation State: ";
  switch (this->RepresentationState)
  {
    case Outside:
      os << "Outside\n";
      break;
    case Moving:
      os << "Moving\n";
      break;
    case MovingOrigin:
      os << "MovingOrigin\n";
      break;
    case PushingX:
      os << "PushingX\n";
      break;
    case PushingY:
      os << "PushingY\n";
      break;
    case PushingZ:
      os << "PushingZ\n";
      break;
    default:
      os << "Unknown (" << this->RepresentationState << ")\n";
      break;
  }

  // this->InteractionState is printed in superclass
  // this is commented to avoid PrintSelf errors
}

void vtkVolumeInspectionRepresentation::HighlightElement(ElementType elem, int highlight)
{
  switch (elem)
  {
    case Origin:
      this->HighlightOrigin(highlight);
      break;
    case PlaneX:
      this->HighlightPlane(PlaneX, highlight);
      break;
    case PlaneY:
      this->HighlightPlane(PlaneY, highlight);
      break;
    case PlaneZ:
      this->HighlightPlane(PlaneZ, highlight);
      break;
    case Outline:
      this->HighlightOutline(highlight);
      break;
    case NumberOfElements:
      this->HighlightOrigin(highlight);
      this->HighlightPlane(PlaneX, highlight);
      this->HighlightPlane(PlaneY, highlight);
      this->HighlightPlane(PlaneZ, highlight);
      this->HighlightOutline(highlight);
      break;
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::HighlightPlane(ElementType plane, int highlight)
{
  plane = (plane < PlaneX) ? PlaneX : plane;
  plane = (plane > PlaneZ) ? PlaneZ : plane;

  if (highlight)
  {
    this->Elements[plane].Actor->SetProperty(this->SelectedPlaneProperty);
  }
  else
  {
    this->Elements[plane].Actor->SetProperty(this->PlaneProperty);
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::HighlightOutline(int highlight)
{
  if (highlight)
  {
    this->Elements[Outline].Actor->SetProperty(this->SelectedOutlineProperty);
  }
  else
  {
    this->Elements[Outline].Actor->SetProperty(this->OutlineProperty);
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::HighlightOrigin(int highlight)
{
  if (highlight)
  {
    this->Elements[Origin].Actor->SetProperty(this->SelectedSphereProperty);
  }
  else
  {
    this->Elements[Origin].Actor->SetProperty(this->SphereProperty);
  }
}

//----------------------------------------------------------------------------
// Loop through all points and translate them
void vtkVolumeInspectionRepresentation::TranslateOrigin(const double* p1, const double* p2)
{
  // Get the motion vector
  double v[3] = { 0, 0, 0 };

  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  // Add to the current point, project back down onto plane
  double o[3];
  this->GetOrigin(o);
  double newOrigin[3] = { o[0] + v[0], o[1] + v[1], o[2] + v[2] };

  this->SetOrigin(newOrigin);
  this->BuildRepresentation();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::CreateDefaultProperties()
{
  this->SphereProperty = vtkProperty::New();
  this->SphereProperty->SetColor(1, 1, 1);
  this->SphereProperty->SetLineWidth(2);

  this->SelectedSphereProperty = vtkProperty::New();
  this->SelectedSphereProperty->SetColor(1, 0, 0);
  this->SelectedSphereProperty->SetLineWidth(4);

  // Plane properties
  this->PlaneProperty = vtkProperty::New();
  this->PlaneProperty->SetAmbient(1.0);
  this->PlaneProperty->SetAmbientColor(1.0, 1.0, 1.0);
  this->PlaneProperty->SetOpacity(0.5);
  this->Elements[PlaneX].Actor->SetProperty(this->PlaneProperty);
  this->Elements[PlaneY].Actor->SetProperty(this->PlaneProperty);
  this->Elements[PlaneZ].Actor->SetProperty(this->PlaneProperty);

  this->SelectedPlaneProperty = vtkProperty::New();
  this->SelectedPlaneProperty->SetAmbient(1.0);
  this->SelectedPlaneProperty->SetAmbientColor(0.0, 1.0, 0.0);
  this->SelectedPlaneProperty->SetOpacity(0.25);

  // Outline properties
  this->OutlineProperty = vtkProperty::New();
  this->OutlineProperty->SetAmbient(1.0);
  this->OutlineProperty->SetAmbientColor(1.0, 1.0, 1.0);

  this->SelectedOutlineProperty = vtkProperty::New();
  this->SelectedOutlineProperty->SetAmbient(1.0);
  this->SelectedOutlineProperty->SetAmbientColor(0.0, 1.0, 0.0);
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::PlaceWidget(double bounds[6])
{
  // Set up the bounding box
  this->Box->SetOrigin(bounds[0], bounds[2], bounds[4]);
  this->Box->SetSpacing((bounds[1] - bounds[0]), (bounds[3] - bounds[2]), (bounds[5] - bounds[4]));
  this->OutlineBox->Update();

  this->InitialLength = sqrt((bounds[1] - bounds[0]) * (bounds[1] - bounds[0]) +
    (bounds[3] - bounds[2]) * (bounds[3] - bounds[2]) +
    (bounds[5] - bounds[4]) * (bounds[5] - bounds[4]));

  for (int i = 0; i < 6; i++)
  {
    this->InitialBounds[i] = bounds[i];
    this->WidgetBounds[i] = bounds[i];
  }

  this->ValidPick = 1; // since we have positioned the widget successfully
  if (!this->InBuildRepresentation)
  {
    this->BuildRepresentation();
  }
}

double* vtkVolumeInspectionRepresentation::GetOrigin()
{
  this->SliceToOrigin(this->Slice, this->origin);
  return this->origin;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::GetOrigin(double xyz[3]) const
{
  this->SliceToOrigin(this->Slice, xyz);
}

//----------------------------------------------------------------------------
// Description:
// Set the origin of the slices
void vtkVolumeInspectionRepresentation::SetOrigin(double x, double y, double z)
{
  double xyz[3] = { x, y, z };
  this->SetOrigin(xyz);
}

//----------------------------------------------------------------------------
// Description:
// Set the origin of the plane. Note that the origin is clamped slightly inside
// the bounding box or the plane tends to disappear as it hits the boundary (and
// when the plane is parallel to one of the faces of the bounding box).
void vtkVolumeInspectionRepresentation::SetOrigin(double xyz[3])
{
  this->OriginToSlice(xyz, this->Slice);
  this->SetSlice(this->Slice);
}

void vtkVolumeInspectionRepresentation::GetSlice(int slice[3]) const
{
  slice[0] = this->Slice[0];
  slice[1] = this->Slice[1];
  slice[2] = this->Slice[2];
}

const int* vtkVolumeInspectionRepresentation::GetSlice()
{
  return this->Slice;
}

void vtkVolumeInspectionRepresentation::SetSlice(int slice[3], bool rebuild)
{
  this->SetSlice(slice[0], slice[1], slice[2], rebuild);
}

void vtkVolumeInspectionRepresentation::SetSlice(int i, int j, int k, bool rebuild)
{
  vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX].Mapper)->SetSlice(i);
  vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneY].Mapper)->SetSlice(j);
  vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneZ].Mapper)->SetSlice(k);
  this->Slice[0] = i;
  this->Slice[1] = j;
  this->Slice[2] = k;
  if (rebuild)
  {
    this->BuildRepresentation();
  }
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::SetDrawPlane(vtkTypeBool drawPlane)
{
  if (drawPlane == this->DrawPlane)
  {
    return;
  }

  this->Modified();
  this->DrawPlane = drawPlane;
  this->BuildRepresentation();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::SetDrawOrigin(vtkTypeBool drawOrigin)
{
  if (drawOrigin == this->DrawOrigin)
  {
    return;
  }

  this->Modified();
  this->DrawOrigin = drawOrigin;
  this->BuildRepresentation();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::SetDrawOutline(vtkTypeBool val)
{
  if (val == this->DrawOutline)
  {
    return;
  }

  if (val)
  {
    this->Picker->AddPickList(this->Elements[Outline].Actor);
  }
  else
  {
    this->Picker->DeletePickList(this->Elements[Outline].Actor);
  }
  this->Modified();
  this->DrawOutline = val;
  this->BuildRepresentation();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::UpdatePlacement()
{
  this->OutlineBox->Update();
  this->BuildRepresentation();
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::PushSlice(int iPlane, double d)
{
  double org[3];
  this->GetOrigin(org);
  org[iPlane] += d;
  this->SetOrigin(org);
}

void vtkVolumeInspectionRepresentation::PushSlice(int iPlane, const double* p1, const double* p2)
{
  // Get the motion vector
  double v[3];
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];
  // TODO: Are normals always in coordinate directions? Should we record a signed planePerm?
  static double nrm[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };

  int plane = this->planePerm[iPlane];
  this->PushSlice(plane, vtkMath::Dot(v, nrm[plane]));
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::BuildRepresentation()
{
  this->InBuildRepresentation = true;
  if (!this->Renderer || !this->Renderer->GetRenderWindow())
  {
    this->InBuildRepresentation = false;
    return;
  }
  this->UpdateMapper();

  vtkInformation* info = this->GetPropertyKeys();
  for (int ii = 0; ii < NumberOfElements; ++ii)
  {
    this->Elements[ii].Actor->SetPropertyKeys(info);
  }

  bool rebuild = this->GetMTime() > this->BuildTime ||
    this->Elements[PlaneX].Mapper->GetMTime() > this->BuildTime ||
    this->Elements[PlaneY].Mapper->GetMTime() > this->BuildTime ||
    this->Elements[PlaneZ].Mapper->GetMTime() > this->BuildTime ||
    this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime;

  if (!rebuild)
  {
    this->InBuildRepresentation = false;
    return;
  }

  // double org[3];
  // this->GetOrigin(org);
  this->SliceToOrigin(this->Slice, this->origin);
  double* org = this->GetOrigin();
  double bounds[6];
  std::copy(this->WidgetBounds, this->WidgetBounds + 6, bounds);

  if (!this->OutsideBounds)
  {
    // restrict the origin inside InitialBounds
    double* ibounds = this->InitialBounds;
    for (int i = 0; i < 3; i++)
    {
      if (org[i] < ibounds[2 * i])
      {
        org[i] = ibounds[2 * i];
      }
      else if (org[i] > ibounds[2 * i + 1])
      {
        org[i] = ibounds[2 * i + 1];
      }
    }
  }

  if (this->ConstrainToWidgetBounds)
  {
    if (!this->OutsideBounds)
    {
      // org cannot move outside InitialBounds. Therefore, restrict
      // movement of the Box.
      double v[3] = { 0.0, 0.0, 0.0 };
      for (int i = 0; i < 3; ++i)
      {
        if (org[i] <= bounds[2 * i])
        {
          v[i] = org[i] - bounds[2 * i] - FLT_EPSILON;
        }
        else if (org[i] >= bounds[2 * i + 1])
        {
          v[i] = org[i] - bounds[2 * i + 1] + FLT_EPSILON;
        }
        bounds[2 * i] += v[i];
        bounds[2 * i + 1] += v[i];
      }
    }

    // restrict origin inside bounds
    for (int i = 0; i < 3; ++i)
    {
      if (org[i] <= bounds[2 * i])
      {
        org[i] = bounds[2 * i] + FLT_EPSILON;
      }
      if (org[i] >= bounds[2 * i + 1])
      {
        org[i] = bounds[2 * i + 1] - FLT_EPSILON;
      }
    }
  }
  else // plane can move freely, adjust the bounds to change with it
  {
    double offset = this->Box->GetLength() * 0.02;
    for (int i = 0; i < 3; ++i)
    {
      bounds[2 * i] = vtkMath::Min(org[i] - offset, this->WidgetBounds[2 * i]);
      bounds[2 * i + 1] = vtkMath::Max(org[i] + offset, this->WidgetBounds[2 * i + 1]);
    }
  }

  this->Box->SetOrigin(bounds[0], bounds[2], bounds[4]);
  this->Box->SetSpacing((bounds[1] - bounds[0]), (bounds[3] - bounds[2]), (bounds[5] - bounds[4]));
  this->OutlineBox->Update();
  this->OriginSphere->SetCenter(org[0], org[1], org[2]);

  this->OriginToSlice(this->origin, this->Slice);
  for (int ii = 0; ii < 3; ++ii)
  {
    vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX + ii].Mapper)
      ->SetSlice(this->Slice[ii]);
    vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX + ii].Mapper)->Update();
  }

  this->SizeOrigin();
  this->BuildTime.Modified();
  this->InBuildRepresentation = false;
}

//----------------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::SizeOrigin()
{
  double radius =
    this->vtkWidgetRepresentation::SizeHandlesInPixels(1.5, this->OriginSphere->GetCenter());

  this->OriginSphere->SetRadius(radius);
}

//----------------------------------------------------------------------
void vtkVolumeInspectionRepresentation::RegisterPickers()
{
  vtkPickingManager* pm = this->GetPickingManager();
  if (!pm)
  {
    return;
  }
  pm->AddPicker(this->Picker, this);
}

void vtkVolumeInspectionRepresentation::SliceToOrigin(const int slice[3], double origin[3]) const
{
  auto* image = vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX].Mapper)->GetInput();
  if (!image)
  {
    origin[0] = 0.0;
    origin[1] = 0.0;
    origin[2] = 0.0;
    return;
  }
  // double dslice[3]  = {static_cast<double>(slice[0]), static_cast<double>(slice[1]), static_cast<double>(slice[2])};
  // image->TransformContinuousIndexToPhysicalPoint(dslice, origin);
  image->TransformIndexToPhysicalPoint(slice, origin);
}

void vtkVolumeInspectionRepresentation::OriginToSlice(const double org[3], int slice[3]) const
{
  auto* image = vtkPVImageSliceMapper2::SafeDownCast(this->Elements[PlaneX].Mapper)->GetInput();
  if (!image)
  {
    slice[0] = 0;
    slice[1] = 0;
    slice[2] = 0;
    return;
  }
  double dslice[3];
  image->TransformPhysicalPointToContinuousIndex(org, dslice);
  slice[0] = static_cast<int>(dslice[0] + 0.5 - (dslice[0] < 0));
  slice[1] = static_cast<int>(dslice[1] + 0.5 - (dslice[1] < 0));
  slice[2] = static_cast<int>(dslice[2] + 0.5 - (dslice[2] < 0));
  // image->TransformPhysicalPointToIndex(org, slice);
}

void vtkVolumeInspectionRepresentation::UpdateMapper()
{
  if (!this->Image || !this->ImageID)
  {
    return;
  }
  auto inputID = smtk::common::UUID(this->ImageID);
  auto* mbdata = vtkMultiBlockDataSet::SafeDownCast(
    this->Image->GetProducer()->GetOutputDataObject(this->Image->GetIndex()));
  if (!mbdata)
  {
    return;
  }
  auto* mbit = mbdata->NewTreeIterator();
  // Some entity might uses composite data sets.
  mbit->VisitOnlyLeavesOff();
  vtkDataObject* input = nullptr;
  for (mbit->GoToFirstItem(); !mbit->IsDoneWithTraversal(); mbit->GoToNextItem())
  {
    auto* obj = mbit->GetCurrentDataObject();
    auto uid = vtkResourceMultiBlockSource::GetDataObjectUUID(mbit->GetCurrentMetaData());
    if (!obj || !uid)
    {
      continue;
    }
    if (uid == inputID)
    {
      input = obj;
      break;
    }
  }
  mbit->Delete();
  if (input == nullptr)
  {
    return;
  }

  auto* imagedata = dynamic_cast<vtkImageData*>(input);
  if (!imagedata)
  {
    return;
  }

  this->SetInputData(imagedata);
}

void vtkVolumeInspectionRepresentation::SetLookupTable(vtkScalarsToColors* lkup)
{
  if (this->LookupTable == lkup)
  {
    return;
  }

  if (this->LookupTable)
  {
    this->LookupTable->Delete();
  }
  this->Modified();
  this->LookupTable = lkup;
  if (this->LookupTable)
  {
    this->LookupTable->Register(this);

    // Now push to our mappers, if they exist yet.
    for (int ii = 0; ii < 3; ++ii)
    {
      auto* mapper =
        dynamic_cast<vtkPVImageSliceMapper2*>(this->Elements[PlaneX + ii].Mapper.Get());
      if (mapper)
      {
        mapper->SetLookupTable(this->LookupTable);
      }
    }
  }
}
