// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkBalloonWidget.h"
#include "vtkAssemblyPath.h"
#include "vtkBalloonRepresentation.h"
#include "vtkCommand.h"
#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkPickingManager.h"
#include "vtkProp.h"
#include "vtkPropPicker.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkRendererCollection.h"
#include "vtkStdString.h"
#include "vtkWidgetCallbackMapper.h"
#include "vtkWidgetEvent.h"

#include <cassert>
#include <map>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkBalloonWidget);

//-- Define the PIMPLd array of vtkProp and vtkString --
struct vtkBalloon
{
  std::string Text;
  vtkImageData* Image;

  vtkBalloon()
    : Image(nullptr)
  {
  }
  vtkBalloon(const vtkBalloon& balloon)
    : Text(balloon.Text)
    , Image(balloon.Image)
  {
    if (this->Image)
    {
      this->Image->Register(nullptr);
    }
  }
  vtkBalloon(std::string* str, vtkImageData* img)
  {
    this->Text = *str;
    this->Image = img;
    if (this->Image)
    {
      this->Image->Register(nullptr);
    }
  }
  vtkBalloon(const char* str, vtkImageData* img)
  {
    this->Text = str;
    this->Image = img;
    if (this->Image)
    {
      this->Image->Register(nullptr);
    }
  }
  ~vtkBalloon()
  {
    if (this->Image)
    {
      this->Image->UnRegister(nullptr);
    }
  }
  vtkBalloon& operator=(const vtkBalloon& balloon)
  {
    if (this == &balloon)
    {
      return *this;
    }

    this->Text = balloon.Text;

    // Don't leak if we already have an image.
    if (this->Image)
    {
      this->Image->UnRegister(nullptr);
      this->Image = nullptr;
    }

    this->Image = balloon.Image;
    if (this->Image)
    {
      this->Image->Register(nullptr);
    }

    return *this;
  }
  bool operator==(const vtkBalloon& balloon) const
  {
    if (this->Image == balloon.Image)
    {
      if (this->Text == balloon.Text)
      {
        return true;
      }
    }
    return false;
  }
  bool operator!=(const vtkBalloon& balloon) const { return !(*this == balloon); }
};

class vtkPropMap : public std::map<vtkProp*, vtkBalloon>
{
};
typedef std::map<vtkProp*, vtkBalloon>::iterator vtkPropMapIterator;

//------------------------------------------------------------------------------
vtkBalloonWidget::vtkBalloonWidget()
{
  this->Picker = vtkPropPicker::New();
  this->Picker->PickFromListOn();

  this->CurrentProp = nullptr;
  this->PropMap = new vtkPropMap;
}

