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

#include "vtkCellData.h"
#include "vtkCommunicator.h"
#include "vtkCompositeDataPipeline.h"
#include "vtkDataObject.h"
#include "vtkIdTypeArray.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkMultiProcessController.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPProbeFilter.h"
#include "vtkPointData.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include <iostream>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPResampleFilter);

vtkCxxSetObjectMacro(vtkPResampleFilter, Controller, vtkMultiProcessController);

//------------------------------------------------------------------------------
vtkPResampleFilter::vtkPResampleFilter()
  : UseInputBounds(0)
{
  this->Controller = nullptr;
  this->SetController(vtkMultiProcessController::GetGlobalController());
  this->UseInputBoundsOn();
  this->CustomSamplingBounds[0] = this->CustomSamplingBounds[2] = this->CustomSamplingBounds[4] = 0;
  this->CustomSamplingBounds[1] = this->CustomSamplingBounds[3] = this->CustomSamplingBounds[5] = 1;
  this->SamplingDimension[0] = this->SamplingDimension[1] = this->SamplingDimension[2] = 10;

  vtkMath::UninitializeBounds(this->Bounds);
}

//------------------------------------------------------------------------------
vtkPResampleFilter::~vtkPResampleFilter()
{
  this->SetController(nullptr);
}

//------------------------------------------------------------------------------
double* vtkPResampleFilter::CalculateBounds(vtkDataSet* input)
{
  double localBounds[6];
  input->GetBounds(localBounds);

  if (!this->Controller)
  {
    memcpy(this->Bounds, localBounds, 6 * sizeof(double));
  }
  else
  {
    double localBoundsMin[3], globalBoundsMin[3];
    double localBoundsMax[3], globalBoundsMax[3];
    for (int i = 0; i < 3; i++)
    {
      // Change uninitialized bounds to something that will work
      // with collective MPI calls.
      if (localBounds[2 * i] > localBounds[2 * i + 1])
      {
        localBounds[2 * i] = VTK_DOUBLE_MAX;
        localBounds[2 * i + 1] = -VTK_DOUBLE_MAX;
      }
      localBoundsMin[i] = localBounds[2 * i];
      localBoundsMax[i] = localBounds[2 * i + 1];
    }
    this->Controller->AllReduce(localBoundsMin, globalBoundsMin, 3, vtkCommunicator::MIN_OP);
    this->Controller->AllReduce(localBoundsMax, globalBoundsMax, 3, vtkCommunicator::MAX_OP);
    for (int i = 0; i < 3; i++)
    {
      if (globalBoundsMin[i] <= globalBoundsMax[i])
      {
        this->Bounds[2 * i] = globalBoundsMin[i];
        this->Bounds[2 * i + 1] = globalBoundsMax[i];
      }
      else
      {
        this->Bounds[2 * i] = 0;
        this->Bounds[2 * i + 1] = 0;
      }
    }
  }

  std::cout << "Bounds: " << localBounds[0] << " " << localBounds[1] << " " << localBounds[2] << " "
            << localBounds[3] << " " << localBounds[4] << " " << localBounds[5] << " " << std::endl;
  return this->Bounds;
}

//------------------------------------------------------------------------------
int vtkPResampleFilter::RequestInformation(
  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
{
  int wholeExtent[6] = { 0, this->SamplingDimension[0] - 1, 0, this->SamplingDimension[1] - 1, 0,
    this->SamplingDimension[2] - 1 };

  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent, 6);

  return 1;
}

//------------------------------------------------------------------------------
int vtkPResampleFilter::RequestUpdateExtent(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector*)
{
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  // This needs to be here because input and output extents are not
  // necessarily related. The output extent is controlled by the
  // resampled dataset whereas the input extent is controlled by
  // input data.
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(),
    inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()), 6);
  return 1;
}

//------------------------------------------------------------------------------
int vtkPResampleFilter::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
  vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkDataSet* output = vtkDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Create Image Data for resampling
  vtkNew<vtkImageData> source;
  double* boundsToSample =
    (this->UseInputBounds == 1) ? this->CalculateBounds(input) : this->CustomSamplingBounds;
  source->SetOrigin(boundsToSample[0], boundsToSample[2], boundsToSample[4]);
  source->SetDimensions(this->SamplingDimension);
  source->SetSpacing(
    (boundsToSample[1] - boundsToSample[0]) / static_cast<double>(this->SamplingDimension[0] - 1),
    (boundsToSample[3] - boundsToSample[2]) / static_cast<double>(this->SamplingDimension[1] - 1),
    (boundsToSample[5] - boundsToSample[4]) / static_cast<double>(this->SamplingDimension[2] - 1));

  // Probe data
  vtkNew<vtkPProbeFilter> probeFilter;
  probeFilter->SetController(this->Controller);
  probeFilter->SetSourceData(input);
  probeFilter->SetInputData(source);
  probeFilter->Update();
  output->ShallowCopy(probeFilter->GetOutput());

  return 1;
}

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

//------------------------------------------------------------------------------
void vtkPResampleFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Controller " << this->Controller << std::endl;
  os << indent << "UseInputBounds " << this->UseInputBounds << std::endl;
  if (this->UseInputBounds == 0)
  {
    os << indent << "CustomSamplingBounds [" << this->CustomSamplingBounds[0] << ", "
       << this->CustomSamplingBounds[1] << ", " << this->CustomSamplingBounds[2] << ", "
       << this->CustomSamplingBounds[3] << ", " << this->CustomSamplingBounds[4] << ", "
       << this->CustomSamplingBounds[5] << "]" << std::endl;
  }
  os << indent << "SamplingDimension " << this->SamplingDimension[0] << " x "
     << this->SamplingDimension[1] << " x " << this->SamplingDimension[2] << std::endl;
}
VTK_ABI_NAMESPACE_END
