// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkLightActor.h"

#include "vtkActor.h"
#include "vtkBoundingBox.h"
#include "vtkCamera.h"
#include "vtkCameraActor.h"
#include "vtkConeSource.h"
#include "vtkLight.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkLightActor);
vtkCxxSetObjectMacro(vtkLightActor, Light, vtkLight);

//------------------------------------------------------------------------------
vtkLightActor::vtkLightActor()
{
  this->Light = nullptr;
  this->ClippingRange[0] = 0.5;
  this->ClippingRange[1] = 10.0;

  this->ConeSource = nullptr;
  this->ConeMapper = nullptr;
  this->ConeActor = nullptr;
  this->CameraLight = nullptr;
  this->FrustumActor = nullptr;

  this->BoundingBox = new vtkBoundingBox;
}

//------------------------------------------------------------------------------
vtkLightActor::~vtkLightActor()
{
  this->SetLight(nullptr);
  if (this->ConeActor != nullptr)
  {
    this->ConeActor->Delete();
  }

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

  if (this->FrustumActor != nullptr)
  {
    this->FrustumActor->Delete();
  }
  if (this->ConeSource != nullptr)
  {
    this->ConeSource->Delete();
  }
  if (this->CameraLight != nullptr)
  {
    this->CameraLight->Delete();
  }
  delete this->BoundingBox;
}

//------------------------------------------------------------------------------
// Description:
// Set/Get the location of the near and far clipping planes along the
// direction of projection.  Both of these values must be positive.
// Initial values are  (0.5,11.0)
void vtkLightActor::SetClippingRange(double dNear, double dFar)
{
  this->ClippingRange[0] = dNear;
  this->ClippingRange[1] = dFar;
}

//------------------------------------------------------------------------------
void vtkLightActor::SetClippingRange(const double a[2])
{
  this->SetClippingRange(a[0], a[1]);
}

//------------------------------------------------------------------------------
vtkProperty* vtkLightActor::GetConeProperty()
{
  if (this->ConeActor == nullptr)
  {
    this->UpdateViewProps();
  }
  return this->ConeActor->GetProperty();
}

//------------------------------------------------------------------------------
vtkProperty* vtkLightActor::GetFrustumProperty()
{
  if (this->FrustumActor == nullptr)
  {
    this->UpdateViewProps();
  }
  return this->FrustumActor->GetProperty();
}

//------------------------------------------------------------------------------
// Description:
// Support the standard render methods.
int vtkLightActor::RenderOpaqueGeometry(vtkViewport* viewport)
{
  this->UpdateViewProps();

  int result = 0;

  if (this->ConeActor != nullptr && this->ConeActor->GetMapper() != nullptr)
  {
    result = this->ConeActor->RenderOpaqueGeometry(viewport);
    result += this->FrustumActor->RenderOpaqueGeometry(viewport);
  }

  return result;
}

//------------------------------------------------------------------------------
// Description:
// Does this prop have some translucent polygonal geometry? No.
vtkTypeBool vtkLightActor::HasTranslucentPolygonalGeometry()
{
  return false;
}

//------------------------------------------------------------------------------
void vtkLightActor::ReleaseGraphicsResources(vtkWindow* window)
{
  if (this->ConeActor != nullptr)
  {
    this->ConeActor->ReleaseGraphicsResources(window);
    this->FrustumActor->ReleaseGraphicsResources(window);
  }
}

//------------------------------------------------------------------------------
// Get the bounds for this Actor as (Xmin,Xmax,Ymin,Ymax,Zmin,Zmax).
double* vtkLightActor::GetBounds()
{
  this->Bounds[0] = this->Bounds[2] = this->Bounds[4] = VTK_DOUBLE_MAX;
  this->Bounds[1] = this->Bounds[3] = this->Bounds[5] = -VTK_DOUBLE_MAX;

  this->UpdateViewProps();

  this->BoundingBox->Reset();

  if (this->ConeActor != nullptr)
  {
    if (this->ConeActor->GetUseBounds())
    {
      this->BoundingBox->AddBounds(this->ConeActor->GetBounds());
    }
    if (this->FrustumActor->GetUseBounds())
    {
      this->BoundingBox->AddBounds(this->FrustumActor->GetBounds());
    }
  }

  int i = 0;
  while (i < 6)
  {
    this->Bounds[i] = this->BoundingBox->GetBound(i);
    ++i;
  }
  if (this->Bounds[0] == VTK_DOUBLE_MAX)
  {
    // we cannot initialize the Bounds the same way vtkBoundingBox does because
    // vtkProp3D::GetLength() does not check if the Bounds are initialized or
    // not and makes a call to sqrt(). This call to sqrt with invalid values
    // would raise a floating-point overflow exception (notably on BCC).
    // As vtkMath::UninitializeBounds initialized finite invalid bounds, it
    // passes silently and GetLength() returns 0.
    vtkMath::UninitializeBounds(this->Bounds);
  }

  return this->Bounds;
}

