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

#include "vtkCell.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkContourGrid.h"
#include "vtkContourValues.h"
#include "vtkDoubleArray.h"
#include "vtkGenericAdaptorCell.h"
#include "vtkGenericAttribute.h"
#include "vtkGenericAttributeCollection.h"
#include "vtkGenericCellIterator.h"
#include "vtkGenericCellTessellator.h"
#include "vtkGenericContourFilter.h"
#include "vtkGenericDataSet.h"
#include "vtkIncrementalPointLocator.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLine.h"
#include "vtkMergePoints.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkTimerLog.h"
#include "vtkUnstructuredGrid.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkGenericContourFilter);

vtkCxxSetObjectMacro(vtkGenericContourFilter, Locator, vtkIncrementalPointLocator);

// Construct object with initial range (0,1) and single contour value
// of 0.0.
vtkGenericContourFilter::vtkGenericContourFilter()
{
  this->ContourValues = vtkContourValues::New();

  this->ComputeNormals = 1;
  this->ComputeGradients = 0;
  this->ComputeScalars = 1;

  this->Locator = nullptr;

  this->InputScalarsSelection = nullptr;

  this->InternalPD = vtkPointData::New();
  this->SecondaryPD = vtkPointData::New();
  this->SecondaryCD = vtkCellData::New();
}

//------------------------------------------------------------------------------
vtkGenericContourFilter::~vtkGenericContourFilter()
{
  this->ContourValues->Delete();
  if (this->Locator)
  {
    this->Locator->UnRegister(this);
    this->Locator = nullptr;
  }
  this->SetInputScalarsSelection(nullptr);
  this->InternalPD->Delete();
  this->SecondaryPD->Delete();
  this->SecondaryCD->Delete();
}

//------------------------------------------------------------------------------
// Overload standard modified time function. If contour values are modified,
// then this object is modified as well.
vtkMTimeType vtkGenericContourFilter::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

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

  // mTime should also take into account the fact that tessellator is view
  // dependent

  return mTime;
}

//------------------------------------------------------------------------------
// General contouring filter.  Handles arbitrary input.
int vtkGenericContourFilter::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()));

  vtkDebugMacro(<< "Executing contour filter");

  if (!input)
  {
    vtkErrorMacro("No input specified");
    return 1;
  }
  vtkPointData* outPd = output->GetPointData();
  vtkCellData* outCd = output->GetCellData();

  // Create objects to hold output of contour operation. First estimate
  // allocation size.
  vtkIdType numCells = input->GetNumberOfCells();
  vtkIdType estimatedSize = input->GetEstimatedSize();
  estimatedSize = estimatedSize / 1024 * 1024; // multiple of 1024
  estimatedSize = std::max<vtkIdType>(estimatedSize, 1024);

  vtkPoints* newPts = vtkPoints::New();
  newPts->Allocate(estimatedSize, estimatedSize);
  vtkCellArray* newVerts = vtkCellArray::New();
  newVerts->AllocateExact(estimatedSize, estimatedSize);
  vtkCellArray* newLines = vtkCellArray::New();
  newLines->AllocateExact(estimatedSize, estimatedSize);
  vtkCellArray* newPolys = vtkCellArray::New();
  newPolys->AllocateExact(estimatedSize, estimatedSize);

  // locator used to merge potentially duplicate points
  if (this->Locator == nullptr)
  {
    this->CreateDefaultLocator();
  }
  this->Locator->InitPointInsertion(newPts, input->GetBounds(), estimatedSize);

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

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

  int attributeType;

  for (vtkIdType i = 0; i < c; ++i)
  {
    attribute = attributes->GetAttribute(i);
    attributeType = attribute->GetType();
    if (attribute->GetCentering() == vtkPointCentered)
    {
      secondaryAttributes = this->SecondaryPD;

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

    attributeArray = vtkDataArray::CreateDataArray(attribute->GetComponentType());
    attributeArray->SetNumberOfComponents(attribute->GetNumberOfComponents());
    attributeArray->SetName(attribute->GetName());
    secondaryAttributes->AddArray(attributeArray);
    attributeArray->Delete();

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

  outPd->InterpolateAllocate(this->SecondaryPD, estimatedSize, estimatedSize);
  outCd->CopyAllocate(this->SecondaryCD, estimatedSize, estimatedSize);

  vtkGenericAdaptorCell* cell;

  //----------- Begin of contouring algorithm --------------------//
  vtkGenericCellIterator* cellIt = input->NewCellIterator();

  if (this->InputScalarsSelection)
  {
    int attrib = input->GetAttributes()->FindAttribute(this->InputScalarsSelection);
    if (attrib != -1)
    {
      vtkGenericAttribute* a = input->GetAttributes()->GetAttribute(attrib);
      if (a->GetNumberOfComponents() == 1)
      {
        input->GetAttributes()->SetActiveAttribute(attrib, 0);
      }
    }
  }

  vtkIdType updateCount = numCells / 20 + 1; // update roughly every 5%
  vtkIdType count = 0;
  bool abortExecute = false;

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

  for (cellIt->Begin(); !cellIt->IsAtEnd() && !abortExecute; cellIt->Next())
  {
    if (!(count % updateCount))
    {
      this->UpdateProgress(static_cast<double>(count) / numCells);
      abortExecute = this->CheckAbort();
    }

    cell = cellIt->GetCell();
    cell->Contour(this->ContourValues, nullptr, input->GetAttributes(), input->GetTessellator(),
      this->Locator, newVerts, newLines, newPolys, outPd, outCd, this->InternalPD,
      this->SecondaryPD, this->SecondaryCD);
    ++count;
  } // for each cell
  cellIt->Delete();

  vtkDebugMacro(<< "Created: " << newPts->GetNumberOfPoints() << " points, "
                << newVerts->GetNumberOfCells() << " verts, " << newLines->GetNumberOfCells()
                << " lines, " << newPolys->GetNumberOfCells() << " triangles");

  //----------- End of contouring algorithm ----------------------//

  // Update ourselves.  Because we don't know up front how many verts, lines,
  // polys we've created, take care to reclaim memory.
  //
  output->SetPoints(newPts);
  newPts->Delete();

  if (newVerts->GetNumberOfCells() > 0)
  {
    output->SetVerts(newVerts);
  }
  newVerts->Delete();

  if (newLines->GetNumberOfCells() > 0)
  {
    output->SetLines(newLines);
  }
  newLines->Delete();

  if (newPolys->GetNumberOfCells() > 0)
  {
    output->SetPolys(newPolys);
  }
  newPolys->Delete();

  this->Locator->Initialize(); // releases leftover memory
  output->Squeeze();
  return 1;
}

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

//------------------------------------------------------------------------------
void vtkGenericContourFilter::SelectInputScalars(const char* fieldName)
{
  this->SetInputScalarsSelection(fieldName);
}

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

  if (this->InputScalarsSelection)
  {
    os << indent << "InputScalarsSelection: " << this->InputScalarsSelection << endl;
  }

  os << indent << "Compute Gradients: " << (this->ComputeGradients ? "On\n" : "Off\n");
  os << indent << "Compute Normals: " << (this->ComputeNormals ? "On\n" : "Off\n");
  os << indent << "Compute Scalars: " << (this->ComputeScalars ? "On\n" : "Off\n");

  this->ContourValues->PrintSelf(os, indent.GetNextIndent());

  if (this->Locator)
  {
    os << indent << "Locator: " << this->Locator << "\n";
  }
  else
  {
    os << indent << "Locator: (none)\n";
  }
}

