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

#include "vtkActor.h"
#include "vtkAssemblyNode.h"
#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkCellPicker.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPickingManager.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPointWidget);

vtkPointWidget::vtkPointWidget()
{
  this->State = vtkPointWidget::Start;
  this->EventCallbackCommand->SetCallback(vtkPointWidget::ProcessEvents);

  // Represent the line
  this->Cursor3D = vtkCursor3D::New();
  this->Mapper = vtkPolyDataMapper::New();
  this->Mapper->SetInputConnection(this->Cursor3D->GetOutputPort());
  this->Actor = vtkActor::New();
  this->Actor->SetMapper(this->Mapper);

  // 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->CursorPicker = vtkCellPicker::New();
  this->CursorPicker->PickFromListOn();
  this->CursorPicker->AddPickList(this->Actor);
  this->CursorPicker->SetTolerance(0.005); // need some fluff

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

  // Constraints not set
  this->ConstraintAxis = -1;

  // Override superclass'
  this->PlaceFactor = 1.0;

  // The size of the hot spot
  this->HotSpotSize = 0.05;
  this->WaitingForMotion = 0;
}

vtkPointWidget::~vtkPointWidget()
{
  this->Actor->Delete();
  this->Mapper->Delete();
  this->Cursor3D->Delete();

  this->CursorPicker->Delete();

  this->Property->Delete();
  this->SelectedProperty->Delete();
}

