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

#include "vtkAssemblyNode.h"
#include "vtkAssemblyPath.h"
#include "vtkAssemblyPaths.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkProp.h"
#include "vtkPropCollection.h"
#include "vtkViewport.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPropAssembly);

// Construct object with no children.
vtkPropAssembly::vtkPropAssembly()
{
  this->Parts = vtkPropCollection::New();
  vtkMath::UninitializeBounds(this->Bounds);
}

vtkPropAssembly::~vtkPropAssembly()
{
  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 vtkPropAssembly::AddPart(vtkProp* prop)
{
  if (this->Parts->IndexOfFirstOccurrence(prop) < 0)
  {
    this->Parts->AddItem(prop);
    prop->AddConsumer(this);
    this->Modified();
  }
}

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

// Get the list of parts for this prop assembly.
vtkPropCollection* vtkPropAssembly::GetParts()
{
  return this->Parts;
}

// Render this assembly and all of its Parts. The rendering process is recursive.
int vtkPropAssembly::RenderTranslucentPolygonalGeometry(vtkViewport* ren)
{
  vtkProp* prop;
  vtkAssemblyPath* path;
  double fraction;
  int renderedSomething = 0;

  // Make sure the paths are up-to-date
  this->UpdatePaths();

  double numberOfItems = static_cast<double>(this->Parts->GetNumberOfItems());
  fraction =
    numberOfItems >= 1.0 ? this->AllocatedRenderTime / numberOfItems : this->AllocatedRenderTime;

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

  return renderedSomething;
}

// Description:
// Does this prop have some translucent polygonal geometry?
vtkTypeBool vtkPropAssembly::HasTranslucentPolygonalGeometry()
{
  vtkProp* prop;
  vtkAssemblyPath* path;
  int result = 0;

  // Make sure the paths are up-to-date
  this->UpdatePaths();

  // render the Paths
  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); !result && (path = this->Paths->GetNextPath(sit));)
  {
    prop = path->GetLastNode()->GetViewProp();
    if (prop->GetVisibility())
    {
      prop->SetPropertyKeys(this->GetPropertyKeys());
      result = prop->HasTranslucentPolygonalGeometry();
    }
  }
  return result;
}

// Render this assembly and all of its Parts. The rendering process is recursive.
int vtkPropAssembly::RenderVolumetricGeometry(vtkViewport* ren)
{
  vtkProp* prop;
  vtkAssemblyPath* path;
  double fraction;
  int renderedSomething = 0;

  // Make sure the paths are up-to-date
  this->UpdatePaths();

  double numberOfItems = static_cast<double>(this->Parts->GetNumberOfItems());
  fraction =
    numberOfItems >= 1.0 ? this->AllocatedRenderTime / numberOfItems : this->AllocatedRenderTime;

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

  return renderedSomething;
}

// Render this assembly and all its parts. The rendering process is recursive.
int vtkPropAssembly::RenderOpaqueGeometry(vtkViewport* ren)
{
  vtkProp* prop;
  vtkAssemblyPath* path;
  double fraction;
  int renderedSomething = 0;

  // Make sure the paths are up-to-date
  this->UpdatePaths();

  double numberOfItems = static_cast<double>(this->Parts->GetNumberOfItems());
  fraction =
    numberOfItems >= 1.0 ? this->AllocatedRenderTime / numberOfItems : this->AllocatedRenderTime;

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

  return renderedSomething;
}

// Render this assembly and all its parts. The rendering process is recursive.
int vtkPropAssembly::RenderOverlay(vtkViewport* ren)
{
  vtkProp* prop;
  vtkAssemblyPath* path;
  double fraction;
  int renderedSomething = 0;

  // Make sure the paths are up-to-date
  this->UpdatePaths();

  double numberOfItems = static_cast<double>(this->Parts->GetNumberOfItems());
  fraction =
    numberOfItems >= 1.0 ? this->AllocatedRenderTime / numberOfItems : this->AllocatedRenderTime;

  vtkCollectionSimpleIterator sit;
  for (this->Paths->InitTraversal(sit); (path = this->Paths->GetNextPath(sit));)
  {
    prop = path->GetLastNode()->GetViewProp();
    if (prop->GetVisibility())
    {
      prop->SetPropertyKeys(this->GetPropertyKeys());
      prop->SetAllocatedRenderTime(fraction, ren);
      prop->PokeMatrix(path->GetLastNode()->GetMatrix());
      renderedSomething += prop->RenderOverlay(ren);
      prop->PokeMatrix(nullptr);
    }
  }

  return renderedSomething;
}

