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

#include "vtkArrayReader.h"

#include "vtkArrayData.h"
#include "vtkCommand.h"
#include "vtkDenseArray.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkSparseArray.h"
#include "vtksys/FStream.hxx"

#include <sstream>
#include <stdexcept>
#include <string>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkArrayReader);

namespace
{

template <typename ValueT>
void ExtractValue(istream& stream, ValueT& value)
{
  stream >> value;
}

void ExtractValue(istream& stream, vtkStdString& value)
{
  std::getline(stream, value);
  vtkStdString::size_type begin, end;
  begin = 0;
  end = value.size();
  while ((begin < end) && isspace(value[begin]))
    begin++;
  while ((begin < end) && isspace(value[end - 1]))
    end--;
  value = value.substr(begin, end);
}

void ReadHeader(
  istream& stream, vtkArrayExtents& extents, vtkArrayExtents::SizeT& non_null_size, vtkArray* array)
{
  if (!array)
    throw std::runtime_error("Missing array.");

  // Load the array name ...
  std::string name;
  std::getline(stream, name);
  array->SetName(name);

  // Load array extents ...
  std::string extents_string;
  std::getline(stream, extents_string);
  std::istringstream extents_buffer(extents_string);

  vtkArrayExtents::CoordinateT extent;
  std::vector<vtkArrayExtents::CoordinateT> temp_extents;
  for (extents_buffer >> extent; extents_buffer; extents_buffer >> extent)
    temp_extents.push_back(extent);

  extents.SetDimensions(0);
  while (temp_extents.size() > 1)
  {
    const vtkArrayExtents::CoordinateT begin = temp_extents.front();
    temp_extents.erase(temp_extents.begin());
    const vtkArrayExtents::CoordinateT end = temp_extents.front();
    temp_extents.erase(temp_extents.begin());
    extents.Append(vtkArrayRange(begin, end));
  }

  if (extents.GetDimensions() < 1)
    throw std::runtime_error("Array cannot have fewer than one dimension.");

  if (temp_extents.empty())
    throw std::runtime_error("Missing non null size.");

  non_null_size = temp_extents.back();

  array->Resize(extents);

  // Load dimension-labels ...
  for (vtkArrayExtents::DimensionT i = 0; i != extents.GetDimensions(); ++i)
  {
    std::string label;
    std::getline(stream, label);
    array->SetDimensionLabel(i, label);
  }
}

void ReadEndianOrderMark(istream& stream, bool& swap_endian)
{
  // Load the endian-order mark ...
  vtkTypeUInt32 endian_order = 0;
  stream.read(reinterpret_cast<char*>(&endian_order), sizeof(endian_order));

  swap_endian = endian_order != 0x12345678;
}

template <typename ValueT>
vtkSparseArray<ValueT>* ReadSparseArrayBinary(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkSparseArray<ValueT>> array = vtkSmartPointer<vtkSparseArray<ValueT>>::New();

  // Read the file header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  bool swap_endian = false;
  ReadHeader(stream, extents, non_null_size, array);
  ReadEndianOrderMark(stream, swap_endian);

  // Read the array nullptr value ...
  ValueT null_value;
  stream.read(reinterpret_cast<char*>(&null_value), sizeof(ValueT));
  array->SetNullValue(null_value);

  // Read array coordinates ...
  array->ReserveStorage(non_null_size);

  for (vtkArray::DimensionT i = 0; i != array->GetDimensions(); ++i)
  {
    stream.read(reinterpret_cast<char*>(array->GetCoordinateStorage(i)),
      non_null_size * sizeof(vtkArray::CoordinateT));
  }

  // Read array values ...
  stream.read(reinterpret_cast<char*>(array->GetValueStorage()), non_null_size * sizeof(ValueT));

  array->Register(nullptr);
  return array;
}

template <>
vtkSparseArray<vtkStdString>* ReadSparseArrayBinary<vtkStdString>(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkSparseArray<vtkStdString>> array =
    vtkSmartPointer<vtkSparseArray<vtkStdString>>::New();

  // Read the file header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  bool swap_endian = false;
  ReadHeader(stream, extents, non_null_size, array);
  ReadEndianOrderMark(stream, swap_endian);

  // Read the array nullptr value ...
  std::string null_value;
  for (int character = stream.get(); stream; character = stream.get())
  {
    if (character == 0)
    {
      array->SetNullValue(null_value);
      break;
    }
    else
    {
      null_value += static_cast<char>(character);
    }
  }

  // Read array coordinates ...
  array->ReserveStorage(non_null_size);

  for (vtkArray::DimensionT i = 0; i != array->GetDimensions(); ++i)
  {
    stream.read(reinterpret_cast<char*>(array->GetCoordinateStorage(i)),
      non_null_size * sizeof(vtkArray::CoordinateT));
  }

  // Read array values ...
  std::string buffer;
  vtkArray::SizeT n = 0;
  for (int character = stream.get(); stream; character = stream.get())
  {
    if (character == 0)
    {
      array->SetValueN(n++, buffer);
      buffer.resize(0);
    }
    else
    {
      buffer += static_cast<char>(character);
    }
  }

  array->Register(nullptr);
  return array;
}

template <typename ValueT>
vtkDenseArray<ValueT>* ReadDenseArrayBinary(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkDenseArray<ValueT>> array = vtkSmartPointer<vtkDenseArray<ValueT>>::New();

  // Read the file header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  bool swap_endian = false;
  ReadHeader(stream, extents, non_null_size, array);
  ReadEndianOrderMark(stream, swap_endian);

  // Read array values ...
  stream.read(reinterpret_cast<char*>(array->GetStorage()), non_null_size * sizeof(ValueT));

  if (stream.eof())
    throw std::runtime_error("Premature end-of-file.");
  if (stream.bad())
    throw std::runtime_error("Error while reading file.");

  array->Register(nullptr);
  return array;
}

template <>
vtkDenseArray<vtkStdString>* ReadDenseArrayBinary<vtkStdString>(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkDenseArray<vtkStdString>> array =
    vtkSmartPointer<vtkDenseArray<vtkStdString>>::New();

  // Read the file header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  bool swap_endian = false;
  ReadHeader(stream, extents, non_null_size, array);
  ReadEndianOrderMark(stream, swap_endian);

  // Read array values ...
  std::string buffer;
  vtkArray::SizeT n = 0;
  for (int character = stream.get(); stream; character = stream.get())
  {
    if (character == 0)
    {
      array->SetValueN(n++, buffer);
      buffer.resize(0);
    }
    else
    {
      buffer += static_cast<char>(character);
    }
  }

  array->Register(nullptr);
  return array;
}

template <typename ValueT>
vtkSparseArray<ValueT>* ReadSparseArrayAscii(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkSparseArray<ValueT>> array = vtkSmartPointer<vtkSparseArray<ValueT>>::New();

  // Read the stream header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  ReadHeader(stream, extents, non_null_size, array);

  if (non_null_size > extents.GetSize())
    throw std::runtime_error("Too many values for a sparse array.");

  // Read the array nullptr value ...
  std::string line_buffer;
  std::getline(stream, line_buffer);
  if (!stream)
    throw std::runtime_error("Premature end-of-stream reading nullptr value.");

  std::istringstream line_stream(line_buffer);
  ValueT null_value;
  ExtractValue(line_stream, null_value);
  if (!line_stream)
    throw std::runtime_error("Missing nullptr value.");
  array->SetNullValue(null_value);

  // Setup storage for the stream contents ...
  array->ReserveStorage(non_null_size);
  std::vector<vtkArray::CoordinateT*> coordinates(array->GetDimensions());
  for (vtkArray::DimensionT j = 0; j != array->GetDimensions(); ++j)
    coordinates[j] = array->GetCoordinateStorage(j);
  ValueT* value = array->GetValueStorage();

  // Read the stream contents ...
  vtkArray::SizeT value_count = 0;
  for (; value_count < non_null_size; ++value_count)
  {
    std::getline(stream, line_buffer);

    if (!stream)
      break;

    line_stream.clear();
    line_stream.str(line_buffer);

    for (vtkArray::DimensionT j = 0; j != array->GetDimensions(); ++j)
    {
      line_stream >> *(coordinates[j] + value_count);
      if (!extents[j].Contains(*(coordinates[j] + value_count)))
        throw std::runtime_error("Coordinate out-of-bounds.");
      if (!line_stream)
        throw std::runtime_error("Missing coordinate.");
    }

    ExtractValue(line_stream, *(value + value_count));
    if (!line_stream)
      throw std::runtime_error("Missing value.");
  }

  // Ensure we loaded enough values ...
  if (value_count != non_null_size)
    throw std::runtime_error("Stream doesn't contain enough values.");

  array->Register(nullptr);
  return array;
}

template <typename ValueT>
vtkDenseArray<ValueT>* ReadDenseArrayAscii(istream& stream)
{
  // Create the array ...
  vtkSmartPointer<vtkDenseArray<ValueT>> array = vtkSmartPointer<vtkDenseArray<ValueT>>::New();

  // Read the file header ...
  vtkArrayExtents extents;
  vtkArrayExtents::SizeT non_null_size = 0;
  ReadHeader(stream, extents, non_null_size, array);

  if (non_null_size != extents.GetSize())
    throw std::runtime_error("Incorrect number of values for a dense array.");

  // Read the file contents ...
  ValueT value;
  vtkArray::SizeT n = 0;
  vtkArrayCoordinates coordinates;
  for (; n < non_null_size; ++n)
  {
    ExtractValue(stream, value);
    if (!stream)
      break;
    extents.GetRightToLeftCoordinatesN(n, coordinates);
    array->SetValue(coordinates, value);
  }

  if (n != non_null_size)
    throw std::runtime_error("Stream doesn't contain enough values.");

  // If there is more in the stream (e.g. in vtkArrayDataReader),
  // eat the newline so the stream is ready for the next vtkArray.
  if (stream)
  {
    stream.get();
  }

  array->Register(nullptr);
  return array;
}

} // End anonymous namespace

