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

#include "vtkDataArray.h"
#include "vtkDataSetAttributes.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkSetGet.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkTable.h"
#include <string>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkTemporalDelimitedTextReader);

//------------------------------------------------------------------------------
vtkTemporalDelimitedTextReader::vtkTemporalDelimitedTextReader()
{
  this->DetectNumericColumnsOn();
}

//------------------------------------------------------------------------------
void vtkTemporalDelimitedTextReader::SetTimeColumnName(std::string name)
{
  if (this->TimeColumnName != name)
  {
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting TimeColumnName to "
                  << name);
    this->TimeColumnName = name;
    this->InternalModified();
  }
}

//------------------------------------------------------------------------------
void vtkTemporalDelimitedTextReader::SetTimeColumnId(int idx)
{
  if (idx != this->TimeColumnId)
  {
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting TimeColumnId to" << idx);
    this->TimeColumnId = idx;
    this->InternalModified();
  }
}

//------------------------------------------------------------------------------
void vtkTemporalDelimitedTextReader::SetRemoveTimeStepColumn(bool rts)
{
  if (rts != this->RemoveTimeStepColumn)
  {
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting RemoveTimeStepColumn to "
                  << rts);
    this->RemoveTimeStepColumn = rts;
    this->InternalModified();
  }
}

//------------------------------------------------------------------------------
vtkMTimeType vtkTemporalDelimitedTextReader::GetMTime()
{
  return std::max(this->MTime, this->InternalMTime);
}

//------------------------------------------------------------------------------
int vtkTemporalDelimitedTextReader::RequestInformation(
  vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  if (strlen(this->FieldDelimiterCharacters) == 0)
  {
    // This reader does not give any output as long as the
    // FieldDelimiterCharacters is not set by the user as we need to parse the
    // input file in the RequestInformation to set the time range (which
    // requires the FieldDelimiterCharacters).
    return 1;
  }

  if (this->MTime > this->LastReadTime)
  {
    // fill the ReadTable with the actual input
    // only if modified has been called
    this->ReadTable->Initialize();
    this->ReadData(this->ReadTable);
    this->LastReadTime = this->GetMTime();
  }

  if (!this->EnforceColumnName())
  {
    // Bad user input
    return 0;
  }

  if (this->InternalColumnName.empty())
  {
    // Output the whole input data, not temporal
    return this->Superclass::RequestInformation(request, inputVector, outputVector);
  }

  // Store each line id in the TimeMap, at the given time step
  vtkDataArray* inputColumn =
    vtkDataArray::SafeDownCast(this->ReadTable->GetColumnByName(this->InternalColumnName.c_str()));
  const vtkIdType nbRows = inputColumn->GetNumberOfTuples();
  this->TimeMap.clear();
  for (vtkIdType r = 0; r < nbRows; r++)
  {
    double v = inputColumn->GetTuple1(r);
    if (vtkMath::IsNan(v))
    {
      vtkWarningMacro("The time step indicator column has a nan value at line: " << r);
    }
    else
    {
      this->TimeMap[v].emplace_back(r);
    }
  }

  // Get the time range (first and last key of the TimeMap)
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  double timeRange[2] = { this->TimeMap.cbegin()->first, this->TimeMap.crbegin()->first };
  outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), timeRange, 2);

  // Get the discrete time steps from the TimeMap keys
  std::vector<double> timeStepsArray;
  timeStepsArray.reserve(this->TimeMap.size());
  for (const auto& mapEl : this->TimeMap)
  {
    timeStepsArray.emplace_back(mapEl.first);
  }
  const int nbTimeSteps = static_cast<int>(timeStepsArray.size());
  outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), timeStepsArray.data(), nbTimeSteps);

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

