// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkTexturedButtonRepresentation.h"
#include "vtkActor.h"
#include "vtkAssemblyPath.h"
#include "vtkCamera.h"
#include "vtkCellPicker.h"
#include "vtkCoordinate.h"
#include "vtkFollower.h"
#include "vtkImageData.h"
#include "vtkInteractorObserver.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"
#include "vtkSmartPointer.h"
#include "vtkTexture.h"

#include <algorithm>
#include <map>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkTexturedButtonRepresentation);

vtkCxxSetObjectMacro(vtkTexturedButtonRepresentation, Property, vtkProperty);
vtkCxxSetObjectMacro(vtkTexturedButtonRepresentation, HoveringProperty, vtkProperty);
vtkCxxSetObjectMacro(vtkTexturedButtonRepresentation, SelectingProperty, vtkProperty);

// Map of textures
class vtkTextureArray : public std::map<int, vtkSmartPointer<vtkImageData>>
{
};
typedef std::map<int, vtkSmartPointer<vtkImageData>>::iterator vtkTextureArrayIterator;

//------------------------------------------------------------------------------
vtkTexturedButtonRepresentation::vtkTexturedButtonRepresentation()
{
  this->Mapper = vtkPolyDataMapper::New();
  this->Texture = vtkTexture::New();
  this->Texture->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_ADD);
  this->Actor = vtkActor::New();
  this->Actor->SetMapper(this->Mapper);
  this->Actor->SetTexture(this->Texture);
  this->Follower = vtkFollower::New();
  this->Follower->SetMapper(this->Mapper);
  this->Follower->SetTexture(this->Texture);

  // Following
  this->FollowCamera = 0;

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

  // List of textures
  this->TextureArray = new vtkTextureArray;

  this->Picker = vtkCellPicker::New();
  this->Picker->AddPickList(this->Actor);
  this->Picker->AddPickList(this->Follower);
  this->Picker->PickFromListOn();
}