//------------------------------------------------------------------------------
vtkMTimeType vtkLightActor::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  if (this->Light != nullptr)
  {
    vtkMTimeType time;
    time = this->Light->GetMTime();
    mTime = std::max(time, mTime);
  }
  return mTime;
}

//------------------------------------------------------------------------------
void vtkLightActor::UpdateViewProps()
{
  if (this->Light == nullptr)
  {
    vtkDebugMacro(<< "no light.");
    return;
  }
  double angle = this->Light->GetConeAngle();

  if (this->Light->GetPositional() && angle < 90.0)
  {
    if (this->ConeSource == nullptr)
    {
      this->ConeSource = vtkConeSource::New();
    }

    this->ConeSource->SetResolution(24);
    double* pos = this->Light->GetPosition();
    double* f = this->Light->GetFocalPoint();

    double direction[3];
    int i = 0;
    while (i < 3)
    {
      direction[i] = pos[i] - f[i];
      ++i;
    }
    double height = 1.0;
    double center[3]; //=pos

    double n = vtkMath::Norm(direction);

    // cone center is the middle of its axis, not the center of the base...
    i = 0;
    while (i < 3)
    {
      center[i] = pos[i] - 0.5 * height / n * direction[i];
      ++i;
    }
    this->ConeSource->SetCenter(center);
    this->ConeSource->SetDirection(direction);
    this->ConeSource->SetHeight(height);
    this->ConeSource->SetAngle(angle);

    if (this->ConeMapper == nullptr)
    {
      this->ConeMapper = vtkPolyDataMapper::New();
      this->ConeMapper->SetInputConnection(this->ConeSource->GetOutputPort());
      this->ConeMapper->SetScalarVisibility(0);
    }

    if (this->ConeActor == nullptr)
    {
      this->ConeActor = vtkActor::New();
      this->ConeActor->SetMapper(this->ConeMapper);
    }

    this->ConeActor->SetVisibility(this->Light->GetSwitch());

    vtkProperty* p = this->ConeActor->GetProperty();
    p->SetLighting(false);
    p->SetColor(this->Light->GetDiffuseColor());
    p->SetRepresentationToWireframe();

    if (this->CameraLight == nullptr)
    {
      this->CameraLight = vtkCamera::New();
    }

    this->CameraLight->SetPosition(this->Light->GetPosition());
    this->CameraLight->SetFocalPoint(this->Light->GetFocalPoint());
    this->CameraLight->SetViewUp(0.0, 1.0, 0.0);
    // view angle is an aperture, but cone (or light) angle is between
    // the axis of the cone and a ray along the edge of the cone.
    this->CameraLight->SetViewAngle(angle * 2.0);
    // initial clip=(0.1,1000). near>0, far>near);
    this->CameraLight->SetClippingRange(this->ClippingRange);

    if (this->FrustumActor == nullptr)
    {
      this->FrustumActor = vtkCameraActor::New();
    }
    this->FrustumActor->SetCamera(this->CameraLight);
    this->FrustumActor->SetWidthByHeightRatio(1.0); // camera light is square
    this->FrustumActor->SetUseBounds(false);
  }
  else
  {
    if (this->ConeActor)
    {
      this->ConeActor->SetMapper(nullptr);
    }
    if (this->FrustumActor)
    {
      this->FrustumActor->SetCamera(nullptr);
    }
    vtkErrorMacro(<< "not a spotlight.");
    return;
  }
}

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

  os << indent << "Light: ";
  if (this->Light == nullptr)
  {
    os << "(none)" << endl;
  }
  else
  {
    this->Light->PrintSelf(os, indent);
  }

  os << indent << "ClippingRange: " << this->ClippingRange[0] << "," << this->ClippingRange[1]
     << endl;
}
VTK_ABI_NAMESPACE_END
