// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkContourWidget.h"
#include "vtkCallbackCommand.h"
#include "vtkCommand.h"
#include "vtkEvent.h"
#include "vtkObjectFactory.h"
#include "vtkOrientedGlyphContourRepresentation.h"
#include "vtkPolyData.h"
#include "vtkProperty.h"
#include "vtkProperty2D.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSphereSource.h"
#include "vtkWidgetCallbackMapper.h"
#include "vtkWidgetEvent.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkContourWidget);

//------------------------------------------------------------------------------
vtkContourWidget::vtkContourWidget()
{
  this->ManagesCursor = 0;
  this->WidgetState = vtkContourWidget::Start;
  this->CurrentHandle = 0;
  this->AllowNodePicking = 0;
  this->FollowCursor = 0;
  this->ContinuousDraw = 0;
  this->ContinuousActive = 0;

  // These are the event callbacks supported by this widget
  this->CallbackMapper->SetCallbackMethod(
    vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, vtkContourWidget::SelectAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonPressEvent,
    vtkWidgetEvent::AddFinalPoint, this, vtkContourWidget::AddFinalPointAction);
  this->CallbackMapper->SetCallbackMethod(
    vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, vtkContourWidget::MoveAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
    vtkWidgetEvent::EndSelect, this, vtkContourWidget::EndSelectAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::NoModifier, 127, 1,
    "Delete", vtkWidgetEvent::Delete, this, vtkContourWidget::DeleteAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::NoModifier, 8, 1,
    "BackSpace", vtkWidgetEvent::Delete, this, vtkContourWidget::DeleteAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::KeyPressEvent, vtkEvent::ShiftModifier, 127,
    1, "Delete", vtkWidgetEvent::Reset, this, vtkContourWidget::ResetAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonPressEvent,
    vtkWidgetEvent::Translate, this, vtkContourWidget::TranslateContourAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonReleaseEvent,
    vtkWidgetEvent::EndTranslate, this, vtkContourWidget::EndSelectAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonPressEvent, vtkWidgetEvent::Scale,
    this, vtkContourWidget::ScaleContourAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonReleaseEvent,
    vtkWidgetEvent::EndScale, this, vtkContourWidget::EndSelectAction);

  this->CreateDefaultRepresentation();
}

//------------------------------------------------------------------------------
vtkContourWidget::~vtkContourWidget() = default;

