//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt 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 "vtk/aeva/ext/vtkProportionalEditRepresentation.h"

#include "vtk/aeva/ext/vtkProportionalEditElements.h"
#include "vtk/aeva/ext/vtkProportionalEditFilter.h"

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

#include "vtkActor.h"
#include "vtkAssemblyNode.h"
#include "vtkAssemblyPath.h"
#include "vtkBox.h"
#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkCellPicker.h"
#include "vtkCommand.h"
#include "vtkConeSource.h"
#include "vtkDataObjectTreeIterator.h"
#include "vtkDoubleArray.h"
#include "vtkGlyph3DMapper.h"
#include "vtkImageData.h"
#include "vtkInteractorObserver.h"
#include "vtkLineSource.h"
#include "vtkLookupTable.h"
#include "vtkMath.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkObjectFactory.h"
#include "vtkOutlineFilter.h"
#include "vtkPickingManager.h"
#include "vtkPlane.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkSphereSource.h"
#include "vtkTransform.h"
#include "vtkTubeFilter.h"
#include "vtkVectorOperators.h"
#include "vtkWindow.h"

#include <algorithm>
#include <cfloat> //for FLT_EPSILON

using UUID = smtk::common::UUID;

vtkStandardNewMacro(vtkProportionalEditRepresentation);

vtkProportionalEditRepresentation::vtkProportionalEditRepresentation()
  : BumpDistance(0.01)
  , AlongXAxis(0)
  , AlongYAxis(0)
  , AlongZAxis(0)
  , Resolution(16)
  , Tolerance(1e-8)
  , ScaleEnabled(1)
  , DrawRegion(0)
  , Tubing(1)
  , Cylindrical(0)
  , Surface(nullptr)
  , SurfaceID(nullptr)
{
  this->HandleSize = 7.5;
  this->Geometry->SetResolution(this->Resolution);

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

  for (int ii = 0; ii < NumberOfElements; ++ii)
  {
    this->Elements[ii].Actor->SetMapper(this->Elements[ii].Mapper);
    if (ii <= ProjectionHandle)
    {
      this->Elements[ii].Actor->SetProperty(this->HandleProperty);
    }
    else if (ii <= DisplacementAxis)
    {
      this->Elements[ii].Actor->SetProperty(this->AxisProperty);
    }
    else if (ii <= Cylinder)
    {
      this->Elements[ii].Actor->SetProperty(this->RegionProperty);
    }
  }
  this->Elements[Preview].Actor->GetProperty()->SetOpacity(0.5);
  this->Elements[Preview].Actor->GetProperty()->SetRepresentation(VTK_WIREFRAME);
  this->Elements[Preview].Actor->GetProperty()->SetColor(this->EdgeColor);
  this->Elements[Preview].Actor->GetProperty()->SetLineWidth(2);

  // Set up the pipelines for the visual elements
  this->AxisTuber->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::Axis));
  this->AxisTuber->SetNumberOfSides(12);
  this->Elements[DisplacementAxis].Mapper->SetInputConnection(this->AxisTuber->GetOutputPort());
  this->Elements[DisplacementAxis].Actor->SetMapper(this->Elements[DisplacementAxis].Mapper);

  // Create the endpoint geometry source
  this->HandleSphere->SetThetaResolution(16);
  this->HandleSphere->SetPhiResolution(8);

  this->BottomHandleMapper->SetSourceConnection(this->HandleSphere->GetOutputPort());
  this->BottomHandleMapper->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::AnchorVertex));
  this->Elements[BottomHandle].Actor->SetMapper(this->BottomHandleMapper);

  this->TopHandleMapper->SetSourceConnection(this->HandleSphere->GetOutputPort());
  this->TopHandleMapper->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::DisplacementVertex));
  this->Elements[TopHandle].Actor->SetMapper(this->TopHandleMapper);

  this->ProjectionHandleMapper->SetSourceConnection(this->HandleSphere->GetOutputPort());
  this->ProjectionHandleMapper->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::ProjectionVertex));
  this->Elements[ProjectionHandle].Actor->SetMapper(this->ProjectionHandleMapper);

  this->Elements[Sphere].Mapper->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::Sphere));

  this->Elements[Cylinder].Mapper->SetInputConnection(
    this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::Cylinder));

  // Define the point coordinates
  double bounds[6];
  bounds[0] = -0.5;
  bounds[1] = 0.5;
  bounds[2] = -0.5;
  bounds[3] = 0.5;
  bounds[4] = -0.5;
  bounds[5] = 0.5;

  // Initial creation of the widget, serves to initialize it
  this->PlaceWidget(bounds);

  //Manage the picking stuff
  this->Picker->SetTolerance(0.005);
  for (int ii = BottomHandle; ii < Sphere; ++ii)
  {
    this->Picker->AddPickList(this->Elements[ii].Actor);
  }
  // not showing initially.
  this->Picker->DeletePickList(this->Elements[ProjectionHandle].Actor);
  this->Picker->PickFromListOn();

  this->RegionPicker->SetTolerance(0.005);
  // Initially pick sphere only
  this->RegionPicker->AddPickList(this->Elements[Sphere].Actor);
  this->RegionPicker->PickFromListOn();

  this->RepresentationState = vtkProportionalEditRepresentation::Outside;
}

