// 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 "vtkRISReader.h"
#include "vtkCommand.h"
#include "vtkObjectFactory.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkVariant.h"
#include "vtksys/FStream.hxx"

#include <map>
#include <string>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkRISReader);

// Not all platforms support std::getline(istream&, std::string) so
// we have to provide our own
static istream& my_getline(istream& input, std::string& output, char delimiter = '\n');

//------------------------------------------------------------------------------

vtkRISReader::vtkRISReader()
  : FileName(nullptr)
  , Delimiter(nullptr)
  , MaxRecords(0)
{
  this->SetDelimiter(";");

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

//------------------------------------------------------------------------------

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

//------------------------------------------------------------------------------

void vtkRISReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "FileName: " << (this->FileName ? this->FileName : "(none)") << endl;
  os << indent << "Delimiter: " << (this->Delimiter ? this->Delimiter : "(none)") << endl;
  os << indent << "MaxRecords: " << this->MaxRecords << endl;
}

//------------------------------------------------------------------------------

int vtkRISReader::RequestData(
  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
{
  // Check that the filename has been specified
  if (!this->FileName)
  {
    vtkErrorMacro("vtkRISReader: You must specify a filename!");
    return 0;
  }

  // Open the file
  vtksys::ifstream file(this->FileName, ios::in | ios::binary);
  if (!file)
  {
    vtkErrorMacro(<< "vtkRISReader could not open file " << this->FileName);
    return 0;
  }

  // Get the total size of the file ...
  file.seekg(0, ios::end);
  const int total_bytes = file.tellg();

  // Go back to the top of the file
  file.seekg(0, ios::beg);

  // Store the text data into a vtkTable
  vtkTable* table = vtkTable::GetData(outputVector);

  // Keep a mapping of column-name to column-index for quick lookups ...
  std::map<std::string, vtkIdType> columns;

  std::string line_buffer;

  const std::string delimiter(this->Delimiter ? this->Delimiter : "");
  int record_count = 0;

  // For each record in the file ...
  for (my_getline(file, line_buffer); file; my_getline(file, line_buffer))
  {
    // Skip empty lines ...
    if (line_buffer.empty())
      continue;

    // Stop if we exceed the maximum number of records ...
    if (this->MaxRecords && record_count >= this->MaxRecords)
      break;

    double progress =
      total_bytes ? static_cast<double>(file.tellg()) / static_cast<double>(total_bytes) : 0.5;

    this->InvokeEvent(vtkCommand::ProgressEvent, &progress);

    // Add a new row to the table for the record ...
    table->InsertNextBlankRow();

    // For each field in the record ...
    for (; file;)
    {
      const std::string tag_type = line_buffer.size() >= 6 && line_buffer[2] == ' ' &&
          line_buffer[3] == ' ' && line_buffer[4] == '-' && line_buffer[5] == ' '
        ? line_buffer.substr(0, 2)
        : std::string();

      if (tag_type == "ER")
        break;

      std::string tag_value = line_buffer.size() > 6 ? line_buffer.substr(6) : std::string();

      // For each line in the field ...
      for (my_getline(file, line_buffer); file; my_getline(file, line_buffer))
      {
        const std::string next_tag_type = line_buffer.size() >= 6 && line_buffer[2] == ' ' &&
            line_buffer[3] == ' ' && line_buffer[4] == '-' && line_buffer[5] == ' '
          ? line_buffer.substr(0, 2)
          : std::string();

        if (next_tag_type == tag_type)
        {
          const std::string next_tag_value =
            line_buffer.size() > 6 ? line_buffer.substr(6) : std::string();
          tag_value += delimiter + next_tag_value;
        }
        else if (next_tag_type.empty())
        {
          tag_value += line_buffer;
        }
        else
        {
          break;
        }
      }

      // If necessary, add a new column to the table to store this value ...
      if (!columns.count(tag_type))
      {
        vtkStringArray* const new_column = vtkStringArray::New();
        new_column->SetName(tag_type.c_str());
        new_column->SetNumberOfTuples(record_count + 1);
        columns[tag_type] = table->GetNumberOfColumns();
        table->AddColumn(new_column);
        new_column->Delete();
      }

      // Set the table value ...
      table->SetValue(record_count, columns[tag_type], tag_value.c_str());
    }

    // Keep track of the current record count ...
    ++record_count;
  }
  /*
    // Loop through every line in the file ...
    std::string tag;
    std::string tag_type;
    std::string tag_value;
    bool explicit_tag;
    for(my_getline(file, line_buffer); file; my_getline(file, line_buffer))
      {
      if(this->MaxRecords && record_count >= this->MaxRecords)
        break;

      // Skip empty lines ...
      if(line_buffer.empty())
        continue;

      double progress = total_bytes
        ? static_cast<double>(file.tellg()) / static_cast<double>(total_bytes)
        : 0.5;

      this->InvokeEvent(vtkCommand::ProgressEvent, &progress);

      // Try to extract the tag, tag type, and tag value ...
      if(line_buffer.size() >= 6 && line_buffer[2] == ' ' && line_buffer[3] == ' ' && line_buffer[4]
    == '-' && line_buffer[5] == ' ')
        {
        explicit_tag = true;
        tag_type = line_buffer.substr(0, 2);
        tag_value = line_buffer.substr(6);
        }
      else
        {
        explicit_tag = false;
        tag_value = line_buffer;
        }

      // If this is the end of a record ...
      if(tag_type == "ER")
        {
        ++record_count;
        continue;
        }

      // If necessary, add a new row to the table to store this value ...
      while(table->GetNumberOfRows() < record_count + 1)
        table->InsertNextBlankRow();

      // If necessary, add a new column to the table to store this value ...
      if(!columns.count(tag_type))
        {
        vtkStringArray* const new_column = vtkStringArray::New();
        new_column->SetName(tag_type.c_str());
        new_column->SetNumberOfTuples(record_count + 1);
        columns[tag_type] = table->GetNumberOfColumns();
        table->AddColumn(new_column);
        new_column->Delete();
        }

      // Set the table value ...
      std::string old_value = table->GetValue(record_count, columns[tag_type]).ToString();
      if(old_value.empty())
        {
        table->SetValue(record_count, columns[tag_type], tag_value);
        }
      else
        {
        if(explicit_tag)
          {
          table->SetValue(record_count, columns[tag_type], (old_value + delimiter +
    tag_value).c_str());
          }
        else
          {
          table->SetValue(record_count, columns[tag_type], (old_value + " " + tag_value).c_str());
          }
        }
      }
  */

  return 1;
}

//------------------------------------------------------------------------------
static istream& my_getline(istream& input, std::string& output, char delimiter)
{
  output = "";

  unsigned int numCharactersRead = 0;
  int nextValue = 0;

  while ((nextValue = input.get()) != EOF && numCharactersRead < output.max_size())
  {
    ++numCharactersRead;

    char downcast = static_cast<char>(nextValue);
    if (downcast == delimiter || (delimiter == '\n' && downcast == '\r'))
    {
      if (delimiter == '\n' && downcast == '\r' && input.peek() == '\n')
      {
        input.get();
      }
      return input;
    }
    else
    {
      output += downcast;
    }
  }

  return input;
}
VTK_ABI_NAMESPACE_END