//------------------------------------------------------------------------------
void vtkContourWidget::CreateDefaultRepresentation()
{
  if (!this->WidgetRep)
  {
    vtkOrientedGlyphContourRepresentation* rep = vtkOrientedGlyphContourRepresentation::New();

    this->WidgetRep = rep;

    vtkSphereSource* ss = vtkSphereSource::New();
    ss->SetRadius(0.5);
    ss->Update();
    rep->SetActiveCursorShape(ss->GetOutput());
    ss->Delete();

    rep->GetProperty()->SetColor(0.25, 1.0, 0.25);

    vtkProperty* property = vtkProperty::SafeDownCast(rep->GetActiveProperty());
    if (property)
    {
      property->SetRepresentationToSurface();
      property->SetAmbient(0.1);
      property->SetDiffuse(0.9);
      property->SetSpecular(0.0);
    }
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::CloseLoop()
{
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep);
  if (!rep->GetClosedLoop() && rep->GetNumberOfNodes() > 1)
  {
    this->WidgetState = vtkContourWidget::Manipulate;
    rep->ClosedLoopOn();
    this->Render();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::SetEnabled(int enabling)
{
  // The handle widgets are not actually enabled until they are placed.
  // The handle widgets take their representation from the vtkContourRepresentation.
  if (enabling)
  {
    if (this->WidgetState == vtkContourWidget::Start)
    {
      reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep)->VisibilityOff();
    }
    else
    {
      reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep)->VisibilityOn();
    }
  }

  this->Superclass::SetEnabled(enabling);
}

// The following methods are the callbacks that the contour widget responds to.
//------------------------------------------------------------------------------
void vtkContourWidget::SelectAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  int X = self->Interactor->GetEventPosition()[0];
  int Y = self->Interactor->GetEventPosition()[1];
  double pos[2];
  pos[0] = X;
  pos[1] = Y;

  if (self->ContinuousDraw)
  {
    self->ContinuousActive = 0;
  }

  switch (self->WidgetState)
  {
    case vtkContourWidget::Start:
    case vtkContourWidget::Define:
    {
      // If we are following the cursor, let's add 2 nodes rightaway, on the
      // first click. The second node is the one that follows the cursor
      // around.
      if ((self->FollowCursor || self->ContinuousDraw) && (rep->GetNumberOfNodes() == 0))
      {
        self->AddNode();
      }
      self->AddNode();
      if (self->ContinuousDraw)
      {
        self->ContinuousActive = 1;
      }
      break;
    }

    case vtkContourWidget::Manipulate:
    {
      if (rep->ActivateNode(X, Y))
      {
        self->Superclass::StartInteraction();
        self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
        self->StartInteraction();
        rep->SetCurrentOperationToTranslate();
        rep->StartWidgetInteraction(pos);
        self->EventCallbackCommand->SetAbortFlag(1);
      }
      else if (rep->AddNodeOnContour(X, Y))
      {
        if (rep->ActivateNode(X, Y))
        {
          rep->SetCurrentOperationToTranslate();
          rep->StartWidgetInteraction(pos);
        }
        self->EventCallbackCommand->SetAbortFlag(1);
      }
      else if (!rep->GetNeedToRender())
      {
        rep->SetRebuildLocator(true);
      }
      break;
    }
  }

  if (rep->GetNeedToRender())
  {
    self->Render();
    rep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::AddFinalPointAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  if (self->WidgetState != vtkContourWidget::Manipulate && rep->GetNumberOfNodes() >= 1)
  {
    // In follow cursor and continuous draw mode, the "extra" node
    // has already been added for us.
    if (!self->FollowCursor && !self->ContinuousDraw)
    {
      self->AddNode();
    }

    if (self->ContinuousDraw)
    {
      self->ContinuousActive = 0;
    }

    self->WidgetState = vtkContourWidget::Manipulate;
    self->EventCallbackCommand->SetAbortFlag(1);
    self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
  }

  if (rep->GetNeedToRender())
  {
    self->Render();
    rep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::AddNode()
{
  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // If the rep already has at least 2 nodes, check how close we are to
  // the first
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep);

  int numNodes = rep->GetNumberOfNodes();
  if (numNodes > 1)
  {
    int pixelTolerance = rep->GetPixelTolerance();
    int pixelTolerance2 = pixelTolerance * pixelTolerance;

    double displayPos[2];
    if (!rep->GetNthNodeDisplayPosition(0, displayPos))
    {
      vtkErrorMacro("Can't get first node display position!");
      return;
    }

    // if in continuous draw mode, we don't want to close the loop until we are at least
    // numNodes > pixelTolerance away

    int distance2 = static_cast<int>(
      (X - displayPos[0]) * (X - displayPos[0]) + (Y - displayPos[1]) * (Y - displayPos[1]));

    if ((distance2 < pixelTolerance2 && numNodes > 2) ||
      (this->ContinuousDraw && numNodes > pixelTolerance && distance2 < pixelTolerance2))
    {
      // yes - we have made a loop. Stop defining and switch to
      // manipulate mode
      this->WidgetState = vtkContourWidget::Manipulate;
      rep->ClosedLoopOn();
      this->Render();
      this->EventCallbackCommand->SetAbortFlag(1);
      this->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
      return;
    }
  }

  if (rep->AddNodeAtDisplayPosition(X, Y))
  {
    if (this->WidgetState == vtkContourWidget::Start)
    {
      this->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
    }

    this->WidgetState = vtkContourWidget::Define;
    rep->VisibilityOn();
    this->EventCallbackCommand->SetAbortFlag(1);
    this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
  }
}

//------------------------------------------------------------------------------
// Note that if you select the contour at a location that is not moused over
// a control point, the translate action makes the closest contour node
// jump to the current mouse location. Perhaps we should either
// (a) Disable translations when not moused over a control point
// (b) Fix the jumping behaviour by calculating motion vectors from the start
//     of the interaction.
void vtkContourWidget::TranslateContourAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);

  if (self->WidgetState != vtkContourWidget::Manipulate)
  {
    return;
  }

  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  int X = self->Interactor->GetEventPosition()[0];
  int Y = self->Interactor->GetEventPosition()[1];
  double pos[2];
  pos[0] = X;
  pos[1] = Y;

  if (rep->ActivateNode(X, Y))
  {
    self->Superclass::StartInteraction();
    self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
    self->StartInteraction();
    rep->SetCurrentOperationToShift(); // Here
    rep->StartWidgetInteraction(pos);
    self->EventCallbackCommand->SetAbortFlag(1);
  }
  else
  {
    double p[3];
    int idx;
    if (rep->FindClosestPointOnContour(X, Y, p, &idx))
    {
      rep->GetNthNodeDisplayPosition(idx, pos);
      rep->ActivateNode(pos);
      self->Superclass::StartInteraction();
      self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
      self->StartInteraction();
      rep->SetCurrentOperationToShift(); // Here
      rep->StartWidgetInteraction(pos);
      self->EventCallbackCommand->SetAbortFlag(1);
    }
  }

  if (rep->GetNeedToRender())
  {
    self->Render();
    rep->NeedToRenderOff();
  }
}
//------------------------------------------------------------------------------
// Note that if you select the contour at a location that is not moused over
// a control point, the scale action makes the closest contour node
// jump to the current mouse location. Perhaps we should either
// (a) Disable scaling when not moused over a control point
// (b) Fix the jumping behaviour by calculating motion vectors from the start
//     of the interaction.
void vtkContourWidget::ScaleContourAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);

  if (self->WidgetState != vtkContourWidget::Manipulate)
    return;

  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  int X = self->Interactor->GetEventPosition()[0];
  int Y = self->Interactor->GetEventPosition()[1];
  double pos[2];
  pos[0] = X;
  pos[1] = Y;

  if (rep->ActivateNode(X, Y))
  {
    self->Superclass::StartInteraction();
    self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
    self->StartInteraction();
    rep->SetCurrentOperationToScale(); // Here
    rep->StartWidgetInteraction(pos);
    self->EventCallbackCommand->SetAbortFlag(1);
  }
  else
  {
    double p[3];
    int idx;
    if (rep->FindClosestPointOnContour(X, Y, p, &idx))
    {
      rep->GetNthNodeDisplayPosition(idx, pos);
      rep->ActivateNode(pos);
      self->Superclass::StartInteraction();
      self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
      self->StartInteraction();
      rep->SetCurrentOperationToScale(); // Here
      rep->StartWidgetInteraction(pos);
      self->EventCallbackCommand->SetAbortFlag(1);
    }
  }

  if (rep->GetNeedToRender())
  {
    self->Render();
    rep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::DeleteAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);

  if (self->WidgetState == vtkContourWidget::Start)
  {
    return;
  }

  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  if (self->WidgetState == vtkContourWidget::Define)
  {
    if (rep->DeleteLastNode())
    {
      self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
    }
  }
  else
  {
    int X = self->Interactor->GetEventPosition()[0];
    int Y = self->Interactor->GetEventPosition()[1];
    rep->ActivateNode(X, Y);
    if (rep->DeleteActiveNode())
    {
      self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
    }
    rep->ActivateNode(X, Y);
    int numNodes = rep->GetNumberOfNodes();
    if (numNodes < 3)
    {
      rep->ClosedLoopOff();
      if (numNodes < 2)
      {
        self->WidgetState = vtkContourWidget::Define;
      }
    }
  }

  if (rep->GetNeedToRender())
  {
    self->Render();
    rep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::MoveAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);

  if (self->WidgetState == vtkContourWidget::Start)
  {
    return;
  }

  int X = self->Interactor->GetEventPosition()[0];
  int Y = self->Interactor->GetEventPosition()[1];
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  if (self->WidgetState == vtkContourWidget::Define)
  {
    if (self->FollowCursor || self->ContinuousDraw)
    {
      // Have the last node follow the mouse in this case...
      const int numNodes = rep->GetNumberOfNodes();

      // First check if the last node is near the first node, if so, we intend
      // closing the loop.
      if (numNodes > 1)
      {
        double displayPos[2];
        int pixelTolerance = rep->GetPixelTolerance();
        int pixelTolerance2 = pixelTolerance * pixelTolerance;

        rep->GetNthNodeDisplayPosition(0, displayPos);

        int distance2 = static_cast<int>(
          (X - displayPos[0]) * (X - displayPos[0]) + (Y - displayPos[1]) * (Y - displayPos[1]));

        const bool mustCloseLoop = (distance2 < pixelTolerance2 && numNodes > 2) ||
          (self->ContinuousDraw && numNodes > pixelTolerance && distance2 < pixelTolerance2);

        if (mustCloseLoop != (rep->GetClosedLoop() == 1))
        {
          if (rep->GetClosedLoop())
          {
            // We need to open the closed loop.
            // We do this by adding a node at (X,Y). If by chance the point
            // placer says that (X,Y) is invalid, we'll add it at the location
            // of the first control point (which we know is valid).

            if (!rep->AddNodeAtDisplayPosition(X, Y))
            {
              double closedLoopPoint[3];
              rep->GetNthNodeWorldPosition(0, closedLoopPoint);
              rep->AddNodeAtDisplayPosition(closedLoopPoint);
            }
            rep->ClosedLoopOff();
          }
          else
          {
            // We need to close the open loop. Delete the node that's following
            // the mouse cursor and close the loop between the previous node and
            // the first node.
            rep->DeleteLastNode();
            rep->ClosedLoopOn();
          }
        }
        else if (rep->GetClosedLoop() == 0)
        {
          if (self->ContinuousDraw && self->ContinuousActive)
          {
            rep->AddNodeAtDisplayPosition(X, Y);
          }
          else
          {
            // If we aren't changing the loop topology, simply update the position
            // of the latest node to follow the mouse cursor position (X,Y).
            rep->SetNthNodeDisplayPosition(numNodes - 1, X, Y);
          }
        }
      }
    }
    else
    {
      return;
    }
  }

  if (rep->GetCurrentOperation() == vtkContourRepresentation::Inactive)
  {
    rep->ComputeInteractionState(X, Y);
    rep->ActivateNode(X, Y);
  }
  else
  {
    double pos[2];
    pos[0] = X;
    pos[1] = Y;
    self->WidgetRep->WidgetInteraction(pos);
    self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
  }

  if (self->WidgetRep->GetNeedToRender())
  {
    self->Render();
    self->WidgetRep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::EndSelectAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);
  vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(self->WidgetRep);

  if (self->ContinuousDraw)
  {
    self->ContinuousActive = 0;
  }

  // Do nothing if inactive
  if (rep->GetCurrentOperation() == vtkContourRepresentation::Inactive)
  {
    rep->SetRebuildLocator(true);
    return;
  }

  rep->SetCurrentOperationToInactive();
  self->EventCallbackCommand->SetAbortFlag(1);
  self->Superclass::EndInteraction();
  self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);

  // Node picking
  if (self->AllowNodePicking && self->Interactor->GetControlKey() &&
    self->WidgetState == vtkContourWidget::Manipulate)
  {
    rep->ToggleActiveNodeSelected();
  }

  if (self->WidgetRep->GetNeedToRender())
  {
    self->Render();
    self->WidgetRep->NeedToRenderOff();
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::ResetAction(vtkAbstractWidget* w)
{
  vtkContourWidget* self = reinterpret_cast<vtkContourWidget*>(w);
  self->Initialize(nullptr);
}

//------------------------------------------------------------------------------
void vtkContourWidget::Initialize(vtkPolyData* pd, int state, vtkIdList* idList)
{
  if (!this->GetEnabled())
  {
    vtkErrorMacro(<< "Enable widget before initializing");
  }

  if (this->WidgetRep)
  {
    vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep);

    if (pd == nullptr)
    {
      while (rep->DeleteLastNode())
      {
      }
      rep->ClosedLoopOff();
      this->Render();
      rep->NeedToRenderOff();
      rep->VisibilityOff();
      this->WidgetState = vtkContourWidget::Start;
    }
    else
    {
      rep->Initialize(pd, idList);
      this->WidgetState = (rep->GetClosedLoop() || state == 1) ? vtkContourWidget::Manipulate
                                                               : vtkContourWidget::Define;
    }
  }
}

//------------------------------------------------------------------------------
void vtkContourWidget::SetAllowNodePicking(vtkTypeBool val)
{
  if (this->AllowNodePicking == val)
  {
    return;
  }
  this->AllowNodePicking = val;
  if (this->AllowNodePicking)
  {
    vtkContourRepresentation* rep = reinterpret_cast<vtkContourRepresentation*>(this->WidgetRep);
    rep->SetShowSelectedNodes(this->AllowNodePicking);
  }
}

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

  os << indent << "WidgetState: " << this->WidgetState << endl;
  os << indent << "CurrentHandle: " << this->CurrentHandle << endl;
  os << indent << "AllowNodePicking: " << this->AllowNodePicking << endl;
  os << indent << "FollowCursor: " << (this->FollowCursor ? "On" : "Off") << endl;
  os << indent << "ContinuousDraw: " << (this->ContinuousDraw ? "On" : "Off") << endl;
}
VTK_ABI_NAMESPACE_END