//------------------------------------------------------------------------------
vtkBalloonWidget::~vtkBalloonWidget()
{
  this->Picker->Delete();

  if (this->CurrentProp)
  {
    this->CurrentProp->Delete();
    this->CurrentProp = nullptr;
  }

  this->PropMap->clear();
  delete this->PropMap;
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::SetEnabled(int enabling)
{
  this->Superclass::SetEnabled(enabling);

  if (this->Interactor && this->Interactor->GetRenderWindow())
  {
    this->SetCurrentRenderer(
      this->Interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer());
  }
  if (!this->CurrentRenderer)
  {
    return;
  }

  if (enabling)
  {
    this->CreateDefaultRepresentation();
    this->WidgetRep->SetRenderer(this->CurrentRenderer);
    this->WidgetRep->BuildRepresentation();
    this->CurrentRenderer->AddViewProp(this->WidgetRep);
  }
  else
  {
    this->CurrentRenderer->RemoveViewProp(this->WidgetRep);
    this->SetCurrentRenderer(nullptr);
  }
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::SetPicker(vtkAbstractPropPicker* picker)
{
  if (picker == nullptr || picker == this->Picker)
  {
    return;
  }

  // Configure picker appropriately
  picker->PickFromListOn();

  this->Picker->Delete();
  this->Picker = picker;
  this->Picker->Register(this);

  this->UnRegisterPickers();
  this->RegisterPickers();
  this->Modified();
}

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

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

//------------------------------------------------------------------------------
void vtkBalloonWidget::AddBalloon(vtkProp* prop, vtkStdString* str, vtkImageData* img)
{
  assert(prop);
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter == this->PropMap->end() || (*this->PropMap)[prop] != vtkBalloon(str, img))
  {
    (*this->PropMap)[prop] = vtkBalloon(str, img);
    this->Picker->DeletePickList(prop); // ensure only entered once
    this->Picker->AddPickList(prop);
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::AddBalloon(vtkProp* prop, const char* str, vtkImageData* img)
{
  vtkStdString s;
  if (str)
  {
    s = vtkStdString(str);
  }
  this->AddBalloon(prop, &s, img);
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::RemoveBalloon(vtkProp* prop)
{
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter != this->PropMap->end())
  {
    this->PropMap->erase(iter);
    if (prop != nullptr)
    {
      this->Picker->DeletePickList(prop);
    }
    this->Modified();
  }
}

//------------------------------------------------------------------------------
const char* vtkBalloonWidget::GetBalloonString(vtkProp* prop)
{
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter != this->PropMap->end())
  {
    return (*iter).second.Text.c_str();
  }
  return nullptr;
}

//------------------------------------------------------------------------------
vtkImageData* vtkBalloonWidget::GetBalloonImage(vtkProp* prop)
{
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter != this->PropMap->end())
  {
    return (*iter).second.Image;
  }
  return nullptr;
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::UpdateBalloonString(vtkProp* prop, const char* str)
{
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter != this->PropMap->end())
  {
    (*iter).second.Text = str;
    this->WidgetRep->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkBalloonWidget::UpdateBalloonImage(vtkProp* prop, vtkImageData* image)
{
  vtkPropMapIterator iter = this->PropMap->find(prop);
  if (iter != this->PropMap->end())
  {
    (*iter).second.Image = image;
    this->WidgetRep->Modified();
  }
}

//------------------------------------------------------------------------------
int vtkBalloonWidget::SubclassHoverAction()
{
  double e[2];
  e[0] = static_cast<double>(this->Interactor->GetEventPosition()[0]);
  e[1] = static_cast<double>(this->Interactor->GetEventPosition()[1]);
  if (this->CurrentProp)
  {
    this->CurrentProp->UnRegister(this);
    this->CurrentProp = nullptr;
  }

  vtkAssemblyPath* path = this->GetAssemblyPath(e[0], e[1], 0., this->Picker);

  if (path != nullptr)
  {
    vtkPropMapIterator iter = this->PropMap->find(path->GetFirstNode()->GetViewProp());
    if (iter != this->PropMap->end())
    {
      this->CurrentProp = (*iter).first;
      this->CurrentProp->Register(this);
      reinterpret_cast<vtkBalloonRepresentation*>(this->WidgetRep)
        ->SetBalloonText((*iter).second.Text.c_str());
      reinterpret_cast<vtkBalloonRepresentation*>(this->WidgetRep)
        ->SetBalloonImage((*iter).second.Image);
      this->WidgetRep->StartWidgetInteraction(e);
      this->Render();
    }
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkBalloonWidget::SubclassEndHoverAction()
{
  double e[2];
  e[0] = static_cast<double>(this->Interactor->GetEventPosition()[0]);
  e[1] = static_cast<double>(this->Interactor->GetEventPosition()[1]);
  this->WidgetRep->EndWidgetInteraction(e);
  this->Render();

  return 1;
}

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

  os << indent << "Current Prop: ";
  if (this->CurrentProp)
  {
    os << this->CurrentProp << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Picker: " << this->Picker << "\n";
}
VTK_ABI_NAMESPACE_END
