// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include <algorithm>

#include "vtkGenericGeometryFilter.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkDoubleArray.h"
#include "vtkGenericAdaptorCell.h"
#include "vtkGenericAttribute.h"
#include "vtkGenericAttributeCollection.h"
#include "vtkGenericCell.h"
#include "vtkGenericCellIterator.h"
#include "vtkGenericCellTessellator.h"
#include "vtkGenericDataSet.h"
#include "vtkGenericPointIterator.h"
#include "vtkHexahedron.h"
#include "vtkIncrementalPointLocator.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMergePoints.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPyramid.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStructuredGrid.h"
#include "vtkTetra.h"
#include "vtkUnsignedCharArray.h"
#include "vtkUnstructuredGrid.h"
#include "vtkVoxel.h"
#include "vtkWedge.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkGenericGeometryFilter);

vtkCxxSetObjectMacro(vtkGenericGeometryFilter, Locator, vtkIncrementalPointLocator);
//------------------------------------------------------------------------------
// Construct with all types of clipping turned off.
vtkGenericGeometryFilter::vtkGenericGeometryFilter()
{
  this->PointMinimum = 0;
  this->PointMaximum = VTK_ID_MAX;

  this->CellMinimum = 0;
  this->CellMaximum = VTK_ID_MAX;

  this->Extent[0] = VTK_DOUBLE_MIN;
  this->Extent[1] = VTK_DOUBLE_MAX;
  this->Extent[2] = VTK_DOUBLE_MIN;
  this->Extent[3] = VTK_DOUBLE_MAX;
  this->Extent[4] = VTK_DOUBLE_MIN;
  this->Extent[5] = VTK_DOUBLE_MAX;

  this->PointClipping = 0;
  this->CellClipping = 0;
  this->ExtentClipping = 0;

  this->Merging = 1;
  this->Locator = nullptr;
  this->InternalPD = vtkPointData::New();

  this->PassThroughCellIds = 0;
}
//------------------------------------------------------------------------------
vtkGenericGeometryFilter::~vtkGenericGeometryFilter()
{
  if (this->Locator)
  {
    this->Locator->UnRegister(this);
    this->Locator = nullptr;
  }
  this->InternalPD->Delete();
}

//------------------------------------------------------------------------------
// Specify a (xmin,xmax, ymin,ymax, zmin,zmax) bounding box to clip data.
void vtkGenericGeometryFilter::SetExtent(
  double xMin, double xMax, double yMin, double yMax, double zMin, double zMax)
{
  double extent[6];

  extent[0] = xMin;
  extent[1] = xMax;
  extent[2] = yMin;
  extent[3] = yMax;
  extent[4] = zMin;
  extent[5] = zMax;

  this->SetExtent(extent);
}

//------------------------------------------------------------------------------
// Specify a (xmin,xmax, ymin,ymax, zmin,zmax) bounding box to clip data.
void vtkGenericGeometryFilter::SetExtent(double extent[6])
{
  int i;

  if (extent[0] != this->Extent[0] || extent[1] != this->Extent[1] ||
    extent[2] != this->Extent[2] || extent[3] != this->Extent[3] || extent[4] != this->Extent[4] ||
    extent[5] != this->Extent[5])
  {
    this->Modified();
    for (i = 0; i < 3; i++)
    {
      extent[2 * i + 1] = std::max(extent[2 * i + 1], extent[2 * i]);
      this->Extent[2 * i] = extent[2 * i];
      this->Extent[2 * i + 1] = extent[2 * i + 1];
    }
  }
}