vtkProportionalEditRepresentation::~vtkProportionalEditRepresentation() = default;

bool vtkProportionalEditRepresentation::SetEndpoint(bool isBottom, double x, double y, double z)
{
  return this->SetEndpoint(isBottom, vtkVector3d(x, y, z));
}

bool vtkProportionalEditRepresentation::SetEndpoint(bool isBottom, const vtkVector3d& pt)
{
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d p1(this->Geometry->GetDisplacementPoint());
  vtkVector3d p2(this->Geometry->GetProjectionPoint());

  if (p0 == pt || p1 == pt || p2 == pt)
  {
    // If pt is already the existing value, or would be coincident, do nothing.
    return false;
  }
  vtkVector3d temp(pt); // Because vtkSetVectorMacro is not const correct.
  if (isBottom)
  {
    // when we move the anchor point, move the displacement and proj point by
    // the same amount.
    vtkVector3d move = temp - p0;
    p1 = p1 + move;
    p2 = p2 + move;
    this->Geometry->SetAnchorPoint(temp.GetData());
    this->Geometry->SetDisplacementPoint(p1.GetData());
    this->Geometry->SetProjectionPoint(p2.GetData());
  }
  else
  {
    this->Geometry->SetDisplacementPoint(temp.GetData());
  }
  this->Modified();
  return true;
}

vtkVector3d vtkProportionalEditRepresentation::GetEndpoint(bool isBottom) const
{
  return vtkVector3d(
    isBottom ? this->Geometry->GetAnchorPoint() : this->Geometry->GetDisplacementPoint());
}

double* vtkProportionalEditRepresentation::GetBottomPoint()
{
  return this->Geometry->GetAnchorPoint();
}

double* vtkProportionalEditRepresentation::GetTopPoint()
{
  return this->Geometry->GetDisplacementPoint();
}

void vtkProportionalEditRepresentation::SetDisplacement(double x, double y, double z)
{
  double pt[3] = { x, y, z };
  this->SetDisplacement(pt);
}
void vtkProportionalEditRepresentation::SetDisplacement(double* x)
{
  this->DisplacementVector.Set(x[0], x[1], x[2]);
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d pt = p0 + this->DisplacementVector;
  vtkVector3d p1(this->Geometry->GetDisplacementPoint());
  if (p0 == pt || p1 == pt)
  {
    // If pt is already the existing value, or would be coincident, do nothing.
    return;
  }
  vtkVector3d temp(pt); // Because vtkSetVectorMacro is not const correct.
  this->Geometry->SetDisplacementPoint(temp.GetData());
  this->Modified();
}

double* vtkProportionalEditRepresentation::GetDisplacement()
{
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d p1(this->Geometry->GetDisplacementPoint());
  this->DisplacementVector = p1 - p0;
  return this->DisplacementVector.GetData();
}

void vtkProportionalEditRepresentation::SetProjectionVector(double x, double y, double z)
{
  double pt[3] = { x, y, z };
  this->SetProjectionVector(pt);
}
void vtkProportionalEditRepresentation::SetProjectionVector(double* x)
{
  this->ProjectionVector.Set(x[0], x[1], x[2]);
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d pt = p0 + this->ProjectionVector;
  vtkVector3d p2(this->Geometry->GetProjectionPoint());
  if (p0 == pt || p2 == pt)
  {
    // If pt is already the existing value, or would be coincident, do nothing.
    return;
  }
  vtkVector3d temp(pt); // Because vtkSetVectorMacro is not const correct.
  this->Geometry->SetProjectionPoint(temp.GetData());
  this->Modified();
}

double* vtkProportionalEditRepresentation::GetProjectionVector()
{
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d p2(this->Geometry->GetProjectionPoint());
  this->ProjectionVector = p2 - p0;
  return this->ProjectionVector.GetData();
}

