/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkPResampleFilter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPResampleFilter.h"

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

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 unitialized 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;
      }
    }
  }

  cout << "Bounds: "
    << localBounds[0] << " "
    << localBounds[1] << " "
    << localBounds[2] << " "
    << localBounds[3] << " "
    << localBounds[4] << " "
    << localBounds[5] << " "
    << 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 << endl;
  os << indent << "UseInputBounds " << this->UseInputBounds << 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] << "]"
       << endl;
  }
  os << indent << "SamplingDimension "
     << this->SamplingDimension[0] << " x "
     << this->SamplingDimension[1] << " x "
     << this->SamplingDimension[2] << endl;
}
