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

// NOTE TO DEVELOPERS: ========================================================
//
// This is a parallel version of the LSDynaReader.
// Its primary tasks are to determine which parts should be read on each process
// and to send the relevant information from the master node to all slave nodes

#include "vtkPLSDynaReader.h"
#include "LSDynaFamily.h"
#include "LSDynaMetaData.h"
#include "vtkLSDynaPartCollection.h"

#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkMultiProcessController.h"
#include "vtkObjectFactory.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkUnstructuredGrid.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPLSDynaReader);

struct vtkPLSDynaReader::vtkPLSDynaReaderInternal
{
  unsigned int MinDataset;
  unsigned int MaxDataset;
  unsigned int UpdatePiece;
  unsigned int UpdateNumPieces;

  vtkIdType NumProcesses;
  vtkIdType ProcessRank;

  vtkPLSDynaReaderInternal()
    : MinDataset(0)
    , MaxDataset(0)
    , UpdatePiece(0)
    , UpdateNumPieces(0)
  {
  }
};

//------------------------------------------------------------------------------
vtkPLSDynaReader::vtkPLSDynaReader()
{
  this->Controller = nullptr;

  // need to construct the internal datastructure before call SetController
  this->Internal = new vtkPLSDynaReader::vtkPLSDynaReaderInternal();
  this->SetController(vtkMultiProcessController::GetGlobalController());
}

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

  delete this->Internal;
}

void vtkPLSDynaReader::PrintSelf(ostream& os, vtkIndent indent)
{
  os << indent << "Controller: " << this->Controller << endl;
  this->Superclass::PrintSelf(os, indent);
}

//------------------------------------------------------------------------------
void vtkPLSDynaReader::SetController(vtkMultiProcessController* c)
{
  if ((c == nullptr) || (c->GetNumberOfProcesses() == 0))
  {
    this->Internal->NumProcesses = 1;
    this->Internal->ProcessRank = 0;
  }

  vtkSetObjectBodyMacro(Controller, vtkMultiProcessController, c);

  if (c)
  {
    this->Internal->NumProcesses = c->GetNumberOfProcesses();
    this->Internal->ProcessRank = c->GetLocalProcessId();
  }
}

//------------------------------------------------------------------------------
int vtkPLSDynaReader::CanReadFile(const char* fname)
{
  return this->Superclass::CanReadFile(fname);
}

//------------------------------------------------------------------------------
int vtkPLSDynaReader::RequestInformation(
  vtkInformation* request, vtkInformationVector** iinfo, vtkInformationVector* outputVector)
{
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // call the parents request information on all the nodes.
  // This is not optimal, but sooo much information is stored in the
  // metadata that is read during request information that sending it over the wire
  // might not be faster than each node contending for the info. Plus it would
  // be a massive chunk of code
  this->Superclass::RequestInformation(request, iinfo, outputVector);

  outInfo->Set(CAN_HANDLE_PIECE_REQUEST(), 1);
  return 1;
}

//------------------------------------------------------------------------------
int vtkPLSDynaReader::RequestData(
  vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the information needed to determine which subsection of the full
  // data set we need to load
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  this->Internal->UpdatePiece =
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER());
  this->Internal->UpdateNumPieces =
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES());

  return this->Superclass::RequestData(request, inputVector, outputVector);
}

//------------------------------------------------------------------------------
int vtkPLSDynaReader::ReadTopology()
{
  bool readTopology = false;
  if (!this->Parts)
  {
    readTopology = true;
    this->Parts = vtkLSDynaPartCollection::New();
    vtkIdType* minCellIds = new vtkIdType[LSDynaMetaData::NUM_CELL_TYPES];
    vtkIdType* maxCellIds = new vtkIdType[LSDynaMetaData::NUM_CELL_TYPES];
    this->GetPartRanges(minCellIds, maxCellIds);

    this->Parts->InitCollection(this->P, minCellIds, maxCellIds);
    delete[] minCellIds;
    delete[] maxCellIds;
  }
  if (!readTopology)
  {
    return 0;
  }

  if (this->ReadPartSizes())
  {
    vtkErrorMacro("Could not read cell sizes.");
    return 1;
  }

  if (this->ReadConnectivityAndMaterial())
  {
    vtkErrorMacro("Could not read connectivity.");
    return 1;
  }

  // finalize the topology on each process, each process will remove
  // any part that it doesn't have a cell for.
  this->Parts->FinalizeTopology();

  if (this->ReadNodes())
  {
    vtkErrorMacro("Could not read static node values.");
    return 1;
  }

  // we need to read the user ids after we have read the topology
  // so we know how many cells are in each part
  if (this->ReadUserIds())
  {
    vtkErrorMacro("Could not read user node/element IDs.");
    return 1;
  }

  return 0;
}

//------------------------------------------------------------------------------
// determine which parts will be read by this processor
void vtkPLSDynaReader::GetPartRanges(vtkIdType* mins, vtkIdType* maxs)
{
  // 1 == load the whole data
  // determine which domains in this mesh this processor is responsible for
  if (this->Internal->UpdateNumPieces > 1)
  {
    double numCells;
    for (int i = 0; i < LSDynaMetaData::NUM_CELL_TYPES; ++i)
    {
      numCells = static_cast<double>(this->P->NumberOfCells[i]);
      if (numCells > 1000)
      {
        double percent = (1.0 / this->Internal->UpdateNumPieces) * numCells;
        mins[i] = static_cast<vtkIdType>(percent * this->Internal->UpdatePiece);
        maxs[i] = static_cast<vtkIdType>(percent * (this->Internal->UpdatePiece + 1));
      }
      else
      {
        // else not enough cells to worth dividing the reading
        mins[i] = 0;
        maxs[i] = static_cast<vtkIdType>((this->Internal->ProcessRank == 0) ? numCells : 0);
      }
    }
  }
  else
  {
    for (int i = 0; i < LSDynaMetaData::NUM_CELL_TYPES; ++i)
    {
      mins[i] = 0;
      maxs[i] = this->P->NumberOfCells[i];
    }
  }
}
VTK_ABI_NAMESPACE_END