//------------------------------------------------------------------------------
int vtkGenericGeometryFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the input and output
  vtkGenericDataSet* input =
    vtkGenericDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkIdType cellId;
  int i, j;
  vtkIdType numPts = input->GetNumberOfPoints();
  vtkIdType numCells = input->GetNumberOfCells();
  char* cellVis;
  vtkGenericAdaptorCell* cell;
  double x[3] = { 0, 0, 0 };
  vtkIdType ptIds[4];

  vtkIdType ptId;
  int allVisible;
  vtkPointData* outputPD = output->GetPointData();
  vtkCellData* outputCD = output->GetCellData();

  if (numCells == 0)
  {
    vtkErrorMacro(<< "Number of cells is zero, no data to process.");
    return 1;
  }

  vtkDebugMacro(<< "Executing geometry filter");

  if ((!this->CellClipping) && (!this->PointClipping) && (!this->ExtentClipping))
  {
    allVisible = 1;
    cellVis = nullptr;
  }
  else
  {
    allVisible = 0;
    cellVis = new char[numCells];
  }

  vtkGenericCellIterator* cellIt = input->NewCellIterator();
  // Mark cells as being visible or not
  //
  if (!allVisible)
  {
    for (cellIt->Begin(); !cellIt->IsAtEnd(); cellIt->Next())
    {
      cell = cellIt->GetCell();
      cellId = cell->GetId();
      if (this->CellClipping && (cellId < this->CellMinimum || cellId > this->CellMaximum))
      {
        cellVis[cellId] = 0;
      }
      else
      {
        vtkGenericPointIterator* pointIt = input->NewPointIterator();
        cell->GetPointIterator(pointIt);
        pointIt->Begin();

        cell->GetPointIds(ptIds);
        for (i = 0; i < cell->GetNumberOfPoints(); i++)
        {
          ptId = ptIds[i];

          // Get point coordinate
          pointIt->GetPosition(x);
          pointIt->Next();

          if ((this->PointClipping && (ptId < this->PointMinimum || ptId > this->PointMaximum)) ||
            (this->ExtentClipping &&
              (x[0] < this->Extent[0] || x[0] > this->Extent[1] || x[1] < this->Extent[2] ||
                x[1] > this->Extent[3] || x[2] < this->Extent[4] || x[2] > this->Extent[5])))
          {
            cellVis[cellId] = 0;
            break;
          }
        }
        if (i >= cell->GetNumberOfPoints())
        {
          cellVis[cellId] = 1;
        }
        pointIt->Delete();
      }
    }
  }

  // Allocate
  //
  vtkIdType estimatedSize = input->GetEstimatedSize();
  estimatedSize = (estimatedSize / 1024 + 1) * 1024; // multiple of 1024
  estimatedSize = std::max<vtkIdType>(estimatedSize, 1024);
  output->AllocateEstimate(numCells, 1);

  vtkPoints* newPts = vtkPoints::New();
  vtkCellArray* cellArray = vtkCellArray::New();

  newPts->Allocate(estimatedSize, numPts);
  cellArray->AllocateEstimate(numCells, 1);

  // prepare the output attributes
  vtkGenericAttributeCollection* attributes = input->GetAttributes();
  vtkGenericAttribute* attribute;
  vtkDataArray* attributeArray;

  int c = attributes->GetNumberOfAttributes();
  vtkDataSetAttributes* dsAttributes;

  int attributeType;

  this->InternalPD->Initialize();
  for (i = 0; i < c; ++i)
  {
    attribute = attributes->GetAttribute(i);
    attributeType = attribute->GetType();
    int centering = attribute->GetCentering();
    if (centering != vtkPointCentered && centering != vtkCellCentered)
    { // skip boundary-centered attributes.
      continue;
    }

    if (centering == vtkPointCentered)
    {
      dsAttributes = outputPD;

      attributeArray = vtkDataArray::CreateDataArray(attribute->GetComponentType());
      attributeArray->SetNumberOfComponents(attribute->GetNumberOfComponents());
      attributeArray->SetName(attribute->GetName());
      this->InternalPD->AddArray(attributeArray);
      attributeArray->Delete();
      if (this->InternalPD->GetAttribute(attributeType) == nullptr)
      {
        this->InternalPD->SetActiveAttribute(
          this->InternalPD->GetNumberOfArrays() - 1, attributeType);
      }
    }
    else // vtkCellCentered
    {
      dsAttributes = outputCD;
    }
    attributeArray = vtkDataArray::CreateDataArray(attribute->GetComponentType());
    attributeArray->SetNumberOfComponents(attribute->GetNumberOfComponents());
    attributeArray->SetName(attribute->GetName());
    dsAttributes->AddArray(attributeArray);
    attributeArray->Delete();

    if (dsAttributes->GetAttribute(attributeType) == nullptr)
    {
      dsAttributes->SetActiveAttribute(dsAttributes->GetNumberOfArrays() - 1, attributeType);
    }
  }

  vtkIncrementalPointLocator* locator = nullptr;
  if (this->Merging)
  {
    if (this->Locator == nullptr)
    {
      this->CreateDefaultLocator();
    }
    this->Locator->InitPointInsertion(newPts, input->GetBounds());
    locator = this->Locator;
  }

  // Traverse cells to extract geometry
  //
  bool abort = false;
  vtkIdType progressInterval = numCells / 20 + 1;

  vtkIdList* faceList = vtkIdList::New();
  faceList->SetNumberOfIds(3);

  input->GetTessellator()->InitErrorMetrics(input);

  vtkIdTypeArray* originalCellIds = nullptr;
  if (this->PassThroughCellIds)
  {
    originalCellIds = vtkIdTypeArray::New();
    originalCellIds->SetName("vtkOriginalCellIds");
    originalCellIds->SetNumberOfComponents(1);
  }

  for (cellId = 0, cellIt->Begin(); !cellIt->IsAtEnd() && !abort; cellIt->Next(), cellId++)
  {
    cell = cellIt->GetCell();
    // Progress and abort method support
    if (!(cellId % progressInterval))
    {
      vtkDebugMacro(<< "Process cell #" << cellId);
      this->UpdateProgress(static_cast<double>(cellId) / numCells);
      abort = this->CheckAbort();
    }

    vtkIdType BeginTopOutCId = outputCD->GetNumberOfTuples();
    if (allVisible || cellVis[cellId])
    {
      switch (cell->GetDimension())
      {
        // create new points and then cell
        case 0:
        case 1:
          vtkErrorMacro("Cell of dimension " << cell->GetDimension() << " not handled yet.");
          break;
        case 2:
          if (cell->IsOnBoundary())
          {
            cell->Tessellate(input->GetAttributes(), input->GetTessellator(), newPts, locator,
              cellArray, this->InternalPD, outputPD, outputCD, nullptr); // newScalars );
          }
          break;
        case 3:
          //          int numFaces = cell->GetNumberOfFaces();
          int numFaces = cell->GetNumberOfBoundaries(2);
          for (j = 0; j < numFaces; j++)
          {
            if (cell->IsFaceOnBoundary(j))
            {
              cell->TriangulateFace(input->GetAttributes(), input->GetTessellator(), j, newPts,
                locator, cellArray, this->InternalPD, outputPD, outputCD);
            }
          }
          break;

      } // switch
    }   // if visible

    vtkIdType EndTopOutCId = outputCD->GetNumberOfTuples();
    if (this->PassThroughCellIds)
    {
      for (vtkIdType cId = BeginTopOutCId; cId < EndTopOutCId; cId++)
      {
        originalCellIds->InsertNextValue(cellId);
      }
    }

  } // for all cells

  if (this->PassThroughCellIds)
  {
    outputCD->AddArray(originalCellIds);
    originalCellIds->Delete();
    originalCellIds = nullptr;
  }

  cellIt->Delete();
  vtkDebugMacro(<< "Extracted " << newPts->GetNumberOfPoints() << " points,"
                << output->GetNumberOfCells() << " cells.");

  // Update ourselves and release memory
  //
  output->SetPoints(newPts);
  output->SetPolys(cellArray);

  cellArray->Delete();
  newPts->Delete();
  faceList->Delete();

  // free storage
  if (!this->Merging && this->Locator)
  {
    this->Locator->Initialize();
  }
  output->Squeeze();

  delete[] cellVis;

  return 1;
}

