/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkArrayWriter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

#include "vtkArrayWriter.h"
#include "vtkArrayData.h"
#include "vtkArrayPrint.h"
#include "vtkDenseArray.h"
#include "vtkExecutive.h"
#include "vtkInformation.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkSparseArray.h"
#include "vtkUnicodeString.h"

#include <cmath>
#include <limits>
#include <stdexcept>
#include <sstream>

namespace {

template<typename T>
inline void WriteValue(std::ostream& stream, const T& value)
{
  stream << value;
}

inline void WriteValue(std::ostream& stream, const double& value)
{
  if(std::abs(value) < std::numeric_limits<double>::min())
    stream << 0;
  else
    stream << value;
}

inline void WriteValue(std::ostream& stream, const vtkStdString& value)
{
  stream << value;
}

inline void WriteValue(std::ostream& stream, const vtkUnicodeString& value)
{
  stream << value.utf8_str();
}

void WriteHeader(const vtkStdString& array_type,
                 const vtkStdString& type_name,
                 vtkArray* array,
                 ostream& stream,
                 bool WriteBinary)
{
  // Serialize the array type ...
  stream << array_type << " " << type_name << "\n";

  // Serialize output format, binary or ascii
  WriteBinary ? stream << "binary" <<"\n" : stream << "ascii" << "\n";

  const vtkArrayExtents extents = array->GetExtents();
  const vtkIdType dimensions = array->GetDimensions();

  // Serialize the array name ...
  stream << array->GetName() << "\n";

  // Serialize the array extents and number of non-NULL values ...
  for(vtkIdType i = 0; i != dimensions; ++i)
    stream << extents[i].GetBegin() << " " << extents[i].GetEnd() << " ";
  stream << array->GetNonNullSize() << "\n";

  // Serialize the dimension-label for each dimension ...
  for(vtkIdType i = 0; i != dimensions; ++i)
    stream << array->GetDimensionLabel(i) << "\n";
}

void WriteEndianOrderMark(ostream& stream)
{
  // Serialize an endian-order mark ...
  const vtkTypeUInt32 endian_order = 0x12345678;
  stream.write(
    reinterpret_cast<const char*>(&endian_order),
    sizeof(endian_order));
}

template<typename ValueT>
bool WriteSparseArrayBinary(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkSparseArray<ValueT>* const concrete_array = vtkSparseArray<ValueT>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-sparse-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array NULL value ...
  stream.write(
    reinterpret_cast<const char*>(&concrete_array->GetNullValue()),
    sizeof(ValueT));

  // Serialize the array coordinates ...
  for(vtkIdType i = 0; i != array->GetDimensions(); ++i)
    {
    stream.write(
      reinterpret_cast<char*>(concrete_array->GetCoordinateStorage(i)),
      concrete_array->GetNonNullSize() * sizeof(vtkIdType));
    }

  // Serialize the array values ...
  stream.write(
    reinterpret_cast<char*>(concrete_array->GetValueStorage()),
    concrete_array->GetNonNullSize() * sizeof(ValueT));

  return true;
}

template<>
bool WriteSparseArrayBinary<vtkStdString>(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkSparseArray<vtkStdString>* const concrete_array = vtkSparseArray<vtkStdString>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-sparse-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array NULL value ...
  stream.write(
    concrete_array->GetNullValue().c_str(),
    concrete_array->GetNullValue().size() + 1);

  // Serialize the array coordinates ...
  for(vtkIdType i = 0; i != array->GetDimensions(); ++i)
    {
    stream.write(
      reinterpret_cast<char*>(concrete_array->GetCoordinateStorage(i)),
      concrete_array->GetNonNullSize() * sizeof(vtkIdType));
    }

  // Serialize the array values as a set of packed, NULL-terminated strings ...
  const vtkIdType value_count = array->GetNonNullSize();
  for(vtkIdType n = 0; n != value_count; ++n)
    {
    const vtkStdString& value = concrete_array->GetValueN(n);

    stream.write(
      value.c_str(),
      value.size() + 1);
    }

  return true;
}

template<>
bool WriteSparseArrayBinary<vtkUnicodeString>(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkSparseArray<vtkUnicodeString>* const concrete_array = vtkSparseArray<vtkUnicodeString>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-sparse-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array NULL value ...
  stream.write(
    concrete_array->GetNullValue().utf8_str(),
    strlen(concrete_array->GetNullValue().utf8_str()) + 1);

  // Serialize the array coordinates ...
  for(vtkIdType i = 0; i != array->GetDimensions(); ++i)
    {
    stream.write(
      reinterpret_cast<char*>(concrete_array->GetCoordinateStorage(i)),
      concrete_array->GetNonNullSize() * sizeof(vtkIdType));
    }

  // Serialize the array values as a set of packed, NULL-terminated strings ...
  const vtkIdType value_count = array->GetNonNullSize();
  for(vtkIdType n = 0; n != value_count; ++n)
    {
    const vtkUnicodeString& value = concrete_array->GetValueN(n);

    stream.write(
      value.utf8_str(),
      strlen(value.utf8_str()) + 1);
    }

  return true;
}

template<typename ValueT>
bool WriteDenseArrayBinary(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkDenseArray<ValueT>* const concrete_array = vtkDenseArray<ValueT>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-dense-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array values ...
  stream.write(
    reinterpret_cast<char*>(concrete_array->GetStorage()),
    concrete_array->GetNonNullSize() * sizeof(ValueT));

  return true;
}

template<>
bool WriteDenseArrayBinary<vtkStdString>(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkDenseArray<vtkStdString>* const concrete_array = vtkDenseArray<vtkStdString>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-dense-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array values as a set of packed, NULL-terminated strings ...
  const vtkIdType value_count = array->GetNonNullSize();
  for(vtkIdType n = 0; n != value_count; ++n)
    {
    const vtkStdString& value = concrete_array->GetValueN(n);

    stream.write(
      value.c_str(),
      value.size() + 1);
    }

  return true;
}

template<>
bool WriteDenseArrayBinary<vtkUnicodeString>(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkDenseArray<vtkUnicodeString>* const concrete_array = vtkDenseArray<vtkUnicodeString>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the array header ...
  WriteHeader("vtk-dense-array", type_name, array, stream, true);
  WriteEndianOrderMark(stream);

  // Serialize the array values as a set of packed, NULL-terminated strings ...
  const vtkIdType value_count = array->GetNonNullSize();
  for(vtkIdType n = 0; n != value_count; ++n)
    {
    const vtkUnicodeString& value = concrete_array->GetValueN(n);

    stream.write(
      value.utf8_str(),
      strlen(value.utf8_str()) + 1);
    }

  return true;
}

template<typename ValueT>
bool WriteSparseArrayAscii(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkSparseArray<ValueT>* const concrete_array = vtkSparseArray<ValueT>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the header ...
  WriteHeader("vtk-sparse-array", type_name, array, stream, false);

  // Ensure that floating-point types are serialized with full precision
  if(std::numeric_limits<ValueT>::is_specialized)
    stream.precision(std::numeric_limits<ValueT>::digits10 + 1);

  // Write the array NULL value ...
  WriteValue(stream, concrete_array->GetNullValue());
  stream << "\n";

  // Write the array contents ...
  const vtkIdType dimensions = array->GetDimensions();
  const vtkIdType non_null_size = array->GetNonNullSize();

  vtkArrayCoordinates coordinates;
  for(vtkIdType n = 0; n != non_null_size; ++n)
    {
    array->GetCoordinatesN(n, coordinates);
    for(vtkIdType i = 0; i != dimensions; ++i)
      stream << coordinates[i] << " ";
    WriteValue(stream, concrete_array->GetValueN(n));
    stream << "\n";
    }

  return true;
}

template<typename ValueT>
bool WriteDenseArrayAscii(const vtkStdString& type_name, vtkArray* array, ostream& stream)
{
  vtkDenseArray<ValueT>* const concrete_array = vtkDenseArray<ValueT>::SafeDownCast(array);
  if(!concrete_array)
    return false;

  // Write the header ...
  WriteHeader("vtk-dense-array", type_name, array, stream, false);

  // Write the array contents ...
  const vtkArrayExtents extents = array->GetExtents();

  // Ensure that floating-point types are serialized with full precision
  if(std::numeric_limits<ValueT>::is_specialized)
    stream.precision(std::numeric_limits<ValueT>::digits10 + 1);

  vtkArrayCoordinates coordinates;
  for(vtkArrayExtents::SizeT n = 0; n != extents.GetSize(); ++n)
    {
    extents.GetRightToLeftCoordinatesN(n, coordinates);
    WriteValue(stream, concrete_array->GetValue(coordinates));
    stream << "\n";
    }

  return true;
}

} // End anonymous namespace

