// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkHandleWidget.h"
#include "vtkCallbackCommand.h"
#include "vtkCommand.h"
#include "vtkEvent.h"
#include "vtkEventData.h"
#include "vtkObjectFactory.h"
#include "vtkPointHandleRepresentation3D.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkWidgetCallbackMapper.h"
#include "vtkWidgetEvent.h"
#include "vtkWidgetEventTranslator.h"

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkHandleWidget);

//------------------------------------------------------------------------------
vtkHandleWidget::vtkHandleWidget()
{
  // Set the initial state
  this->WidgetState = vtkHandleWidget::Inactive;

  // Okay, define the events for this widget
  this->CallbackMapper->SetCallbackMethod(
    vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, vtkHandleWidget::SelectAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
    vtkWidgetEvent::EndSelect, this, vtkHandleWidget::EndSelectAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonPressEvent,
    vtkWidgetEvent::Translate, this, vtkHandleWidget::TranslateAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonReleaseEvent,
    vtkWidgetEvent::EndTranslate, this, vtkHandleWidget::EndSelectAction);
  this->CallbackMapper->SetCallbackMethod(
    vtkCommand::RightButtonPressEvent, vtkWidgetEvent::Scale, this, vtkHandleWidget::ScaleAction);
  this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonReleaseEvent,
    vtkWidgetEvent::EndScale, this, vtkHandleWidget::EndSelectAction);
  this->CallbackMapper->SetCallbackMethod(
    vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, vtkHandleWidget::MoveAction);

  {
    vtkNew<vtkEventDataDevice3D> ed;
    ed->SetDevice(vtkEventDataDevice::Any);
    ed->SetInput(vtkEventDataDeviceInput::Any);
    ed->SetAction(vtkEventDataAction::Press);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::Select3DEvent, ed, vtkWidgetEvent::Select3D,
      this, vtkHandleWidget::SelectAction3D);
  }

  {
    vtkNew<vtkEventDataDevice3D> ed;
    ed->SetDevice(vtkEventDataDevice::Any);
    ed->SetInput(vtkEventDataDeviceInput::Any);
    ed->SetAction(vtkEventDataAction::Release);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::Select3DEvent, ed,
      vtkWidgetEvent::EndSelect3D, this, vtkHandleWidget::EndSelectAction);
  }

  {
    vtkNew<vtkEventDataDevice3D> ed;
    ed->SetDevice(vtkEventDataDevice::Any);
    ed->SetInput(vtkEventDataDeviceInput::Any);
    this->CallbackMapper->SetCallbackMethod(
      vtkCommand::Move3DEvent, ed, vtkWidgetEvent::Move3D, this, vtkHandleWidget::MoveAction3D);
  }
  this->LastDevice = static_cast<int>(vtkEventDataDevice::Any);

  this->ShowInactive = false;
  this->EnableAxisConstraint = 1;
  this->EnableTranslation = 1;
  this->AllowHandleResize = 1;

  this->KeyEventCallbackCommand = vtkCallbackCommand::New();
  this->KeyEventCallbackCommand->SetClientData(this);
  this->KeyEventCallbackCommand->SetCallback(vtkHandleWidget::ProcessKeyEvents);
}

//------------------------------------------------------------------------------
vtkHandleWidget::~vtkHandleWidget()
{
  this->KeyEventCallbackCommand->Delete();
}

//------------------------------------------------------------------------------
void vtkHandleWidget::CreateDefaultRepresentation()
{
  if (!this->WidgetRep)
  {
    this->WidgetRep = vtkPointHandleRepresentation3D::New();
  }
}

