// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkAssembly.h"
#include "vtkActor.h"
#include "vtkAssemblyNode.h"
#include "vtkAssemblyPaths.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkProp3DCollection.h"
#include "vtkRenderWindow.h"
#include "vtkVolume.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAssembly);

// Construct object with no children.
vtkAssembly::vtkAssembly()
{
  this->Parts = vtkProp3DCollection::New();
}

vtkAssembly::~vtkAssembly()
{
  vtkCollectionSimpleIterator pit;
  vtkProp* part;
  for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp(pit));)
  {
    part->RemoveConsumer(this);
  }

  this->Parts->Delete();
  this->Parts = nullptr;
}

// Add a part to the list of Parts.
void vtkAssembly::AddPart(vtkProp3D* prop)
{
  if (this->Parts->IndexOfFirstOccurence(prop) < 0)
  {
    this->Parts->AddItem(prop);
    prop->AddConsumer(this);
    this->Modified();
  }
}

// Remove a part from the list of parts,
void vtkAssembly::RemovePart(vtkProp3D* prop)
{
  if (this->Parts->IndexOfFirstOccurence(prop) >= 0)
  {
    prop->RemoveConsumer(this);
    this->Parts->RemoveItem(prop);
    this->Modified();
  }
}

// Shallow copy another assembly.
void vtkAssembly::ShallowCopy(vtkProp* prop)
{
  vtkAssembly* p = vtkAssembly::SafeDownCast(prop);
  if (p && p != this)
  {
    vtkCollectionSimpleIterator pit;
    vtkProp3D* part;
    for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp3D(pit));)
    {
      part->RemoveConsumer(this);
    }
    this->Parts->RemoveAllItems();
    for (p->Parts->InitTraversal(pit); (part = p->Parts->GetNextProp3D(pit));)
    {
      this->AddPart(part);
    }
  }

  // Now do superclass
  this->vtkProp3D::ShallowCopy(prop);
}

// Render this assembly and all its Parts. The rendering process is recursive.
// Note that a mapper need not be defined. If not defined, then no geometry
// will be drawn for this assembly. This allows you to create "logical"
// assemblies; that is, assemblies that only serve to group and transform
// its Parts.
int vtkAssembly::RenderTranslucentPolygonalGeometry(vtkViewport* ren)
{
  this->UpdatePaths();

  int renderedSomething = 0;

  // for allocating render time between components
  // simple equal allocation
  double fraction =
    this->AllocatedRenderTime / static_cast<double>(this->Paths->GetNumberOfItems());

  // render the Paths
  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    if (prop3D->GetVisibility())
    {
      prop3D->SetPropertyKeys(this->GetPropertyKeys());
      prop3D->SetAllocatedRenderTime(fraction, ren);
      prop3D->PokeMatrix(path->GetLastNode()->GetMatrix());
      renderedSomething += prop3D->RenderTranslucentPolygonalGeometry(ren);
      prop3D->PokeMatrix(nullptr);
    }
  }

  return (renderedSomething > 0) ? 1 : 0;
}

// Description:
// Does this prop have some translucent polygonal geometry?
vtkTypeBool vtkAssembly::HasTranslucentPolygonalGeometry()
{
  this->UpdatePaths();

  int result = 0;

  // render the Paths
  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); !result && (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    if (prop3D->GetVisibility())
    {
      prop3D->SetPropertyKeys(this->GetPropertyKeys());
      result = prop3D->HasTranslucentPolygonalGeometry();
    }
  }

  return result;
}