//------------------------------------------------------------------------------
int vtkGenericGeometryFilter::FillInputPortInformation(int port, vtkInformation* info)
{
  if (!this->Superclass::FillInputPortInformation(port, info))
  {
    return 0;
  }
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGenericDataSet");
  return 1;
}

//------------------------------------------------------------------------------
// Specify a spatial locator for merging points. By
// default an instance of vtkMergePoints is used.
void vtkGenericGeometryFilter::CreateDefaultLocator()
{
  if (this->Locator == nullptr)
  {
    this->Locator = vtkMergePoints::New();
  }
}

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

  os << indent << "Point Minimum : " << this->GetPointMinimum() << "\n";
  os << indent << "Point Maximum : " << this->GetPointMaximum() << "\n";

  os << indent << "Cell Minimum : " << this->GetCellMinimum() << "\n";
  os << indent << "Cell Maximum : " << this->GetCellMaximum() << "\n";

  os << indent << "Extent: \n";
  os << indent << "  Xmin,Xmax: (" << this->Extent[0] << ", " << this->Extent[1] << ")\n";
  os << indent << "  Ymin,Ymax: (" << this->Extent[2] << ", " << this->Extent[3] << ")\n";
  os << indent << "  Zmin,Zmax: (" << this->Extent[4] << ", " << this->Extent[5] << ")\n";

  os << indent << "PointClipping: " << (this->GetPointClipping() ? "On\n" : "Off\n");
  os << indent << "CellClipping: " << (this->GetCellClipping() ? "On\n" : "Off\n");
  os << indent << "ExtentClipping: " << (this->GetExtentClipping() ? "On\n" : "Off\n");

  os << indent << "Merging: " << (this->GetMerging() ? "On\n" : "Off\n");
  if (this->GetLocator())
  {
    os << indent << "Locator: " << this->GetLocator() << "\n";
  }
  else
  {
    os << indent << "Locator: (none)\n";
  }

  os << indent << "PassThroughCellIds: " << (this->GetPassThroughCellIds() ? "On\n" : "Off\n");
}

//------------------------------------------------------------------------------
vtkMTimeType vtkGenericGeometryFilter::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

  if (this->Locator != nullptr)
  {
    time = this->Locator->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }
  return mTime;
}

//------------------------------------------------------------------------------
int vtkGenericGeometryFilter::RequestUpdateExtent(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  int piece, numPieces, ghostLevels;

  piece = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER());
  numPieces = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES());
  ghostLevels = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS());

  if (numPieces > 1)
  {
    ++ghostLevels;
  }

  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(), piece);
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(), numPieces);
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(), ghostLevels);
  inInfo->Set(vtkStreamingDemandDrivenPipeline::EXACT_EXTENT(), 1);

  return 1;
}
VTK_ABI_NAMESPACE_END
