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

#include "vtkCellData.h"
#include "vtkDataSet.h"
#include "vtkFloatArray.h"
#include "vtkGarbageCollector.h"
#include "vtkImplicitFunction.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkSMPTools.h"
#include "vtkStreamingDemandDrivenPipeline.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkSampleImplicitFunctionFilter);
vtkCxxSetObjectMacro(vtkSampleImplicitFunctionFilter, ImplicitFunction, vtkImplicitFunction);

// Interface between vtkSMPTools and the VTK pipeline
namespace
{

struct SampleDataSet
{
  vtkDataSet* Input;
  vtkImplicitFunction* Function;
  float* Scalars;
  vtkSampleImplicitFunctionFilter* Filter;

  // Constructor
  SampleDataSet(
    vtkDataSet* input, vtkImplicitFunction* imp, float* s, vtkSampleImplicitFunctionFilter* filter)
    : Input(input)
    , Function(imp)
    , Scalars(s)
    , Filter(filter)
  {
  }

  void operator()(vtkIdType ptId, vtkIdType endPtId)
  {
    double x[3];
    float* n = this->Scalars + ptId;
    bool isFirst = vtkSMPTools::GetSingleThread();
    for (; ptId < endPtId; ++ptId)
    {
      if (isFirst)
      {
        this->Filter->CheckAbort();
      }
      if (this->Filter->GetAbortOutput())
      {
        break;
      }
      this->Input->GetPoint(ptId, x);
      *n++ = this->Function->FunctionValue(x);
    }
  }
};

struct SampleDataSetWithGradients
{
  vtkDataSet* Input;
  vtkImplicitFunction* Function;
  float* Scalars;
  float* Gradients;
  vtkSampleImplicitFunctionFilter* Filter;

  // Constructor
  SampleDataSetWithGradients(vtkDataSet* input, vtkImplicitFunction* imp, float* s, float* g,
    vtkSampleImplicitFunctionFilter* filter)
    : Input(input)
    , Function(imp)
    , Scalars(s)
    , Gradients(g)
    , Filter(filter)
  {
  }

  void operator()(vtkIdType ptId, vtkIdType endPtId)
  {
    double x[3], g[3];
    float* n = this->Scalars + ptId;
    float* v = this->Gradients + 3 * ptId;
    bool isFirst = vtkSMPTools::GetSingleThread();

    for (; ptId < endPtId; ++ptId)
    {
      if (isFirst)
      {
        this->Filter->CheckAbort();
      }
      if (this->Filter->GetAbortOutput())
      {
        break;
      }
      this->Input->GetPoint(ptId, x);
      *n++ = this->Function->FunctionValue(x);
      this->Function->FunctionGradient(x, g);
      *v++ = g[0];
      *v++ = g[1];
      *v++ = g[2];
    }
  }
};

} // anonymous namespace

//------------------------------------------------------------------------------
// Okay define the VTK class proper
vtkSampleImplicitFunctionFilter::vtkSampleImplicitFunctionFilter()
{
  this->ImplicitFunction = nullptr;

  this->ComputeGradients = 1;

  this->ScalarArrayName = nullptr;
  this->SetScalarArrayName("Implicit scalars");

  this->GradientArrayName = nullptr;
  this->SetGradientArrayName("Implicit gradients");
}

//------------------------------------------------------------------------------
vtkSampleImplicitFunctionFilter::~vtkSampleImplicitFunctionFilter()
{
  this->SetImplicitFunction(nullptr);
  this->SetScalarArrayName(nullptr);
  this->SetGradientArrayName(nullptr);
}

//------------------------------------------------------------------------------
// Produce the output data
int vtkSampleImplicitFunctionFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkDebugMacro(<< "Generating implicit data");

  // get the info objects
  vtkInformation* inInfo = inputVector[0]->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()));

  // Check the input
  if (!input || !output)
  {
    return 1;
  }
  vtkIdType numPts = input->GetNumberOfPoints();
  if (numPts < 1)
  {
    return 1;
  }

  // Ensure implicit function is specified
  //
  if (!this->ImplicitFunction)
  {
    vtkErrorMacro(<< "No implicit function specified");
    return 1;
  }

  // The output geometic structure is the same as the input
  output->CopyStructure(input);

  // Pass the output attribute data
  output->GetPointData()->PassData(input->GetPointData());
  output->GetCellData()->PassData(input->GetCellData());

  // Set up for execution
  vtkFloatArray* newScalars = vtkFloatArray::New();
  newScalars->SetNumberOfTuples(numPts);
  float* scalars = newScalars->WritePointer(0, numPts);

  vtkFloatArray* newGradients = nullptr;
  float* gradients = nullptr;
  if (this->ComputeGradients)
  {
    newGradients = vtkFloatArray::New();
    newGradients->SetNumberOfComponents(3);
    newGradients->SetNumberOfTuples(numPts);
    gradients = newGradients->WritePointer(0, numPts);
  }

  // Threaded execute
  if (this->ComputeGradients)
  {
    SampleDataSetWithGradients sample(input, this->ImplicitFunction, scalars, gradients, this);
    vtkSMPTools::For(0, numPts, sample);
  }
  else
  {
    SampleDataSet sample(input, this->ImplicitFunction, scalars, this);
    vtkSMPTools::For(0, numPts, sample);
  }

  // Update self
  newScalars->SetName(this->ScalarArrayName);
  output->GetPointData()->AddArray(newScalars);
  output->GetPointData()->SetActiveScalars(this->ScalarArrayName);
  newScalars->Delete();

  if (this->ComputeGradients)
  {
    newGradients->SetName(this->GradientArrayName);
    output->GetPointData()->AddArray(newGradients);
    output->GetPointData()->SetActiveVectors(this->GradientArrayName);
    newGradients->Delete();
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkSampleImplicitFunctionFilter::FillInputPortInformation(int, vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
  return 1;
}

//------------------------------------------------------------------------------
vtkMTimeType vtkSampleImplicitFunctionFilter::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType impFuncMTime;

  if (this->ImplicitFunction != nullptr)
  {
    impFuncMTime = this->ImplicitFunction->GetMTime();
    mTime = (impFuncMTime > mTime ? impFuncMTime : mTime);
  }

  return mTime;
}

//------------------------------------------------------------------------------
void vtkSampleImplicitFunctionFilter::ReportReferences(vtkGarbageCollector* collector)
{
  this->Superclass::ReportReferences(collector);
  vtkGarbageCollectorReport(collector, this->ImplicitFunction, "ImplicitFunction");
}

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

  if (this->ImplicitFunction)
  {
    os << indent << "Implicit Function: " << this->ImplicitFunction << "\n";
  }
  else
  {
    os << indent << "No Implicit function defined\n";
  }

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

  os << indent << "Scalar Array Name: ";
  if (this->ScalarArrayName != nullptr)
  {
    os << this->ScalarArrayName << endl;
  }
  else
  {
    os << "(none)" << endl;
  }

  os << indent << "Gradient Array Name: ";
  if (this->GradientArrayName != nullptr)
  {
    os << this->GradientArrayName << endl;
  }
  else
  {
    os << "(none)" << endl;
  }
}
VTK_ABI_NAMESPACE_END
