/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkCompositePolyDataMapper.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

// Hide VTK_DEPRECATED_IN_9_3_0() warnings for this class.
#define VTK_DEPRECATION_LEVEL 0
#include "vtkLegacy.h"

#include "vtkCompositePolyDataMapper.h"

#include "vtkActor.h"
#include "vtkCellData.h"
#include "vtkCommand.h"
#include "vtkCompositeDataDisplayAttributes.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataPipeline.h"
#include "vtkCompositeDataSet.h"
#include "vtkCompositePolyDataMapperDelegator.h"
#include "vtkDataObjectTree.h"
#include "vtkDataObjectTreeIterator.h"
#include "vtkDataObjectTreeRange.h"
#include "vtkExecutive.h"
#include "vtkGarbageCollector.h"
#include "vtkInformation.h"
#include "vtkMapper.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkScalarsToColors.h"
#include "vtkTexture.h"

#include <map>
#include <stack>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkCompositePolyDataMapper);

class vtkCompositePolyDataMapper::vtkInternals
{
public:
  struct RenderBlockState
  {
    std::stack<double> Opacity;
    std::stack<bool> Visibility;
    std::stack<bool> Pickability;
    std::stack<vtkColor3d> AmbientColor;
    std::stack<vtkColor3d> DiffuseColor;
    std::stack<vtkColor3d> SpecularColor;
    std::stack<vtkColor3d> SelectionColor;
    std::stack<double> SelectionOpacity;
    std::stack<int> ScalarMode;
    std::stack<std::string> ScalarArrayName;
    std::stack<int> ArrayComponent;
  };
  RenderBlockState BlockState;

  std::vector<vtkPolyData*> RenderedList;

  /**
   * Mappers created per block along with time of creation.
   */
  std::map<vtkPolyDataMapper::MapperHashType, vtkSmartPointer<vtkCompositePolyDataMapperDelegator>>
    BatchedDelegators;
};

//------------------------------------------------------------------------------
vtkCompositePolyDataMapper::vtkCompositePolyDataMapper()
  : Internals(std::unique_ptr<vtkInternals>(new vtkInternals()))
{
}

//------------------------------------------------------------------------------
vtkCompositePolyDataMapper::~vtkCompositePolyDataMapper()
{
  this->SetPointIdArrayName(nullptr);
  this->SetCellIdArrayName(nullptr);
  this->SetProcessIdArrayName(nullptr);
  this->SetCompositeIdArrayName(nullptr);
}

//------------------------------------------------------------------------------
// Specify the type of data this mapper can handle. If we are
// working with a regular (not hierarchical) pipeline, then we
// need vtkPolyData. For composite data pipelines, then
// vtkCompositeDataSet is required, and we'll check when
// building our structure whether all the part of the composite
// data set are polydata.
int vtkCompositePolyDataMapper::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataObject");
  return 1;
}

//------------------------------------------------------------------------------
vtkExecutive* vtkCompositePolyDataMapper::CreateDefaultExecutive()
{
  return vtkCompositeDataPipeline::New();
}

//------------------------------------------------------------------------------
// Looks at each DataSet and finds the union of all the bounds
void vtkCompositePolyDataMapper::ComputeBounds()
{
  vtkDataObjectTree* input = vtkDataObjectTree::SafeDownCast(this->GetInputDataObject(0, 0));

  // If we don't have hierarchical data, test to see if we have
  // plain old polydata. In this case, the bounds are simply
  // the bounds of the input polydata.
  if (!input)
  {
    this->Superclass::ComputeBounds();
    return;
  }

  if (input->GetMTime() < this->BoundsMTime && this->GetMTime() < this->BoundsMTime)
  {
    return;
  }

  // computing bounds with only visible blocks
  vtkCompositeDataDisplayAttributes::ComputeVisibleBounds(
    this->CompositeAttributes, input, this->Bounds);
  this->BoundsMTime.Modified();
}