//------------------------------------------------------------------------------
int vtkTemporalDelimitedTextReader::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  if (strlen(this->FieldDelimiterCharacters) == 0)
  {
    vtkErrorMacro(
      "You need to set the FieldDelimiterCharacters before requesting data with this reader");
    return 0;
  }

  if (!this->EnforceColumnName())
  {
    vtkErrorMacro("Invalid user input for the Time step indicator.");
    return 0;
  }

  if (this->InternalColumnName.empty())
  {
    // Shallow copy the internal reader's output as the time column
    // has not been set
    vtkTable* outputTable = vtkTable::GetData(outputVector, 0);
    outputTable->ShallowCopy(this->ReadTable);
    this->UpdateProgress(1);
    return 1;
  }

  vtkDebugMacro(<< this->GetClassName() << " (" << this << "): process column "
                << this->InternalColumnName);

  // Retrieve the current time step
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  double updateTimeStep = 0;
  if (outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()))
  {
    updateTimeStep = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP());
  }

  this->UpdateProgress(0.5);

  if (!this->TimeMap.empty())
  {
    // Generate an empty output with the same structure
    vtkTable* outputTable = vtkTable::GetData(outputVector, 0);
    vtkDataSetAttributes* outAttributes = outputTable->GetRowData();
    auto timeStepDataIt = this->TimeMap.lower_bound(updateTimeStep);
    if (timeStepDataIt == this->TimeMap.end())
    {
      // update time is too high, take last element.
      --timeStepDataIt;
    }
    vtkIdType nbRow = static_cast<vtkIdType>(timeStepDataIt->second.size());
    outAttributes->CopyAllocate(this->ReadTable->GetRowData(), nbRow);
    for (auto r : timeStepDataIt->second)
    {
      outputTable->InsertNextRow(this->ReadTable->GetRow(r));
    }

    // Get rid of the time column in the result
    if (this->RemoveTimeStepColumn)
    {
      outputTable->RemoveColumnByName(this->InternalColumnName.c_str());
    }
  }

  this->UpdateProgress(1);

  return 1;
}

//------------------------------------------------------------------------------
bool vtkTemporalDelimitedTextReader::EnforceColumnName()
{
  this->InternalColumnName = "";

  if (this->TimeColumnName.empty() && this->TimeColumnId == -1)
  {
    // No user specified input, the reader simply output the whole content of
    // the input file.
    return true;
  }

  // Set TimeColumnName from user input
  if (this->TimeColumnId != -1)
  {
    // use id to retrieve column
    if (this->TimeColumnId >= 0 && this->TimeColumnId < this->ReadTable->GetNumberOfColumns())
    {
      this->InternalColumnName = this->ReadTable->GetColumnName(this->TimeColumnId);
    }
    else
    {
      vtkErrorMacro("Invalid column id: " << this->TimeColumnId);
      return false;
    }
  }
  else if (!this->TimeColumnName.empty())
  {
    // use name to retrieve column
    vtkAbstractArray* arr = this->ReadTable->GetColumnByName(this->TimeColumnName.c_str());
    if (arr == nullptr)
    {
      vtkErrorMacro("Invalid column name: " << this->TimeColumnName);
      return false;
    }
    else
    {
      // check valid array
      vtkDataArray* numArr = vtkDataArray::SafeDownCast(arr);
      if (numArr == nullptr)
      {
        vtkErrorMacro("Not a numerical column: " << this->TimeColumnName);
        return false;
      }
      else if (numArr->GetNumberOfComponents() != 1)
      {
        vtkErrorMacro("The time column must have only one component: " << this->TimeColumnName);
        return false;
      }
    }
    this->InternalColumnName = this->TimeColumnName;
  }

  return true;
}

//------------------------------------------------------------------------------
void vtkTemporalDelimitedTextReader::InternalModified()
{
  this->InternalMTime.Modified();
}

//------------------------------------------------------------------------------
void vtkTemporalDelimitedTextReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << "TimeColumnName: " << this->TimeColumnName << endl;
  os << "TimeColumnId: " << this->TimeColumnId << endl;
  os << "RemoveTimeStepColumn: " << this->RemoveTimeStepColumn << endl;
}
VTK_ABI_NAMESPACE_END