bool vtkProportionalEditRepresentation::SetRadius(double r)
{
  double prev = this->Geometry->GetInfluenceRadius();
  if (prev == r)
  {
    return false;
  }
  this->Geometry->SetInfluenceRadius(r);
  this->Modified();
  return true;
}

double vtkProportionalEditRepresentation::GetRadius() const
{
  return this->Geometry->GetInfluenceRadius();
}

bool vtkProportionalEditRepresentation::SetCylindrical(vtkTypeBool isCylindrical)
{
  if (isCylindrical == this->Cylindrical)
  {
    return false;
  }
  this->Cylindrical = isCylindrical;
  // swap sphere/cylinder in picker, even if preview geometry is visible.
  if (isCylindrical)
  {
    this->RegionPicker->DeletePickList(this->Elements[Sphere].Actor);
    this->RegionPicker->AddPickList(this->Elements[Cylinder].Actor);
    this->Picker->AddPickList(this->Elements[ProjectionHandle].Actor);
  }
  else
  {
    this->RegionPicker->DeletePickList(this->Elements[Cylinder].Actor);
    this->RegionPicker->AddPickList(this->Elements[Sphere].Actor);
    this->Picker->DeletePickList(this->Elements[ProjectionHandle].Actor);
  }

  this->Modified();
  return true;
}

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

  // The second picker may need to be called. This is done because the region
  // wraps around things that can be picked; thus the region is the selection
  // of last resort.
  if (path == nullptr)
  {
    this->RegionPicker->Pick(X, Y, 0., this->Renderer);
    path = this->RegionPicker->GetPath();
  }

  if (path == nullptr) // Nothing picked
  {
    this->SetRepresentationState(vtkProportionalEditRepresentation::Outside);
    this->InteractionState = vtkProportionalEditRepresentation::Outside;
    return this->InteractionState;
  }

  // Something picked, continue
  this->ValidPick = 1;

  // Depending on the interaction state (set by the widget) we modify
  // this state based on what is picked.
  if (this->InteractionState == vtkProportionalEditRepresentation::Moving)
  {
    vtkProp* prop = path->GetFirstNode()->GetViewProp();
    if (prop == this->Elements[DisplacementAxis].Actor)
    {
      this->InteractionState = vtkProportionalEditRepresentation::RotatingAxis;
      this->SetRepresentationState(vtkProportionalEditRepresentation::RotatingAxis);
    }
    else if (prop == this->Elements[BottomHandle].Actor)
    {
      this->InteractionState = vtkProportionalEditRepresentation::MovingBottomHandle;
      this->SetRepresentationState(vtkProportionalEditRepresentation::MovingBottomHandle);
    }
    else if (prop == this->Elements[TopHandle].Actor)
    {
      this->InteractionState = vtkProportionalEditRepresentation::MovingTopHandle;
      this->SetRepresentationState(vtkProportionalEditRepresentation::MovingTopHandle);
    }
    else if (prop == this->Elements[ProjectionHandle].Actor)
    {
      this->InteractionState = vtkProportionalEditRepresentation::MovingProjectionHandle;
      this->SetRepresentationState(vtkProportionalEditRepresentation::MovingProjectionHandle);
    }
    else if (prop == this->Elements[Sphere].Actor || prop == this->Elements[Cylinder].Actor)
    {
      this->InteractionState = vtkProportionalEditRepresentation::AdjustingRadius;
      this->SetRepresentationState(vtkProportionalEditRepresentation::AdjustingRadius);
    }
    else
    {
      this->InteractionState = vtkProportionalEditRepresentation::Outside;
      this->SetRepresentationState(vtkProportionalEditRepresentation::Outside);
    }
  }
  // We may add a condition to allow the camera to work IO scaling
  else if (this->InteractionState != vtkProportionalEditRepresentation::Scaling)
  {
    this->InteractionState = vtkProportionalEditRepresentation::Outside;
  }

  return this->InteractionState;
}

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

  // Clamp the state
  state = (state < vtkProportionalEditRepresentation::Outside
      ? vtkProportionalEditRepresentation::Outside
      : (state > vtkProportionalEditRepresentation::Scaling
            ? vtkProportionalEditRepresentation::Scaling
            : state));

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

#if 0
  // For debugging, it is handy to see state changes:
  std::cout
    << "   State "
    << vtkProportionalEditRepresentation::InteractionStateToString(this->RepresentationState)
    << "\n";
