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

#include "vtkCell.h"
#include "vtkCellData.h"
#include "vtkExecutive.h"
#include "vtkGenericAdaptorCell.h"
#include "vtkGenericAttribute.h"
#include "vtkGenericAttributeCollection.h"
#include "vtkGenericCellIterator.h"
#include "vtkGenericDataSet.h"
#include "vtkIdTypeArray.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"

#include <iostream>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkGenericProbeFilter);

//------------------------------------------------------------------------------
vtkGenericProbeFilter::vtkGenericProbeFilter()
{
  this->ValidPoints = vtkIdTypeArray::New();
  this->SetNumberOfInputPorts(2);
}

//------------------------------------------------------------------------------
vtkGenericProbeFilter::~vtkGenericProbeFilter()
{
  this->ValidPoints->Delete();
  this->ValidPoints = nullptr;
}

//------------------------------------------------------------------------------
void vtkGenericProbeFilter::SetSourceData(vtkGenericDataSet* input)
{
  this->SetInputData(1, input);
}

//------------------------------------------------------------------------------
vtkGenericDataSet* vtkGenericProbeFilter::GetSource()
{
  if (this->GetNumberOfInputConnections(1) < 1)
  {
    return nullptr;
  }

  return vtkGenericDataSet::SafeDownCast(this->GetExecutive()->GetInputData(1, 0));
}

//------------------------------------------------------------------------------
int vtkGenericProbeFilter::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkInformation* sourceInfo = inputVector[1]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // A variation of the bug fix from John Biddiscombe.
  // Make sure that the scalar type and number of components
  // are propagated from the source not the input.
  if (vtkImageData::HasScalarType(sourceInfo))
  {
    vtkImageData::SetScalarType(vtkImageData::GetScalarType(sourceInfo), outInfo);
  }
  if (vtkImageData::HasNumberOfScalarComponents(sourceInfo))
  {
    vtkImageData::SetNumberOfScalarComponents(
      vtkImageData::GetNumberOfScalarComponents(sourceInfo), outInfo);
  }
  return 1;
}

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

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

  vtkGenericDataSet* source =
    vtkGenericDataSet::SafeDownCast(sourceInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkIdType ptId, numPts;
  double x[3], tol2;
  int subId;

  double pcoords[3];

  vtkDebugMacro(<< "Probing data");

  if (source == nullptr)
  {
    vtkErrorMacro(<< "Source is nullptr.");
    return 1;
  }

  // First, copy the input to the output as a starting point
  output->CopyStructure(input);

  numPts = input->GetNumberOfPoints();
  this->ValidPoints->Allocate(numPts);

  vtkPointData* outputPD = output->GetPointData();
  vtkCellData* outputCD = output->GetCellData();

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

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

  int attributeType;

  double* tuples = new double[attributes->GetMaxNumberOfComponents()];

  for (int i = 0; i < c; ++i)
  {
    attribute = attributes->GetAttribute(i);
    attributeType = attribute->GetType();
    if (attribute->GetCentering() == vtkPointCentered)
    {
      dsAttributes = outputPD;
    }
    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);
    }
  }

  // Use tolerance as a function of size of source data
  //
  tol2 = source->GetLength();
  tol2 = tol2 ? tol2 * tol2 / 1000.0 : 0.001;
  std::cout << "tol2=" << tol2 << "\n";
  // Loop over all input points, interpolating source data
  //
  bool abort = false;

  // Need to use source to create a cellIt since this class is virtual
  vtkGenericCellIterator* cellIt = source->NewCellIterator();

  vtkIdType progressInterval = numPts / 20 + 1;
  for (ptId = 0; ptId < numPts && !abort; ptId++)
  {
    if (!(ptId % progressInterval))
    {
      this->UpdateProgress(static_cast<double>(ptId) / numPts);
      abort = CheckAbort();
    }

    // Get the xyz coordinate of the point in the input dataset
    input->GetPoint(ptId, x);

    // Find the cell that contains xyz and get it
    if (source->FindCell(x, cellIt, tol2, subId, pcoords))
    {
      vtkGenericAdaptorCell* cellProbe = cellIt->GetCell();

      // for each cell-centered attribute: copy the value
      for (int attrib = 0; attrib < c; ++attrib)
      {
        if (attributes->GetAttribute(attrib)->GetCentering() == vtkCellCentered)
        {
          vtkDataArray* array = outputCD->GetArray(attributes->GetAttribute(attrib)->GetName());
          double* values = attributes->GetAttribute(attrib)->GetTuple(cellProbe);
          array->InsertNextTuple(values);
        }
      }

      // for each point-centered attribute: interpolate the value
      int j = 0;
      for (int attribute_idx = 0; attribute_idx < c; ++attribute_idx)
      {
        vtkGenericAttribute* a = attributes->GetAttribute(attribute_idx);
        if (a->GetCentering() == vtkPointCentered)
        {
          cellProbe->InterpolateTuple(a, pcoords, tuples);
          outputPD->GetArray(j)->InsertTuple(ptId, tuples);
          ++j;
        }
      }
      this->ValidPoints->InsertNextValue(ptId);
    }
    else
    {
      outputPD->NullData(ptId);
    }
  }
  cellIt->Delete();
  delete[] tuples;

  return 1;
}

//------------------------------------------------------------------------------
void vtkGenericProbeFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  vtkGenericDataSet* source = this->GetSource();

  this->Superclass::PrintSelf(os, indent);

  os << indent << "Source: " << source << "\n";
  os << indent << "ValidPoints: " << this->ValidPoints << "\n";
}

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