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

#include "vtkAlgorithmOutput.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkObjectFactory.h"
#include "vtkPipelineSize.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringFormatter.h"

#include "vtksys/FStream.hxx"

#define vtkPIWCloseFile                                                                            \
  do                                                                                               \
  {                                                                                                \
    if (file && fileOpenedHere)                                                                    \
    {                                                                                              \
      this->WriteFileTrailer(file, cache);                                                         \
      vtksys::ofstream* ofile = dynamic_cast<vtksys::ofstream*>(file);                             \
      if (ofile)                                                                                   \
      {                                                                                            \
        ofile->close();                                                                            \
      }                                                                                            \
      delete file;                                                                                 \
    }                                                                                              \
  } while (false)

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPImageWriter);

//------------------------------------------------------------------------------
vtkPImageWriter::vtkPImageWriter()
{
  // Set a default memory limit of a gibibyte
  this->MemoryLimit = 1 * 1024 * 1024;

  this->SizeEstimator = vtkPipelineSize::New();
}

//------------------------------------------------------------------------------
vtkPImageWriter::~vtkPImageWriter()
{
  if (this->SizeEstimator)
  {
    this->SizeEstimator->Delete();
  }
}

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

  os << indent << "MemoryLimit (in kibibytes): " << this->MemoryLimit << "\n";
}

//------------------------------------------------------------------------------
// Breaks region into pieces with correct dimensionality.
void vtkPImageWriter::RecursiveWrite(
  int axis, vtkImageData* cache, vtkInformation* inInfo, ostream* file)
{
  int min, max, mid;
  vtkImageData* data;
  int fileOpenedHere = 0;
  unsigned long inputMemorySize;

  // if we need to open another slice, do it
  if (!file && (axis + 1) == this->FileDimensionality)
  {
    // determine the name
    if (this->FileName)
    {
      auto result = vtk::format_to_n(
        this->InternalFileName, this->InternalFileNameSize, "{:s}", this->FileName);
      *result.out = '\0';
    }
    else
    {
      if (this->FilePrefix)
      {
        VTK_FORMAT_IF_ERROR_RETURN(
          auto result = vtk::format_to_n(this->InternalFileName, this->InternalFileNameSize,
            this->FilePattern, this->FilePrefix, this->FileNumber);
          *result.out = '\0', );
      }
      else
      {
        VTK_FORMAT_IF_ERROR_RETURN(
          auto result = vtk::format_to_n(this->InternalFileName, this->InternalFileNameSize,
            this->FilePattern, "", this->FileNumber);
          *result.out = '\0', );
      }
    }
    // Open the file
#ifdef _WIN32
    file = new vtksys::ofstream(this->InternalFileName, ios::out | ios::binary);
#else
    file = new vtksys::ofstream(this->InternalFileName, ios::out);
#endif
    fileOpenedHere = 1;
    if (file->fail())
    {
      vtkErrorMacro("RecursiveWrite: Could not open file " << this->InternalFileName);
      delete file;
      return;
    }

    // Subclasses can write a header with this method call.
    this->WriteFileHeader(
      file, cache, inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()));
    ++this->FileNumber;
  }

  // Get the pipeline information for the input
  vtkAlgorithm* inAlg = this->GetInputAlgorithm();

  // Set a hint not to combine with previous requests
  inInfo->Set(
    vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT_INITIALIZED(), VTK_UPDATE_EXTENT_REPLACE);

  // Propagate the update extent so we can determine pipeline size
  inAlg->PropagateUpdateExtent();

  // Go back to the previous behaviour
  inInfo->Set(
    vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT_INITIALIZED(), VTK_UPDATE_EXTENT_COMBINE);

  // Now we can ask how big the pipeline will be
  inputMemorySize = this->SizeEstimator->GetEstimatedSize(this, 0, 0);

  // will the current request fit into memory
  // if so the just get the data and write it out
  if (inputMemorySize < this->MemoryLimit)
  {
#ifndef NDEBUG
    int* ext = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT());
#endif
    vtkDebugMacro("Getting input extent: " << ext[0] << ", " << ext[1] << ", " << ext[2] << ", "
                                           << ext[3] << ", " << ext[4] << ", " << ext[5] << endl);
    this->GetInputAlgorithm()->Update();
    data = cache;
    this->RecursiveWrite(axis, cache, data, inInfo, file);
    vtkPIWCloseFile;
    return;
  }

  // if the current request did not fit into memory
  // the we will split the current axis
  int* updateExtent = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT());
  this->GetInput()->GetAxisUpdateExtent(axis, min, max, updateExtent);

  vtkDebugMacro("Axes: " << axis << "(" << min << ", " << max << "), UpdateMemory: "
                         << inputMemorySize << ", Limit: " << this->MemoryLimit << endl);

  if (min == max)
  {
    if (axis > 0)
    {
      this->RecursiveWrite(axis - 1, cache, inInfo, file);
    }
    else
    {
      vtkWarningMacro("MemoryLimit too small for one pixel of information!!");
    }
    vtkPIWCloseFile;
    return;
  }

  mid = (min + max) / 2;

  int axisUpdateExtent[6];

  // if it is the y axis then flip by default
  if (axis == 1 && !this->FileLowerLeft)
  {
    // first half
    cache->SetAxisUpdateExtent(axis, mid + 1, max, updateExtent, axisUpdateExtent);
    inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), axisUpdateExtent, 6);
    this->RecursiveWrite(axis, cache, inInfo, file);

    // second half
    cache->SetAxisUpdateExtent(axis, min, mid, updateExtent, axisUpdateExtent);
    inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), axisUpdateExtent, 6);
    this->RecursiveWrite(axis, cache, inInfo, file);
  }
  else
  {
    // first half
    cache->SetAxisUpdateExtent(axis, min, mid, updateExtent, axisUpdateExtent);
    inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), axisUpdateExtent, 6);
    this->RecursiveWrite(axis, cache, inInfo, file);

    // second half
    cache->SetAxisUpdateExtent(axis, mid + 1, max, updateExtent, axisUpdateExtent);
    inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), axisUpdateExtent, 6);
    this->RecursiveWrite(axis, cache, inInfo, file);
  }

  // restore original extent
  cache->SetAxisUpdateExtent(axis, min, max, updateExtent, axisUpdateExtent);
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), axisUpdateExtent, 6);

  // if we opened the file here, then we need to close it up
  vtkPIWCloseFile;
}
VTK_ABI_NAMESPACE_END