void vtkPointWidget::SetEnabled(int enabling)
{
  if (!this->Interactor)
  {
    vtkErrorMacro(<< "The interactor must be set prior to enabling/disabling widget");
    return;
  }

  if (enabling) //-----------------------------------------------------------
  {
    vtkDebugMacro(<< "Enabling point widget");

    if (this->Enabled) // already enabled, just return
    {
      return;
    }

    if (!this->CurrentRenderer)
    {
      this->SetCurrentRenderer(this->Interactor->FindPokedRenderer(
        this->Interactor->GetLastEventPosition()[0], this->Interactor->GetLastEventPosition()[1]));
      if (this->CurrentRenderer == nullptr)
      {
        return;
      }
    }

    this->Enabled = 1;

    // listen for the following events
    vtkRenderWindowInteractor* i = this->Interactor;
    i->AddObserver(vtkCommand::MouseMoveEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(vtkCommand::LeftButtonPressEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(vtkCommand::LeftButtonReleaseEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(vtkCommand::MiddleButtonPressEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(
      vtkCommand::MiddleButtonReleaseEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(vtkCommand::RightButtonPressEvent, this->EventCallbackCommand, this->Priority);
    i->AddObserver(vtkCommand::RightButtonReleaseEvent, this->EventCallbackCommand, this->Priority);

    // Add the line
    this->CurrentRenderer->AddActor(this->Actor);
    this->Actor->SetProperty(this->Property);
    this->Cursor3D->Update();
    this->RegisterPickers();

    this->InvokeEvent(vtkCommand::EnableEvent, nullptr);
  }

  else // disabling----------------------------------------------------------
  {
    vtkDebugMacro(<< "Disabling point widget");

    if (!this->Enabled) // already disabled, just return
    {
      return;
    }

    this->Enabled = 0;

    // don't listen for events any more
    this->Interactor->RemoveObserver(this->EventCallbackCommand);

    // turn off the line
    this->CurrentRenderer->RemoveActor(this->Actor);

    this->InvokeEvent(vtkCommand::DisableEvent, nullptr);
    this->SetCurrentRenderer(nullptr);
    this->UnRegisterPickers();
  }

  this->Interactor->Render();
}

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

void vtkPointWidget::ProcessEvents(
  vtkObject* vtkNotUsed(object), unsigned long event, void* clientdata, void* vtkNotUsed(calldata))
{
  vtkPointWidget* self = reinterpret_cast<vtkPointWidget*>(clientdata);

  // okay, let's do the right thing
  switch (event)
  {
    case vtkCommand::LeftButtonPressEvent:
      self->OnLeftButtonDown();
      break;
    case vtkCommand::LeftButtonReleaseEvent:
      self->OnLeftButtonUp();
      break;
    case vtkCommand::MiddleButtonPressEvent:
      self->OnMiddleButtonDown();
      break;
    case vtkCommand::MiddleButtonReleaseEvent:
      self->OnMiddleButtonUp();
      break;
    case vtkCommand::RightButtonPressEvent:
      self->OnRightButtonDown();
      break;
    case vtkCommand::RightButtonReleaseEvent:
      self->OnRightButtonUp();
      break;
    case vtkCommand::MouseMoveEvent:
      self->OnMouseMove();
      break;
  }
}

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

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

  double* pos = this->Cursor3D->GetFocalPoint();
  os << indent << "Position: (" << pos[0] << ", " << pos[1] << ", " << pos[2] << ")\n";

  os << indent << "Outline: " << (this->GetOutline() ? "On\n" : "Off\n");
  os << indent << "XShadows: " << (this->GetXShadows() ? "On\n" : "Off\n");
  os << indent << "YShadows: " << (this->GetYShadows() ? "On\n" : "Off\n");
  os << indent << "ZShadows: " << (this->GetZShadows() ? "On\n" : "Off\n");

  os << indent << "Translation Mode: " << (this->Cursor3D->GetTranslationMode() ? "On\n" : "Off\n");

  os << indent << "Hot Spot Size: " << this->HotSpotSize << "\n";
}

void vtkPointWidget::Highlight(int highlight)
{
  if (highlight)
  {
    this->Actor->SetProperty(this->SelectedProperty);
    this->CursorPicker->GetPickPosition(this->LastPickPosition);
    this->ValidPick = 1;
  }
  else
  {
    this->Actor->SetProperty(this->Property);
  }
}

int vtkPointWidget::DetermineConstraintAxis(int constraint, double* x)
{
  // Look for trivial cases
  if (!this->Interactor->GetShiftKey())
  {
    return -1;
  }
  else if (constraint >= 0 && constraint < 3)
  {
    return constraint;
  }

  // Okay, figure out constraint. First see if the choice is
  // outside the hot spot
  if (!this->WaitingForMotion)
  {
    double p[3], d2, tol;
    this->CursorPicker->GetPickPosition(p);
    d2 = vtkMath::Distance2BetweenPoints(p, this->LastPickPosition);
    tol = this->HotSpotSize * this->InitialLength;
    if (d2 > (tol * tol))
    {
      this->WaitingForMotion = 0;
      return this->CursorPicker->GetCellId();
    }
    else
    {
      this->WaitingForMotion = 1;
      this->WaitCount = 0;
      return -1;
    }
  }
  else if (this->WaitingForMotion && x)
  {
    double v[3];
    this->WaitingForMotion = 0;
    v[0] = fabs(x[0] - this->LastPickPosition[0]);
    v[1] = fabs(x[1] - this->LastPickPosition[1]);
    v[2] = fabs(x[2] - this->LastPickPosition[2]);
    // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator)
    return (v[0] > v[1] ? (v[0] > v[2] ? 0 : 2) : (v[1] > v[2] ? 1 : 2));
  }
  else
  {
    return -1;
  }
}

void vtkPointWidget::OnLeftButtonDown()
{
  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // Okay, make sure that the pick is in the current renderer
  if (!this->CurrentRenderer || !this->CurrentRenderer->IsInViewport(X, Y))
  {
    this->State = vtkPointWidget::Outside;
    return;
  }

  vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->CursorPicker);

  if (path != nullptr)
  {
    this->State = vtkPointWidget::Moving;
    this->Highlight(1);
    this->ConstraintAxis = this->DetermineConstraintAxis(-1, nullptr);
  }
  else
  {
    this->State = vtkPointWidget::Outside;
    this->Highlight(0);
    this->ConstraintAxis = -1;
    return;
  }

  this->EventCallbackCommand->SetAbortFlag(1);
  this->StartInteraction();
  this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnLeftButtonUp()
{
  if (this->State == vtkPointWidget::Outside || this->State == vtkPointWidget::Start)
  {
    return;
  }

  this->State = vtkPointWidget::Start;
  this->Highlight(0);

  this->EventCallbackCommand->SetAbortFlag(1);
  this->EndInteraction();
  this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnMiddleButtonDown()
{
  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // Okay, make sure that the pick is in the current renderer
  if (!this->CurrentRenderer || !this->CurrentRenderer->IsInViewport(X, Y))
  {
    this->State = vtkPointWidget::Outside;
    return;
  }

  // Okay, we can process this.
  vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->CursorPicker);

  if (path != nullptr)
  {
    this->State = vtkPointWidget::Translating;
    this->Highlight(1);
    this->ConstraintAxis = this->DetermineConstraintAxis(-1, nullptr);
  }
  else
  {
    this->State = vtkPointWidget::Outside;
    this->ConstraintAxis = -1;
    return;
  }

  this->EventCallbackCommand->SetAbortFlag(1);
  this->StartInteraction();
  this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnMiddleButtonUp()
{
  if (this->State == vtkPointWidget::Outside || this->State == vtkPointWidget::Start)
  {
    return;
  }

  this->State = vtkPointWidget::Start;
  this->Highlight(0);

  this->EventCallbackCommand->SetAbortFlag(1);
  this->EndInteraction();
  this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnRightButtonDown()
{
  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // Okay, make sure that the pick is in the current renderer
  if (!this->CurrentRenderer || !this->CurrentRenderer->IsInViewport(X, Y))
  {
    this->State = vtkPointWidget::Outside;
    return;
  }

  // Okay, we can process this. Pick the cursor.
  vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->CursorPicker);

  if (path != nullptr)
  {
    this->State = vtkPointWidget::Scaling;
    int idx = this->CursorPicker->GetCellId();
    if (idx >= 0 && idx < 3)
    {
      this->ConstraintAxis = idx;
    }
    this->Highlight(1);
  }
  else
  {
    this->State = vtkPointWidget::Outside;
    this->ConstraintAxis = -1;
    return;
  }

  this->EventCallbackCommand->SetAbortFlag(1);
  this->StartInteraction();
  this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnRightButtonUp()
{
  if (this->State == vtkPointWidget::Outside || this->State == vtkPointWidget::Start)
  {
    return;
  }

  this->State = vtkPointWidget::Start;
  this->Highlight(0);

  this->EventCallbackCommand->SetAbortFlag(1);
  this->EndInteraction();
  this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::OnMouseMove()
{
  // See whether we're active
  if (this->State == vtkPointWidget::Outside || this->State == vtkPointWidget::Start)
  {
    return;
  }

  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // Do different things depending on state
  // Calculations everybody does
  double focalPoint[4], pickPoint[4], prevPickPoint[4];
  double z;

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

  // Compute the two points defining the motion vector
  this->ComputeWorldToDisplay(
    this->LastPickPosition[0], this->LastPickPosition[1], this->LastPickPosition[2], focalPoint);
  z = focalPoint[2];
  this->ComputeDisplayToWorld(double(this->Interactor->GetLastEventPosition()[0]),
    double(this->Interactor->GetLastEventPosition()[1]), z, prevPickPoint);
  this->ComputeDisplayToWorld(double(X), double(Y), z, pickPoint);

  // Process the motion
  if (this->State == vtkPointWidget::Moving)
  {
    if (!this->WaitingForMotion || this->WaitCount++ > 3)
    {
      this->ConstraintAxis = this->DetermineConstraintAxis(this->ConstraintAxis, pickPoint);
      this->MoveFocus(prevPickPoint, pickPoint);
    }
    else
    {
      return; // avoid the extra render
    }
  }

  else if (this->State == vtkPointWidget::Scaling)
  {
    this->Scale(prevPickPoint, pickPoint, X, Y);
  }

  else if (this->State == vtkPointWidget::Translating)
  {
    if (!this->WaitingForMotion || this->WaitCount++ > 3)
    {
      this->ConstraintAxis = this->DetermineConstraintAxis(this->ConstraintAxis, pickPoint);
      this->Translate(prevPickPoint, pickPoint);
    }
    else
    {
      return; // avoid the extra render
    }
  }

  // Interact, if desired
  this->EventCallbackCommand->SetAbortFlag(1);
  this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
  this->Interactor->Render();
}

void vtkPointWidget::MoveFocus(double* p1, 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];

  double focus[3];
  this->Cursor3D->GetFocalPoint(focus);
  if (this->ConstraintAxis >= 0)
  {
    focus[this->ConstraintAxis] += v[this->ConstraintAxis];
  }
  else
  {
    focus[0] += v[0];
    focus[1] += v[1];
    focus[2] += v[2];
  }

  this->Cursor3D->SetFocalPoint(focus);
}

// Translate everything
void vtkPointWidget::Translate(double* p1, 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];

  double* bounds = this->Cursor3D->GetModelBounds();
  double* pos = this->Cursor3D->GetFocalPoint();
  double newBounds[6], newFocus[3];
  int i;

  if (this->ConstraintAxis >= 0)
  { // move along axis
    for (i = 0; i < 3; i++)
    {
      if (i != this->ConstraintAxis)
      {
        v[i] = 0.0;
      }
    }
  }

  for (i = 0; i < 3; i++)
  {
    newBounds[2 * i] = bounds[2 * i] + v[i];
    newBounds[2 * i + 1] = bounds[2 * i + 1] + v[i];
    newFocus[i] = pos[i] + v[i];
  }

  this->Cursor3D->SetModelBounds(newBounds);
  this->Cursor3D->SetFocalPoint(newFocus);
}

void vtkPointWidget::Scale(double* p1, double* p2, int vtkNotUsed(X), int Y)
{
  // 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];

  // int res = this->Cursor3D->GetResolution();
  double* bounds = this->Cursor3D->GetModelBounds();
  double* focus = this->Cursor3D->GetFocalPoint();

  // Compute the scale factor
  double sf = vtkMath::Norm(v) /
    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]));

  if (Y > this->Interactor->GetLastEventPosition()[1])
  {
    sf = 1.0 + sf;
  }
  else
  {
    sf = 1.0 - sf;
  }

  // Move the end points
  double newBounds[6];
  for (int i = 0; i < 3; i++)
  {
    newBounds[2 * i] = sf * (bounds[2 * i] - focus[i]) + focus[i];
    newBounds[2 * i + 1] = sf * (bounds[2 * i + 1] - focus[i]) + focus[i];
  }

  this->Cursor3D->SetModelBounds(newBounds);
  this->Cursor3D->Update();
}

void vtkPointWidget::CreateDefaultProperties()
{
  this->Property = vtkProperty::New();
  this->Property->SetAmbient(1.0);
  this->Property->SetAmbientColor(1.0, 1.0, 1.0);
  this->Property->SetLineWidth(0.5);

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

void vtkPointWidget::PlaceWidget(double bds[6])
{
  int i;
  double bounds[6], center[3];

  this->AdjustBounds(bds, bounds, center);

  this->Cursor3D->SetModelBounds(bounds);
  this->Cursor3D->SetFocalPoint(center);
  this->Cursor3D->Update();

  for (i = 0; i < 6; i++)
  {
    this->InitialBounds[i] = bounds[i];
  }
  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]));
}

void vtkPointWidget::GetPolyData(vtkPolyData* pd)
{
  this->Cursor3D->Update();
  pd->DeepCopy(this->Cursor3D->GetFocus());
}
VTK_ABI_NAMESPACE_END