//------------------------------------------------------------------------------
void vtkHandleWidget::SetCursor(int cState)
{
  if (this->ManagesCursor)
  {
    switch (cState)
    {
      case vtkHandleRepresentation::Outside:
        this->RequestCursorShape(VTK_CURSOR_DEFAULT);
        break;
      default:
        this->RequestCursorShape(VTK_CURSOR_HAND);
    }
  }
}

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

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

  self->WidgetRep->ComputeInteractionState(X, Y);
  if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
  {
    return;
  }

  // We are definitely selected
  if (!self->Parent)
  {
    self->GrabFocus(self->EventCallbackCommand);
  }
  double eventPos[2];
  eventPos[0] = static_cast<double>(X);
  eventPos[1] = static_cast<double>(Y);
  self->WidgetRep->StartWidgetInteraction(eventPos);

  self->WidgetState = vtkHandleWidget::Active;
  reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
    ->SetInteractionState(vtkHandleRepresentation::Selecting);

  vtkHandleWidget::GenericAction(self);
}

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

  self->WidgetRep->ComputeComplexInteractionState(
    self->Interactor, self, vtkWidgetEvent::Select3D, self->CallData);

  if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
  {
    return;
  }

  self->EventCallbackCommand->SetAbortFlag(1);
  self->WidgetRep->StartComplexInteraction(
    self->Interactor, self, vtkWidgetEvent::Select3D, self->CallData);

  // watch for motion events from this device
  vtkEventData* edata = static_cast<vtkEventData*>(self->CallData);
  vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
  if (!edd)
  {
    return;
  }
  self->LastDevice = static_cast<int>(edd->GetDevice());

  self->WidgetState = vtkHandleWidget::Active;
  reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
    ->SetInteractionState(vtkHandleRepresentation::Selecting);

  vtkHandleWidget::GenericAction(self);
}

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

  double eventPos[2];
  eventPos[0] = static_cast<double>(self->Interactor->GetEventPosition()[0]);
  eventPos[1] = static_cast<double>(self->Interactor->GetEventPosition()[1]);
  self->WidgetRep->StartWidgetInteraction(eventPos);

  if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
  {
    return;
  }

  // We are definitely selected
  self->WidgetState = vtkHandleWidget::Active;
  reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
    ->SetInteractionState(vtkHandleRepresentation::Translating);

  vtkHandleWidget::GenericAction(self);
}

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

  if (self->AllowHandleResize)
  {

    double eventPos[2];
    eventPos[0] = static_cast<double>(self->Interactor->GetEventPosition()[0]);
    eventPos[1] = static_cast<double>(self->Interactor->GetEventPosition()[1]);

    self->WidgetRep->StartWidgetInteraction(eventPos);
    if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
    {
      return;
    }

    // We are definitely selected
    self->WidgetState = vtkHandleWidget::Active;
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
      ->SetInteractionState(vtkHandleRepresentation::Scaling);

    vtkHandleWidget::GenericAction(self);
  }
}

//------------------------------------------------------------------------------
void vtkHandleWidget::GenericAction(vtkHandleWidget* self)
{
  // This is redundant but necessary on some systems (windows) because the
  // cursor is switched during OS event processing and reverts to the default
  // cursor.
  self->SetCursor(self->WidgetRep->GetInteractionState());

  // Check to see whether motion is constrained
  if (self->Interactor->GetShiftKey() && self->EnableAxisConstraint)
  {
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->ConstrainedOn();
  }
  else
  {
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->ConstrainedOff();
  }

  // Highlight as necessary
  self->WidgetRep->Highlight(1);

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

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

  if (self->WidgetState != vtkHandleWidget::Active)
  {
    return;
  }

  // Return state to not selected
  self->WidgetState = vtkHandleWidget::Start;
  self->LastDevice = static_cast<int>(vtkEventDataDevice::Any);

  // Highlight as necessary
  self->WidgetRep->Highlight(0);

  // stop adjusting
  if (!self->Parent)
  {
    self->ReleaseFocus();
  }
  self->EventCallbackCommand->SetAbortFlag(1);
  self->EndInteraction();
  self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
  self->WidgetState = vtkHandleWidget::Start;
  self->Render();
}

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

  // compute some info we need for all cases
  int X = self->Interactor->GetEventPosition()[0];
  int Y = self->Interactor->GetEventPosition()[1];

  // Set the cursor appropriately
  if (self->WidgetState == vtkHandleWidget::Start)
  {
    int state = self->WidgetRep->GetInteractionState();
    self->WidgetRep->ComputeInteractionState(X, Y);
    self->SetCursor(self->WidgetRep->GetInteractionState());
    // Must rerender if we change appearance
    if (reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->GetActiveRepresentation() &&
      state != self->WidgetRep->GetInteractionState())
    {
      self->Render();
    }
    return;
  }

  if (!self->EnableTranslation)
  {
    return;
  }

  // Okay, adjust the representation
  double eventPosition[2];
  eventPosition[0] = static_cast<double>(X);
  eventPosition[1] = static_cast<double>(Y);
  self->WidgetRep->WidgetInteraction(eventPosition);

  // Got this event, we are finished
  self->EventCallbackCommand->SetAbortFlag(1);
  self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
  self->Render();
}

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

  // watch for motion events from the selected device
  vtkEventData* edata = static_cast<vtkEventData*>(self->CallData);
  vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
  if (!edd)
  {
    return;
  }
  if (!edd->DeviceMatches(static_cast<vtkEventDataDevice>(self->LastDevice)))
  {
    return;
  }

  // Set the cursor appropriately
  if (self->WidgetState == vtkHandleWidget::Start)
  {
    int state = self->WidgetRep->GetInteractionState();
    self->WidgetRep->ComputeComplexInteractionState(
      self->Interactor, self, vtkWidgetEvent::Move3D, self->CallData);

    self->SetCursor(self->WidgetRep->GetInteractionState());

    // Must rerender if we change appearance
    if (reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->GetActiveRepresentation() &&
      state != self->WidgetRep->GetInteractionState())
    {
      self->Render();
    }
    return;
  }

  // Okay, adjust the representation
  self->WidgetRep->ComplexInteraction(
    self->Interactor, self, vtkWidgetEvent::Move3D, self->CallData);

  // Got this event, we are finished
  self->EventCallbackCommand->SetAbortFlag(1);
  self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
  self->Render();
}

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

  os << indent << "Allow Handle Resize: " << (this->AllowHandleResize ? "On\n" : "Off\n");

  os << indent << "Enable Axis Constraint: " << (this->EnableAxisConstraint ? "On\n" : "Off\n");

  os << indent << "Show Inactive: " << (this->ShowInactive ? "On\n" : "Off\n");
  os << indent << "WidgetState: " << this->WidgetState << endl;
}