// Render this assembly and all its Parts. The rendering process is recursive.
// Note that a mapper need not be defined. If not defined, then no geometry
// will be drawn for this assembly. This allows you to create "logical"
// assemblies; that is, assemblies that only serve to group and transform
// its Parts.
int vtkAssembly::RenderVolumetricGeometry(vtkViewport* ren)
{
  this->UpdatePaths();

  // for allocating render time between components
  // simple equal allocation
  double fraction =
    this->AllocatedRenderTime / static_cast<double>(this->Paths->GetNumberOfItems());

  int renderedSomething = 0;

  // render the Paths
  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    if (prop3D->GetVisibility())
    {
      prop3D->SetPropertyKeys(this->GetPropertyKeys());
      prop3D->SetAllocatedRenderTime(fraction, ren);
      prop3D->PokeMatrix(path->GetLastNode()->GetMatrix());
      renderedSomething += prop3D->RenderVolumetricGeometry(ren);
      prop3D->PokeMatrix(nullptr);
    }
  }

  return (renderedSomething > 0) ? 1 : 0;
}

// Render this assembly and all its Parts. The rendering process is recursive.
// Note that a mapper need not be defined. If not defined, then no geometry
// will be drawn for this assembly. This allows you to create "logical"
// assemblies; that is, assemblies that only serve to group and transform
// its Parts.
int vtkAssembly::RenderOpaqueGeometry(vtkViewport* ren)
{
  this->UpdatePaths();

  // for allocating render time between components
  // simple equal allocation
  double fraction =
    this->AllocatedRenderTime / static_cast<double>(this->Paths->GetNumberOfItems());

  int renderedSomething = 0;

  // render the Paths
  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    if (prop3D->GetVisibility())
    {
      prop3D->SetPropertyKeys(this->GetPropertyKeys());
      prop3D->PokeMatrix(path->GetLastNode()->GetMatrix());
      prop3D->SetAllocatedRenderTime(fraction, ren);
      renderedSomething += prop3D->RenderOpaqueGeometry(ren);
      prop3D->PokeMatrix(nullptr);
    }
  }

  return (renderedSomething > 0) ? 1 : 0;
}

void vtkAssembly::ReleaseGraphicsResources(vtkWindow* renWin)
{
  vtkProp3D* prop3D;

  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (prop3D = this->Parts->GetNextProp3D(pit));)
  {
    prop3D->ReleaseGraphicsResources(renWin);
  }
}

void vtkAssembly::GetActors(vtkPropCollection* ac)
{
  this->UpdatePaths();

  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    vtkActor* actor = vtkActor::SafeDownCast(prop3D);
    if (actor)
    {
      ac->AddItem(actor);
    }
  }
}

void vtkAssembly::GetVolumes(vtkPropCollection* ac)
{
  this->UpdatePaths();

  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    vtkVolume* volume = vtkVolume::SafeDownCast(prop3D);
    if (volume)
    {
      ac->AddItem(volume);
    }
  }
}

void vtkAssembly::InitPathTraversal()
{
  this->UpdatePaths();
  this->Paths->InitTraversal();
}

// Return the next part in the hierarchy of assembly Parts.  This method
// returns a properly transformed and updated actor.
vtkAssemblyPath* vtkAssembly::GetNextPath()
{
  return this->Paths ? this->Paths->GetNextItem() : nullptr;
}

int vtkAssembly::GetNumberOfPaths()
{
  this->UpdatePaths();
  return this->Paths->GetNumberOfItems();
}

// Build the assembly paths if necessary. UpdatePaths()
// is only called when the assembly is at the root
// of the hierarchy; otherwise UpdatePaths() is called.
void vtkAssembly::UpdatePaths()
{
  if (this->GetMTime() > this->PathTime ||
    (this->Paths != nullptr && this->Paths->GetMTime() > this->PathTime))
  {
    if (this->Paths)
    {
      this->Paths->Delete();
      this->Paths = nullptr;
    }

    // Create the list to hold all the paths
    this->Paths = vtkAssemblyPaths::New();
    vtkAssemblyPath* path = vtkAssemblyPath::New();

    // add ourselves to the path to start things off
    path->AddNode(this, this->GetMatrix());

    // Add nodes as we proceed down the hierarchy
    vtkProp3D* prop3D;
    vtkCollectionSimpleIterator pit;
    for (this->Parts->InitTraversal(pit); (prop3D = this->Parts->GetNextProp3D(pit));)
    {
      path->AddNode(prop3D, prop3D->GetMatrix());

      // dive into the hierarchy
      prop3D->BuildPaths(this->Paths, path);

      // when returned, pop the last node off of the
      // current path
      path->DeleteLastNode();
    }

    path->Delete();
    this->PathTime.Modified();
  }
}

