// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkImageActorPointPlacer.h"
#include "vtkBoundedPlanePointPlacer.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkRenderer.h"

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkImageActorPointPlacer);

vtkCxxSetObjectMacro(vtkImageActorPointPlacer, ImageActor, vtkImageActor);

//------------------------------------------------------------------------------
vtkImageActorPointPlacer::vtkImageActorPointPlacer()
{
  this->Placer = vtkBoundedPlanePointPlacer::New();
  this->ImageActor = nullptr;
  this->SavedBounds[0] = 0.0;
  this->SavedBounds[1] = 0.0;
  this->SavedBounds[2] = 0.0;
  this->SavedBounds[3] = 0.0;
  this->SavedBounds[4] = 0.0;
  this->SavedBounds[5] = 0.0;
  this->Bounds[0] = this->Bounds[2] = this->Bounds[4] = VTK_DOUBLE_MAX;
  this->Bounds[1] = this->Bounds[3] = this->Bounds[5] = VTK_DOUBLE_MIN;
}

//------------------------------------------------------------------------------
vtkImageActorPointPlacer::~vtkImageActorPointPlacer()
{
  this->Placer->Delete();
  this->SetImageActor(nullptr);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::ComputeWorldPosition(vtkRenderer* ren, double displayPos[2],
  double* refWorldPos, double worldPos[3], double worldOrient[9])
{
  if (!this->UpdateInternalState())
  {
    return 0;
  }

  return this->Placer->ComputeWorldPosition(ren, displayPos, refWorldPos, worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::ComputeWorldPosition(
  vtkRenderer* ren, double displayPos[2], double worldPos[3], double worldOrient[9])
{
  if (!this->UpdateInternalState())
  {
    return 0;
  }

  return this->Placer->ComputeWorldPosition(ren, displayPos, worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::ValidateWorldPosition(double worldPos[3], double* worldOrient)
{
  if (!this->UpdateInternalState())
  {
    return 0;
  }

  return this->Placer->ValidateWorldPosition(worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::ValidateWorldPosition(double worldPos[3])
{
  if (!this->UpdateInternalState())
  {
    return 0;
  }

  return this->Placer->ValidateWorldPosition(worldPos);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::UpdateWorldPosition(
  vtkRenderer* ren, double worldPos[3], double worldOrient[9])
{
  if (!this->UpdateInternalState())
  {
    return 0;
  }

  return this->Placer->UpdateWorldPosition(ren, worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkImageActorPointPlacer::UpdateInternalState()
{
  if (!this->ImageActor)
  {
    return 0;
  }

  vtkImageData* input;
  input = this->ImageActor->GetInput();
  if (!input)
  {
    return 0;
  }

  double spacing[3];
  input->GetSpacing(spacing);

  double origin[3];
  input->GetOrigin(origin);

  double bounds[6];
  this->ImageActor->GetBounds(bounds);
  if (this->Bounds[0] != VTK_DOUBLE_MAX)
  {
    bounds[0] = (bounds[0] < this->Bounds[0]) ? this->Bounds[0] : bounds[0];
    bounds[1] = (bounds[1] > this->Bounds[1]) ? this->Bounds[1] : bounds[1];
    bounds[2] = (bounds[2] < this->Bounds[2]) ? this->Bounds[2] : bounds[2];
    bounds[3] = (bounds[3] > this->Bounds[3]) ? this->Bounds[3] : bounds[3];
    bounds[4] = (bounds[4] < this->Bounds[4]) ? this->Bounds[4] : bounds[4];
    bounds[5] = (bounds[5] > this->Bounds[5]) ? this->Bounds[5] : bounds[5];
  }

  int displayExtent[6];
  this->ImageActor->GetDisplayExtent(displayExtent);

  int axis;
  double position;
  if (displayExtent[0] == displayExtent[1])
  {
    axis = vtkBoundedPlanePointPlacer::XAxis;
    position = origin[0] + displayExtent[0] * spacing[0];
  }
  else if (displayExtent[2] == displayExtent[3])
  {
    axis = vtkBoundedPlanePointPlacer::YAxis;
    position = origin[1] + displayExtent[2] * spacing[1];
  }
  else if (displayExtent[4] == displayExtent[5])
  {
    axis = vtkBoundedPlanePointPlacer::ZAxis;
    position = origin[2] + displayExtent[4] * spacing[2];
  }
  else
  {
    vtkErrorMacro("Incorrect display extent in Image Actor");
    return 0;
  }

  if (axis != this->Placer->GetProjectionNormal() ||
    position != this->Placer->GetProjectionPosition() || bounds[0] != this->SavedBounds[0] ||
    bounds[1] != this->SavedBounds[1] || bounds[2] != this->SavedBounds[2] ||
    bounds[3] != this->SavedBounds[3] || bounds[4] != this->SavedBounds[4] ||
    bounds[5] != this->SavedBounds[5])
  {
    this->SavedBounds[0] = bounds[0];
    this->SavedBounds[1] = bounds[1];
    this->SavedBounds[2] = bounds[2];
    this->SavedBounds[3] = bounds[3];
    this->SavedBounds[4] = bounds[4];
    this->SavedBounds[5] = bounds[5];

    this->Placer->SetProjectionNormal(axis);
    this->Placer->SetProjectionPosition(position);

    this->Placer->RemoveAllBoundingPlanes();

    vtkPlane* plane;

    if (axis != vtkBoundedPlanePointPlacer::XAxis)
    {
      plane = vtkPlane::New();
      plane->SetOrigin(bounds[0], bounds[2], bounds[4]);
      plane->SetNormal(1.0, 0.0, 0.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();

      plane = vtkPlane::New();
      plane->SetOrigin(bounds[1], bounds[3], bounds[5]);
      plane->SetNormal(-1.0, 0.0, 0.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();
    }

    if (axis != vtkBoundedPlanePointPlacer::YAxis)
    {
      plane = vtkPlane::New();
      plane->SetOrigin(bounds[0], bounds[2], bounds[4]);
      plane->SetNormal(0.0, 1.0, 0.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();

      plane = vtkPlane::New();
      plane->SetOrigin(bounds[1], bounds[3], bounds[5]);
      plane->SetNormal(0.0, -1.0, 0.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();
    }

    if (axis != vtkBoundedPlanePointPlacer::ZAxis)
    {
      plane = vtkPlane::New();
      plane->SetOrigin(bounds[0], bounds[2], bounds[4]);
      plane->SetNormal(0.0, 0.0, 1.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();

      plane = vtkPlane::New();
      plane->SetOrigin(bounds[1], bounds[3], bounds[5]);
      plane->SetNormal(0.0, 0.0, -1.0);
      this->Placer->AddBoundingPlane(plane);
      plane->Delete();
    }

    this->Modified();
  }

  return 1;
}

//------------------------------------------------------------------------------
void vtkImageActorPointPlacer::SetWorldTolerance(double tol)
{
  tol = std::min(std::max(tol, 0.0), VTK_DOUBLE_MAX);
  if (this->WorldTolerance != tol)
  {
    this->WorldTolerance = tol;
    this->Placer->SetWorldTolerance(tol);
    this->Modified();
  }
}

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

  const double* bounds = this->GetBounds();
  if (bounds != nullptr)
  {
    os << indent << "Bounds: \n";
    os << indent << "  Xmin,Xmax: (" << this->Bounds[0] << ", " << this->Bounds[1] << ")\n";
    os << indent << "  Ymin,Ymax: (" << this->Bounds[2] << ", " << this->Bounds[3] << ")\n";
    os << indent << "  Zmin,Zmax: (" << this->Bounds[4] << ", " << this->Bounds[5] << ")\n";
  }
  else
  {
    os << indent << "Bounds: (not defined)\n";
  }

  os << indent << "Image Actor: " << this->ImageActor << "\n";
}
VTK_ABI_NAMESPACE_END