//------------------------------------------------------------------------------
double* vtkCompositePolyDataMapper::GetBounds()
{
  if (!this->GetExecutive()->GetInputData(0, 0))
  {
    vtkMath::UninitializeBounds(this->Bounds);
    return this->Bounds;
  }
  else
  {
    this->Update();

    // only compute bounds when the input data has changed
    vtkCompositeDataPipeline* executive =
      vtkCompositeDataPipeline::SafeDownCast(this->GetExecutive());
    if (executive->GetPipelineMTime() > this->BoundsMTime.GetMTime())
    {
      this->ComputeBounds();
    }

    return this->Bounds;
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::ShallowCopy(vtkAbstractMapper* mapper)
{
  vtkCompositePolyDataMapper* cpdm = vtkCompositePolyDataMapper::SafeDownCast(mapper);
  if (cpdm != nullptr)
  {
    this->SetCompositeDataDisplayAttributes(cpdm->GetCompositeDataDisplayAttributes());
    this->SetColorMissingArraysWithNanColor(cpdm->GetColorMissingArraysWithNanColor());
    this->SetCellIdArrayName(cpdm->GetCellIdArrayName());
    this->SetCompositeIdArrayName(cpdm->GetCompositeIdArrayName());
    this->SetPointIdArrayName(cpdm->GetPointIdArrayName());
    this->SetProcessIdArrayName(cpdm->GetProcessIdArrayName());
  }
  // Now do superclass
  this->vtkPolyDataMapper::ShallowCopy(mapper);
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::ReleaseGraphicsResources(vtkWindow* win)
{
  auto& internals = (*this->Internals);
  for (auto& iter : internals.BatchedDelegators)
  {
    auto& delegator = iter.second;
    delegator->GetDelegate()->ReleaseGraphicsResources(win);
  }
  internals.BatchedDelegators.clear();
  this->Modified();
  this->Superclass::ReleaseGraphicsResources(win);
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetVBOShiftScaleMethod(ShiftScaleMethodType method)
{
  if (this->ShiftScaleMethod == method)
  {
    return;
  }
  this->ShiftScaleMethod = method;

  this->Superclass::SetVBOShiftScaleMethod(method);

  auto& internals = (*this->Internals);
  for (auto& iter : internals.BatchedDelegators)
  {
    auto& delegator = iter.second;
    delegator->GetDelegate()->SetVBOShiftScaleMethod(method);
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetPauseShiftScale(bool pauseShiftScale)
{
  if (pauseShiftScale == this->PauseShiftScale)
  {
    return;
  }

  this->Superclass::SetPauseShiftScale(pauseShiftScale);
  auto& internals = (*this->Internals);
  for (auto& iter : internals.BatchedDelegators)
  {
    auto& delegator = iter.second;
    delegator->GetDelegate()->SetPauseShiftScale(pauseShiftScale);
  }
}

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

//------------------------------------------------------------------------------
vtkCompositePolyDataMapperDelegator* vtkCompositePolyDataMapper::CreateADelegator()
{
  auto delegator = vtkCompositePolyDataMapperDelegator::New();
  return delegator;
}

//------------------------------------------------------------------------------
vtkDataObjectTreeIterator* vtkCompositePolyDataMapper::MakeAnIterator(vtkCompositeDataSet* dataset)
{
  auto iter = vtkDataObjectTreeIterator::New();
  iter->SetDataSet(dataset);
  iter->SkipEmptyNodesOn();
  iter->VisitOnlyLeavesOn();
  return iter;
}

//------------------------------------------------------------------------------
// simple tests, the mapper is tolerant of being
// called both on opaque and translucent
bool vtkCompositePolyDataMapper::HasOpaqueGeometry()
{
  return true;
}

//------------------------------------------------------------------------------
// look at children
bool vtkCompositePolyDataMapper::HasTranslucentPolygonalGeometry()
{
  // Make sure that we have been properly initialized.
  if (this->GetInputAlgorithm() == nullptr)
  {
    return false;
  }

  if (!this->Static)
  {
    this->InvokeEvent(vtkCommand::StartEvent, nullptr);
    this->GetInputAlgorithm()->Update();
    this->InvokeEvent(vtkCommand::EndEvent, nullptr);
  }

  if (this->GetInputDataObject(0, 0) == nullptr)
  {
    return false;
  }

  // rebuild the render values if needed
  vtkCompositeDataDisplayAttributes* cda = this->GetCompositeDataDisplayAttributes();
  vtkScalarsToColors* lut = this->ScalarVisibility ? this->GetLookupTable() : nullptr;

  this->TempState.Clear();
  this->TempState.Append(cda ? cda->GetMTime() : 0, "cda mtime");
  this->TempState.Append(lut ? lut->GetMTime() : 0, "lut mtime");
  this->TempState.Append(this->GetInputDataObject(0, 0)->GetMTime(), "input mtime");
  if (this->TranslucentState != this->TempState)
  {
    this->TranslucentState = this->TempState;
    if (lut)
    {
      // Ensure that the lookup table is built
      lut->Build();
    }

    // Push base-values on the state stack.
    unsigned int flat_index = 0;
    this->HasTranslucentGeometry =
      this->RecursiveHasTranslucentGeometry(this->GetInputDataObject(0, 0), flat_index);
  }

  return this->HasTranslucentGeometry;
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::Render(vtkRenderer* renderer, vtkActor* actor)
{
  auto& internals = (*this->Internals);
  internals.RenderedList.clear();
  // Make sure that we have been propertly initialized.
  if (renderer->GetRenderWindow()->CheckAbortStatus())
  {
    return;
  }
  if (this->GetInputAlgorithm() == nullptr)
  {
    return;
  }
  if (!this->Static)
  {
    this->InvokeEvent(vtkCommand::StartEvent, nullptr);
    this->GetInputAlgorithm()->Update();
    this->InvokeEvent(vtkCommand::EndEvent, nullptr);
  }
  auto input = this->GetInputDataObject(0, 0);
  if (this->GetInputDataObject(0, 0) == nullptr)
  {
    vtkErrorMacro(<< "No input!");
  }

  // The first step is to gather up the polydata based on their
  // signatures (aka have normals, have scalars etc)
  // At a high-level, the following code visits every polydata in this composite dataset,
  // creates/reuses an existing polydata mapper based on a hash string.
  if (this->DelegatorMTime < this->GetInputDataObject(0, 0)->GetMTime() ||
    this->DelegatorMTime < this->GetMTime())
  {
    this->PrototypeMapper->vtkMapper::ShallowCopy(this);
    // unmark old delegates
    for (auto& iter : internals.BatchedDelegators)
    {
      auto& delegator = iter.second;
      delegator->Unmark();
    }
    if (auto tree = vtkDataObjectTree::SafeDownCast(input))
    {
      auto dobjIter = vtk::TakeSmartPointer(this->MakeAnIterator(tree));
      for (dobjIter->InitTraversal(); !dobjIter->IsDoneWithTraversal(); dobjIter->GoToNextItem())
      {
        const auto polydata = vtkPolyData::SafeDownCast(dobjIter->GetCurrentDataObject());
        const auto flatIndex = dobjIter->GetCurrentFlatIndex();
        this->InsertPolyData(polydata, flatIndex);
      }
    }
    else if (auto polydata = vtkPolyData::SafeDownCast(input))
    {
      this->InsertPolyData(polydata, 0);
    }
    else
    {
      vtkErrorMacro(<< "Expected a vtkDataObjectTree or vtkPolyData input. Got "
                    << input->GetClassName());
    }
    // delete unused old helpers/data
    for (auto iter = internals.BatchedDelegators.begin();
         iter != internals.BatchedDelegators.end();)
    {
      iter->second->ClearUnmarkedBatchElements();
      if (!iter->second->GetMarked())
      {
        iter->second->GetDelegate()->ReleaseGraphicsResources(renderer->GetVTKWindow());
        internals.BatchedDelegators.erase(iter++);
      }
      else
      {
        ++iter;
      }
    }
    this->DelegatorMTime.Modified();
  }

  // rebuild the render values if needed.
  this->TempState.Clear();
  this->TempState.Append(actor->GetProperty()->GetMTime(), "actor mtime");
  this->TempState.Append(this->GetMTime(), "this mtime");
  this->TempState.Append(this->DelegatorMTime, "delegator mtime");
  this->TempState.Append(
    actor->GetTexture() ? actor->GetTexture()->GetMTime() : 0, "texture mtime");

  if (this->RenderValuesState != this->TempState)
  {
    this->RenderValuesState = this->TempState;
    auto property = actor->GetProperty();
    if (auto lut = this->GetLookupTable())
    {
      lut->Build();
    }
    auto selColor = property->GetSelectionColor();

    // Push base-values on the state stack.
    internals.BlockState.Visibility.push(true);
    internals.BlockState.Pickability.push(true);
    internals.BlockState.Opacity.push(property->GetOpacity());
    internals.BlockState.AmbientColor.emplace(property->GetAmbientColor());
    internals.BlockState.DiffuseColor.emplace(property->GetDiffuseColor());
    internals.BlockState.SpecularColor.emplace(property->GetSpecularColor());
    internals.BlockState.SelectionColor.emplace(selColor);
    internals.BlockState.SelectionOpacity.push(selColor[3]);

    {
      unsigned int flatIndex = 0;
      this->BuildRenderValues(renderer, actor, this->GetInputDataObject(0, 0), flatIndex);
    }

    internals.BlockState.Visibility.pop();
    internals.BlockState.Pickability.pop();
    internals.BlockState.Opacity.pop();
    internals.BlockState.AmbientColor.pop();
    internals.BlockState.DiffuseColor.pop();
    internals.BlockState.SpecularColor.pop();
    internals.BlockState.SelectionColor.pop();
    internals.BlockState.SelectionOpacity.pop();
  }

  for (auto& iter : internals.BatchedDelegators)
  {
    auto& delegator = iter.second;
    delegator->GetDelegate()->RenderPiece(renderer, actor);

    for (auto& polydata : delegator->GetRenderedList())
    {
      internals.RenderedList.emplace_back(polydata);
    }
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::InsertPolyData(
  vtkPolyData* polydata, const unsigned int& flatIndex)
{
  if (polydata == nullptr)
  {
    vtkDebugMacro(<< "DataObject at flatIndex=" << flatIndex
                  << " is not a vtkPolyData or a vtkPolyData derived instance!");
    return;
  }
  auto& internals = (*this->Internals);
  const auto hash = this->GenerateHash(polydata);
  // Find a mapper. If it doesn't exist, a new one is created.
  internals.BatchedDelegators.emplace(hash, nullptr);
  auto& delegator = internals.BatchedDelegators.at(hash);
  if (delegator == nullptr)
  {
    delegator.TakeReference(this->CreateADelegator());
    delegator->SetParent(this);
  }
  delegator->ShallowCopy(this);
  delegator->Mark();

  vtkCompositePolyDataMapperDelegator::BatchElement batchElement;
  batchElement.PolyData = polydata;
  batchElement.FlatIndex = flatIndex;
  delegator->Insert(std::move(batchElement));
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::BuildRenderValues(
  vtkRenderer* renderer, vtkActor* actor, vtkDataObject* dobj, unsigned int& flatIndex)
{
  auto& internals = (*this->Internals);
  vtkCompositeDataDisplayAttributes* cda = this->GetCompositeDataDisplayAttributes();
  bool overrides_visibility = (cda && cda->HasBlockVisibility(dobj));
  if (overrides_visibility)
  {
    internals.BlockState.Visibility.push(cda->GetBlockVisibility(dobj));
  }
  bool overrides_pickability = (cda && cda->HasBlockPickability(dobj));
  if (overrides_pickability)
  {
    internals.BlockState.Pickability.push(cda->GetBlockPickability(dobj));
  }

  bool overrides_opacity = (cda && cda->HasBlockOpacity(dobj));
  if (overrides_opacity)
  {
    internals.BlockState.Opacity.push(cda->GetBlockOpacity(dobj));
  }

  bool overrides_color = (cda && cda->HasBlockColor(dobj));
  if (overrides_color)
  {
    vtkColor3d color = cda->GetBlockColor(dobj);
    internals.BlockState.AmbientColor.push(color);
    internals.BlockState.DiffuseColor.push(color);
    internals.BlockState.SpecularColor.push(color);
  }

  // Advance flat-index. After this point, flatIndex no longer points to this
  // block.
  flatIndex++;

  bool textureOpaque = true;
  if (actor->GetTexture() != nullptr && actor->GetTexture()->IsTranslucent())
  {
    textureOpaque = false;
  }

  if (auto dObjTree = vtkDataObjectTree::SafeDownCast(dobj))
  {
    using Opts = vtk::DataObjectTreeOptions;
    for (vtkDataObject* child : vtk::Range(dObjTree, Opts::None))
    {
      if (!child)
      {
        ++flatIndex;
      }
      else
      {
        this->BuildRenderValues(renderer, actor, child, flatIndex);
      }
    }
  }
  else
  {
    vtkPolyData* polydata = vtkPolyData::SafeDownCast(dobj);
    const auto hash = this->GenerateHash(polydata);
    const auto& delegate = internals.BatchedDelegators[hash];
    // because it was incremented few lines above.
    if (auto inputItem = delegate->Get(polydata))
    {
      inputItem->Opacity = internals.BlockState.Opacity.top();
      inputItem->Visibility = internals.BlockState.Visibility.top();
      inputItem->Pickability = internals.BlockState.Pickability.top();
      inputItem->AmbientColor = internals.BlockState.AmbientColor.top();
      inputItem->DiffuseColor = internals.BlockState.DiffuseColor.top();
      inputItem->SelectionColor = internals.BlockState.SelectionColor.top();
      inputItem->SelectionOpacity = internals.BlockState.SelectionOpacity.top();
      inputItem->OverridesColor = (internals.BlockState.AmbientColor.size() > 1);
      inputItem->IsOpaque = (inputItem->Opacity >= 1.0) ? textureOpaque : false;
      // if we think it is opaque check the scalars
      if (inputItem->IsOpaque && this->ScalarVisibility)
      {
        vtkScalarsToColors* lut = this->GetLookupTable();
        int cellFlag;
        vtkDataArray* scalars = vtkCompositePolyDataMapper::GetScalars(polydata, this->ScalarMode,
          this->ArrayAccessMode, this->ArrayId, this->ArrayName, cellFlag);

        unsigned char ghostsToSkip;
        vtkUnsignedCharArray* ghosts =
          vtkAbstractMapper::GetGhostArray(polydata, this->ScalarMode, ghostsToSkip);

        if (!lut->IsOpaque(scalars, this->ColorMode, this->ArrayComponent, ghosts, ghostsToSkip))
        {
          inputItem->IsOpaque = false;
        }
      }
    }
  }
  if (overrides_color)
  {
    internals.BlockState.AmbientColor.pop();
    internals.BlockState.DiffuseColor.pop();
    internals.BlockState.SpecularColor.pop();
  }
  if (overrides_opacity)
  {
    internals.BlockState.Opacity.pop();
  }
  if (overrides_pickability)
  {
    internals.BlockState.Pickability.pop();
  }
  if (overrides_visibility)
  {
    internals.BlockState.Visibility.pop();
  }
}

//------------------------------------------------------------------------------
bool vtkCompositePolyDataMapper::RecursiveHasTranslucentGeometry(
  vtkDataObject* dobj, unsigned int& flat_index)
{
  vtkCompositeDataDisplayAttributes* cda = this->GetCompositeDataDisplayAttributes();
  bool overrides_opacity = (cda && cda->HasBlockOpacity(dobj));
  if (overrides_opacity)
  {
    if (cda->GetBlockOpacity(dobj) < 1.0)
    {
      return true;
    }
  }

  // Advance flat-index. After this point, flat_index no longer points to this
  // block.
  flat_index++;

  if (auto dObjTree = vtkDataObjectTree::SafeDownCast(dobj))
  {
    using Opts = vtk::DataObjectTreeOptions;
    for (vtkDataObject* child : vtk::Range(dObjTree, Opts::None))
    {
      if (!child)
      {
        ++flat_index;
      }
      else
      {
        if (this->RecursiveHasTranslucentGeometry(child, flat_index))
        {
          return true;
        }
      }
    }
    return false;
  }
  else
  {
    bool overrides_visibility = (cda && cda->HasBlockVisibility(dobj));
    if (overrides_visibility)
    {
      if (!cda->GetBlockVisibility(dobj))
      {
        return false;
      }
    }

    vtkPolyData* pd = vtkPolyData::SafeDownCast(dobj);

    // if we think it is opaque check the scalars
    if (this->ScalarVisibility)
    {
      vtkScalarsToColors* lut = this->GetLookupTable();
      int cellFlag;
      vtkDataArray* scalars = vtkCompositePolyDataMapper::GetScalars(
        pd, this->ScalarMode, this->ArrayAccessMode, this->ArrayId, this->ArrayName, cellFlag);

      unsigned char ghostsToSkip;
      vtkUnsignedCharArray* ghosts =
        vtkAbstractMapper::GetGhostArray(pd, this->ScalarMode, ghostsToSkip);

      if (lut->IsOpaque(scalars, this->ColorMode, this->ArrayComponent, ghosts, ghostsToSkip) == 0)
      {
        return true;
      }
    }
  }

  return false;
}

//------------------------------------------------------------------------------
vtkPolyDataMapper::MapperHashType vtkCompositePolyDataMapper::GenerateHash(vtkPolyData* polydata)
{
  return this->PrototypeMapper->GenerateHash(polydata);
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetCompositeDataDisplayAttributes(
  vtkCompositeDataDisplayAttributes* attributes)
{
  if (this->CompositeAttributes != attributes)
  {
    this->CompositeAttributes = attributes;
    this->Modified();
  }
}

//------------------------------------------------------------------------------
vtkCompositeDataDisplayAttributes* vtkCompositePolyDataMapper::GetCompositeDataDisplayAttributes()
{
  return this->CompositeAttributes;
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetBlockVisibility(unsigned int index, bool visible)
{
  if (this->CompositeAttributes)
  {
    auto dataObj =
      vtkCompositeDataDisplayAttributes::DataObjectFromIndex(index, this->GetInputDataObject(0, 0));
    if (dataObj)
    {
      this->CompositeAttributes->SetBlockVisibility(dataObj, visible);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
bool vtkCompositePolyDataMapper::GetBlockVisibility(unsigned int index)
{
  if (this->CompositeAttributes)
  {
    auto dataObj =
      vtkCompositeDataDisplayAttributes::DataObjectFromIndex(index, this->GetInputDataObject(0, 0));
    if (dataObj)
    {
      return this->CompositeAttributes->GetBlockVisibility(dataObj);
    }
  }

  return true;
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockVisibility(unsigned int index)
{
  if (this->CompositeAttributes)
  {
    auto dataObj =
      vtkCompositeDataDisplayAttributes::DataObjectFromIndex(index, this->GetInputDataObject(0, 0));
    if (dataObj)
    {
      this->CompositeAttributes->RemoveBlockVisibility(dataObj);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockVisibilities()
{
  if (this->CompositeAttributes)
  {
    this->CompositeAttributes->RemoveBlockVisibilities();
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetBlockColor(unsigned int index, const double color[3])
{
  if (this->CompositeAttributes)
  {
    auto dataObj =
      vtkCompositeDataDisplayAttributes::DataObjectFromIndex(index, this->GetInputDataObject(0, 0));

    if (dataObj)
    {
      this->CompositeAttributes->SetBlockColor(dataObj, color);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::GetBlockColor(unsigned int index, double color[3])
{
  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      this->CompositeAttributes->GetBlockColor(dataObj, color);
    }
  }
  else
  {
    color[0] = 1.0;
    color[1] = 1.0;
    color[2] = 1.0;
  }
}

//------------------------------------------------------------------------------
double* vtkCompositePolyDataMapper::GetBlockColor(unsigned int index)
{
  VTK_LEGACY_REPLACED_BODY(double* vtkCompositePolyDataMapper::GetBlockColor(unsigned int index),
    "VTK 9.3", void vtkCompositePolyDataMapper::GetBlockColor(unsigned int index, double color[3]));
  static double white[3] = { 1.0, 1.0, 1.0 };

  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      this->CompositeAttributes->GetBlockColor(dataObj, this->ColorResult.data());
    }

    return this->ColorResult.data();
  }
  else
  {
    return white;
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockColor(unsigned int index)
{
  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      this->CompositeAttributes->RemoveBlockColor(dataObj);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockColors()
{
  if (this->CompositeAttributes)
  {
    this->CompositeAttributes->RemoveBlockColors();
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetBlockOpacity(unsigned int index, double opacity)
{
  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      this->CompositeAttributes->SetBlockOpacity(dataObj, opacity);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
double vtkCompositePolyDataMapper::GetBlockOpacity(unsigned int index)
{
  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      return this->CompositeAttributes->GetBlockOpacity(dataObj);
    }
  }
  return 1.;
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockOpacity(unsigned int index)
{
  if (this->CompositeAttributes)
  {
    unsigned int start_index = 0;
    auto dataObj = vtkCompositeDataDisplayAttributes::DataObjectFromIndex(
      index, this->GetInputDataObject(0, 0), start_index);
    if (dataObj)
    {
      this->CompositeAttributes->RemoveBlockOpacity(dataObj);
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::RemoveBlockOpacities()
{
  if (this->CompositeAttributes)
  {
    this->CompositeAttributes->RemoveBlockOpacities();
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetInputArrayToProcess(int idx, vtkInformation* inInfo)
{
  this->Superclass::SetInputArrayToProcess(idx, inInfo);

  const auto& internals = (*this->Internals);
  // set inputs to helpers
  for (auto& item : internals.BatchedDelegators)
  {
    item.second->GetDelegate()->SetInputArrayToProcess(idx, inInfo);
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetInputArrayToProcess(
  int idx, int port, int connection, int fieldAssociation, int attributeType)
{
  this->Superclass::SetInputArrayToProcess(idx, port, connection, fieldAssociation, attributeType);

  const auto& internals = (*this->Internals);
  // set inputs to helpers
  for (auto& item : internals.BatchedDelegators)
  {
    item.second->GetDelegate()->SetInputArrayToProcess(
      idx, port, connection, fieldAssociation, attributeType);
  }
}

//------------------------------------------------------------------------------
void vtkCompositePolyDataMapper::SetInputArrayToProcess(
  int idx, int port, int connection, int fieldAssociation, const char* name)
{
  this->Superclass::SetInputArrayToProcess(idx, port, connection, fieldAssociation, name);

  const auto& internals = (*this->Internals);
  // set inputs to helpers
  for (auto& item : internals.BatchedDelegators)
  {
    item.second->GetDelegate()->SetInputArrayToProcess(
      idx, port, connection, fieldAssociation, name);
  }
}

//-----------------------------------------------------------------------------
std::vector<vtkPolyData*> vtkCompositePolyDataMapper::GetRenderedList()
{
  return this->Internals->RenderedList;
}

//-----------------------------------------------------------------------------
void vtkCompositePolyDataMapper::ProcessSelectorPixelBuffers(
  vtkHardwareSelector* sel, std::vector<unsigned int>& pixeloffsets, vtkProp* prop)
{
  const auto& internals = (*this->Internals);
  // forward to helper
  for (auto& item : internals.BatchedDelegators)
  {
    item.second->GetDelegate()->ProcessSelectorPixelBuffers(sel, pixeloffsets, prop);
  }
}

//-----------------------------------------------------------------------------
vtkMTimeType vtkCompositePolyDataMapper::GetMTime()
{
  if (this->CompositeAttributes)
  {
    return std::max(this->Superclass::GetMTime(), this->CompositeAttributes->GetMTime());
  }
  return this->Superclass::GetMTime();
}
VTK_ABI_NAMESPACE_END
