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

#include "vtkCellData.h"
#include "vtkExtractStructuredGridHelper.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkRectilinearGrid.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStructuredData.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkExtractRectilinearGrid);

// Construct object to extract all of the input data.
vtkExtractRectilinearGrid::vtkExtractRectilinearGrid()
{
  this->VOI[0] = this->VOI[2] = this->VOI[4] = 0;
  this->VOI[1] = this->VOI[3] = this->VOI[5] = VTK_INT_MAX;

  this->SampleRate[0] = this->SampleRate[1] = this->SampleRate[2] = 1;

  this->IncludeBoundary = 0;
  this->Internal = vtkExtractStructuredGridHelper::New();
}

//------------------------------------------------------------------------------
vtkExtractRectilinearGrid::~vtkExtractRectilinearGrid()
{
  if (this->Internal != nullptr)
  {
    this->Internal->Delete();
  }
}

//------------------------------------------------------------------------------
int vtkExtractRectilinearGrid::RequestUpdateExtent(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  if (!this->Internal->IsValid())
  {
    return 0;
  }

  int i;
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);

  bool emptyExtent = false;
  int uExt[6];
  for (i = 0; i < 3; i++)
  {
    if (this->Internal->GetSize(i) < 1)
    {
      uExt[0] = uExt[2] = uExt[4] = 0;
      uExt[1] = uExt[3] = uExt[5] = -1;
      emptyExtent = true;
      break;
    }
  }

  if (!emptyExtent)
  {
    // Find input update extent based on requested output
    // extent
    int oUExt[6];
    outputVector->GetInformationObject(0)->Get(
      vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), oUExt);
    int oWExt[6]; // For parallel partition this will be different.
    this->Internal->GetOutputWholeExtent(oWExt);
    for (i = 0; i < 3; i++)
    {
      int idx = oUExt[2 * i] - oWExt[2 * i]; // Extent value to index
      if (idx < 0 || idx >= this->Internal->GetSize(i))
      {
        vtkWarningMacro("Requested extent outside whole extent.");
        idx = 0;
      }
      uExt[2 * i] = this->Internal->GetMappedExtentValueFromIndex(i, idx);
      int jdx = oUExt[2 * i + 1] - oWExt[2 * i]; // Extent value to index
      if (jdx < idx || jdx >= this->Internal->GetSize(i))
      {
        vtkWarningMacro("Requested extent outside whole extent.");
        jdx = 0;
      }
      uExt[2 * i + 1] = this->Internal->GetMappedExtentValueFromIndex(i, jdx);
    }
  }
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), uExt, 6);
  // We can handle anything.
  inInfo->Set(vtkStreamingDemandDrivenPipeline::EXACT_EXTENT(), 0);

  return 1;
}

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

  int wholeExtent[6], outWholeExt[6];

  inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent);

  this->Internal->Initialize(
    this->VOI, wholeExtent, this->SampleRate, (this->IncludeBoundary == 1));
  this->Internal->GetOutputWholeExtent(outWholeExt);

  if (!this->Internal->IsValid())
  {
    vtkDebugMacro("Error while initializing filter.");
    return 0;
  }

  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), outWholeExt, 6);
  return 1;
}

//------------------------------------------------------------------------------
int vtkExtractRectilinearGrid::RequestData(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // Reset internal helper to the actual extents of the piece we're working on:
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkRectilinearGrid* inGrid = vtkRectilinearGrid::GetData(inInfo);
  this->Internal->Initialize(
    this->VOI, inGrid->GetExtent(), this->SampleRate, (this->IncludeBoundary != 0));

  if (!this->Internal->IsValid())
  {
    return 0;
  }

  // Set the output extent -- this is how RequestDataImpl knows what to copy.
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkRectilinearGrid* output =
    vtkRectilinearGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
  output->SetExtent(this->Internal->GetOutputWholeExtent());

  return this->RequestDataImpl(inputVector, outputVector) ? 1 : 0;
}

//------------------------------------------------------------------------------
bool vtkExtractRectilinearGrid::RequestDataImpl(
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  if ((this->SampleRate[0] < 1) || (this->SampleRate[1] < 1) || (this->SampleRate[2] < 1))
  {
    vtkErrorMacro("SampleRate must be >= 1 in all 3 dimensions!");
    return false;
  }

  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

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

  if (input->GetNumberOfPoints() == 0)
  {
    return true;
  }

  vtkPointData* pd = input->GetPointData();
  vtkCellData* cd = input->GetCellData();
  vtkPointData* outPD = output->GetPointData();
  vtkCellData* outCD = output->GetCellData();

  int* inExt = input->GetExtent();
  int* outExt = output->GetExtent();

  int outDims[3];
  vtkStructuredData::GetDimensionsFromExtent(outExt, outDims);

  vtkDebugMacro(<< "Extracting Grid");
  this->Internal->CopyPointsAndPointData(inExt, outExt, pd, nullptr, outPD, nullptr);
  this->Internal->CopyCellData(inExt, outExt, cd, outCD);

  // copy coordinates
  vtkDataArray* in_coords[3];
  in_coords[0] = input->GetXCoordinates();
  in_coords[1] = input->GetYCoordinates();
  in_coords[2] = input->GetZCoordinates();

  vtkDataArray* out_coords[3];

  bool abort = false;
  int checkAbortInterval = 0;

  for (int dim = 0; dim < 3 && !abort; ++dim)
  {
    // Allocate coordinates array for this dimension
    out_coords[dim] = vtkDataArray::CreateDataArray(in_coords[dim]->GetDataType());
    out_coords[dim]->SetNumberOfTuples(outDims[dim]);

    checkAbortInterval = std::min((outExt[2 * dim + 1] - outExt[2 * dim]) / 10 + 1, 1000);

    for (int oExtVal = outExt[2 * dim]; oExtVal <= outExt[2 * dim + 1]; ++oExtVal)
    {
      if (oExtVal % checkAbortInterval == 0 && this->CheckAbort())
      {
        abort = true;
        break;
      }
      int outExtIdx = oExtVal - outExt[2 * dim];
      int inExtIdx = this->Internal->GetMappedIndex(dim, outExtIdx);
      out_coords[dim]->SetTuple(outExtIdx, inExtIdx, in_coords[dim]);
    } // END for all points along this dimension in the output
  }   // END for all dimensions

  output->SetXCoordinates(out_coords[0]);
  output->SetYCoordinates(out_coords[1]);
  output->SetZCoordinates(out_coords[2]);
  out_coords[0]->Delete();
  out_coords[1]->Delete();
  out_coords[2]->Delete();

  this->CheckAbort();
  return true;
}

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

  os << indent << "VOI: \n";
  os << indent << "  Imin,Imax: (" << this->VOI[0] << ", " << this->VOI[1] << ")\n";
  os << indent << "  Jmin,Jmax: (" << this->VOI[2] << ", " << this->VOI[3] << ")\n";
  os << indent << "  Kmin,Kmax: (" << this->VOI[4] << ", " << this->VOI[5] << ")\n";

  os << indent << "Sample Rate: (" << this->SampleRate[0] << ", " << this->SampleRate[1] << ", "
     << this->SampleRate[2] << ")\n";

  os << indent << "Include Boundary: " << (this->IncludeBoundary ? "On\n" : "Off\n");
}
VTK_ABI_NAMESPACE_END