//------------------------------------------------------------------------------
vtkTexturedButtonRepresentation::~vtkTexturedButtonRepresentation()
{
  this->Actor->Delete();
  this->Follower->Delete();
  this->Mapper->Delete();
  this->Texture->Delete();

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

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

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

  delete this->TextureArray;

  this->Picker->Delete();
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::SetButtonGeometry(vtkPolyData* pd)
{
  this->Mapper->SetInputData(pd);
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::SetButtonGeometryConnection(vtkAlgorithmOutput* algOutput)
{
  this->Mapper->SetInputConnection(algOutput);
}

//------------------------------------------------------------------------------
vtkPolyData* vtkTexturedButtonRepresentation::GetButtonGeometry()
{
  return this->Mapper->GetInput();
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::SetButtonTexture(int i, vtkImageData* image)
{
  i = std::max(i, 0);
  i = std::min(i, this->NumberOfStates - 1);

  (*this->TextureArray)[i] = image;
}

//------------------------------------------------------------------------------
vtkImageData* vtkTexturedButtonRepresentation::GetButtonTexture(int i)
{
  i = std::max(i, 0);
  i = std::min(i, this->NumberOfStates - 1);

  vtkTextureArrayIterator iter = this->TextureArray->find(i);
  if (iter != this->TextureArray->end())
  {
    return (*iter).second;
  }
  else
  {
    return nullptr;
  }
}

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

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::PlaceWidget(double scale, double xyz[3], double normal[3])
{
  // Translate the center
  double bds[6], center[3];
  this->Actor->GetBounds(bds);
  center[0] = (bds[0] + bds[1]) / 2.0;
  center[1] = (bds[2] + bds[3]) / 2.0;
  center[2] = (bds[4] + bds[5]) / 2.0;

  this->Actor->AddPosition(xyz[0] - center[0], xyz[1] - center[1], xyz[2] - center[2]);
  this->Follower->AddPosition(xyz[0] - center[0], xyz[1] - center[1], xyz[2] - center[2]);

  // Scale the button
  this->Actor->SetScale(scale, scale, scale);
  this->Follower->SetScale(scale, scale, scale);

  // Rotate the button to align with the normal Cross the z axis with the
  // normal to get a rotation vector. Then rotate around it.
  double zAxis[3];
  zAxis[0] = zAxis[1] = 0.0;
  zAxis[2] = 1.0;
  double rotAxis[3], angle;

  vtkMath::Normalize(normal);
  vtkMath::Cross(zAxis, normal, rotAxis);
  angle = acos(vtkMath::Dot(zAxis, normal));
  this->Actor->RotateWXYZ(vtkMath::DegreesFromRadians(angle), rotAxis[0], rotAxis[1], rotAxis[2]);
  this->Follower->RotateWXYZ(
    vtkMath::DegreesFromRadians(angle), rotAxis[0], rotAxis[1], rotAxis[2]);
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::PlaceWidget(double bds[6])
{
  double bounds[6], center[3], aBds[6], aCenter[3];

  this->AdjustBounds(bds, bounds, center);
  for (int 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]));

  // Get the bounds of the actor
  this->Actor->GetBounds(aBds);
  aCenter[0] = (aBds[0] + aBds[1]) / 2.0;
  aCenter[1] = (aBds[2] + aBds[3]) / 2.0;
  aCenter[2] = (aBds[4] + aBds[5]) / 2.0;

  // Now fit the actor bounds in the place bounds by tampering with its
  // transform.
  this->Actor->AddPosition(center[0] - aCenter[0], center[1] - aCenter[1], center[2] - aCenter[2]);
  this->Follower->AddPosition(
    center[0] - aCenter[0], center[1] - aCenter[1], center[2] - aCenter[2]);

  double s[3], sMin;
  for (int i = 0; i < 3; ++i)
  {
    if ((bounds[2 * i + 1] - bounds[2 * i]) <= 0.0 || (aBds[2 * i + 1] - aBds[2 * i]) <= 0.0)
    {
      s[i] = VTK_FLOAT_MAX;
    }
    else
    {
      s[i] = (bounds[2 * i + 1] - bounds[2 * i]) / (aBds[2 * i + 1] - aBds[2 * i]);
    }
  }
  sMin = std::min({ s[0], s[1], s[2] });

  this->Actor->SetScale(sMin, sMin, sMin);
  this->Follower->SetScale(sMin, sMin, sMin);
}

//------------------------------------------------------------------------------
int vtkTexturedButtonRepresentation ::ComputeInteractionState(int X, int Y, int vtkNotUsed(modify))
{
  this->VisibilityOn(); // actor must be on to be picked

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

  if (path != nullptr)
  {
    this->InteractionState = vtkButtonRepresentation::Inside;
  }
  else
  {
    this->InteractionState = vtkButtonRepresentation::Outside;
  }

  return this->InteractionState;
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::Highlight(int highlight)
{
  this->Superclass::Highlight(highlight);

  vtkProperty* initialProperty = this->Actor->GetProperty();
  vtkProperty* selectedProperty;

  if (highlight == vtkButtonRepresentation::HighlightHovering)
  {
    this->Actor->SetProperty(this->HoveringProperty);
    this->Follower->SetProperty(this->HoveringProperty);
    selectedProperty = this->HoveringProperty;
  }
  else if (highlight == vtkButtonRepresentation::HighlightSelecting)
  {
    this->Actor->SetProperty(this->SelectingProperty);
    this->Follower->SetProperty(this->SelectingProperty);
    selectedProperty = this->SelectingProperty;
  }
  else // if ( highlight == vtkButtonRepresentation::HighlightNormal )
  {
    this->Actor->SetProperty(this->Property);
    this->Follower->SetProperty(this->Property);
    selectedProperty = this->Property;
  }

  if (selectedProperty != initialProperty)
  {
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::CreateDefaultProperties()
{
  this->Property = vtkProperty::New();
  this->Property->SetColor(1, 1, 1);

  this->HoveringProperty = vtkProperty::New();
  this->HoveringProperty->SetAmbient(1.0);

  this->SelectingProperty = vtkProperty::New();
  this->SelectingProperty->SetAmbient(0.2);
  this->SelectingProperty->SetAmbientColor(0.2, 0.2, 0.2);
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::BuildRepresentation()
{
  // The net effect is to resize the handle
  if (this->GetMTime() > this->BuildTime ||
    (this->Renderer && this->Renderer->GetVTKWindow() &&
      this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    // In case follower is being used
    if (this->FollowCamera)
    {
      this->Follower->VisibilityOn();
      this->Actor->VisibilityOff();
      this->Follower->SetCamera(this->Renderer->GetActiveCamera());
    }
    else
    {
      this->Follower->VisibilityOff();
      this->Actor->VisibilityOn();
    }

    vtkTextureArrayIterator iter = this->TextureArray->find(this->State);
    if (iter != this->TextureArray->end())
    {
      this->Texture->SetInputData((*iter).second);
    }
    else
    {
      this->Texture->SetInputData(nullptr);
    }

    this->BuildTime.Modified();
  }
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::ShallowCopy(vtkProp* prop)
{
  vtkTexturedButtonRepresentation* rep = vtkTexturedButtonRepresentation::SafeDownCast(prop);
  if (rep)
  {
    this->Mapper->ShallowCopy(rep->Mapper);
    this->Property->DeepCopy(rep->Property);
    this->HoveringProperty->DeepCopy(rep->HoveringProperty);
    this->SelectingProperty->DeepCopy(rep->SelectingProperty);

    vtkTextureArrayIterator iter;
    for (iter = rep->TextureArray->begin(); iter != rep->TextureArray->end(); ++iter)
    {
      (*this->TextureArray)[(*iter).first] = (*iter).second;
    }
    this->FollowCamera = rep->FollowCamera;
  }

  this->Superclass::ShallowCopy(prop);
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::ReleaseGraphicsResources(vtkWindow* win)
{
  this->Actor->ReleaseGraphicsResources(win);
  this->Follower->ReleaseGraphicsResources(win);
}

//------------------------------------------------------------------------------
int vtkTexturedButtonRepresentation::RenderOpaqueGeometry(vtkViewport* viewport)
{
  this->BuildRepresentation();

  if (this->FollowCamera)
  {
    return this->Follower->RenderOpaqueGeometry(viewport);
  }
  else
  {
    return this->Actor->RenderOpaqueGeometry(viewport);
  }
}

//------------------------------------------------------------------------------
int vtkTexturedButtonRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* viewport)
{
  this->BuildRepresentation();

  if (this->FollowCamera)
  {
    return this->Follower->RenderTranslucentPolygonalGeometry(viewport);
  }
  else
  {
    return this->Actor->RenderTranslucentPolygonalGeometry(viewport);
  }
}
//------------------------------------------------------------------------------
vtkTypeBool vtkTexturedButtonRepresentation::HasTranslucentPolygonalGeometry()
{
  this->BuildRepresentation();

  if (this->FollowCamera)
  {
    return this->Follower->HasTranslucentPolygonalGeometry();
  }
  else
  {
    return this->Actor->HasTranslucentPolygonalGeometry();
  }
}

//------------------------------------------------------------------------------
double* vtkTexturedButtonRepresentation::GetBounds()
{
  return this->Actor->GetBounds();
}

//------------------------------------------------------------------------------
void vtkTexturedButtonRepresentation::GetActors(vtkPropCollection* pc)
{
  if (pc != nullptr && this->GetVisibility())
  {
    if (this->FollowCamera)
    {
      this->Follower->GetActors(pc);
    }
    else
    {
      this->Actor->GetActors(pc);
    }
  }
  this->Superclass::GetActors(pc);
}

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

  os << indent << "Button Geometry: " << this->GetButtonGeometry() << "\n";

  os << indent << "Follow Camera: " << (this->FollowCamera ? "On\n" : "Off\n");

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

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

  if (this->SelectingProperty)
  {
    os << indent << "Selecting Property: " << this->SelectingProperty << "\n";
  }
  else
  {
    os << indent << "Selecting Property: (none)\n";
  }
}
VTK_ABI_NAMESPACE_END