//------------------------------------------------------------------------------
// Description:
// Set a particular contour value at contour number i. The index i ranges
// between 0<=i<NumberOfContours.
void vtkGenericContourFilter::SetValue(int i, float value)
{
  this->ContourValues->SetValue(i, value);
}

//------------------------------------------------------------------------------
// Description:
// Get the ith contour value.
double vtkGenericContourFilter::GetValue(int i)
{
  return this->ContourValues->GetValue(i);
}

//------------------------------------------------------------------------------
// Description:
// Get a pointer to an array of contour values. There will be
// GetNumberOfContours() values in the list.
double* vtkGenericContourFilter::GetValues()
{
  return this->ContourValues->GetValues();
}

//------------------------------------------------------------------------------
// Description:
// Fill a supplied list with contour values. There will be
// GetNumberOfContours() values in the list. Make sure you allocate
// enough memory to hold the list.
void vtkGenericContourFilter::GetValues(double* contourValues)
{
  this->ContourValues->GetValues(contourValues);
}

//------------------------------------------------------------------------------
// Description:
// Set the number of contours to place into the list. You only really
// need to use this method to reduce list size. The method SetValue()
// will automatically increase list size as needed.
void vtkGenericContourFilter::SetNumberOfContours(int number)
{
  this->ContourValues->SetNumberOfContours(number);
}

//------------------------------------------------------------------------------
// Description:
// Get the number of contours in the list of contour values.
vtkIdType vtkGenericContourFilter::GetNumberOfContours()
{
  return this->ContourValues->GetNumberOfContours();
}

//------------------------------------------------------------------------------
// Description:
// Generate numContours equally spaced contour values between specified
// range. Contour values will include min/max range values.
void vtkGenericContourFilter::GenerateValues(int numContours, double range[2])
{
  this->ContourValues->GenerateValues(numContours, range);
}

//------------------------------------------------------------------------------
// Description:
// Generate numContours equally spaced contour values between specified
// range. Contour values will include min/max range values.
void vtkGenericContourFilter::GenerateValues(int numContours, double rangeStart, double rangeEnd)
{
  this->ContourValues->GenerateValues(numContours, rangeStart, rangeEnd);
}

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