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

#include "vtkAbstractArray.h"
#include "vtkDataSet.h"
#include "vtkExtentSplitter.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLStructuredDataReader.h"

#include <sstream>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
vtkXMLPStructuredDataReader::vtkXMLPStructuredDataReader()
{
  this->ExtentSplitter = vtkExtentSplitter::New();
  this->PieceExtents = nullptr;
  memset(this->UpdateExtent, 0, sizeof(this->UpdateExtent));
  memset(this->PointDimensions, 0, sizeof(this->PointDimensions));
  memset(this->PointIncrements, 0, sizeof(this->PointIncrements));
  memset(this->CellDimensions, 0, sizeof(this->CellDimensions));
  memset(this->CellIncrements, 0, sizeof(this->CellIncrements));
  memset(this->SubExtent, 0, sizeof(this->SubExtent));
  memset(this->SubPointDimensions, 0, sizeof(this->SubPointDimensions));
  memset(this->SubCellDimensions, 0, sizeof(this->SubCellDimensions));
  memset(this->SubPieceExtent, 0, sizeof(this->SubPieceExtent));
  memset(this->SubPiecePointDimensions, 0, sizeof(this->SubPiecePointDimensions));
  memset(this->SubPiecePointIncrements, 0, sizeof(this->SubPiecePointIncrements));
  memset(this->SubPieceCellDimensions, 0, sizeof(this->SubPieceCellDimensions));
  memset(this->SubPieceCellIncrements, 0, sizeof(this->SubPieceCellIncrements));
}

//------------------------------------------------------------------------------
vtkXMLPStructuredDataReader::~vtkXMLPStructuredDataReader()
{
  if (this->NumberOfPieces)
  {
    this->DestroyPieces();
  }
  this->ExtentSplitter->Delete();
}

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

//------------------------------------------------------------------------------
vtkIdType vtkXMLPStructuredDataReader::GetNumberOfPoints()
{
  return (static_cast<vtkIdType>(this->PointDimensions[0]) *
    static_cast<vtkIdType>(this->PointDimensions[1]) *
    static_cast<vtkIdType>(this->PointDimensions[2]));
}

//------------------------------------------------------------------------------
vtkIdType vtkXMLPStructuredDataReader::GetNumberOfCells()
{
  return (static_cast<vtkIdType>(this->CellDimensions[0]) *
    static_cast<vtkIdType>(this->CellDimensions[1]) *
    static_cast<vtkIdType>(this->CellDimensions[2]));
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::ReadXMLData()
{
  // Get the requested Update Extent.
  vtkInformation* outInfo = this->GetCurrentOutputInformation();
  outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), this->UpdateExtent);

  vtkDebugMacro("Updating extent " << this->UpdateExtent[0] << " " << this->UpdateExtent[1] << " "
                                   << this->UpdateExtent[2] << " " << this->UpdateExtent[3] << " "
                                   << this->UpdateExtent[4] << " " << this->UpdateExtent[5]);

  // Prepare increments for the update extent.
  this->ComputePointDimensions(this->UpdateExtent, this->PointDimensions);
  this->ComputePointIncrements(this->UpdateExtent, this->PointIncrements);
  this->ComputeCellDimensions(this->UpdateExtent, this->CellDimensions);
  this->ComputeCellIncrements(this->UpdateExtent, this->CellIncrements);

  // Let superclasses read data.  This also allocates output data.
  this->Superclass::ReadXMLData();

  // Use the ExtentSplitter to split the update extent into
  // sub-extents read by each piece.
  if (!this->ComputePieceSubExtents())
  {
    // Not all needed data are available.
    this->DataError = 1;
    return;
  }

  // Split current progress range based on fraction contributed by
  // each sub-extent.
  float progressRange[2] = { 0, 0 };
  this->GetProgressRange(progressRange);

  // Calculate the cumulative fraction of data contributed by each
  // sub-extent (for progress).
  int n = this->ExtentSplitter->GetNumberOfSubExtents();
  float* fractions = new float[n + 1];
  int i;
  fractions[0] = 0;
  for (i = 0; i < n; ++i)
  {
    // Get this sub-extent.
    this->ExtentSplitter->GetSubExtent(i, this->SubExtent);

    // Add this sub-extent's volume to the cumulative volume.
    int pieceDims[3] = { 0, 0, 0 };
    this->ComputePointDimensions(this->SubExtent, pieceDims);
    fractions[i + 1] = fractions[i] + pieceDims[0] * pieceDims[1] * pieceDims[2];
  }
  if (fractions[n] == 0)
  {
    fractions[n] = 1;
  }
  for (i = 1; i <= n; ++i)
  {
    fractions[i] = fractions[i] / fractions[n];
  }

  // Read the data needed from each sub-extent.
  for (i = 0; (i < n && !this->AbortExecute && !this->DataError); ++i)
  {
    // Set the range of progress for this sub-extent.
    this->SetProgressRange(progressRange, i, fractions);

    // Get this sub-extent and the piece from which to read it.
    int piece = this->ExtentSplitter->GetSubExtentSource(i);
    this->ExtentSplitter->GetSubExtent(i, this->SubExtent);

    vtkDebugMacro("Reading extent " << this->SubExtent[0] << " " << this->SubExtent[1] << " "
                                    << this->SubExtent[2] << " " << this->SubExtent[3] << " "
                                    << this->SubExtent[4] << " " << this->SubExtent[5]
                                    << " from piece " << piece);

    this->ComputePointDimensions(this->SubExtent, this->SubPointDimensions);
    this->ComputeCellDimensions(this->SubExtent, this->SubCellDimensions);

    // Read the data from this piece.
    if (!this->Superclass::ReadPieceData(piece))
    {
      // An error occurred while reading the piece.
      this->DataError = 1;
    }
  }

  delete[] fractions;

  // We filled the exact update extent in the output.
  this->SetOutputExtent(this->UpdateExtent);
}