#endif

  this->HighlightElement(NumberOfElements, 0); // Turn everything off
  if (state == vtkProportionalEditRepresentation::RotatingAxis ||
    state == vtkProportionalEditRepresentation::TranslatingCenter)
  {
    this->HighlightAxis(1);
  }
  else if (state == vtkProportionalEditRepresentation::AdjustingRadius ||
    state == vtkProportionalEditRepresentation::MovingWhole)
  {
    this->HighlightRegion(1);
  }
  else if (state == vtkProportionalEditRepresentation::Scaling && this->ScaleEnabled)
  {
    this->HighlightAxis(1);
    this->HighlightRegion(1);
    // this->HighlightOutline(1);
  }
  else
  {
    this->HighlightAxis(0);
    this->HighlightRegion(0);
    // this->HighlightOutline(0);
  }
}

void vtkProportionalEditRepresentation::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 vtkProportionalEditRepresentation::WidgetInteraction(double newEventPos[2])
{
  // Do different things depending on state
  // Calculations everybody does
  double focalPoint[4];
  double pickPoint[4];
  double prevPickPoint[4];
  double z;
  double vpn[3];

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

  // Process the motion
  if (this->InteractionState == vtkProportionalEditRepresentation::AdjustingRadius)
  {
    this->AdjustRadius(newEventPos[0], newEventPos[1], prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::MovingBottomHandle)
  {
    this->TranslateHandle(true, prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::MovingTopHandle)
  {
    this->TranslateHandle(false, prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::MovingProjectionHandle)
  {
    this->TranslateProjectionHandle(prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::MovingWhole)
  {
    this->TranslateCenter(prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::TranslatingCenter)
  {
    this->TranslateCenterOnAxis(prevPickPoint, pickPoint);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::Scaling &&
    this->ScaleEnabled)
  {
    this->Scale(prevPickPoint, pickPoint, newEventPos[0], newEventPos[1]);
  }
  else if (this->InteractionState == vtkProportionalEditRepresentation::RotatingAxis)
  {
    camera->GetViewPlaneNormal(vpn);
    this->Rotate(newEventPos[0], newEventPos[1], prevPickPoint, pickPoint, vpn);
  }

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

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

double* vtkProportionalEditRepresentation::GetBounds()
{
  this->BuildRepresentation();
  this->BoundingBox->SetBounds(this->Elements[DisplacementAxis].Actor->GetBounds());
  for (int ii = Sphere; ii < NumberOfElements; ++ii)
  {
    this->BoundingBox->AddBounds(this->Elements[ii].Actor->GetBounds());
  }

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

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

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

int vtkProportionalEditRepresentation::RenderOpaqueGeometry(vtkViewport* v)
{
  int count = 0;
  this->BuildRepresentation();

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

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

  if (this->DrawRegion)
  {
    if (this->Cylindrical)
    {
      count += this->Elements[Cylinder].Actor->RenderOpaqueGeometry(v);
    }
    else
    {
      count += this->Elements[Sphere].Actor->RenderOpaqueGeometry(v);
    }
  }
  else if (this->Surface)
  {
    count += this->Elements[Preview].Actor->RenderOpaqueGeometry(v);
  }

  return count;
}

int vtkProportionalEditRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* v)
{
  int count = 0;
  this->BuildRepresentation();

  count += this->Elements[BottomHandle].Actor->RenderTranslucentPolygonalGeometry(v);
  count += this->Elements[TopHandle].Actor->RenderTranslucentPolygonalGeometry(v);
  count += this->Elements[DisplacementAxis].Actor->RenderTranslucentPolygonalGeometry(v);

  if (this->Cylindrical)
  {
    count += this->Elements[ProjectionHandle].Actor->RenderTranslucentPolygonalGeometry(v);
  }

  if (this->DrawRegion)
  {
    if (this->Cylindrical)
    {
      count += this->Elements[Cylinder].Actor->RenderTranslucentPolygonalGeometry(v);
    }
    else
    {
      count += this->Elements[Sphere].Actor->RenderTranslucentPolygonalGeometry(v);
    }
  }
  else if (this->Surface)
  {
    count += this->Elements[Preview].Actor->RenderTranslucentPolygonalGeometry(v);
  }

  return count;
}

vtkTypeBool vtkProportionalEditRepresentation::HasTranslucentPolygonalGeometry()
{
  int result = 0;

  result |= this->Elements[BottomHandle].Actor->HasTranslucentPolygonalGeometry();
  result |= this->Elements[TopHandle].Actor->HasTranslucentPolygonalGeometry();
  result |= this->Elements[DisplacementAxis].Actor->HasTranslucentPolygonalGeometry();

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

  if (this->DrawRegion)
  {
    if (this->Cylindrical)
    {
      result |= this->Elements[Cylinder].Actor->HasTranslucentPolygonalGeometry();
    }
    else
    {
      result |= this->Elements[Sphere].Actor->HasTranslucentPolygonalGeometry();
    }
  }
  else if (this->Surface)
  {
    result |= this->Elements[Preview].Actor->HasTranslucentPolygonalGeometry();
  }

  return result;
}

void vtkProportionalEditRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Resolution: " << this->Resolution << "\n";

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

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

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

  os << indent << "Along X Axis: " << (this->AlongXAxis ? "On" : "Off") << "\n";
  os << indent << "Along Y Axis: " << (this->AlongYAxis ? "On" : "Off") << "\n";
  os << indent << "ALong Z Axis: " << (this->AlongZAxis ? "On" : "Off") << "\n";

  os << indent << "Tubing: " << (this->Tubing ? "On" : "Off") << "\n";
  os << indent << "Scale Enabled: " << (this->ScaleEnabled ? "On" : "Off") << "\n";
  os << indent << "Draw Region: " << (this->DrawRegion ? "On" : "Off") << "\n";
  os << indent << "Bump Distance: " << this->BumpDistance << "\n";
  os << indent << "Cylindrical: " << (this->Cylindrical ? "On" : "Off") << "\n";

  os << indent << "Representation State: "
     << vtkProportionalEditRepresentation::InteractionStateToString(this->RepresentationState)
     << "\n";

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

void vtkProportionalEditRepresentation::HighlightElement(ElementType elem, int highlight)
{
  switch (elem)
  {
    case DisplacementAxis:
      this->HighlightAxis(highlight);
      break;
    case BottomHandle:
      this->HighlightHandle(true, highlight);
      break;
    case TopHandle:
      this->HighlightHandle(false, highlight);
      break;
    case ProjectionHandle:
      this->HighlightProjectionHandle(highlight);
      break;
    case Sphere:
    case Cylinder:
      this->HighlightRegion(highlight);
      break;
    case Preview:
    case NumberOfElements:
      // Set everything to the given highlight state.
      this->HighlightAxis(highlight);
      this->HighlightRegion(highlight);
      this->HighlightHandle(true, highlight);
      this->HighlightHandle(false, highlight);
      this->HighlightProjectionHandle(highlight);
      break;
  }
}

void vtkProportionalEditRepresentation::HighlightRegion(int highlight)
{
  if (highlight)
  {
    if (this->Cylindrical)
    {
      this->Elements[Cylinder].Actor->SetProperty(this->SelectedRegionProperty);
    }
    else
    {
      this->Elements[Sphere].Actor->SetProperty(this->SelectedRegionProperty);
    }
  }
  else
  {
    if (this->Cylindrical)
    {
      this->Elements[Cylinder].Actor->SetProperty(this->RegionProperty);
    }
    else
    {
      this->Elements[Sphere].Actor->SetProperty(this->RegionProperty);
    }
  }
}

void vtkProportionalEditRepresentation::HighlightAxis(int highlight)
{
  if (highlight)
  {
    this->Elements[DisplacementAxis].Actor->SetProperty(this->SelectedAxisProperty);
  }
  else
  {
    this->Elements[DisplacementAxis].Actor->SetProperty(this->AxisProperty);
  }
}

void vtkProportionalEditRepresentation::HighlightHandle(bool isBottom, int highlight)
{
  ElementType elem = isBottom ? BottomHandle : TopHandle;
  if (highlight)
  {
    this->Elements[elem].Actor->SetProperty(this->SelectedHandleProperty);
  }
  else
  {
    this->Elements[elem].Actor->SetProperty(this->HandleProperty);
  }
}

void vtkProportionalEditRepresentation::HighlightProjectionHandle(int highlight)
{
  if (highlight)
  {
    this->Elements[ProjectionHandle].Actor->SetProperty(this->SelectedHandleProperty);
  }
  else
  {
    this->Elements[ProjectionHandle].Actor->SetProperty(this->HandleProperty);
  }
}

void vtkProportionalEditRepresentation::Rotate(double X,
  double Y,
  const double* p1,
  const double* p2,
  double* vpn)
{
  double v[3];    //vector of motion
  double axis[3]; //axis of rotation
  double theta;   //rotation angle

  // mouse motion vector in world space
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d cp0(this->Geometry->GetAnchorPoint());
  vtkVector3d cp1(this->Geometry->GetDisplacementPoint());
  vtkVector3d center = cp0;

  // Create axis of rotation and angle of rotation
  vtkMath::Cross(vpn, v, axis);
  if (vtkMath::Normalize(axis) == 0.0)
  {
    return;
  }
  int* size = this->Renderer->GetSize();
  double l2 = (X - this->LastEventPosition[0]) * (X - this->LastEventPosition[0]) +
    (Y - this->LastEventPosition[1]) * (Y - this->LastEventPosition[1]);
  theta = 360.0 * sqrt(l2 / (size[0] * size[0] + size[1] * size[1]));

  // Manipulate the transform to reflect the rotation
  this->Transform->Identity();
  this->Transform->Translate(center[0], center[1], center[2]);
  this->Transform->RotateWXYZ(theta, axis);
  this->Transform->Translate(-center[0], -center[1], -center[2]);

  // this->Transform->TransformPoint(cp0.GetData(), cp0.GetData());
  this->Transform->TransformPoint(cp1.GetData(), cp1.GetData());

  // this->Geometry->SetAnchorPoint(cp0.GetData());
  this->Geometry->SetDisplacementPoint(cp1.GetData());
}

void vtkProportionalEditRepresentation::AdjustRadius(double /*X*/,
  double Y,
  const double* p1,
  const double* p2)
{
  if (Y == this->LastEventPosition[1])
  {
    return;
  }

  double dr;
  double radius = this->Geometry->GetInfluenceRadius();
  double v[3]; //vector of motion
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];
  double l = sqrt(vtkMath::Dot(v, v));

  dr = l / 4;
  if (Y < this->LastEventPosition[1])
  {
    dr *= -1.0;
  }

  double nextRadius = radius + dr;

  if (nextRadius < this->Tolerance)
  {
    nextRadius = this->Tolerance;
  }
  else if (nextRadius < 0.)
  {
    nextRadius = 0.0;
  }
  this->Geometry->SetInfluenceRadius(nextRadius);
  this->BuildRepresentation();
}

// Loop through all points and translate them
void vtkProportionalEditRepresentation::TranslateCenter(const double* p1, const double* p2)
{
  //Get the motion vector
  vtkVector3d v;
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d cp0(this->Geometry->GetAnchorPoint());
  vtkVector3d cp1(this->Geometry->GetDisplacementPoint());
  vtkVector3d cp2(this->Geometry->GetProjectionPoint());

  this->Geometry->SetAnchorPoint((cp0 + v).GetData());
  this->Geometry->SetDisplacementPoint((cp1 + v).GetData());
  this->Geometry->SetProjectionPoint((cp2 + v).GetData());

  this->BuildRepresentation();
}

// Translate the center on the axis
void vtkProportionalEditRepresentation::TranslateCenterOnAxis(const double* p1, const double* p2)
{
  // Get the motion vector
  vtkVector3d v;
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d cp0(this->Geometry->GetAnchorPoint());
  vtkVector3d cp1(this->Geometry->GetDisplacementPoint());
  vtkVector3d cp2(this->Geometry->GetProjectionPoint());

  this->Geometry->SetAnchorPoint((cp0 + v).GetData());
  this->Geometry->SetDisplacementPoint((cp1 + v).GetData());
  this->Geometry->SetProjectionPoint((cp2 + v).GetData());

  this->BuildRepresentation();
}

// Loop through all points and translate them
void vtkProportionalEditRepresentation::TranslateHandle(bool isBottomHandle,
  const double* p1,
  const double* p2)
{
  //Get the motion vector
  vtkVector3d v;
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d handle(
    isBottomHandle ? this->Geometry->GetAnchorPoint() : this->Geometry->GetDisplacementPoint());

  this->SetEndpoint(isBottomHandle, (handle + v));

  this->BuildRepresentation();
}

void vtkProportionalEditRepresentation::TranslateProjectionHandle(const double* p1,
  const double* p2)
{
  //Get the motion vector
  vtkVector3d v;
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d cp2(this->Geometry->GetProjectionPoint());

  this->Geometry->SetProjectionPoint((cp2 + v).GetData());
  this->BuildRepresentation();
}

void vtkProportionalEditRepresentation::Scale(const double* p1,
  const double* p2,
  double /*X*/,
  double Y)
{
  //Get the motion vector
  vtkVector3d v;
  v[0] = p2[0] - p1[0];
  v[1] = p2[1] - p1[1];
  v[2] = p2[2] - p1[2];

  vtkVector3d cp0(this->Geometry->GetAnchorPoint());
  vtkVector3d cp1(this->Geometry->GetDisplacementPoint());
  vtkVector3d cp2(this->Geometry->GetProjectionPoint());
  vtkVector3d axis = cp1 - cp0;
  vtkVector3d origin = cp0; //0.5 * (cp1 + cp0);

  // Compute the scale factor
  double sf = v.Norm() / axis.Norm();
  sf = 1.0 + (Y > this->LastEventPosition[1] ? sf : -sf);

  // this->Geometry->SetAnchorPoint((origin + (cp0 - origin) * sf).GetData());
  this->Geometry->SetDisplacementPoint((origin + (cp1 - origin) * sf).GetData());
  this->Geometry->SetProjectionPoint((origin + (cp2 - origin) * sf).GetData());
  this->Geometry->SetInfluenceRadius(this->Geometry->GetInfluenceRadius() * sf);

  this->BuildRepresentation();
}

void vtkProportionalEditRepresentation::SizeHandles()
{
  double radius =
    this->vtkWidgetRepresentation::SizeHandlesInPixels(2.5, this->HandleSphere->GetCenter());

  this->HandleSphere->SetRadius(radius);
  this->AxisTuber->SetRadius(0.25 * radius);
}

void vtkProportionalEditRepresentation::CreateDefaultProperties()
{
  this->HandleProperty->SetColor(1., 1., 1.);

  this->AxisProperty->SetColor(1., 1., 1.);
  this->AxisProperty->SetLineWidth(3);

  this->RegionProperty->SetColor(1., 1., 1.);
  this->RegionProperty->SetOpacity(0.5);
  this->RegionProperty->SetRepresentation(VTK_WIREFRAME);

  this->SelectedHandleProperty->SetColor(0.0, 1.0, 0.);
  this->SelectedHandleProperty->SetAmbient(1.0);

  this->SelectedAxisProperty->SetColor(0., 1.0, 0.0);
  this->SelectedAxisProperty->SetLineWidth(3);

  this->SelectedRegionProperty->SetColor(0., 1., 0.);
  this->SelectedRegionProperty->SetOpacity(0.5);
  this->SelectedRegionProperty->SetRepresentation(VTK_WIREFRAME);
}

void vtkProportionalEditRepresentation::PlaceWidget(double bounds[6])
{
  vtkVector3d lo(bounds[0], bounds[2], bounds[4]);
  vtkVector3d hi(bounds[1], bounds[3], bounds[5]);
  vtkVector3d md = 0.5 * (lo + hi);

  this->InitialLength = (hi - lo).Norm();

  if (this->AlongYAxis)
  {
    this->Geometry->SetAnchorPoint(md[0], lo[1], md[2]);
    this->Geometry->SetDisplacementPoint(md[0], hi[1], md[2]);
    this->Geometry->SetProjectionPoint(hi[0], lo[1], md[2]);
    double radius = hi[2] - md[2] > hi[0] - md[0] ? hi[0] - md[0] : hi[2] - md[2];
    this->Geometry->SetInfluenceRadius(radius);
  }
  else if (this->AlongZAxis)
  {
    this->Geometry->SetAnchorPoint(md[0], md[1], lo[2]);
    this->Geometry->SetDisplacementPoint(md[0], md[1], hi[2]);
    this->Geometry->SetProjectionPoint(md[0], hi[1], lo[2]);
    double radius = hi[0] - md[0] > hi[1] - md[1] ? hi[1] - md[1] : hi[0] - md[0];
    this->Geometry->SetInfluenceRadius(radius);
  }
  else //default or x-normal
  {
    this->Geometry->SetAnchorPoint(lo[0], md[1], md[2]);
    this->Geometry->SetDisplacementPoint(hi[0], md[1], md[2]);
    this->Geometry->SetProjectionPoint(lo[0], md[1], hi[2]);
    double radius = hi[2] - md[2] > hi[1] - md[1] ? hi[1] - md[1] : hi[2] - md[2];
    this->Geometry->SetInfluenceRadius(radius);
  }

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

void vtkProportionalEditRepresentation::SetDrawRegion(vtkTypeBool drawCyl)
{
  if (drawCyl == this->DrawRegion)
  {
    return;
  }

  this->Modified();
  this->DrawRegion = drawCyl;
  this->BuildRepresentation();
}

void vtkProportionalEditRepresentation::SetAlongXAxis(vtkTypeBool var)
{
  if (this->AlongXAxis != var)
  {
    this->AlongXAxis = var;
    this->Modified();
  }
  if (var)
  {
    this->AlongYAxisOff();
    this->AlongZAxisOff();
  }
}

void vtkProportionalEditRepresentation::SetAlongYAxis(vtkTypeBool var)
{
  if (this->AlongYAxis != var)
  {
    this->AlongYAxis = var;
    this->Modified();
  }
  if (var)
  {
    this->AlongXAxisOff();
    this->AlongZAxisOff();
  }
}

void vtkProportionalEditRepresentation::SetAlongZAxis(vtkTypeBool var)
{
  if (this->AlongZAxis != var)
  {
    this->AlongZAxis = var;
    this->Modified();
  }
  if (var)
  {
    this->AlongXAxisOff();
    this->AlongYAxisOff();
  }
}

void vtkProportionalEditRepresentation::UpdatePlacement()
{
  this->BuildRepresentation();
}

void vtkProportionalEditRepresentation::BumpCone(int dir, double factor)
{
  // Compute the distance
  double d = this->InitialLength * this->BumpDistance * factor;

  // Push the cylinder
  this->PushCone((dir > 0 ? d : -d));
}

void vtkProportionalEditRepresentation::PushCone(double d)
{
  vtkCamera* camera = this->Renderer->GetActiveCamera();
  if (!camera)
  {
    return;
  }
  vtkVector3d vpn;
  vtkVector3d p0(this->Geometry->GetAnchorPoint());
  vtkVector3d p1(this->Geometry->GetDisplacementPoint());
  camera->GetViewPlaneNormal(vpn.GetData());

  p0 = p0 + d * vpn;
  p1 = p1 + d * vpn;

  this->Geometry->SetAnchorPoint(p0.GetData());
  this->Geometry->SetDisplacementPoint(p1.GetData());
  this->BuildRepresentation();
}

std::string vtkProportionalEditRepresentation::InteractionStateToString(int state)
{
  switch (state)
  {
    case Outside:
      return "Outside";
      break;
    case Moving:
      return "Moving";
      break;
    case AdjustingRadius:
      return "AdjustingRadius";
      break;
    case MovingBottomHandle:
      return "MovingBottomHandle";
      break;
    case MovingTopHandle:
      return "MovingTopHandle";
      break;
    case MovingProjectionHandle:
      return "MovingProjectionHandle";
      break;
    case MovingWhole:
      return "MovingWhole";
      break;
    case RotatingAxis:
      return "RotatingAxis";
      break;
    case TranslatingCenter:
      return "TranslatingCenter";
      break;
    case Scaling:
      return "Scaling";
      break;
    default:
      break;
  }
  return "Invalid";
}

void vtkProportionalEditRepresentation::BuildPreviewGeometry()
{
  if (!this->Surface || !this->Filter)
  {
    return;
  }
  // find the input by matching UUID.
  UUID inputID = UUID(this->SurfaceID);
  auto* mbdata = vtkMultiBlockDataSet::SafeDownCast(
    this->Surface->GetProducer()->GetOutputDataObject(this->Surface->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* propEdit = this->Filter.Get();
  propEdit->SetInfluenceRadius(this->GetRadius());

  vtkVector3d anchorPoint = this->GetEndpoint(true);
  propEdit->SetAnchorPointCoords(anchorPoint[0], anchorPoint[1], anchorPoint[2]);
  vtkVector3d dispVec(this->GetDisplacement());
  propEdit->SetAnchorPointDisplacement(dispVec[0], dispVec[1], dispVec[2]);

  propEdit->SetProjected(this->GetCylindrical());
  vtkVector3d projVec(this->GetProjectionVector());
  propEdit->SetProjectionDirection(projVec[0], projVec[1], projVec[2]);
  // propEdit->PassThroughCellIdsOn();
  propEdit->SetInputDataObject(input);
  // propEdit->PrintSelf(std::cout, vtkIndent(4));
  propEdit->Update();
  vtkPolyData* outputPD = propEdit->GetOutput();
  this->Elements[Preview].Mapper->SetInputDataObject(outputPD);
  this->Elements[Preview].Actor->GetProperty()->SetColor(this->EdgeColor);
}

void vtkProportionalEditRepresentation::BuildRepresentation()
{
  if (!this->Renderer || !this->Renderer->GetRenderWindow())
  {
    return;
  }

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

  if (this->GetMTime() > this->BuildTime || this->Geometry->GetMTime() > this->BuildTime ||
    this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime)
  {
    vtkVector3d p0(this->Geometry->GetAnchorPoint());
    vtkVector3d p1(this->Geometry->GetDisplacementPoint());

    // Control the look of the edges
    if (this->Tubing)
    {
      this->Elements[DisplacementAxis].Mapper->SetInputConnection(this->AxisTuber->GetOutputPort());
    }
    else
    {
      this->Elements[DisplacementAxis].Mapper->SetInputConnection(
        this->Geometry->GetOutputPort(vtkProportionalEditElements::OutputPorts::Axis));
    }

    this->SizeHandles();
    this->BuildPreviewGeometry();
    this->BuildTime.Modified();
  }
}

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