void vtkPropAssembly::ReleaseGraphicsResources(vtkWindow* renWin)
{
  vtkProp* part;

  vtkProp::ReleaseGraphicsResources(renWin);

  // broadcast the message down the Parts
  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp(pit));)
  {
    part->ReleaseGraphicsResources(renWin);
  }
}

// Get the bounds for the assembly as (Xmin,Xmax,Ymin,Ymax,Zmin,Zmax).
double* vtkPropAssembly::GetBounds()
{
  vtkProp* part;
  int i, n;
  double bbox[24];
  int partVisible = 0;

  // carefully compute the bounds
  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp(pit));)
  {
    if (part->GetVisibility() && part->GetUseBounds())
    {
      const double* bounds = part->GetBounds();

      if (bounds != nullptr)
      {
        //  For the purposes of GetBounds, an object is visible only if
        //  its visibility is on and it has visible parts.
        if (!partVisible)
        {
          // initialize the 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;
          partVisible = 1;
        }

        // 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 (i = 0; i < 8; i++)
        {
          for (n = 0; n < 3; n++)
          {
            this->Bounds[n * 2] = std::min(bbox[i * 3 + n], this->Bounds[n * 2]);
            this->Bounds[n * 2 + 1] = std::max(bbox[i * 3 + n], this->Bounds[n * 2 + 1]);
          }
        } // for each point of box
      }   // if bounds
    }     // for each part
  }       // for each part

  if (!partVisible)
  {
    return nullptr;
  }
  else
  {
    return this->Bounds;
  }
}

vtkMTimeType vtkPropAssembly::GetMTime()
{
  vtkMTimeType mTime = this->vtkProp::GetMTime();
  vtkMTimeType time;
  vtkProp* part;

  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp(pit));)
  {
    time = part->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

// Shallow copy another vtkPropAssembly.
void vtkPropAssembly::ShallowCopy(vtkProp* prop)
{
  vtkPropAssembly* propAssembly = vtkPropAssembly::SafeDownCast(prop);
  if (propAssembly != nullptr && propAssembly != this)
  {
    vtkCollectionSimpleIterator pit;
    vtkProp* part;
    for (this->Parts->InitTraversal(pit); (part = this->Parts->GetNextProp(pit));)
    {
      part->RemoveConsumer(this);
    }
    this->Parts->RemoveAllItems();
    for (propAssembly->Parts->InitTraversal(pit); (part = propAssembly->Parts->GetNextProp(pit));)
    {
      this->AddPart(part);
    }
  }

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

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

vtkAssemblyPath* vtkPropAssembly::GetNextPath()
{
  if (this->Paths)
  {
    return this->Paths->GetNextItem();
  }
  return nullptr;
}

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

// Build the assembly paths if necessary.
void vtkPropAssembly::UpdatePaths()
{
  if (this->GetMTime() > this->PathTime)
  {
    if (this->Paths != nullptr)
    {
      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, nullptr);

    vtkProp* prop;
    // Add nodes as we proceed down the hierarchy
    vtkCollectionSimpleIterator pit;
    for (this->Parts->InitTraversal(pit); (prop = this->Parts->GetNextProp(pit));)
    {
      // add a matrix, if any
      path->AddNode(prop, prop->GetMatrix());

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

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

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

void vtkPropAssembly::BuildPaths(vtkAssemblyPaths* paths, vtkAssemblyPath* path)
{
  vtkProp* prop;

  vtkCollectionSimpleIterator pit;
  for (this->Parts->InitTraversal(pit); (prop = this->Parts->GetNextProp(pit));)
  {
    path->AddNode(prop, nullptr);

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

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

void vtkPropAssembly::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