//------------------------------------------------------------------------------
int vtkXMLPStructuredDataReader::RequestInformation(
  vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  outputVector->GetInformationObject(0)->Set(CAN_PRODUCE_SUB_EXTENT(), 1);

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

//------------------------------------------------------------------------------
int vtkXMLPStructuredDataReader::ReadPrimaryElement(vtkXMLDataElement* ePrimary)
{
  if (!this->Superclass::ReadPrimaryElement(ePrimary))
  {
    return 0;
  }

  // Get the whole extent attribute.
  int extent[6];
  if (ePrimary->GetVectorAttribute("WholeExtent", 6, extent) == 6)
  {
    // Set the output's whole extent.
    vtkInformation* outInfo = this->GetCurrentOutputInformation();
    outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), extent, 6);

    // Check each axis to see if it has cells.
    for (int a = 0; a < 3; ++a)
    {
      this->AxesEmpty[a] = (extent[2 * a + 1] > extent[2 * a]) ? 0 : 1;
    }
  }
  else
  {
    vtkErrorMacro(<< this->GetDataSetName() << " element has no WholeExtent.");
    return 0;
  }

  return 1;
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::CopyOutputInformation(vtkInformation* outInfo, int port)
{
  // Let the superclass copy information first.
  this->Superclass::CopyOutputInformation(outInfo, port);

  // All structured data has a whole extent.
  vtkInformation* localInfo = this->GetExecutive()->GetOutputInformation(port);
  if (localInfo->Has(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()))
  {
    outInfo->CopyEntry(localInfo, vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
  }
}

void vtkXMLPStructuredDataReader::SetupOutputData()
{
  this->Superclass::SetupOutputData();
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::SetupPieces(int numPieces)
{
  this->Superclass::SetupPieces(numPieces);
  if (!numPieces)
  {
    return;
  }

  this->PieceExtents = new int[6 * this->NumberOfPieces];
  int i;
  for (i = 0; i < this->NumberOfPieces; ++i)
  {
    int* extent = this->PieceExtents + i * 6;
    extent[0] = 0;
    extent[1] = -1;
    extent[2] = 0;
    extent[3] = -1;
    extent[4] = 0;
    extent[5] = -1;
  }
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::DestroyPieces()
{
  delete[] this->PieceExtents;
  this->PieceExtents = nullptr;
  this->Superclass::DestroyPieces();
}

//------------------------------------------------------------------------------
int vtkXMLPStructuredDataReader::ReadPiece(vtkXMLDataElement* ePiece)
{
  // Superclass will create a reader for the piece's file.
  if (!this->Superclass::ReadPiece(ePiece))
  {
    return 0;
  }

  // Get the extent of the piece.
  int* pieceExtent = this->PieceExtents + this->Piece * 6;
  if (ePiece->GetVectorAttribute("Extent", 6, pieceExtent) < 6)
  {
    vtkErrorMacro("Piece " << this->Piece << " has invalid Extent.");
    return 0;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLPStructuredDataReader::ReadPieceData()
{
  // Use the internal reader to read the piece.
  this->PieceReaders[this->Piece]->UpdateExtent(this->SubExtent);

  // Skip rest of read if aborting.
  if (this->AbortExecute)
  {
    return 0;
  }

  // Get the actual portion of the piece that was read.
  this->GetPieceInputExtent(this->Piece, this->SubPieceExtent);
  this->ComputePointDimensions(this->SubPieceExtent, this->SubPiecePointDimensions);
  this->ComputePointIncrements(this->SubPieceExtent, this->SubPiecePointIncrements);
  this->ComputeCellDimensions(this->SubPieceExtent, this->SubPieceCellDimensions);
  this->ComputeCellIncrements(this->SubPieceExtent, this->SubPieceCellIncrements);

  // Let the superclass read the data it wants.
  return this->Superclass::ReadPieceData();
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::CopyArrayForPoints(
  vtkAbstractArray* inArray, vtkAbstractArray* outArray)
{
  if (!inArray || !outArray)
  {
    return;
  }
  this->CopySubExtent(this->SubPieceExtent, this->SubPiecePointDimensions,
    this->SubPiecePointIncrements, this->UpdateExtent, this->PointDimensions, this->PointIncrements,
    this->SubExtent, this->SubPointDimensions, inArray, outArray);
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader::CopyArrayForCells(
  vtkAbstractArray* inArray, vtkAbstractArray* outArray)
{
  if (!inArray || !outArray)
  {
    return;
  }
  this->CopySubExtent(this->SubPieceExtent, this->SubPieceCellDimensions,
    this->SubPieceCellIncrements, this->UpdateExtent, this->CellDimensions, this->CellIncrements,
    this->SubExtent, this->SubCellDimensions, inArray, outArray);
}

//------------------------------------------------------------------------------
void vtkXMLPStructuredDataReader ::CopySubExtent(int* inExtent, int* inDimensions,
  vtkIdType* inIncrements, int* outExtent, int* outDimensions, vtkIdType* outIncrements,
  int* subExtent, int* subDimensions, vtkAbstractArray* inArray, vtkAbstractArray* outArray)
{
  unsigned int components = inArray->GetNumberOfComponents();
  unsigned int tupleSize = inArray->GetDataTypeSize() * components;

  if ((inDimensions[0] == outDimensions[0]) && (inDimensions[1] == outDimensions[1]))
  {
    if (inDimensions[2] == outDimensions[2])
    {
      // Copy the whole volume at once.
      vtkIdType volumeTuples = (static_cast<vtkIdType>(inDimensions[0]) *
        static_cast<vtkIdType>(inDimensions[1]) * static_cast<vtkIdType>(inDimensions[2]));
      memcpy(outArray->GetVoidPointer(0), inArray->GetVoidPointer(0), volumeTuples * tupleSize);
    }
    else
    {
      // Copy an entire slice at a time.
      vtkIdType sliceTuples =
        static_cast<vtkIdType>(inDimensions[0]) * static_cast<vtkIdType>(inDimensions[1]);
      int k;
      for (k = 0; k < subDimensions[2]; ++k)
      {
        vtkIdType sourceTuple =
          this->GetStartTuple(inExtent, inIncrements, subExtent[0], subExtent[2], subExtent[4] + k);
        vtkIdType destTuple = this->GetStartTuple(
          outExtent, outIncrements, subExtent[0], subExtent[2], subExtent[4] + k);
        memcpy(outArray->GetVoidPointer(destTuple * components),
          inArray->GetVoidPointer(sourceTuple * components), sliceTuples * tupleSize);
      }
    }
  }
  else
  {
    // Copy a row at a time.
    vtkIdType rowTuples = subDimensions[0];
    int j, k;
    for (k = 0; k < subDimensions[2]; ++k)
    {
      for (j = 0; j < subDimensions[1]; ++j)
      {
        vtkIdType sourceTuple = this->GetStartTuple(
          inExtent, inIncrements, subExtent[0], subExtent[2] + j, subExtent[4] + k);
        vtkIdType destTuple = this->GetStartTuple(
          outExtent, outIncrements, subExtent[0], subExtent[2] + j, subExtent[4] + k);
        memcpy(outArray->GetVoidPointer(destTuple * components),
          inArray->GetVoidPointer(sourceTuple * components), rowTuples * tupleSize);
      }
    }
  }
}

//------------------------------------------------------------------------------
int vtkXMLPStructuredDataReader::ComputePieceSubExtents()
{
  // Reset the extent splitter.
  this->ExtentSplitter->RemoveAllExtentSources();

  // Add each readable piece as an extent source.
  int i;
  for (i = 0; i < this->NumberOfPieces; ++i)
  {
    // if(this->CanReadPiece(i))
    {
      // Add the exact extent provided by the piece to the splitter.
      // BUG: This was causing all processes to read the meta-data for all
      // pieces which is a nightmare!!! Using the this->PieceExtents information
      // which we had already collected while reading the pvti file for
      // determining the piece extents.
      // int extent[6];
      // this->PieceReaders[i]->UpdateInformation();
      // this->PieceReaders[i]->GetOutputAsDataSet()->GetWholeExtent(extent);
      int* extent = this->PieceExtents + 6 * i;
      this->ExtentSplitter->AddExtentSource(i, 0, extent);
    }
  }

  // We want to split the entire update extent across the pieces.
  this->ExtentSplitter->AddExtent(this->UpdateExtent);

  // Compute the sub-extents.
  if (!this->ExtentSplitter->ComputeSubExtents())
  {
    // A portion of the extent is not available.
    std::ostringstream e_with_warning_C4701;
    e_with_warning_C4701 << "No available piece provides data for the following extents:\n";
    for (i = 0; i < this->ExtentSplitter->GetNumberOfSubExtents(); ++i)
    {
      if (this->ExtentSplitter->GetSubExtentSource(i) < 0)
      {
        int extent[6];
        this->ExtentSplitter->GetSubExtent(i, extent);
        e_with_warning_C4701 << "    " << extent[0] << " " << extent[1] << "  " << extent[2] << " "
                             << extent[3] << "  " << extent[4] << " " << extent[5] << "\n";
      }
    }
    e_with_warning_C4701 << "The UpdateExtent cannot be filled.";
    vtkErrorMacro(<< e_with_warning_C4701.str());
    return 0;
  }

  return 1;
}
VTK_ABI_NAMESPACE_END