//------------------------------------------------------------------------------
void vtkHandleWidget::SetEnabled(int enabling)
{
  int enabled = this->Enabled;
  if (this->Enabled == enabling)
  {
    return;
  }
  if (!this->ShowInactive)
  {
    // Forward to superclass
    this->Superclass::SetEnabled(enabling);
    if (enabling)
    {
      this->WidgetState = vtkHandleWidget::Start;
    }
    else
    {
      this->WidgetState = vtkHandleWidget::Inactive;
    }
  }
  else
  {
    if (enabling) //----------------
    {
      this->Superclass::SetEnabled(enabling);
      this->WidgetState = vtkHandleWidget::Start;
    }

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

      this->Enabled = 0;

      // don't listen for events any more
      if (!this->Parent)
      {
        this->Interactor->RemoveObserver(this->EventCallbackCommand);
      }
      else
      {
        this->Parent->RemoveObserver(this->EventCallbackCommand);
      }

      this->WidgetState = vtkHandleWidget::Inactive;
      this->InvokeEvent(vtkCommand::DisableEvent, nullptr);
    }
  }

  // We defer enabling the handles until the selection process begins
  if (enabling && !enabled)
  {
    if (this->Parent)
    {
      this->Parent->AddObserver(
        vtkCommand::KeyPressEvent, this->KeyEventCallbackCommand, this->Priority);
      this->Parent->AddObserver(
        vtkCommand::KeyReleaseEvent, this->KeyEventCallbackCommand, this->Priority);
    }
    else
    {
      this->Interactor->AddObserver(
        vtkCommand::KeyPressEvent, this->KeyEventCallbackCommand, this->Priority);
      this->Interactor->AddObserver(
        vtkCommand::KeyReleaseEvent, this->KeyEventCallbackCommand, this->Priority);
    }
  }
  else if (!enabling && enabled)
  {
    if (this->Parent)
    {
      this->Parent->RemoveObserver(this->KeyEventCallbackCommand);
    }
    else
    {
      this->Interactor->RemoveObserver(this->KeyEventCallbackCommand);
    }
  }
}

//------------------------------------------------------------------------------
void vtkHandleWidget::ProcessKeyEvents(vtkObject*, unsigned long event, void* clientdata, void*)
{
  vtkHandleWidget* self = static_cast<vtkHandleWidget*>(clientdata);
  vtkHandleRepresentation* rep = vtkHandleRepresentation::SafeDownCast(self->WidgetRep);
  char* cKeySym = self->Interactor->GetKeySym();
  std::string keySym = cKeySym != nullptr ? cKeySym : "";
  std::transform(keySym.begin(), keySym.end(), keySym.begin(), ::toupper);
  if (event == vtkCommand::KeyPressEvent)
  {
    if (keySym == "X")
    {
      rep->SetXTranslationAxisOn();
    }
    else if (keySym == "Y")
    {
      rep->SetYTranslationAxisOn();
    }
    else if (keySym == "Z")
    {
      rep->SetZTranslationAxisOn();
    }
  }
  else if (event == vtkCommand::KeyReleaseEvent)
  {
    if (keySym == "X" || keySym == "Y" || keySym == "Z")
    {
      rep->SetTranslationAxisOff();
    }
  }
}
VTK_ABI_NAMESPACE_END
