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

#include "vtkAppendPolyData.h"
#include "vtkCellData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMultiProcessController.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSocketController.h"
#include "vtkStreamingDemandDrivenPipeline.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkDuplicatePolyData);

vtkCxxSetObjectMacro(vtkDuplicatePolyData, Controller, vtkMultiProcessController);
vtkCxxSetObjectMacro(vtkDuplicatePolyData, SocketController, vtkSocketController);

//------------------------------------------------------------------------------
vtkDuplicatePolyData::vtkDuplicatePolyData()
{
  // Controller keeps a reference to this object as well.
  this->Controller = nullptr;
  this->SetController(vtkMultiProcessController::GetGlobalController());
  this->Synchronous = 1;

  this->Schedule = nullptr;
  this->ScheduleLength = 0;
  this->NumberOfProcesses = 0;

  this->SocketController = nullptr;
  this->ClientFlag = 0;
  this->MemorySize = 0;
}

//------------------------------------------------------------------------------
vtkDuplicatePolyData::~vtkDuplicatePolyData()
{
  this->SetController(nullptr);
  // Free the schedule memory.
  this->InitializeSchedule(0);
}

#define vtkDPDPow2(j) (1 << (j))
static inline int vtkDPDLog2(int j, int& exact)
{
  int counter = 0;
  exact = 1;
  while (j)
  {
    if ((j & 1) && (j >> 1))
    {
      exact = 0;
    }
    j = j >> 1;
    counter++;
  }
  return counter - 1;
}

//------------------------------------------------------------------------------
void vtkDuplicatePolyData::InitializeSchedule(int numProcs)
{
  int i, j, k, exact;
  int* procFlags = nullptr;

  if (this->NumberOfProcesses == numProcs)
  {
    return;
  }

  // Free old schedule.
  for (i = 0; i < this->NumberOfProcesses; ++i)
  {
    delete[] this->Schedule[i];
    this->Schedule[i] = nullptr;
  }
  delete[] this->Schedule;
  this->Schedule = nullptr;

  this->NumberOfProcesses = numProcs;
  if (numProcs == 0)
  {
    return;
  }

  i = vtkDPDLog2(numProcs, exact);
  if (!exact)
  {
    ++i;
  }
  this->ScheduleLength = vtkDPDPow2(i) - 1;
  this->Schedule = new int*[numProcs];
  for (i = 0; i < numProcs; ++i)
  {
    this->Schedule[i] = new int[this->ScheduleLength];
    for (j = 0; j < this->ScheduleLength; ++j)
    {
      this->Schedule[i][j] = -1;
    }
  }

  // Temporary array to record which processes have been used.
  procFlags = new int[numProcs];

  for (j = 0; j < this->ScheduleLength; ++j)
  {
    for (i = 0; i < numProcs; ++i)
    {
      if (this->Schedule[i][j] == -1)
      {
        // Try to find a available process that we have not paired with yet.
        for (k = 0; k < numProcs; ++k)
        {
          procFlags[k] = 0;
        }
        // Eliminate this process as a candidate.
        procFlags[i] = 1;
        // Eliminate procs already communicating during this cycle.
        for (k = 0; k < numProcs; ++k)
        {
          if (this->Schedule[k][j] != -1)
          {
            procFlags[this->Schedule[k][j]] = 1;
          }
        }
        // Eliminate process we have already paired with.
        for (k = 0; k < j; ++k)
        {
          if (this->Schedule[i][k] != -1)
          {
            procFlags[this->Schedule[i][k]] = 1;
          }
        }
        // Look for the first appropriate process.
        for (k = 0; k < numProcs; ++k)
        {
          if (procFlags[k] == 0)
          {
            // Set the pair in the schedule for communication.
            this->Schedule[i][j] = k;
            this->Schedule[k][j] = i;
            // Break the loop.
            break;
          }
        }
      }
    }
  }

  delete[] procFlags;
  procFlags = nullptr;
}

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

  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(),
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()));
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(),
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()));
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(),
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS()));

  return 1;
}

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

  int myId, partner;
  int idx;

  if (this->SocketController && this->ClientFlag)
  {
    this->ClientExecute(output);
    return 1;
  }

  if (this->Controller == nullptr)
  {
    output->CopyStructure(input);
    output->GetPointData()->PassData(input->GetPointData());
    output->GetCellData()->PassData(input->GetCellData());
    if (this->SocketController && !this->ClientFlag)
    {
      this->SocketController->Send(output, 1, 18732);
    }
    return 1;
  }

  myId = this->Controller->GetLocalProcessId();

  // Collect.

  vtkAppendPolyData* append = vtkAppendPolyData::New();
  // First append the input from this process.
  vtkPolyData* pd = vtkPolyData::New();
  pd->CopyStructure(input);
  pd->GetPointData()->PassData(input->GetPointData());
  pd->GetCellData()->PassData(input->GetCellData());
  append->AddInputData(pd);
  pd->Delete();

  for (idx = 0; idx < this->ScheduleLength; ++idx)
  {
    partner = this->Schedule[myId][idx];
    if (partner >= 0)
    {
      // Matching the order may not be necessary and may slow things down,
      // but it is a reasonable precaution.
      if (partner > myId || !this->Synchronous)
      {
        this->Controller->Send(input, partner, 131767);

        pd = vtkPolyData::New();
        this->Controller->Receive(pd, partner, 131767);
        append->AddInputData(pd);
        pd->Delete();
        pd = nullptr;
      }
      else
      {
        pd = vtkPolyData::New();
        this->Controller->Receive(pd, partner, 131767);
        append->AddInputData(pd);
        pd->Delete();
        pd = nullptr;

        this->Controller->Send(input, partner, 131767);
      }
    }
  }
  append->Update();
  input = append->GetOutput();

  // Copy to output.
  output->CopyStructure(input);
  output->GetPointData()->PassData(input->GetPointData());
  output->GetCellData()->PassData(input->GetCellData());
  append->Delete();
  append = nullptr;

  if (this->SocketController && !this->ClientFlag)
  {
    this->SocketController->Send(output, 1, 18732);
  }

  this->MemorySize = output->GetActualMemorySize();

  return 1;
}