vtkArrayReader::vtkArrayReader()
  : FileName(nullptr)
{
  this->SetNumberOfInputPorts(0);
  this->ReadFromInputString = false;
}

vtkArrayReader::~vtkArrayReader()
{
  this->SetFileName(nullptr);
}

void vtkArrayReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "FileName: " << (this->FileName ? this->FileName : "(none)") << endl;
  os << indent << "InputString: " << this->InputString << endl;
  os << indent << "ReadFromInputString: " << (this->ReadFromInputString ? "on" : "off") << endl;
}

void vtkArrayReader::SetInputString(const vtkStdString& string)
{
  this->InputString = string;
  this->Modified();
}

vtkStdString vtkArrayReader::GetInputString()
{
  return this->InputString;
}

int vtkArrayReader::RequestData(
  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
{
  try
  {
    vtkArray* array = nullptr;
    if (this->ReadFromInputString)
    {
      array = vtkArrayReader::Read(this->InputString);
    }
    else
    {
      if (!this->FileName)
        throw std::runtime_error("FileName not set.");

      vtksys::ifstream file(this->FileName, std::ios::binary);

      array = vtkArrayReader::Read(file);
    }
    if (!array)
      throw std::runtime_error("Error reading array.");

    vtkArrayData* const array_data = vtkArrayData::GetData(outputVector);
    array_data->ClearArrays();
    array_data->AddArray(array);
    array->Delete();

    return 1;
  }
  catch (std::exception& e)
  {
    vtkErrorMacro(<< e.what());
  }

  return 0;
}

vtkArray* vtkArrayReader::Read(const vtkStdString& str)
{
  std::istringstream iss(str);
  return vtkArrayReader::Read(iss);
}

vtkArray* vtkArrayReader::Read(istream& stream)
{
  try
  {
    // Read enough of the file header to identify the type ...
    std::string header_string;
    std::getline(stream, header_string);
    std::istringstream header_buffer(header_string);

    std::string header_magic;
    std::string header_type;
    header_buffer >> header_magic >> header_type;

    // Read input file type, binary or ascii
    std::string header_file_string;
    std::string header_file_type;
    std::getline(stream, header_file_string);
    std::istringstream header_file_type_buffer(header_file_string);
    header_file_type_buffer >> header_file_type;

    bool read_binary = false;
    if (header_file_type == "binary")
    {
      read_binary = true;
    }
    else if (header_file_type != "ascii")
    {
      throw std::runtime_error("Unknown file type: " + header_file_type);
    }

    if (header_magic == "vtk-sparse-array")
    {
      if (header_type == "integer")
      {
        return (read_binary ? ReadSparseArrayBinary<vtkIdType>(stream)
                            : ReadSparseArrayAscii<vtkIdType>(stream));
      }
      else if (header_type == "double")
      {
        return (read_binary ? ReadSparseArrayBinary<double>(stream)
                            : ReadSparseArrayAscii<double>(stream));
      }
      else if ((header_type == "string") || header_type == "unicode-string")
      {
        return (read_binary ? ReadSparseArrayBinary<vtkStdString>(stream)
                            : ReadSparseArrayAscii<vtkStdString>(stream));
      }
      else
      {
        throw std::runtime_error("Unknown array type: " + header_type);
      }
    }
    else if (header_magic == "vtk-dense-array")
    {
      if (header_type == "integer")
      {
        return (read_binary ? ReadDenseArrayBinary<vtkIdType>(stream)
                            : ReadDenseArrayAscii<vtkIdType>(stream));
      }
      else if (header_type == "double")
      {
        return (
          read_binary ? ReadDenseArrayBinary<double>(stream) : ReadDenseArrayAscii<double>(stream));
      }
      else if ((header_type == "string") || (header_type == "unicode-string"))
      {
        return (read_binary ? ReadDenseArrayBinary<vtkStdString>(stream)
                            : ReadDenseArrayAscii<vtkStdString>(stream));
      }
      else
      {
        throw std::runtime_error("Unknown array type: " + header_type);
      }
    }
    else
    {
      throw std::runtime_error("Unknown file type: " + header_magic);
    }
  }
  catch (std::exception& e)
  {
    vtkGenericWarningMacro(<< e.what());
  }

  return nullptr;
}
VTK_ABI_NAMESPACE_END