// Build assembly paths from this current assembly. A path consists of
// an ordered sequence of props, with transformations properly concatenated.
void vtkAssembly::BuildPaths(vtkAssemblyPaths* paths, vtkAssemblyPath* path)
{
  vtkProp3D* prop3D;

  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (prop3D = this->Parts->GetNextProp3D(pit));)
  {
    path->AddNode(prop3D, prop3D->GetMatrix());

    // dive into the hierarchy
    prop3D->BuildPaths(paths, path);

    // when returned, pop the last node off of the
    // current path
    path->DeleteLastNode();
  }
}

// Get the bounds for the assembly as (Xmin,Xmax,Ymin,Ymax,Zmin,Zmax).
double* vtkAssembly::GetBounds()
{
  this->UpdatePaths();

  // now calculate the new bounds
  this->Bounds[0] = this->Bounds[2] = this->Bounds[4] = VTK_DOUBLE_MAX;
  this->Bounds[1] = this->Bounds[3] = this->Bounds[5] = -VTK_DOUBLE_MAX;

  int propVisible = 0;

  vtkAssemblyPath* path;
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    vtkProp3D* prop3D = static_cast<vtkProp3D*>(path->GetLastNode()->GetViewProp());
    if (prop3D->GetVisibility() && prop3D->GetUseBounds())
    {
      prop3D->PokeMatrix(path->GetLastNode()->GetMatrix());
      const double* bounds = prop3D->GetBounds();
      prop3D->PokeMatrix(nullptr);

      // Skip any props that have uninitialized bounds
      if (bounds == nullptr || !vtkMath::AreBoundsInitialized(bounds))
      {
        continue;
      }
      // Only set the prop as visible if at least one prop has a valid bounds
      propVisible = 1;

      double bbox[24];
      // fill out vertices of a bounding box
      bbox[0] = bounds[1];
      bbox[1] = bounds[3];
      bbox[2] = bounds[5];
      bbox[3] = bounds[1];
      bbox[4] = bounds[2];
      bbox[5] = bounds[5];
      bbox[6] = bounds[0];
      bbox[7] = bounds[2];
      bbox[8] = bounds[5];
      bbox[9] = bounds[0];
      bbox[10] = bounds[3];
      bbox[11] = bounds[5];
      bbox[12] = bounds[1];
      bbox[13] = bounds[3];
      bbox[14] = bounds[4];
      bbox[15] = bounds[1];
      bbox[16] = bounds[2];
      bbox[17] = bounds[4];
      bbox[18] = bounds[0];
      bbox[19] = bounds[2];
      bbox[20] = bounds[4];
      bbox[21] = bounds[0];
      bbox[22] = bounds[3];
      bbox[23] = bounds[4];

      for (int i = 0; i < 8; i++)
      {
        for (int n = 0; n < 3; n++)
        {
          if (bbox[i * 3 + n] < this->Bounds[n * 2])
          {
            this->Bounds[n * 2] = bbox[i * 3 + n];
          }
          if (bbox[i * 3 + n] > this->Bounds[n * 2 + 1])
          {
            this->Bounds[n * 2 + 1] = bbox[i * 3 + n];
          }
        } // for each coordinate axis
      }   // for each point of box
    }     // if visible && prop3d
  }       // for each path

  if (!propVisible)
  {
    vtkMath::UninitializeBounds(this->Bounds);
  }

  return this->Bounds;
}

vtkMTimeType vtkAssembly::GetMTime()
{
  vtkMTimeType mTime = this->vtkProp3D::GetMTime();
  vtkProp3D* prop;

  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (prop = this->Parts->GetNextProp3D(pit));)
  {
    vtkMTimeType time = prop->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

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

  os << indent << "There are: " << this->Parts->GetNumberOfItems() << " parts in this assembly\n";
}
VTK_ABI_NAMESPACE_END
