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

#include "vtkAOSDataArrayTemplate.h"
#include "vtkArrayDispatch.h"
#include "vtkArrayIteratorIncludes.h"
#include "vtkBase64OutputStream.h"
#include "vtkBitArray.h"
#include "vtkByteSwap.h"
#include "vtkCellData.h"
#include "vtkCommand.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDoubleArray.h"
#include "vtkEndian.h"
#include "vtkErrorCode.h"
#include "vtkInformation.h"
#include "vtkInformationDoubleKey.h"
#include "vtkInformationDoubleVectorKey.h"
#include "vtkInformationIdTypeKey.h"
#include "vtkInformationIntegerKey.h"
#include "vtkInformationIntegerVectorKey.h"
#include "vtkInformationIterator.h"
#include "vtkInformationKeyLookup.h"
#include "vtkInformationStringKey.h"
#include "vtkInformationStringVectorKey.h"
#include "vtkInformationUnsignedLongKey.h"
#include "vtkInformationVector.h"
#include "vtkNew.h"
#include "vtkOutputStream.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkStdString.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkUnsignedCharArray.h"
#include "vtkZLibDataCompressor.h"
#define vtkXMLOffsetsManager_DoNotInclude
#include "vtkXMLOffsetsManager.h"
#undef vtkXMLOffsetsManager_DoNotInclude
#define vtkXMLDataHeaderPrivate_DoNotInclude
#include "vtkXMLDataHeaderPrivate.h"
#undef vtkXMLDataHeaderPrivate_DoNotInclude
#include "vtkInformationQuadratureSchemeDefinitionVectorKey.h"
#include "vtkInformationStringKey.h"
#include "vtkNumberToString.h"
#include "vtkQuadratureSchemeDefinition.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLReaderVersion.h"
#include "vtksys/Encoding.hxx"
#include "vtksys/FStream.hxx"
#include <memory>

#include <cassert>
#include <cmath>
#include <sstream>
#include <string>

#if !defined(_WIN32) || defined(__CYGWIN__)
#include <unistd.h> /* unlink */
#else
#include <io.h> /* unlink */
#endif

#include <cctype> // for isalnum
#include <locale> // C++ locale

VTK_ABI_NAMESPACE_BEGIN

//*****************************************************************************
// Friend class to enable access for template functions to the protected
// writer methods.
class vtkXMLWriterHelper
{
public:
  static inline void SetProgressPartial(vtkXMLWriter* writer, double progress)
  {
    writer->SetProgressPartial(progress);
  }
  static inline int WriteBinaryDataBlock(
    vtkXMLWriter* writer, unsigned char* in_data, size_t numWords, int wordType)
  {
    return writer->WriteBinaryDataBlock(in_data, numWords, wordType);
  }
  static inline void* GetInt32IdTypeBuffer(vtkXMLWriter* writer)
  {
    return static_cast<void*>(writer->Int32IdTypeBuffer);
  }
  static inline unsigned char* GetByteSwapBuffer(vtkXMLWriter* writer)
  {
    return writer->ByteSwapBuffer;
  }
};

namespace
{

struct WriteBinaryDataBlockWorker
{
  vtkXMLWriter* Writer;
  int WordType;
  size_t MemWordSize;
  size_t OutWordSize;
  size_t NumWords;
  bool Result;

  WriteBinaryDataBlockWorker(
    vtkXMLWriter* writer, int wordType, size_t memWordSize, size_t outWordSize, size_t numWords)
    : Writer(writer)
    , WordType(wordType)
    , MemWordSize(memWordSize)
    , OutWordSize(outWordSize)
    , NumWords(numWords)
    , Result(false)
  {
  }

  //----------------------------------------------------------------------------
  // Specialize for AoS arrays.
  template <class ValueType>
  void operator()(vtkAOSDataArrayTemplate<ValueType>* array)
  {
    // Get the raw pointer to the array data:
    ValueType* iter = array->GetPointer(0);

    // generic implementation for fixed component length arrays.
    size_t blockWords = this->Writer->GetBlockSize() / this->OutWordSize;
    size_t memBlockSize = blockWords * this->MemWordSize;

    // Prepare a pointer and counter to move through the data.
    unsigned char* ptr = reinterpret_cast<unsigned char*>(iter);
    size_t wordsLeft = this->NumWords;

    // Do the complete blocks.
    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 0);
    this->Result = true;
    while (this->Result && (wordsLeft >= blockWords))
    {
      if (!vtkXMLWriterHelper::WriteBinaryDataBlock(this->Writer, ptr, blockWords, this->WordType))
      {
        this->Result = false;
      }
      ptr += memBlockSize;
      wordsLeft -= blockWords;
      vtkXMLWriterHelper::SetProgressPartial(
        this->Writer, static_cast<float>(this->NumWords - wordsLeft) / this->NumWords);
    }

    // Do the last partial block if any.
    if (this->Result && (wordsLeft > 0))
    {
      if (!vtkXMLWriterHelper::WriteBinaryDataBlock(this->Writer, ptr, wordsLeft, this->WordType))
      {
        this->Result = false;
      }
    }
    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 1);
  }

#if defined(__clang__) && defined(__has_warning)
#if __has_warning("-Wunused-template")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-template"
#endif
#endif

  //----------------------------------------------------------------------------
  // Specialize for vtkBitArray
  void operator()(vtkBitArray* array)
  {
    // Get the raw pointer to the bit array data:
    unsigned char* data = array->GetPointer(0);

    // generic implementation for fixed component length arrays.
    size_t blockSize = this->Writer->GetBlockSize();

    // Prepare a pointer and counter to move through the data.
    unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
    size_t totalBytes = (this->NumWords + 7) / 8;
    size_t bytesLeft = totalBytes;

    // Write out complete blocks.
    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 0);
    this->Result = true;
    while (this->Result && (bytesLeft >= blockSize))
    {
      this->Result =
        vtkXMLWriterHelper::WriteBinaryDataBlock(this->Writer, ptr, blockSize, this->WordType) != 0;
      ptr += blockSize;
      bytesLeft -= blockSize;
      vtkXMLWriterHelper::SetProgressPartial(
        this->Writer, 1.f - static_cast<float>(bytesLeft) / totalBytes);
    }

    // Do the last partial block if any.
    if (this->Result && (bytesLeft > 0))
    {
      this->Result =
        vtkXMLWriterHelper::WriteBinaryDataBlock(this->Writer, ptr, bytesLeft, this->WordType) != 0;
    }
    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 1);
  }

  //----------------------------------------------------------------------------
  // Specialize for non-AoS generic arrays:
  template <class DerivedType, typename ValueType>
  void operator()(vtkGenericDataArray<DerivedType, ValueType>* array)
  {
    // generic implementation for fixed component length arrays.
    size_t blockWords = this->Writer->GetBlockSize() / this->OutWordSize;

    // Prepare a buffer to move through the data.
    std::vector<unsigned char> buffer(blockWords * this->MemWordSize);
    size_t wordsLeft = this->NumWords;

    if (buffer.empty())
    {
      // No data -- bail here, since the calls to buffer[0] below will segfault.
      this->Result = false;
      return;
    }

    // Do the complete blocks.
    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 0);
    this->Result = true;
    vtkIdType valueIdx = 0;
    while (this->Result && (wordsLeft >= blockWords))
    {
      // Copy data to contiguous buffer:
      ValueType* bufferIter = reinterpret_cast<ValueType*>(buffer.data());
      for (size_t i = 0; i < blockWords; ++i, ++valueIdx)
      {
        *bufferIter++ = array->GetValue(valueIdx);
      }

      if (!vtkXMLWriterHelper::WriteBinaryDataBlock(
            this->Writer, buffer.data(), blockWords, this->WordType))
      {
        this->Result = false;
      }
      wordsLeft -= blockWords;
      vtkXMLWriterHelper::SetProgressPartial(
        this->Writer, static_cast<float>(this->NumWords - wordsLeft) / this->NumWords);
    }

    // Do the last partial block if any.
    if (this->Result && (wordsLeft > 0))
    {
      ValueType* bufferIter = reinterpret_cast<ValueType*>(buffer.data());
      for (size_t i = 0; i < wordsLeft; ++i, ++valueIdx)
      {
        *bufferIter++ = array->GetValue(valueIdx);
      }

      if (!vtkXMLWriterHelper::WriteBinaryDataBlock(
            this->Writer, buffer.data(), wordsLeft, this->WordType))
      {
        this->Result = false;
      }
    }

    vtkXMLWriterHelper::SetProgressPartial(this->Writer, 1);
  }

// Undo warning suppression.
#if defined(__clang__) && defined(__has_warning)
#if __has_warning("-Wunused-template")
#pragma clang diagnostic pop
#endif
#endif

}; // End WriteBinaryDataBlockWorker

//------------------------------------------------------------------------------
// Specialize for vtkDataArrays, which implicitly cast everything to double:
template <class ValueType>
void WriteDataArrayFallback(ValueType*, vtkDataArray* array, WriteBinaryDataBlockWorker& worker)
{
  // generic implementation for fixed component length arrays.
  size_t blockWords = worker.Writer->GetBlockSize() / worker.OutWordSize;

  // Prepare a buffer to move through the data.
  std::vector<unsigned char> buffer(blockWords * worker.MemWordSize);
  size_t wordsLeft = worker.NumWords;

  if (buffer.empty())
  {
    // No data -- bail here, since the calls to buffer[0] below will segfault.
    worker.Result = false;
    return;
  }

  vtkIdType nComponents = array->GetNumberOfComponents();

  // Do the complete blocks.
  vtkXMLWriterHelper::SetProgressPartial(worker.Writer, 0);
  worker.Result = true;
  vtkIdType valueIdx = 0;
  while (worker.Result && (wordsLeft >= blockWords))
  {
    // Copy data to contiguous buffer:
    ValueType* bufferIter = reinterpret_cast<ValueType*>(buffer.data());
    for (size_t i = 0; i < blockWords; ++i, ++valueIdx)
    {
      *bufferIter++ =
        static_cast<ValueType>(array->GetComponent(valueIdx / nComponents, valueIdx % nComponents));
    }

    if (!vtkXMLWriterHelper::WriteBinaryDataBlock(
          worker.Writer, buffer.data(), blockWords, worker.WordType))
    {
      worker.Result = false;
    }
    wordsLeft -= blockWords;
    vtkXMLWriterHelper::SetProgressPartial(
      worker.Writer, static_cast<float>(worker.NumWords - wordsLeft) / worker.NumWords);
  }

  // Do the last partial block if any.
  if (worker.Result && (wordsLeft > 0))
  {
    ValueType* bufferIter = reinterpret_cast<ValueType*>(buffer.data());
    for (size_t i = 0; i < wordsLeft; ++i, ++valueIdx)
    {
      *bufferIter++ =
        static_cast<ValueType>(array->GetComponent(valueIdx / nComponents, valueIdx % nComponents));
    }

    if (!vtkXMLWriterHelper::WriteBinaryDataBlock(
          worker.Writer, buffer.data(), wordsLeft, worker.WordType))
    {
      worker.Result = false;
    }
  }

  vtkXMLWriterHelper::SetProgressPartial(worker.Writer, 1);
}