//------------------------------------------------------------------------------
void vtkDuplicatePolyData::ClientExecute(vtkPolyData* output)
{
  vtkPolyData* tmp = vtkPolyData::New();

  // No data is on the client, so we just have to get the data
  // from node 0 of the server.
  this->SocketController->Receive(tmp, 1, 18732);
  output->CopyStructure(tmp);
  output->GetPointData()->PassData(tmp->GetPointData());
  output->GetCellData()->PassData(tmp->GetCellData());
}

//------------------------------------------------------------------------------
void vtkDuplicatePolyData::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  int i, j;

  os << indent << "Controller: (" << this->Controller << ")\n";
  if (this->SocketController)
  {
    os << indent << "SocketController: (" << this->SocketController << ")\n";
    os << indent << "ClientFlag: " << this->ClientFlag << endl;
  }
  os << indent << "Synchronous: " << this->Synchronous << endl;

  os << indent << "Schedule:\n";
  indent = indent.GetNextIndent();
  for (i = 0; i < this->NumberOfProcesses; ++i)
  {
    os << indent << i << ": ";
    if (this->Schedule[i][0] >= 0)
    {
      os << this->Schedule[i][0];
    }
    else
    {
      os << "X";
    }
    for (j = 1; j < this->ScheduleLength; ++j)
    {
      os << ", ";
      if (this->Schedule[i][j] >= 0)
      {
        os << this->Schedule[i][j];
      }
      else
      {
        os << "X";
      }
    }
    os << endl;
  }

  os << indent << "MemorySize: " << this->MemorySize << endl;
}
VTK_ABI_NAMESPACE_END