vtkStandardNewMacro(vtkArrayWriter);

vtkArrayWriter::vtkArrayWriter() :
  FileName(0),
  Binary(false),
  WriteToOutputString(false)
{
}

vtkArrayWriter::~vtkArrayWriter()
{
  this->SetFileName(0);
}

void vtkArrayWriter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "FileName: " << (this->FileName ? this->FileName : "(none)") << endl;
  os << indent << "Binary: " << this->Binary << endl;
  os << indent << "WriteToOutputString: " << (this->WriteToOutputString ? "on" : "off") << endl;
  os << indent << "OutputString: " << this->OutputString << endl;
}

int vtkArrayWriter::FillInputPortInformation( int vtkNotUsed(port), vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkArrayData");
  return 1;
}

void vtkArrayWriter::WriteData()
{
  if(this->WriteToOutputString)
    {
    this->OutputString = this->Write(this->Binary > 0 ? true : false);
    }
  else
    {
    this->Write(this->FileName ? this->FileName : "", this->Binary > 0 ? true : false);
    }
}

int vtkArrayWriter::Write()
{
  return Superclass::Write();
}

bool vtkArrayWriter::Write(const vtkStdString& file_name, bool WriteBinary)
{
  ofstream file(file_name.c_str(), std::ios::binary);
  return this->Write(file, WriteBinary);
}