//------------------------------------------------------------------------------
// Specialize for string arrays:
int vtkXMLWriterWriteBinaryDataBlocks(vtkXMLWriter* writer,
  vtkArrayIteratorTemplate<vtkStdString>* iter, int wordType, size_t outWordSize, size_t numStrings,
  int)
{
  vtkXMLWriterHelper::SetProgressPartial(writer, 0);
  vtkStdString::value_type* allocated_buffer = nullptr;
  vtkStdString::value_type* temp_buffer = nullptr;
  if (vtkXMLWriterHelper::GetInt32IdTypeBuffer(writer))
  {
    temp_buffer =
      reinterpret_cast<vtkStdString::value_type*>(vtkXMLWriterHelper::GetInt32IdTypeBuffer(writer));
  }
  else if (vtkXMLWriterHelper::GetByteSwapBuffer(writer))
  {
    temp_buffer =
      reinterpret_cast<vtkStdString::value_type*>(vtkXMLWriterHelper::GetByteSwapBuffer(writer));
  }
  else
  {
    allocated_buffer = new vtkStdString::value_type[writer->GetBlockSize() / outWordSize];
    temp_buffer = allocated_buffer;
  }

  // For string arrays, writing as binary requires that the strings are written
  // out into a contiguous block. This is essential since the compressor can
  // only compress complete blocks of data.
  size_t maxCharsPerBlock = writer->GetBlockSize() / outWordSize;

  size_t index = 0; // index in string array.
  int result = 1;
  vtkIdType stringOffset = 0; // num of chars of string written in previous block.
                              // this is required since a string may not fit completely in a block.

  while (result && index < numStrings) // write one block at a time.
  {
    size_t cur_offset = 0; // offset into the temp_buffer.
    while (index < numStrings && cur_offset < maxCharsPerBlock)
    {
      vtkStdString& str = iter->GetValue(static_cast<vtkIdType>(index));
      vtkStdString::size_type length = str.size();
      const char* data = str.c_str();
      data += stringOffset; // advance by the chars already written.
      length -= stringOffset;
      if (length == 0)
      {
        // just write the string termination char.
        temp_buffer[cur_offset++] = 0x0;
        stringOffset = 0;
        index++; // advance to the next string
      }
      else
      {
        size_t new_offset = cur_offset + length + 1; // (+1) for termination char.
        if (new_offset <= maxCharsPerBlock)
        {
          memcpy(&temp_buffer[cur_offset], data, length);
          cur_offset += length;
          temp_buffer[cur_offset++] = 0x0;
          stringOffset = 0;
          index++; // advance to the next string
        }
        else
        {
          size_t bytes_to_copy = (maxCharsPerBlock - cur_offset);
          stringOffset += static_cast<vtkIdType>(bytes_to_copy);
          memcpy(&temp_buffer[cur_offset], data, bytes_to_copy);
          cur_offset += bytes_to_copy;
          // do not advance, only partially written current string
        }
      }
    }
    if (cur_offset > 0)
    {
      // We have a block of data to write.
      result = vtkXMLWriterHelper::WriteBinaryDataBlock(
        writer, reinterpret_cast<unsigned char*>(temp_buffer), cur_offset, wordType);
      vtkXMLWriterHelper::SetProgressPartial(writer, static_cast<float>(index) / numStrings);
    }
  }

  delete[] allocated_buffer;
  allocated_buffer = nullptr;

  vtkXMLWriterHelper::SetProgressPartial(writer, 1);
  return result;
}

} // end anon namespace
//*****************************************************************************

//------------------------------------------------------------------------------
vtkXMLWriter::vtkXMLWriter()
{
  this->Stream = nullptr;

  // Default binary data mode is base-64 encoding.
  this->DataStream = vtkBase64OutputStream::New();

  // Initialize compression data.
  this->CompressionHeader = nullptr;
  this->Int32IdTypeBuffer = nullptr;
  this->ByteSwapBuffer = nullptr;

  this->AppendedDataPosition = 0;
  this->ProgressRange[0] = 0;
  this->ProgressRange[1] = 1;

  this->SetNumberOfOutputPorts(0);
  this->SetNumberOfInputPorts(1);

  this->OutFile = nullptr;
  this->OutStringStream = nullptr;

  // Time support
  this->NumberOfTimeSteps = 1;
  this->CurrentTimeIndex = 0;
  this->UserContinueExecuting = -1; // invalid state
  this->NumberOfTimeValues = nullptr;
  this->FieldDataOM = new OffsetsManagerGroup;
}

