// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkSamplePlaneSource.h"

#include "vtkCommunicator.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkDummyController.h"
#include "vtkInformation.h"
#include "vtkObjectFactory.h"
#include "vtkPlaneSource.h"
#include "vtkPolyData.h"

#include "vtkSmartPointer.h"
#define VTK_CREATE(type, name) vtkSmartPointer<type> name = vtkSmartPointer<type>::New()

#include <algorithm>
#include <cmath>

//=============================================================================
vtkStandardNewMacro(vtkSamplePlaneSource);

vtkCxxSetObjectMacro(vtkSamplePlaneSource, Controller, vtkMultiProcessController);

//-----------------------------------------------------------------------------
vtkSamplePlaneSource::vtkSamplePlaneSource()
{
  this->Center[0] = this->Center[1] = this->Center[2] = 0.0;
  this->Normal[0] = 0.0;
  this->Normal[1] = 0.0;
  this->Normal[2] = 1.0;
  this->Resolution = 100;

  this->Controller = nullptr;
  this->SetController(vtkMultiProcessController::GetGlobalController());
  if (!this->Controller)
  {
    VTK_CREATE(vtkDummyController, dummyController);
    this->SetController(dummyController);
  }
}

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

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

  os << indent << "Center: (" << this->Center[0] << ", " << this->Center[1] << ", "
     << this->Center[2] << ")" << endl;
  os << indent << "Normal: (" << this->Normal[0] << ", " << this->Normal[1] << ", "
     << this->Normal[2] << ")" << endl;
  ;
  os << indent << "Resolution: " << this->Resolution << endl;
  os << indent << "Controller: " << this->Controller << endl;
}

//-----------------------------------------------------------------------------
int vtkSamplePlaneSource::FillInputPortInformation(int port, vtkInformation* info)
{
  if (port == 0)
  {
    info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE());
    info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
    info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkCompositeDataSet");
    return 1;
  }
  return 0;
}

//-----------------------------------------------------------------------------
int vtkSamplePlaneSource::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkDataObject* input = vtkDataObject::GetData(inputVector[0]);
  vtkPolyData* output = vtkPolyData::GetData(outputVector);

  double bounds[6];
  this->ComputeLocalBounds(input, bounds);
  this->ResolveParallelBounds(bounds);
  this->CreatePlane(bounds, output);

  return 1;
}

//-----------------------------------------------------------------------------
void vtkSamplePlaneSource::ComputeLocalBounds(vtkDataObject* input, double bounds[6])
{
  bounds[0] = bounds[2] = bounds[4] = VTK_DOUBLE_MAX;
  bounds[1] = bounds[3] = bounds[5] = -VTK_DOUBLE_MAX;

  vtkDataSet* dsInput = vtkDataSet::SafeDownCast(input);
  if (dsInput)
  {
    dsInput->GetBounds(bounds);
    return;
  }

  vtkCompositeDataSet* cInput = vtkCompositeDataSet::SafeDownCast(input);
  if (cInput)
  {
    vtkSmartPointer<vtkCompositeDataIterator> iter;
    iter.TakeReference(cInput->NewIterator());
    for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
    {
      double subbounds[6];
      this->ComputeLocalBounds(iter->GetCurrentDataObject(), subbounds);
      bounds[0] = std::min(bounds[0], subbounds[0]);
      bounds[1] = std::max(bounds[1], subbounds[1]);
      bounds[2] = std::min(bounds[2], subbounds[2]);
      bounds[3] = std::max(bounds[3], subbounds[3]);
      bounds[4] = std::min(bounds[4], subbounds[4]);
      bounds[5] = std::max(bounds[5], subbounds[5]);
    }
    return;
  }

  vtkWarningMacro(<< "Unknown data type: " << input->GetClassName());
}

//-----------------------------------------------------------------------------
void vtkSamplePlaneSource::ResolveParallelBounds(double bounds[6])
{
  this->Controller->AllReduce(&bounds[0], &bounds[0], 1, vtkCommunicator::MIN_OP);
  this->Controller->AllReduce(&bounds[1], &bounds[1], 1, vtkCommunicator::MAX_OP);
  this->Controller->AllReduce(&bounds[2], &bounds[2], 1, vtkCommunicator::MIN_OP);
  this->Controller->AllReduce(&bounds[3], &bounds[3], 1, vtkCommunicator::MAX_OP);
  this->Controller->AllReduce(&bounds[4], &bounds[4], 1, vtkCommunicator::MIN_OP);
  this->Controller->AllReduce(&bounds[5], &bounds[5], 1, vtkCommunicator::MAX_OP);
}

//-----------------------------------------------------------------------------
void vtkSamplePlaneSource::CreatePlane(const double bounds[6], vtkPolyData* output)
{
  double dims[3];
  for (int i = 0; i < 3; i++)
  {
    dims[i] = bounds[2 * i + 1] - bounds[2 * i];
    dims[i] = std::max<double>(dims[i], 0);
  }

  double diagonal = sqrt(dims[0] * dims[0] + dims[1] * dims[1] + dims[2] * dims[2]);
  if (diagonal <= 0)
  {
    // No space.  Just do nothing.
    return;
  }

  // Use the plane source to create the geometry.
  VTK_CREATE(vtkPlaneSource, planeSource);
  planeSource->SetXResolution(2 * this->Resolution);
  planeSource->SetYResolution(2 * this->Resolution);

  planeSource->SetOrigin(0.0, 0.0, 0.0);
  planeSource->SetPoint1(2 * diagonal, 0.0, 0.0);
  planeSource->SetPoint2(0.0, 2 * diagonal, 0.0);

  planeSource->SetCenter(this->Center);
  planeSource->SetNormal(this->Normal);

  planeSource->Update();
  output->ShallowCopy(planeSource->GetOutput());
}