bool vtkArrayWriter::Write(vtkArray* array, const vtkStdString& file_name, bool WriteBinary)
{
  ofstream file(file_name.c_str(), std::ios::binary);
  return vtkArrayWriter::Write(array, file, WriteBinary);
}

bool vtkArrayWriter::Write(ostream& stream, bool WriteBinary)
{
  try
    {
    if(this->GetNumberOfInputConnections(0) != 1)
      throw std::runtime_error("Exactly one input required.");

    vtkArrayData* const array_data = vtkArrayData::SafeDownCast(this->GetExecutive()->GetInputData(0, 0));
    if(!array_data)
      throw std::runtime_error("vtkArrayData input required.");

    if(array_data->GetNumberOfArrays() != 1)
      throw std::runtime_error("vtkArrayData with exactly one array required.");

    vtkArray* const array = array_data->GetArray(static_cast<vtkIdType>(0));
    if(!array)
      throw std::runtime_error("Cannot serialize NULL vtkArray.");

    return this->Write(array, stream, WriteBinary);
    }
  catch(std::exception& e)
    {
    vtkErrorMacro("caught exception: " << e.what());
    }
  return false;
}

bool vtkArrayWriter::Write(vtkArray* array, ostream& stream, bool WriteBinary)
{
  try
    {
    if(!array)
      throw std::runtime_error("Cannot serialize NULL vtkArray.");

    if(WriteBinary)
      {
      if(WriteSparseArrayBinary<vtkIdType>("integer", array, stream)) return true;
      if(WriteSparseArrayBinary<double>("double", array, stream)) return true;
      if(WriteSparseArrayBinary<vtkStdString>("string", array, stream)) return true;
      if(WriteSparseArrayBinary<vtkUnicodeString>("unicode-string", array, stream)) return true;

      if(WriteDenseArrayBinary<vtkIdType>("integer", array, stream)) return true;
      if(WriteDenseArrayBinary<double>("double", array, stream)) return true;
      if(WriteDenseArrayBinary<vtkStdString>("string", array, stream)) return true;
      if(WriteDenseArrayBinary<vtkUnicodeString>("unicode-string", array, stream)) return true;
      }
    else
      {
      if(WriteSparseArrayAscii<vtkIdType>("integer", array, stream)) return true;
      if(WriteSparseArrayAscii<double>("double", array, stream)) return true;
      if(WriteSparseArrayAscii<vtkStdString>("string", array, stream)) return true;
      if(WriteSparseArrayAscii<vtkUnicodeString>("unicode-string", array, stream)) return true;

      if(WriteDenseArrayAscii<vtkIdType>("integer", array, stream)) return true;
      if(WriteDenseArrayAscii<double>("double", array, stream)) return true;
      if(WriteDenseArrayAscii<vtkStdString>("string", array, stream)) return true;
      if(WriteDenseArrayAscii<vtkUnicodeString>("unicode-string", array, stream)) return true;
      }

    throw std::runtime_error(std::string("Unhandled array type: ") + array->GetClassName());
    }
  catch(std::exception& e)
    {
    vtkGenericWarningMacro("caught exception: " << e.what());
    }
  return false;
}

vtkStdString vtkArrayWriter::Write(bool WriteBinary)
{
  std::ostringstream oss;
  this->Write(oss, WriteBinary);
  return oss.str();
}

vtkStdString vtkArrayWriter::Write(vtkArray* array, bool WriteBinary)
{
  std::ostringstream oss;
  vtkArrayWriter::Write(array, oss, WriteBinary);
  return oss.str();
}