//------------------------------------------------------------------------------
vtkXMLWriter::~vtkXMLWriter()
{
  this->DataStream->Delete();
  delete this->OutFile;
  this->OutFile = nullptr;
  delete this->OutStringStream;
  this->OutStringStream = nullptr;
  delete this->FieldDataOM;
  delete[] this->NumberOfTimeValues;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  if (this->Stream)
  {
    os << indent << "Stream: " << this->Stream << "\n";
  }
  else
  {
    os << indent << "Stream: (none)\n";
  }
  os << indent << "NumberOfTimeSteps:" << this->NumberOfTimeSteps << "\n";
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetInputData(vtkDataObject* input)
{
  this->SetInputData(0, input);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetInputData(int index, vtkDataObject* input)
{
  this->SetInputDataInternal(index, input);
}

//------------------------------------------------------------------------------
vtkDataObject* vtkXMLWriter::GetInput(int port)
{
  if (this->GetNumberOfInputConnections(port) < 1)
  {
    return nullptr;
  }
  return this->GetExecutive()->GetInputData(port, 0);
}

//------------------------------------------------------------------------------
vtkTypeBool vtkXMLWriter::ProcessRequest(
  vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // generate the data
  if (request->Has(vtkDemandDrivenPipeline::REQUEST_DATA()))
  {
    return this->RequestData(request, inputVector, outputVector);
  }

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

//------------------------------------------------------------------------------
int vtkXMLWriter::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* vtkNotUsed(outputVector))
{
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  if (inInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS()))
  {
    this->NumberOfTimeSteps = inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* vtkNotUsed(outputVector))
{
  this->SetErrorCode(vtkErrorCode::NoError);

  // Make sure we have a file to write.
  if (!this->Stream && !this->FileName && !this->WriteToOutputString)
  {
    vtkErrorMacro("Writer called with no FileName set.");
    this->SetErrorCode(vtkErrorCode::NoFileNameError);
    return 0;
  }

  // We are just starting to write.  Do not call
  // UpdateProgressDiscrete because we want a 0 progress callback the
  // first time.
  this->UpdateProgress(0);

  // Initialize progress range to entire 0..1 range.
  float wholeProgressRange[2] = { 0.f, 1.f };
  this->SetProgressRange(wholeProgressRange, 0, 1);

  // Check input validity and call the real writing code.
  int result = this->WriteInternal();

  // If writing failed, delete the file.
  if (!result)
  {
    vtkErrorMacro("Ran out of disk space; deleting file: " << this->FileName);
    this->DeleteAFile();
  }

  // We have finished writing.
  this->UpdateProgressDiscrete(1);

  return result;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::OpenStream()
{
  if (this->Stream)
  {
    // Rewind stream to the beginning.
    this->Stream->seekp(0);
  }
  else
  {
    if (this->WriteToOutputString)
    {
      if (!this->OpenString())
      {
        return 0;
      }
    }
    else
    {
      if (!this->OpenFile())
      {
        return 0;
      }
    }
  }

  // Make sure sufficient precision is used in the ascii
  // representation of data and meta-data.
  this->Stream->precision(11);

  // Setup the output streams.
  this->DataStream->SetStream(this->Stream);

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::OpenFile()
{
  delete this->OutFile;
  this->OutFile = nullptr;

  // Strip trailing whitespace from the filename.
  int len = static_cast<int>(strlen(this->FileName));
  for (int i = len - 1; i >= 0; i--)
  {
    if (isalnum(this->FileName[i]))
    {
      break;
    }
    this->FileName[i] = 0;
  }

  std::ios_base::openmode mode = ios::out;
#ifdef _WIN32
  mode |= ios::binary;
#endif
  this->OutFile = new vtksys::ofstream(this->FileName, mode);
  if (!this->OutFile || !*this->OutFile)
  {
    vtkErrorMacro("Error opening output file \"" << this->FileName << "\"");
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    vtkErrorMacro(
      "Error code \"" << vtkErrorCode::GetStringFromErrorCode(this->GetErrorCode()) << "\"");
    return 0;
  }
  this->Stream = this->OutFile;

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::OpenString()
{
  delete this->OutStringStream;
  this->OutStringStream = new std::ostringstream();
  this->Stream = this->OutStringStream;

  return 1;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::CloseStream()
{
  // Cleanup the output streams.
  this->DataStream->SetStream(nullptr);

  if (this->WriteToOutputString)
  {
    this->CloseString();
  }
  else
  {
    this->CloseFile();
  }

  this->Stream = nullptr;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::CloseFile()
{
  if (this->OutFile)
  {
    // We opened a file.  Close it.
    delete this->OutFile;
    this->OutFile = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::CloseString()
{
  if (this->OutStringStream)
  {
    this->OutputString = this->OutStringStream->str();
    delete this->OutStringStream;
    this->OutStringStream = nullptr;
  }
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteInternal()
{
  if (!this->OpenStream())
  {
    return 0;
  }

  (*this->Stream).imbue(std::locale::classic());

  // Tell the subclass to write the data.
  int result = this->WriteData();

  // if user manipulate execution don't try closing file
  if (this->UserContinueExecuting != 1)
  {
    this->CloseStream();
  }

  return result;
}

//------------------------------------------------------------------------------
vtkDataSet* vtkXMLWriter::GetInputAsDataSet()
{
  return static_cast<vtkDataSet*>(this->GetInput());
}

//------------------------------------------------------------------------------
int vtkXMLWriter::StartFile()
{
  ostream& os = *(this->Stream);

  // If this will really be a valid XML file, put the XML header at
  // the top.
  if (this->EncodeAppendedData)
  {
    os << "<?xml version=\"1.0\"?>\n";
  }

  os.imbue(std::locale::classic());

  // Open the document-level element.  This will contain the rest of
  // the elements.
  os << "<VTKFile";
  this->WriteFileAttributes();
  os << ">\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    return 0;
  }

  return 1;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteFileAttributes()
{
  ostream& os = *(this->Stream);

  // Write the file's type.
  this->WriteStringAttribute("type", this->GetDataSetName());

  // Write the version number of the file.
  os << " version=\"" << this->GetDataSetMajorVersion() << "." << this->GetDataSetMinorVersion()
     << "\"";

  // Write the byte order for the file.
  if (this->ByteOrder == vtkXMLWriter::BigEndian)
  {
    os << " byte_order=\"BigEndian\"";
  }
  else
  {
    os << " byte_order=\"LittleEndian\"";
  }

  // Write the header type for binary data.
  if (this->HeaderType == vtkXMLWriter::UInt64)
  {
    os << " header_type=\"UInt64\"";
  }
  else
  {
    os << " header_type=\"UInt32\"";
  }

  // Write the compressor that will be used for the file.
  if (this->Compressor)
  {
    os << " compressor=\"" << this->Compressor->GetClassName() << "\"";
  }
}

//------------------------------------------------------------------------------
int vtkXMLWriter::EndFile()
{
  ostream& os = *(this->Stream);

  // Close the document-level element.
  os << "</VTKFile>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    return 0;
  }

  return 1;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::DeleteAFile()
{
  if (!this->Stream && this->FileName)
  {
    this->DeleteAFile(this->FileName);
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::DeleteAFile(const char* name)
{
  unlink(name);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::StartAppendedData()
{
  ostream& os = *(this->Stream);
  os << "  <AppendedData encoding=\"" << (this->EncodeAppendedData ? "base64" : "raw") << "\">\n";
  os << "   _";
  this->AppendedDataPosition = os.tellp();

  // Setup proper output encoding.
  if (this->EncodeAppendedData)
  {
    vtkBase64OutputStream* base64 = vtkBase64OutputStream::New();
    this->SetDataStream(base64);
    base64->Delete();
  }
  else
  {
    vtkOutputStream* raw = vtkOutputStream::New();
    this->SetDataStream(raw);
    raw->Delete();
  }

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::EndAppendedData()
{
  ostream& os = *(this->Stream);
  os << "\n";
  os << "  </AppendedData>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
vtkTypeInt64 vtkXMLWriter::ReserveAttributeSpace(const char* attr, size_t length)
{
  // Save the starting stream position.
  ostream& os = *(this->Stream);
  vtkTypeInt64 startPosition = os.tellp();

  // By default write an empty valid xml: attr="".  In most case it
  // will be overwritten but we guarantee that the xml produced will
  // be valid in case we stop writing too early.
  os << " " << attr << "=\"\"";

  // Now reserve space for the value.
  for (size_t i = 0; i < length; ++i)
  {
    os << " ";
  }

  // Flush the stream to make sure the system tries to write now and
  // test for a write error reported by the system.
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }

  // Return the position at which to write the attribute later.
  return startPosition;
}

//------------------------------------------------------------------------------
vtkTypeInt64 vtkXMLWriter::GetAppendedDataOffset()
{
  vtkTypeInt64 pos = this->Stream->tellp();
  return (pos - this->AppendedDataPosition);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteAppendedDataOffset(
  vtkTypeInt64 streamPos, vtkTypeInt64& lastoffset, const char* attr)
{
  // Write an XML attribute with the given name.  The value is the
  // current appended data offset.  Starts writing at the given stream
  // position, and returns the ending position.  If attr is 0, writes
  // only the double quotes.  In all cases, the final stream position
  // is left the same as before the call.
  ostream& os = *(this->Stream);
  vtkTypeInt64 returnPos = os.tellp();
  vtkTypeInt64 offset = returnPos - this->AppendedDataPosition;
  lastoffset = offset; // saving result
  os.seekp(std::streampos(streamPos));
  if (attr)
  {
    os << " " << attr << "=";
  }
  os << "\"" << offset << "\"";
  os.seekp(std::streampos(returnPos));

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::ForwardAppendedDataOffset(
  vtkTypeInt64 streamPos, vtkTypeInt64 offset, const char* attr)
{
  ostream& os = *(this->Stream);
  std::streampos returnPos = os.tellp();
  os.seekp(std::streampos(streamPos));
  if (attr)
  {
    os << " " << attr << "=";
  }
  os << "\"" << offset << "\"";
  os.seekp(returnPos);

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::ForwardAppendedDataDouble(vtkTypeInt64 streamPos, double value, const char* attr)
{
  ostream& os = *(this->Stream);
  std::streampos returnPos = os.tellp();
  os.seekp(std::streampos(streamPos));
  if (attr)
  {
    os << " " << attr << "=";
  }
  os << "\"" << value << "\"";
  os.seekp(returnPos);

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteBinaryData(vtkAbstractArray* a)
{
  int wordType = a->GetDataType();

  size_t dataSize;
  if (wordType != VTK_BIT)
  {
    dataSize = this->GetOutputWordTypeSize(wordType) * static_cast<size_t>(a->GetDataSize());
  }
  else
  { // vtkBitArray returns 0 for GetDataSize:
    dataSize = (a->GetNumberOfValues() + 7) / 8;
  }

  if (this->Compressor)
  {
    // Need to compress the data.  Create compression header.  This
    // reserves enough space in the output.
    if (!this->CreateCompressionHeader(dataSize))
    {
      return 0;
    }
    // Start writing the data.
    int result = this->DataStream->StartWriting();

    // Process the actual data.
    if (result && !this->WriteBinaryDataInternal(a))
    {
      result = 0;
    }

    // Finish writing the data.
    if (result && !this->DataStream->EndWriting())
    {
      result = 0;
    }

    // Go back and write the real compression header in its proper place.
    if (result && !this->WriteCompressionHeader())
    {
      result = 0;
    }

    // Destroy the compression header if it was used.
    delete this->CompressionHeader;
    this->CompressionHeader = nullptr;

    return result;
  }
  else
  {
    // Start writing the data.
    if (!this->DataStream->StartWriting())
    {
      return 0;
    }

    // No data compression.  The header is just the length of the data.
    std::unique_ptr<vtkXMLDataHeader> uh(vtkXMLDataHeader::New(this->HeaderType, 1));
    if (!uh->Set(0, dataSize))
    {
      vtkErrorMacro("Array \"" << a->GetName() << "\" is too large.  Set HeaderType to UInt64.");
      this->SetErrorCode(vtkErrorCode::FileFormatError);
      return 0;
    }
    this->PerformByteSwap(uh->Data(), uh->WordCount(), uh->WordSize());
    int writeRes = this->DataStream->Write(uh->Data(), uh->DataSize());
    this->Stream->flush();
    if (this->Stream->fail())
    {
      this->SetErrorCode(vtkErrorCode::GetLastSystemError());
      return 0;
    }
    if (!writeRes)
    {
      return 0;
    }

    // Process the actual data.
    if (!this->WriteBinaryDataInternal(a))
    {
      return 0;
    }

    // Finish writing the data.
    if (!this->DataStream->EndWriting())
    {
      return 0;
    }
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteBinaryDataInternal(vtkAbstractArray* a)
{
  // Break into blocks and handle each one separately.  This allows
  // for better random access when reading compressed data and saves
  // memory during writing.

  // The size of the blocks written (before compression) is
  // this->BlockSize.  We need to support the possibility that the
  // size of data in memory and the size on disk are different.  This
  // is necessary to allow vtkIdType to be converted to UInt32 for
  // writing.

  int wordType = a->GetDataType();
  size_t memWordSize = this->GetWordTypeSize(wordType);
  size_t outWordSize = this->GetOutputWordTypeSize(wordType);

#ifdef VTK_USE_64BIT_IDS
  // If the type is vtkIdType, it may need to be converted to the type
  // requested for output.
  if ((wordType == VTK_ID_TYPE) && (this->IdType == vtkXMLWriter::Int32))
  {
    size_t blockWordsEstimate = this->BlockSize / outWordSize;
    this->Int32IdTypeBuffer = new Int32IdType[blockWordsEstimate];
  }
#endif

  // Decide if we need to byte swap.
#ifdef VTK_WORDS_BIGENDIAN
  if (outWordSize > 1 && this->ByteOrder != vtkXMLWriter::BigEndian)
#else
  if (outWordSize > 1 && this->ByteOrder != vtkXMLWriter::LittleEndian)
#endif
  {
    // We need to byte swap.  Prepare a buffer large enough for one
    // block.
    if (this->Int32IdTypeBuffer)
    {
      // Just swap in-place in the converted id-type buffer.
      this->ByteSwapBuffer = reinterpret_cast<unsigned char*>(this->Int32IdTypeBuffer);
    }
    else
    {
      // The maximum nlock size if this->BlockSize. The actual data in the block
      // may be lesser.
      this->ByteSwapBuffer = new unsigned char[this->BlockSize];
    }
  }
  int ret;

  size_t numValues = static_cast<size_t>(a->GetNumberOfComponents() * a->GetNumberOfTuples());

  if (wordType == VTK_STRING)
  {
    vtkArrayIterator* aiter = a->NewIterator();
    vtkArrayIteratorTemplate<vtkStdString>* iter =
      vtkArrayIteratorTemplate<vtkStdString>::SafeDownCast(aiter);
    if (iter)
    {
      ret = vtkXMLWriterWriteBinaryDataBlocks(this, iter, wordType, outWordSize, numValues, 1);
    }
    else
    {
      vtkWarningMacro("Unsupported iterator for data type : " << wordType);
      ret = 0;
    }
    aiter->Delete();
  }
  else if (vtkDataArray* da = vtkArrayDownCast<vtkDataArray>(a))
  {
    // Create a dispatcher that also handles vtkBitArray:
    using vtkArrayDispatch::AllArrays;
    using XMLArrays = vtkTypeList::Append<AllArrays, vtkBitArray>::Result;
    using Dispatcher = vtkArrayDispatch::DispatchByArray<XMLArrays>;

    WriteBinaryDataBlockWorker worker(this, wordType, memWordSize, outWordSize, numValues);
    if (!Dispatcher::Execute(da, worker))
    {
      switch (wordType)
      {
        case VTK_LONG_LONG:
        case VTK_UNSIGNED_LONG_LONG:
#ifdef VTK_USE_64BIT_IDS
        case VTK_ID_TYPE:
#endif
          vtkWarningMacro("Using legacy vtkDataArray API, which may result "
                          "in precision loss");
          break;
        default:
          break;
      }

      switch (wordType)
      {
        vtkTemplateMacro(WriteDataArrayFallback(static_cast<VTK_TT*>(nullptr), da, worker));
        default:
          vtkWarningMacro("Unsupported data type: " << wordType);
          break;
      }
    }
    ret = worker.Result ? 1 : 0;
  }
  else
  {
    vtkWarningMacro("Not writing array '" << a->GetName()
                                          << "': Unsupported "
                                             "array type: "
                                          << a->GetClassName());
    ret = 0;
  }

  // Free the byte swap buffer if it was allocated.
  if (!this->Int32IdTypeBuffer)
  {
    delete[] this->ByteSwapBuffer;
    this->ByteSwapBuffer = nullptr;
  }

#ifdef VTK_USE_64BIT_IDS
  // Free the id-type conversion buffer if it was allocated.
  delete[] this->Int32IdTypeBuffer;
  this->Int32IdTypeBuffer = nullptr;
  // The swap and ID buffers are shared. Guard against double frees:
  this->ByteSwapBuffer = nullptr;
#endif
  return ret;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteBinaryDataBlock(unsigned char* in_data, size_t numWords, int wordType)
{
  unsigned char* data = in_data;
#ifdef VTK_USE_64BIT_IDS
  // If the type is vtkIdType, it may need to be converted to the type
  // requested for output.
  if ((wordType == VTK_ID_TYPE) && (this->IdType == vtkXMLWriter::Int32))
  {
    vtkIdType* idBuffer = reinterpret_cast<vtkIdType*>(in_data);

    for (size_t i = 0; i < numWords; ++i)
    {
      this->Int32IdTypeBuffer[i] = static_cast<Int32IdType>(idBuffer[i]);
    }

    data = reinterpret_cast<unsigned char*>(this->Int32IdTypeBuffer);
  }
#endif

  // Get the word size of the data buffer.  This is now the size that
  // will be written.
  size_t wordSize = this->GetOutputWordTypeSize(wordType);

  // If we need to byte swap, do it now.
  if (this->ByteSwapBuffer)
  {
    // If we are converting vtkIdType to 32-bit integer data, the data
    // are already in the byte swap buffer because we share the
    // conversion buffer.  Otherwise, we need to copy the data before
    // byte swapping.
    if (data != this->ByteSwapBuffer)
    {
      memcpy(this->ByteSwapBuffer, data, numWords * wordSize);
      data = this->ByteSwapBuffer;
    }
    this->PerformByteSwap(this->ByteSwapBuffer, numWords, wordSize);
  }

  // Now pass the data to the next write phase.
  if (this->Compressor)
  {
    int res = this->WriteCompressionBlock(data, numWords * wordSize);
    this->Stream->flush();
    if (this->Stream->fail())
    {
      this->SetErrorCode(vtkErrorCode::GetLastSystemError());
      return 0;
    }
    return res;
  }
  else
  {
    int res = this->DataStream->Write(data, numWords * wordSize);
    this->Stream->flush();
    if (this->Stream->fail())
    {
      this->SetErrorCode(vtkErrorCode::GetLastSystemError());
      return 0;
    }
    return res;
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::PerformByteSwap(void* data, size_t numWords, size_t wordSize)
{
  char* ptr = static_cast<char*>(data);
  if (this->ByteOrder == vtkXMLWriter::BigEndian)
  {
    switch (wordSize)
    {
      case 1:
        break;
      case 2:
        vtkByteSwap::Swap2BERange(ptr, numWords);
        break;
      case 4:
        vtkByteSwap::Swap4BERange(ptr, numWords);
        break;
      case 8:
        vtkByteSwap::Swap8BERange(ptr, numWords);
        break;
      default:
        vtkErrorMacro("Unsupported data type size " << wordSize);
    }
  }
  else
  {
    switch (wordSize)
    {
      case 1:
        break;
      case 2:
        vtkByteSwap::Swap2LERange(ptr, numWords);
        break;
      case 4:
        vtkByteSwap::Swap4LERange(ptr, numWords);
        break;
      case 8:
        vtkByteSwap::Swap8LERange(ptr, numWords);
        break;
      default:
        vtkErrorMacro("Unsupported data type size " << wordSize);
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetDataStream(vtkOutputStream* arg)
{
  if (this->DataStream != arg)
  {
    if (this->DataStream != nullptr)
    {
      this->DataStream->UnRegister(this);
    }
    this->DataStream = arg;
    if (this->DataStream != nullptr)
    {
      this->DataStream->Register(this);
      this->DataStream->SetStream(this->Stream);
    }
  }
}

//------------------------------------------------------------------------------
int vtkXMLWriter::CreateCompressionHeader(size_t size)
{
  // Allocate and initialize the compression header.
  // The format is this:
  //  struct header {
  //    HeaderType number_of_blocks;
  //    HeaderType uncompressed_block_size;
  //    HeaderType uncompressed_last_block_size;
  //    HeaderType compressed_block_sizes[number_of_blocks];
  //  }

  // Find the size and number of blocks.
  size_t numFullBlocks = size / this->BlockSize;
  size_t lastBlockSize = size % this->BlockSize;
  size_t numBlocks = numFullBlocks + (lastBlockSize ? 1 : 0);
  this->CompressionHeader = vtkXMLDataHeader::New(this->HeaderType, 3 + numBlocks);

  // Write out dummy header data.
  this->CompressionHeaderPosition = this->Stream->tellp();
  int result = (this->DataStream->StartWriting() &&
    this->DataStream->Write(this->CompressionHeader->Data(), this->CompressionHeader->DataSize()) &&
    this->DataStream->EndWriting());

  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    return 0;
  }

  // Fill in known header data now.
  this->CompressionHeader->Set(0, numBlocks);
  this->CompressionHeader->Set(1, this->BlockSize);
  this->CompressionHeader->Set(2, lastBlockSize);

  // Initialize counter for block writing.
  this->CompressionBlockNumber = 0;

  return result;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteCompressionBlock(unsigned char* data, size_t size)
{
  // Compress the data.
  vtkUnsignedCharArray* outputArray = this->Compressor->Compress(data, size);

  // Find the compressed size.
  size_t outputSize = outputArray->GetNumberOfTuples();
  unsigned char* outputPointer = outputArray->GetPointer(0);

  // Write the compressed data.
  int result = this->DataStream->Write(outputPointer, outputSize);
  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }

  // Store the resulting compressed size in the compression header.
  this->CompressionHeader->Set(3 + this->CompressionBlockNumber++, outputSize);

  outputArray->Delete();

  return result;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteCompressionHeader()
{
  // Write real compression header back into stream.
  std::streampos returnPosition = this->Stream->tellp();

  // Need to byte-swap header.
  this->PerformByteSwap(this->CompressionHeader->Data(), this->CompressionHeader->WordCount(),
    this->CompressionHeader->WordSize());

  if (!this->Stream->seekp(std::streampos(this->CompressionHeaderPosition)))
  {
    return 0;
  }
  int result = (this->DataStream->StartWriting() &&
    this->DataStream->Write(this->CompressionHeader->Data(), this->CompressionHeader->DataSize()) &&
    this->DataStream->EndWriting());
  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    return 0;
  }

  if (!this->Stream->seekp(returnPosition))
  {
    return 0;
  }
  return result;
}

//------------------------------------------------------------------------------
size_t vtkXMLWriter::GetOutputWordTypeSize(int dataType)
{
#ifdef VTK_USE_64BIT_IDS
  // If the type is vtkIdType, it may need to be converted to the type
  // requested for output.
  if ((dataType == VTK_ID_TYPE) && (this->IdType == vtkXMLWriter::Int32))
  {
    return 4;
  }
#endif
  return this->GetWordTypeSize(dataType);
}

//------------------------------------------------------------------------------
template <class T>
size_t vtkXMLWriterGetWordTypeSize(T*)
{
  return sizeof(T);
}

//------------------------------------------------------------------------------
size_t vtkXMLWriter::GetWordTypeSize(int dataType)
{
  size_t size = 1;
  switch (dataType)
  {
    vtkTemplateMacro(size = vtkXMLWriterGetWordTypeSize(static_cast<VTK_TT*>(nullptr)));

    case VTK_STRING:
      size = sizeof(vtkStdString::value_type);
      break;

    case VTK_BIT:
      size = 1;
      break;

    default:
      vtkWarningMacro("Unsupported data type: " << dataType);
      break;
  }
  return size;
}

//------------------------------------------------------------------------------
const char* vtkXMLWriter::GetWordTypeName(int dataType)
{
  char isSigned = 0;
  int size = 0;

  // These string values must match vtkXMLDataElement::GetWordTypeAttribute().
  switch (dataType)
  {
    case VTK_BIT:
      return "Bit";
    case VTK_STRING:
      return "String";
    case VTK_FLOAT:
      return "Float32";
    case VTK_DOUBLE:
      return "Float64";
    case VTK_ID_TYPE:
    {
      switch (this->IdType)
      {
        case vtkXMLWriter::Int32:
          return "Int32";
        case vtkXMLWriter::Int64:
          return "Int64";
        default:
          return nullptr;
      }
    }
#if VTK_TYPE_CHAR_IS_SIGNED
    case VTK_CHAR:
      isSigned = 1;
      size = sizeof(char);
      break;
#else
    case VTK_CHAR:
      isSigned = 0;
      size = sizeof(char);
      break;
#endif
    case VTK_INT:
      isSigned = 1;
      size = sizeof(int);
      break;
    case VTK_LONG:
      isSigned = 1;
      size = sizeof(long);
      break;
    case VTK_SHORT:
      isSigned = 1;
      size = sizeof(short);
      break;
    case VTK_SIGNED_CHAR:
      isSigned = 1;
      size = sizeof(signed char);
      break;
    case VTK_UNSIGNED_CHAR:
      isSigned = 0;
      size = sizeof(unsigned char);
      break;
    case VTK_UNSIGNED_INT:
      isSigned = 0;
      size = sizeof(unsigned int);
      break;
    case VTK_UNSIGNED_LONG:
      isSigned = 0;
      size = sizeof(unsigned long);
      break;
    case VTK_UNSIGNED_SHORT:
      isSigned = 0;
      size = sizeof(unsigned short);
      break;
    case VTK_LONG_LONG:
      isSigned = 1;
      size = sizeof(long long);
      break;
    case VTK_UNSIGNED_LONG_LONG:
      isSigned = 0;
      size = sizeof(unsigned long long);
      break;
    default:
    {
      vtkWarningMacro("Unsupported data type: " << dataType);
    }
    break;
  }
  const char* type = nullptr;
  switch (size)
  {
    case 1:
      type = isSigned ? "Int8" : "UInt8";
      break;
    case 2:
      type = isSigned ? "Int16" : "UInt16";
      break;
    case 4:
      type = isSigned ? "Int32" : "UInt32";
      break;
    case 8:
      type = isSigned ? "Int64" : "UInt64";
      break;
    default:
    {
      vtkErrorMacro("Data type size " << size << " not supported by VTK XML format.");
    }
  }
  return type;
}

//------------------------------------------------------------------------------
template <class T>
int vtkXMLWriterWriteVectorAttribute(ostream& os, const char* name, int length, T* data)
{
  vtkNumberToString converter;
  os << " " << name << "=\"";
  if (length)
  {
    os << converter.Convert(data[0]);
    for (int i = 1; i < length; ++i)
    {
      os << " " << converter.Convert(data[i]);
    }
  }
  os << "\"";
  return os ? 1 : 0;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteScalarAttribute(const char* name, int data)
{
  return this->WriteVectorAttribute(name, 1, &data);
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteScalarAttribute(const char* name, float data)
{
  return this->WriteVectorAttribute(name, 1, &data);
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteScalarAttribute(const char* name, double data)
{
  return this->WriteVectorAttribute(name, 1, &data);
}

//------------------------------------------------------------------------------
#ifdef VTK_USE_64BIT_IDS
int vtkXMLWriter::WriteScalarAttribute(const char* name, vtkIdType data)
{
  return this->WriteVectorAttribute(name, 1, &data);
}
#endif

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteVectorAttribute(const char* name, int length, int* data)
{
  int res = vtkXMLWriterWriteVectorAttribute(*(this->Stream), name, length, data);

  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return res;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteVectorAttribute(const char* name, int length, float* data)
{
  int res = vtkXMLWriterWriteVectorAttribute(*(this->Stream), name, length, data);

  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return res;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteVectorAttribute(const char* name, int length, double* data)
{
  int res = vtkXMLWriterWriteVectorAttribute(*(this->Stream), name, length, data);

  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return res;
}

//------------------------------------------------------------------------------
#ifdef VTK_USE_64BIT_IDS
int vtkXMLWriter::WriteVectorAttribute(const char* name, int length, vtkIdType* data)
{
  int res = vtkXMLWriterWriteVectorAttribute(*(this->Stream), name, length, data);

  this->Stream->flush();
  if (this->Stream->fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return res;
}
#endif

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteDataModeAttribute(const char* name)
{
  ostream& os = *(this->Stream);
  os << " " << name << "=\"";
  if (this->DataMode == vtkXMLWriter::Appended)
  {
    os << "appended";
  }
  else if (this->DataMode == vtkXMLWriter::Binary)
  {
    os << "binary";
  }
  else
  {
    os << "ascii";
  }
  os << "\"";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return (os ? 1 : 0);
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteWordTypeAttribute(const char* name, int dataType)
{
  ostream& os = *(this->Stream);
  const char* value = this->GetWordTypeName(dataType);
  if (!value)
  {
    return 0;
  }
  os << " " << name << "=\"" << value << "\"";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return os ? 1 : 0;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteStringAttribute(const char* name, const char* value)
{
  ostream& os = *(this->Stream);
  os << " " << name << "=\"" << value << "\"";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  return os ? 1 : 0;
}

//------------------------------------------------------------------------------
// Methods used for serializing vtkInformation. ------------------------------
namespace
{

// Write generic key information to element.
void prepElementForInfo(vtkInformationKey* key, vtkXMLDataElement* element)
{
  element->SetName("InformationKey");
  element->SetAttribute("name", key->GetName());
  element->SetAttribute("location", key->GetLocation());
}

template <class KeyType>
void writeScalarInfo(KeyType* key, vtkInformation* info, std::ostream& os, vtkIndent indent)
{
  vtkNew<vtkXMLDataElement> element;
  prepElementForInfo(key, element);

  std::ostringstream str;
  str.precision(11); // Same used for ASCII array data.
  str << key->Get(info);

  str.str("");
  str << key->Get(info);
  element->SetCharacterData(str.str().c_str(), static_cast<int>(str.str().size()));

  element->PrintXML(os, indent);
}

template <class KeyType>
void writeVectorInfo(KeyType* key, vtkInformation* info, std::ostream& os, vtkIndent indent)
{
  vtkNew<vtkXMLDataElement> element;
  prepElementForInfo(key, element);

  std::ostringstream str;
  str.precision(11); // Same used for ASCII array data.
  int length = key->Length(info);
  str << length;
  element->SetAttribute("length", str.str().c_str());

  for (int i = 0; i < length; ++i)
  {
    vtkNew<vtkXMLDataElement> value;
    value->SetName("Value");

    str.str("");
    str << i;
    value->SetAttribute("index", str.str().c_str());

    str.str("");
    str << key->Get(info, i);
    value->SetCharacterData(str.str().c_str(), static_cast<int>(str.str().size()));

    element->AddNestedElement(value);
  }

  element->PrintXML(os, indent);
}

} // end anon namespace
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool vtkXMLWriter::WriteInformation(vtkInformation* info, vtkIndent indent)
{
  bool result = false;
  vtkNew<vtkInformationIterator> iter;
  iter->SetInformationWeak(info);
  vtkInformationKey* key = nullptr;
  vtkIndent nextIndent = indent.GetNextIndent();
  for (iter->InitTraversal(); (key = iter->GetCurrentKey()); iter->GoToNextItem())
  {
    vtkInformationDoubleKey* dKey = nullptr;
    vtkInformationDoubleVectorKey* dvKey = nullptr;
    vtkInformationIdTypeKey* idKey = nullptr;
    vtkInformationIntegerKey* iKey = nullptr;
    vtkInformationIntegerVectorKey* ivKey = nullptr;
    vtkInformationStringKey* sKey = nullptr;
    vtkInformationStringVectorKey* svKey = nullptr;
    vtkInformationUnsignedLongKey* ulKey = nullptr;
    typedef vtkInformationQuadratureSchemeDefinitionVectorKey QuadDictKey;
    QuadDictKey* qdKey = nullptr;
    if ((dKey = vtkInformationDoubleKey::SafeDownCast(key)))
    {
      writeScalarInfo(dKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((dvKey = vtkInformationDoubleVectorKey::SafeDownCast(key)))
    {
      writeVectorInfo(dvKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((idKey = vtkInformationIdTypeKey::SafeDownCast(key)))
    {
      writeScalarInfo(idKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((iKey = vtkInformationIntegerKey::SafeDownCast(key)))
    {
      writeScalarInfo(iKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((ivKey = vtkInformationIntegerVectorKey::SafeDownCast(key)))
    {
      writeVectorInfo(ivKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((sKey = vtkInformationStringKey::SafeDownCast(key)))
    {
      writeScalarInfo(sKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((svKey = vtkInformationStringVectorKey::SafeDownCast(key)))
    {
      writeVectorInfo(svKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((ulKey = vtkInformationUnsignedLongKey::SafeDownCast(key)))
    {
      writeScalarInfo(ulKey, info, *this->Stream, nextIndent);
      result = true;
    }
    else if ((qdKey = QuadDictKey::SafeDownCast(key)))
    { // Special case:
      vtkNew<vtkXMLDataElement> element;
      qdKey->SaveState(info, element);
      element->PrintXML(*this->Stream, nextIndent);
      result = true;
    }
    else
    {
      vtkDebugMacro("Could not serialize information with key " << key->GetLocation()
                                                                << "::" << key->GetName()
                                                                << ": "
                                                                   "Unsupported key type '"
                                                                << key->GetClassName() << "'.");
    }
  }
  return result;
}

//------------------------------------------------------------------------------
// This method is provided so that the specialization code for certain types
// can be minimal.
template <class T>
inline ostream& vtkXMLWriteAsciiValue(ostream& os, const T& value)
{
  vtkNumberToString converter;
  os << converter.Convert(value);
  return os;
}

//------------------------------------------------------------------------------
template <>
inline ostream& vtkXMLWriteAsciiValue(ostream& os, const char& c)
{
  os << short(c);
  return os;
}

//------------------------------------------------------------------------------
template <>
inline ostream& vtkXMLWriteAsciiValue(ostream& os, const unsigned char& c)
{
  os << static_cast<unsigned short>(c);
  return os;
}

//------------------------------------------------------------------------------
template <>
inline ostream& vtkXMLWriteAsciiValue(ostream& os, const signed char& c)
{
  os << short(c);
  return os;
}

//------------------------------------------------------------------------------
template <>
inline ostream& vtkXMLWriteAsciiValue(ostream& os, const vtkStdString& str)
{
  vtkStdString::const_iterator iter;
  for (iter = str.begin(); iter != str.end(); ++iter)
  {
    vtkXMLWriteAsciiValue(os, *iter);
    os << " ";
  }
  char delim = 0x0;
  return vtkXMLWriteAsciiValue(os, delim);
}

//------------------------------------------------------------------------------
template <class iterT>
int vtkXMLWriteAsciiData(ostream& os, iterT* iter, vtkIndent indent)
{
  if (!iter)
  {
    return 0;
  }
  size_t columns = 6;
  size_t length = iter->GetNumberOfTuples() * iter->GetNumberOfComponents();

  size_t rows = length / columns;
  size_t lastRowLength = length % columns;
  vtkIdType index = 0;
  for (size_t r = 0; r < rows; ++r)
  {
    os << indent;
    vtkXMLWriteAsciiValue(os, iter->GetValue(index++));
    for (size_t c = 1; c < columns; ++c)
    {
      os << " ";
      vtkXMLWriteAsciiValue(os, iter->GetValue(index++));
    }
    os << "\n";
  }
  if (lastRowLength > 0)
  {
    os << indent;
    vtkXMLWriteAsciiValue(os, iter->GetValue(index++));
    for (size_t c = 1; c < lastRowLength; ++c)
    {
      os << " ";
      vtkXMLWriteAsciiValue(os, iter->GetValue(index++));
    }
    os << "\n";
  }
  return os ? 1 : 0;
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WriteAsciiData(vtkAbstractArray* a, vtkIndent indent)
{
  vtkArrayIterator* iter = a->NewIterator();
  ostream& os = *(this->Stream);
  int ret;
  switch (a->GetDataType())
  {
    vtkArrayIteratorTemplateMacro(
      ret = vtkXMLWriteAsciiData(os, static_cast<VTK_TT*>(iter), indent));
    default:
      ret = 0;
      break;
  }
  iter->Delete();
  return ret;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteArrayAppended(vtkAbstractArray* a, vtkIndent indent, OffsetsManager& offs,
  const char* alternateName, int writeNumTuples, int timestep)
{
  ostream& os = *(this->Stream);
  // Write the header <DataArray or <Array:
  this->WriteArrayHeader(a, indent, alternateName, writeNumTuples, timestep);
  int shortFormatTag = 1; // close with: />
  //
  if (vtkArrayDownCast<vtkDataArray>(a))
  {
    // write the scalar range of this data array, we reserver space because we
    // don't actually have the data at this point
    offs.GetRangeMinPosition(timestep) = this->ReserveAttributeSpace("RangeMin");
    offs.GetRangeMaxPosition(timestep) = this->ReserveAttributeSpace("RangeMax");
  }
  else
  {
    // ranges are not written for non-data arrays.
    offs.GetRangeMinPosition(timestep) = -1;
    offs.GetRangeMaxPosition(timestep) = -1;
  }

  //
  offs.GetPosition(timestep) = this->ReserveAttributeSpace("offset");

  // Write information in the recognized keys associated with this array.
  vtkInformation* info = a->GetInformation();
  bool hasInfo = info && info->GetNumberOfKeys() > 0;
  if (hasInfo)
  {
    // close header with </DataArray> or </Array> before writing information:
    os << ">" << endl;
    shortFormatTag = 0; // Tells WriteArrayFooter that the header is closed.

    this->WriteInformation(info, indent);
  }

  // Close tag.
  this->WriteArrayFooter(os, indent, a, shortFormatTag);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteArrayAppendedData(
  vtkAbstractArray* a, vtkTypeInt64 pos, vtkTypeInt64& lastoffset)
{
  this->WriteAppendedDataOffset(pos, lastoffset, "offset");
  this->WriteBinaryData(a);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteArrayHeader(vtkAbstractArray* a, vtkIndent indent,
  const char* alternateName, int writeNumTuples, int timestep)
{
  ostream& os = *(this->Stream);
  if (vtkArrayDownCast<vtkDataArray>(a))
  {
    os << indent << "<DataArray";
  }
  else
  {
    os << indent << "<Array";
  }
  this->WriteWordTypeAttribute("type", a->GetDataType());
  if (a->GetDataType() == VTK_ID_TYPE)
  {
    this->WriteScalarAttribute("IdType", 1);
  }
  if (alternateName)
  {
    this->WriteStringAttribute("Name", alternateName);
  }
  else if (const char* arrayName = a->GetName())
  {
    this->WriteStringAttribute("Name", arrayName);
  }
  else
  {
    // Generate a name for this array.
    std::ostringstream name;
    void* p = a;
    name << "Array " << p;
    this->WriteStringAttribute("Name", name.str().c_str());
  }
  if (a->GetNumberOfComponents() > 1)
  {
    this->WriteScalarAttribute("NumberOfComponents", a->GetNumberOfComponents());
  }

  // always write out component names, even if only 1 component
  std::ostringstream buff;
  const char* compName = nullptr;
  for (int i = 0; i < a->GetNumberOfComponents(); ++i)
  {
    // get the component names
    buff << "ComponentName" << i;
    compName = a->GetComponentName(i);
    if (compName)
    {
      this->WriteStringAttribute(buff.str().c_str(), compName);
      compName = nullptr;
    }
    buff.str("");
    buff.clear();
  }

  if (this->NumberOfTimeSteps > 1)
  {
    this->WriteScalarAttribute("TimeStep", timestep);
  }
  else
  {
    // assert(timestep == -1); //FieldData problem
  }
  if (writeNumTuples)
  {
    this->WriteScalarAttribute("NumberOfTuples", a->GetNumberOfTuples());
  }

  this->WriteDataModeAttribute("format");
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteArrayFooter(
  ostream& os, vtkIndent indent, vtkAbstractArray* a, int shortFormat)
{
  // Close the tag: </DataArray>, </Array> or />
  if (shortFormat)
  {
    os << "/>" << endl;
  }
  else
  {
    vtkDataArray* da = vtkArrayDownCast<vtkDataArray>(a);
    os << indent << (da ? "</DataArray>" : "</Array>") << "\n";
  }
  // Force write and check for errors.
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteInlineData(vtkAbstractArray* a, vtkIndent indent)
{
  if (this->DataMode == vtkXMLWriter::Binary)
  {
    ostream& os = *(this->Stream);
    os << indent;
    this->WriteBinaryData(a);
    os << "\n";
  }
  else
  {
    this->WriteAsciiData(a, indent);
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteArrayInline(
  vtkAbstractArray* a, vtkIndent indent, const char* alternateName, int writeNumTuples)
{
  ostream& os = *(this->Stream);
  // Write the header <DataArray or <Array:
  this->WriteArrayHeader(a, indent, alternateName, writeNumTuples, 0);
  //
  vtkDataArray* da = vtkArrayDownCast<vtkDataArray>(a);
  if (da)
  {
    // write the range
    this->WriteScalarAttribute("RangeMin", da->GetRange(-1)[0]);
    this->WriteScalarAttribute("RangeMax", da->GetRange(-1)[1]);
  }
  // Close the header
  os << ">\n";
  // Write the inline data.
  this->WriteInlineData(a, indent.GetNextIndent());
  // Write information keys associated with this array.
  vtkInformation* info = a->GetInformation();
  if (info && info->GetNumberOfKeys() > 0)
  {
    this->WriteInformation(info, indent);
  }
  // Close tag.
  this->WriteArrayFooter(os, indent, a, 0);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::UpdateFieldData(vtkFieldData* fieldDataCopy)
{
  vtkDataObject* input = this->GetInput();
  vtkFieldData* fieldData = input->GetFieldData();
  vtkInformation* meta = input->GetInformation();
  bool hasTime = meta->Has(vtkDataObject::DATA_TIME_STEP()) != 0;
  if ((!fieldData || !fieldData->GetNumberOfArrays()) && !hasTime)
  {
    fieldDataCopy->Initialize();
    return;
  }

  fieldDataCopy->ShallowCopy(fieldData);
  if (hasTime)
  {
    vtkNew<vtkDoubleArray> time;
    time->SetNumberOfTuples(1);
    time->SetTypedComponent(0, 0, meta->Get(vtkDataObject::DATA_TIME_STEP()));
    time->SetName("TimeValue");
    fieldDataCopy->AddArray(time);
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteFieldData(vtkIndent indent)
{
  vtkNew<vtkFieldData> fieldDataCopy;
  this->UpdateFieldData(fieldDataCopy);

  if (!fieldDataCopy->GetNumberOfArrays())
  {
    return;
  }

  if (this->DataMode == vtkXMLWriter::Appended)
  {
    this->WriteFieldDataAppended(fieldDataCopy, indent, this->FieldDataOM);
  }
  else
  {
    // Write the point data arrays.
    this->WriteFieldDataInline(fieldDataCopy, indent);
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteFieldDataInline(vtkFieldData* fd, vtkIndent indent)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(fd->GetNumberOfArrays());

  os << indent << "<FieldData>\n";

  float progressRange[2] = { 0.f, 0.f };
  this->GetProgressRange(progressRange);
  for (int i = 0; i < fd->GetNumberOfArrays(); ++i)
  {
    this->SetProgressRange(progressRange, i, fd->GetNumberOfArrays());
    this->WriteArrayInline(fd->GetAbstractArray(i), indent.GetNextIndent(), names[i], 1);
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      this->DestroyStringArray(fd->GetNumberOfArrays(), names);
      return;
    }
  }

  os << indent << "</FieldData>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    this->DestroyStringArray(fd->GetNumberOfArrays(), names);
    return;
  }

  this->DestroyStringArray(fd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointDataInline(vtkPointData* pd, vtkIndent indent)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(pd->GetNumberOfArrays());

  os << indent << "<PointData";
  this->WriteAttributeIndices(pd, names);

  if (this->ErrorCode != vtkErrorCode::NoError)
  {
    this->DestroyStringArray(pd->GetNumberOfArrays(), names);
    return;
  }

  os << ">\n";

  float progressRange[2] = { 0.f, 0.f };
  this->GetProgressRange(progressRange);
  for (int i = 0; i < pd->GetNumberOfArrays(); ++i)
  {
    this->SetProgressRange(progressRange, i, pd->GetNumberOfArrays());
    vtkAbstractArray* a = pd->GetAbstractArray(i);
    this->WriteArrayInline(a, indent.GetNextIndent(), names[i]);
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      this->DestroyStringArray(pd->GetNumberOfArrays(), names);
      return;
    }
  }

  os << indent << "</PointData>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    this->DestroyStringArray(pd->GetNumberOfArrays(), names);
    return;
  }

  this->DestroyStringArray(pd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCellDataInline(vtkCellData* cd, vtkIndent indent)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(cd->GetNumberOfArrays());

  os << indent << "<CellData";
  this->WriteAttributeIndices(cd, names);

  if (this->ErrorCode != vtkErrorCode::NoError)
  {
    this->DestroyStringArray(cd->GetNumberOfArrays(), names);
    return;
  }

  os << ">\n";

  float progressRange[2] = { 0.f, 0.f };
  this->GetProgressRange(progressRange);
  for (int i = 0; i < cd->GetNumberOfArrays(); ++i)
  {
    this->SetProgressRange(progressRange, i, cd->GetNumberOfArrays());
    vtkAbstractArray* a = cd->GetAbstractArray(i);
    this->WriteArrayInline(a, indent.GetNextIndent(), names[i]);
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      this->DestroyStringArray(cd->GetNumberOfArrays(), names);
      return;
    }
  }

  os << indent << "</CellData>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    this->DestroyStringArray(cd->GetNumberOfArrays(), names);
    return;
  }

  this->DestroyStringArray(cd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteFieldDataAppended(
  vtkFieldData* fd, vtkIndent indent, OffsetsManagerGroup* fdManager)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(fd->GetNumberOfArrays());

  os << indent << "<FieldData>\n";

  // When we want to write index arrays with String Arrays, we will
  // have to determine the actual arrays written out to the file
  // and allocate the fdManager accordingly.
  fdManager->Allocate(fd->GetNumberOfArrays());
  for (int i = 0; i < fd->GetNumberOfArrays(); ++i)
  {
    fdManager->GetElement(i).Allocate(1);
    this->WriteArrayAppended(
      fd->GetAbstractArray(i), indent.GetNextIndent(), fdManager->GetElement(i), names[i], 1, 0);
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      this->DestroyStringArray(fd->GetNumberOfArrays(), names);
      return;
    }
  }
  os << indent << "</FieldData>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  this->DestroyStringArray(fd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteFieldDataAppendedData(
  vtkFieldData* fd, int timestep, OffsetsManagerGroup* fdManager)
{
  float progressRange[2] = { 0.f, 0.f };
  this->GetProgressRange(progressRange);
  fdManager->Allocate(fd->GetNumberOfArrays());
  for (int i = 0; i < fd->GetNumberOfArrays(); ++i)
  {
    fdManager->GetElement(i).Allocate(this->NumberOfTimeSteps);
    this->SetProgressRange(progressRange, i, fd->GetNumberOfArrays());
    this->WriteArrayAppendedData(fd->GetAbstractArray(i),
      fdManager->GetElement(i).GetPosition(timestep),
      fdManager->GetElement(i).GetOffsetValue(timestep));
    vtkDataArray* da = fd->GetArray(i);
    if (da)
    {
      // Write ranges only for data arrays.
      double* range = da->GetRange(-1);
      this->ForwardAppendedDataDouble(
        fdManager->GetElement(i).GetRangeMinPosition(timestep), range[0], "RangeMin");
      this->ForwardAppendedDataDouble(
        fdManager->GetElement(i).GetRangeMaxPosition(timestep), range[1], "RangeMax");
    }
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointDataAppended(
  vtkPointData* pd, vtkIndent indent, OffsetsManagerGroup* pdManager)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(pd->GetNumberOfArrays());

  os << indent << "<PointData";
  this->WriteAttributeIndices(pd, names);

  if (this->ErrorCode != vtkErrorCode::NoError)
  {
    this->DestroyStringArray(pd->GetNumberOfArrays(), names);
    return;
  }

  os << ">\n";

  pdManager->Allocate(pd->GetNumberOfArrays());
  for (int i = 0; i < pd->GetNumberOfArrays(); ++i)
  {
    pdManager->GetElement(i).Allocate(this->NumberOfTimeSteps);
    for (int t = 0; t < this->NumberOfTimeSteps; ++t)
    {
      this->WriteArrayAppended(
        pd->GetAbstractArray(i), indent.GetNextIndent(), pdManager->GetElement(i), names[i], 0, t);
      if (this->ErrorCode != vtkErrorCode::NoError)
      {
        this->DestroyStringArray(pd->GetNumberOfArrays(), names);
        return;
      }
    }
  }

  os << indent << "</PointData>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  this->DestroyStringArray(pd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointDataAppendedData(
  vtkPointData* pd, int timestep, OffsetsManagerGroup* pdManager)
{
  float progressRange[2] = { 0.f, 0.f };

  this->GetProgressRange(progressRange);
  for (int i = 0; i < pd->GetNumberOfArrays(); ++i)
  {
    this->SetProgressRange(progressRange, i, pd->GetNumberOfArrays());
    vtkMTimeType mtime = pd->GetMTime();
    // Only write pd if MTime has changed
    vtkMTimeType& pdMTime = pdManager->GetElement(i).GetLastMTime();
    vtkAbstractArray* a = pd->GetAbstractArray(i);
    if (pdMTime != mtime || timestep == 0)
    {
      pdMTime = mtime;
      this->WriteArrayAppendedData(a, pdManager->GetElement(i).GetPosition(timestep),
        pdManager->GetElement(i).GetOffsetValue(timestep));
      if (this->ErrorCode != vtkErrorCode::NoError)
      {
        return;
      }
    }
    else
    {
      assert(timestep > 0);
      pdManager->GetElement(i).GetOffsetValue(timestep) =
        pdManager->GetElement(i).GetOffsetValue(timestep - 1);
      this->ForwardAppendedDataOffset(pdManager->GetElement(i).GetPosition(timestep),
        pdManager->GetElement(i).GetOffsetValue(timestep), "offset");
    }
    vtkDataArray* d = vtkArrayDownCast<vtkDataArray>(a);
    if (d)
    {
      // ranges are only written in case of Data Arrays.
      double* range = d->GetRange(-1);
      this->ForwardAppendedDataDouble(
        pdManager->GetElement(i).GetRangeMinPosition(timestep), range[0], "RangeMin");
      this->ForwardAppendedDataDouble(
        pdManager->GetElement(i).GetRangeMaxPosition(timestep), range[1], "RangeMax");
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCellDataAppended(
  vtkCellData* cd, vtkIndent indent, OffsetsManagerGroup* cdManager)
{
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(cd->GetNumberOfArrays());

  os << indent << "<CellData";
  this->WriteAttributeIndices(cd, names);

  if (this->ErrorCode != vtkErrorCode::NoError)
  {
    this->DestroyStringArray(cd->GetNumberOfArrays(), names);
    return;
  }

  os << ">\n";

  cdManager->Allocate(cd->GetNumberOfArrays());
  for (int i = 0; i < cd->GetNumberOfArrays(); ++i)
  {
    cdManager->GetElement(i).Allocate(this->NumberOfTimeSteps);
    for (int t = 0; t < this->NumberOfTimeSteps; ++t)
    {
      this->WriteArrayAppended(
        cd->GetAbstractArray(i), indent.GetNextIndent(), cdManager->GetElement(i), names[i], 0, t);
      if (this->ErrorCode != vtkErrorCode::NoError)
      {
        this->DestroyStringArray(cd->GetNumberOfArrays(), names);
        return;
      }
    }
  }

  os << indent << "</CellData>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
  this->DestroyStringArray(cd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCellDataAppendedData(
  vtkCellData* cd, int timestep, OffsetsManagerGroup* cdManager)
{
  float progressRange[2] = { 0.f, 0.f };
  this->GetProgressRange(progressRange);

  for (int i = 0; i < cd->GetNumberOfArrays(); ++i)
  {
    this->SetProgressRange(progressRange, i, cd->GetNumberOfArrays());
    vtkMTimeType mtime = cd->GetMTime();
    // Only write pd if MTime has changed
    vtkMTimeType& cdMTime = cdManager->GetElement(i).GetLastMTime();
    vtkAbstractArray* a = cd->GetAbstractArray(i);
    if (cdMTime != mtime)
    {
      cdMTime = mtime;
      this->WriteArrayAppendedData(a, cdManager->GetElement(i).GetPosition(timestep),
        cdManager->GetElement(i).GetOffsetValue(timestep));
      if (this->ErrorCode != vtkErrorCode::NoError)
      {
        return;
      }
    }
    else
    {
      assert(timestep > 0);
      cdManager->GetElement(i).GetOffsetValue(timestep) =
        cdManager->GetElement(i).GetOffsetValue(timestep - 1);
      this->ForwardAppendedDataOffset(cdManager->GetElement(i).GetPosition(timestep),
        cdManager->GetElement(i).GetOffsetValue(timestep), "offset");
    }
    vtkDataArray* d = vtkArrayDownCast<vtkDataArray>(a);
    if (d)
    {
      double* range = d->GetRange(-1);
      this->ForwardAppendedDataDouble(
        cdManager->GetElement(i).GetRangeMinPosition(timestep), range[0], "RangeMin");
      this->ForwardAppendedDataDouble(
        cdManager->GetElement(i).GetRangeMaxPosition(timestep), range[1], "RangeMax");
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteAttributeIndices(vtkDataSetAttributes* dsa, char** names)
{
  int attributeIndices[vtkDataSetAttributes::NUM_ATTRIBUTES];
  dsa->GetAttributeIndices(attributeIndices);
  for (int i = 0; i < vtkDataSetAttributes::NUM_ATTRIBUTES; ++i)
  {
    if (attributeIndices[i] >= 0)
    {
      const char* attrName = vtkDataSetAttributes::GetAttributeTypeAsString(i);
      vtkDataArray* a = dsa->GetArray(attributeIndices[i]);
      const char* arrayName = a->GetName();
      if (!arrayName)
      {
        // Assign a name to the array.
        names[attributeIndices[i]] = new char[strlen(attrName) + 2];
        strcpy(names[attributeIndices[i]], attrName);
        strcat(names[attributeIndices[i]], "_");
        arrayName = names[attributeIndices[i]];
      }
      this->WriteStringAttribute(attrName, arrayName);
      if (this->ErrorCode != vtkErrorCode::NoError)
      {
        return;
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointsAppended(
  vtkPoints* points, vtkIndent indent, OffsetsManager* ptManager)
{
  ostream& os = *(this->Stream);

  // Only write points if they exist.
  os << indent << "<Points>\n";
  if (points)
  {
    for (int t = 0; t < this->NumberOfTimeSteps; ++t)
    {
      this->WriteArrayAppended(
        points->GetData(), indent.GetNextIndent(), *ptManager, nullptr, 0, t);
    }
  }
  os << indent << "</Points>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointsAppendedData(
  vtkPoints* points, int timestep, OffsetsManager* ptManager)
{
  // Only write points if they exist.
  if (points)
  {
    vtkMTimeType mtime = points->GetMTime();
    // Only write points if MTime has changed
    vtkMTimeType& pointsMTime = ptManager->GetLastMTime();
    // since points->Data is a vtkDataArray.
    vtkDataArray* outPoints = points->GetData();
    if (pointsMTime != mtime || timestep == 0)
    {
      pointsMTime = mtime;
      this->WriteArrayAppendedData(
        outPoints, ptManager->GetPosition(timestep), ptManager->GetOffsetValue(timestep));
    }
    else
    {
      assert(timestep > 0);
      ptManager->GetOffsetValue(timestep) = ptManager->GetOffsetValue(timestep - 1);
      this->ForwardAppendedDataOffset(
        ptManager->GetPosition(timestep), ptManager->GetOffsetValue(timestep), "offset");
    }
    double* range = outPoints->GetRange(-1);
    this->ForwardAppendedDataDouble(ptManager->GetRangeMinPosition(timestep), range[0], "RangeMin");
    this->ForwardAppendedDataDouble(ptManager->GetRangeMaxPosition(timestep), range[1], "RangeMax");
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePointsInline(vtkPoints* points, vtkIndent indent)
{
  ostream& os = *(this->Stream);
  // Only write points if they exist.
  os << indent << "<Points>\n";
  if (points)
  {
    vtkAbstractArray* outPoints = points->GetData();
    this->WriteArrayInline(outPoints, indent.GetNextIndent());
  }
  os << indent << "</Points>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCoordinatesInline(
  vtkDataArray* xc, vtkDataArray* yc, vtkDataArray* zc, vtkIndent indent)
{
  ostream& os = *(this->Stream);

  // Only write coordinates if they exist.
  os << indent << "<Coordinates>\n";
  if (xc && yc && zc)
  {

    // Split progress over the three coordinates arrays.
    vtkIdType total = (xc->GetNumberOfTuples() + yc->GetNumberOfTuples() + zc->GetNumberOfTuples());
    if (total == 0)
    {
      total = 1;
    }
    float fractions[4] = { 0, float(xc->GetNumberOfTuples()) / total,
      float(xc->GetNumberOfTuples() + yc->GetNumberOfTuples()) / total, 1 };
    float progressRange[2] = { 0.f, 0.f };
    this->GetProgressRange(progressRange);

    this->SetProgressRange(progressRange, 0, fractions);
    this->WriteArrayInline(xc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }

    this->SetProgressRange(progressRange, 1, fractions);
    this->WriteArrayInline(yc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }

    this->SetProgressRange(progressRange, 2, fractions);
    this->WriteArrayInline(zc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }
  }
  os << indent << "</Coordinates>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
    return;
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCoordinatesAppended(vtkDataArray* xc, vtkDataArray* yc, vtkDataArray* zc,
  vtkIndent indent, OffsetsManagerGroup* coordManager)
{
  ostream& os = *(this->Stream);

  // Helper for the 'for' loop
  vtkDataArray* allcoords[3];
  allcoords[0] = xc;
  allcoords[1] = yc;
  allcoords[2] = zc;

  // Only write coordinates if they exist.
  os << indent << "<Coordinates>\n";
  coordManager->Allocate(3);
  if (xc && yc && zc)
  {
    for (int i = 0; i < 3; ++i)
    {
      coordManager->GetElement(i).Allocate(this->NumberOfTimeSteps);
      for (int t = 0; t < this->NumberOfTimeSteps; ++t)
      {
        this->WriteArrayAppended(
          allcoords[i], indent.GetNextIndent(), coordManager->GetElement(i), nullptr, 0, t);
        if (this->ErrorCode != vtkErrorCode::NoError)
        {
          return;
        }
      }
    }
  }
  os << indent << "</Coordinates>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteCoordinatesAppendedData(vtkDataArray* xc, vtkDataArray* yc,
  vtkDataArray* zc, int timestep, OffsetsManagerGroup* coordManager)
{
  // Only write coordinates if they exist.
  if (xc && yc && zc)
  {
    // Split progress over the three coordinates arrays.
    vtkIdType total = (xc->GetNumberOfTuples() + yc->GetNumberOfTuples() + zc->GetNumberOfTuples());
    if (total == 0)
    {
      total = 1;
    }
    float fractions[4] = { 0, float(xc->GetNumberOfTuples()) / total,
      float(xc->GetNumberOfTuples() + yc->GetNumberOfTuples()) / total, 1 };
    float progressRange[2] = { 0.f, 0.f };
    this->GetProgressRange(progressRange);

    // Helper for the 'for' loop
    vtkDataArray* allcoords[3];
    allcoords[0] = xc;
    allcoords[1] = yc;
    allcoords[2] = zc;

    for (int i = 0; i < 3; ++i)
    {
      this->SetProgressRange(progressRange, i, fractions);
      vtkMTimeType mtime = allcoords[i]->GetMTime();
      // Only write pd if MTime has changed
      vtkMTimeType& coordMTime = coordManager->GetElement(i).GetLastMTime();
      if (coordMTime != mtime)
      {
        coordMTime = mtime;
        this->WriteArrayAppendedData(allcoords[i],
          coordManager->GetElement(i).GetPosition(timestep),
          coordManager->GetElement(i).GetOffsetValue(timestep));
        if (this->ErrorCode != vtkErrorCode::NoError)
        {
          return;
        }
      }
      else
      {
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePPointData(vtkPointData* pd, vtkIndent indent)
{
  if (pd->GetNumberOfArrays() == 0)
  {
    return;
  }
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(pd->GetNumberOfArrays());

  os << indent << "<PPointData";
  this->WriteAttributeIndices(pd, names);
  if (this->ErrorCode != vtkErrorCode::NoError)
  {
    this->DestroyStringArray(pd->GetNumberOfArrays(), names);
    return;
  }
  os << ">\n";

  for (int i = 0; i < pd->GetNumberOfArrays(); ++i)
  {
    this->WritePArray(pd->GetAbstractArray(i), indent.GetNextIndent(), names[i]);
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      this->DestroyStringArray(pd->GetNumberOfArrays(), names);
      return;
    }
  }

  os << indent << "</PPointData>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }

  this->DestroyStringArray(pd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePCellData(vtkCellData* cd, vtkIndent indent)
{
  if (cd->GetNumberOfArrays() == 0)
  {
    return;
  }
  ostream& os = *(this->Stream);
  char** names = this->CreateStringArray(cd->GetNumberOfArrays());

  os << indent << "<PCellData";
  this->WriteAttributeIndices(cd, names);
  os << ">\n";

  for (int i = 0; i < cd->GetNumberOfArrays(); ++i)
  {
    this->WritePArray(cd->GetAbstractArray(i), indent.GetNextIndent(), names[i]);
  }

  os << indent << "</PCellData>\n";

  this->DestroyStringArray(cd->GetNumberOfArrays(), names);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePPoints(vtkPoints* points, vtkIndent indent)
{
  ostream& os = *(this->Stream);
  // Only write points if they exist.
  os << indent << "<PPoints>\n";
  if (points)
  {
    this->WritePArray(points->GetData(), indent.GetNextIndent());
  }
  os << indent << "</PPoints>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePArray(vtkAbstractArray* a, vtkIndent indent, const char* alternateName)
{
  vtkDataArray* d = vtkArrayDownCast<vtkDataArray>(a);
  ostream& os = *(this->Stream);
  if (d)
  {
    os << indent << "<PDataArray";
  }
  else
  {
    os << indent << "<PArray";
  }
  this->WriteWordTypeAttribute("type", a->GetDataType());
  if (a->GetDataType() == VTK_ID_TYPE)
  {
    this->WriteScalarAttribute("IdType", 1);
  }
  if (alternateName)
  {
    this->WriteStringAttribute("Name", alternateName);
  }
  else
  {
    const char* arrayName = a->GetName();
    if (arrayName)
    {
      this->WriteStringAttribute("Name", arrayName);
    }
  }
  if (a->GetNumberOfComponents() > 1)
  {
    this->WriteScalarAttribute("NumberOfComponents", a->GetNumberOfComponents());
  }
  os << "/>\n";

  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePCoordinates(
  vtkDataArray* xc, vtkDataArray* yc, vtkDataArray* zc, vtkIndent indent)
{
  ostream& os = *(this->Stream);

  // Only write coordinates if they exist.
  os << indent << "<PCoordinates>\n";
  if (xc && yc && zc)
  {
    this->WritePArray(xc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }
    this->WritePArray(yc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }
    this->WritePArray(zc, indent.GetNextIndent());
    if (this->ErrorCode != vtkErrorCode::NoError)
    {
      return;
    }
  }
  os << indent << "</PCoordinates>\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::GetLastSystemError());
  }
}

//------------------------------------------------------------------------------
char** vtkXMLWriter::CreateStringArray(int numStrings)
{
  char** strings = new char*[numStrings];
  for (int i = 0; i < numStrings; ++i)
  {
    strings[i] = nullptr;
  }
  return strings;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::DestroyStringArray(int numStrings, char** strings)
{
  for (int i = 0; i < numStrings; ++i)
  {
    delete[] strings[i];
  }
  delete[] strings;
}

//------------------------------------------------------------------------------
void vtkXMLWriter::GetProgressRange(float range[2])
{
  range[0] = this->ProgressRange[0];
  range[1] = this->ProgressRange[1];
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetProgressRange(const float range[2], int curStep, int numSteps)
{
  float stepSize = (range[1] - range[0]) / numSteps;
  this->ProgressRange[0] = range[0] + stepSize * curStep;
  this->ProgressRange[1] = range[0] + stepSize * (curStep + 1);
  this->UpdateProgressDiscrete(this->ProgressRange[0]);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetProgressRange(const float range[2], int curStep, const float* fractions)
{
  float width = range[1] - range[0];
  this->ProgressRange[0] = range[0] + fractions[curStep] * width;
  this->ProgressRange[1] = range[0] + fractions[curStep + 1] * width;
  this->UpdateProgressDiscrete(this->ProgressRange[0]);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::SetProgressPartial(float fraction)
{
  float width = this->ProgressRange[1] - this->ProgressRange[0];
  this->UpdateProgressDiscrete(this->ProgressRange[0] + fraction * width);
}

//------------------------------------------------------------------------------
void vtkXMLWriter::UpdateProgressDiscrete(float progress)
{
  if (!this->AbortExecute)
  {
    // Round progress to nearest 100th.
    float rounded = std::round(progress * 100) / 100.f;
    if (this->GetProgress() != rounded)
    {
      this->UpdateProgress(rounded);
    }
  }
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WritePrimaryElementAttributes(ostream& os, vtkIndent indent)
{
  // Write the time step if any:
  if (this->NumberOfTimeSteps > 1)
  {
    // First thing allocate NumberOfTimeValues
    assert(this->NumberOfTimeValues == nullptr);
    this->NumberOfTimeValues = new vtkTypeInt64[this->NumberOfTimeSteps];
    os << indent << "TimeValues=\"\n";

    std::string blankline = std::string(40, ' '); // enough room for precision
    for (int i = 0; i < this->NumberOfTimeSteps; i++)
    {
      this->NumberOfTimeValues[i] = os.tellp();
      os << blankline << "\n";
    }
    os << "\"";
  }
}

//------------------------------------------------------------------------------
int vtkXMLWriter::WritePrimaryElement(ostream& os, vtkIndent indent)
{
  // Open the primary element.
  os << indent << "<" << this->GetDataSetName();

  this->WritePrimaryElementAttributes(os, indent);

  // Close the primary element:
  os << ">\n";
  os.flush();
  if (os.fail())
  {
    this->SetErrorCode(vtkErrorCode::OutOfDiskSpaceError);
    return 0;
  }
  return 1;
}

// The following function are designed to be called outside of the VTK pipeline
// typically from a C interface or when ParaView want to control the writing
//------------------------------------------------------------------------------
void vtkXMLWriter::Start()
{
  // Make sure we have input.
  if (this->GetNumberOfInputConnections(0) < 1)
  {
    vtkErrorMacro("No input provided!");
    return;
  }
  this->UserContinueExecuting = 1;
}

//------------------------------------------------------------------------------
// The function does not make sense in the general case but we need to handle
// the case where the simulation stop before reaching the number of steps
// specified by the user. Therefore the CurrentTimeIndex is never equal
// to NumberOfTimeStep and thus we need to force closing of the xml file
void vtkXMLWriter::Stop()
{
  this->UserContinueExecuting = 0;
  this->Modified();
  this->Update();
  this->UserContinueExecuting = -1; // put back the writer in initial state
}

//------------------------------------------------------------------------------
void vtkXMLWriter::WriteNextTime(double time)
{
  this->Modified();
  this->Update();

  ostream& os = *(this->Stream);

  if (this->NumberOfTimeValues)
  {
    // Write user specified time value in the TimeValues attribute
    std::streampos returnPos = os.tellp();
    vtkTypeInt64 t = this->NumberOfTimeValues[this->CurrentTimeIndex - 1];
    os.seekp(std::streampos(t));
    os << time;
    os.seekp(returnPos);
  }
}

VTK_ABI_NAMESPACE_END
