#include "vtkEnsembleDataReader.h"

#include "vtkDataSetAttributes.h"
#include "vtkDelimitedTextReader.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkStdString.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringArray.h"
#include "vtkTable.h"

#include <algorithm>
#include <cassert>
#include <ctype.h>
#include <functional>
#include <vector>
#include <vtksys/SystemTools.hxx>

//-----------------------------------------------------------------------------
class vtkEnsembleDataReader::vtkInternal
{
public:
  typedef std::vector<vtkSmartPointer<vtkAlgorithm> > VectorOfReaders;
  VectorOfReaders Readers;

  std::vector<vtkStdString> FilePaths;
  vtkSmartPointer<vtkTable> MetaData;

  vtkTimeStamp ReadMetaDataMTime;
  std::string PreviousFileName;
};

vtkStandardNewMacro(vtkEnsembleDataReader);
//-----------------------------------------------------------------------------
vtkEnsembleDataReader::vtkEnsembleDataReader()
  : FileName(0)
  , CurrentMember(0),
  Internal(new vtkEnsembleDataReader::vtkInternal())
{
  this->CurrentMemberRange[0] = this->CurrentMemberRange[1] = 0;
  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(1);
}

//-----------------------------------------------------------------------------
vtkEnsembleDataReader::~vtkEnsembleDataReader()
{
  delete this->Internal;
  this->Internal = NULL;
}

//-----------------------------------------------------------------------------
unsigned int vtkEnsembleDataReader::GetNumberOfMembers() const
{
  return this->Internal->MetaData != NULL?
    static_cast<unsigned int>(this->Internal->FilePaths.size()) : 0;
}

//-----------------------------------------------------------------------------
vtkStdString vtkEnsembleDataReader::GetFilePath(unsigned int member) const
{
  if (member < static_cast<unsigned int>(this->Internal->FilePaths.size()))
    {
    return this->Internal->FilePaths[member];
    }
  return vtkStdString();
}

//-----------------------------------------------------------------------------
void vtkEnsembleDataReader::SetReader(unsigned int rowIndex, vtkAlgorithm *reader)
{
  bool modified = false;
  if (rowIndex >= static_cast<unsigned int>(this->Internal->Readers.size()))
    {
    this->Internal->Readers.resize(rowIndex + 1);
    modified = true;
    }
  if (modified || (this->Internal->Readers[rowIndex] != reader))
    {
    this->Internal->Readers[rowIndex] = reader;
    this->Modified();
    }
}

//-----------------------------------------------------------------------------
void vtkEnsembleDataReader::ResetReaders()
{
  if (this->Internal->Readers.size() > 0)
    {
    this->Internal->Readers.clear();
    this->Modified();
    }
}

//-----------------------------------------------------------------------------
int vtkEnsembleDataReader::ProcessRequest(vtkInformation *request,
  vtkInformationVector **inputVector, vtkInformationVector *outputVector)
{
  if (!this->FileName)
    {
    vtkErrorMacro("No filename is set!");
    return false;
    }

  if (this->Internal->Readers.size() == 0)
    {
    vtkErrorMacro("No reader have been specified! Things may not work as expected.");
    }

  // Read meta data if it hasn't already been read or if FileName has changed
  if (!this->UpdateMetaData())
    {
    return 0;
    }

  if (!this->Superclass::ProcessRequest(request, inputVector, outputVector))
    {
    return 0;
    }

  vtkAlgorithm *currentReader = this->GetCurrentReader();
  if (!currentReader)
    {
    vtkErrorMacro("Cannot determine reader to use for member: " << this->CurrentMember);
    return 0;
    }
  if (!currentReader->ProcessRequest(request, inputVector, outputVector))
    {
    return 0;
    }

  if (request->Has(vtkDemandDrivenPipeline::REQUEST_DATA()))
    {
    // Add ensemble information to the data object.
    vtkDataObject* output = vtkDataObject::GetData(outputVector, 0);
    vtkFieldData* outputFD = output->GetFieldData();

    // Use a temporary to attempt to preserve field data generated by the internal reader.
    vtkNew<vtkFieldData> tmp;
    tmp->CopyStructure(this->Internal->MetaData->GetRowData());
    tmp->InsertNextTuple(static_cast<vtkIdType>(this->CurrentMember),
      this->Internal->MetaData->GetRowData());
    for (int cc=0; cc < tmp->GetNumberOfArrays(); ++cc)
      {
      outputFD->AddArray(tmp->GetAbstractArray(cc));
      }
    }

  return 1;
}

//-----------------------------------------------------------------------------
// string trimmin'
static void ltrim(std::string &s)
{
  s.erase(s.begin(),
          std::find_if(s.begin(),
            s.end(),
            std::not1(std::ptr_fun<int, int>(isspace))));
}
static void rtrim(std::string &s)
{
  s.erase(std::find_if(s.rbegin(),
                       s.rend(),
                       std::not1(std::ptr_fun<int, int>(isspace))).base(),
          s.end());
}
static void trim(std::string &s)
{
  ltrim(s);
  rtrim(s);
}

//-----------------------------------------------------------------------------
vtkAlgorithm *vtkEnsembleDataReader::GetCurrentReader()
{
  if (this->CurrentMember >= static_cast<unsigned int>(this->Internal->Readers.size()))
    {
    return NULL;
    }
  return this->Internal->Readers[this->CurrentMember];
}

//-----------------------------------------------------------------------------
bool vtkEnsembleDataReader::UpdateMetaData()
{
  if (!this->FileName)
    {
    return false;
    }

  if (this->Internal->MetaData != NULL &&
      this->Internal->PreviousFileName == this->FileName)
    {
    return true;
    }

  if (this->GetMTime() < this->Internal->ReadMetaDataMTime)
    {
    // Nothing has changed since the last time we did this; no reason why the reading of meta-data
    // is going to succeed now.
    return false;
    }

  this->Internal->FilePaths.clear();
  this->Internal->MetaData = NULL;
  this->Internal->PreviousFileName = this->FileName;
  this->Internal->ReadMetaDataMTime.Modified();
  this->CurrentMemberRange[0] = this->CurrentMemberRange[1] = 0;

  // Read CSV -- Just pick sensible defaults for now
  vtkNew<vtkDelimitedTextReader> csvReader;
  assert(csvReader.GetPointer());
  csvReader->SetFieldDelimiterCharacters(",");
  csvReader->SetHaveHeaders(true);
  csvReader->SetDetectNumericColumns(true);
  csvReader->SetFileName(FileName);
  csvReader->Update();

  // Initialize a reader for each row in the CSV
  vtkTable *table = csvReader->GetOutput();
  assert(table);
  vtkIdType rowCount = table->GetNumberOfRows();
  vtkIdType columnCount = table->GetNumberOfColumns();
  if (rowCount <= 0 || columnCount <= 0)
    {
    vtkErrorMacro("Empty table loaded.");
    return false;
    }

  this->Internal->FilePaths.resize(rowCount);

  // Read the file names

  // For now, the last column contains a file path.
  vtkStringArray* fileColumn = vtkStringArray::SafeDownCast(table->GetColumn(columnCount - 1));
  if (!fileColumn)
    {
    vtkErrorMacro("The last column must contain file names.");
    return false;
    }

  std::string fdir = vtksys::SystemTools::GetFilenamePath(this->FileName);
  for (vtkIdType row = 0; row < rowCount; ++row)
    {
    vtkStdString filePath = fileColumn->GetValue(row);
    trim(filePath); // fileName may have leading & trailing whitespace

    std::vector<std::string> components;
    components.push_back(fdir);
    components.push_back(filePath);

    // Internal file paths should be relative to the .pve file
    std::string curFileName = fdir + "/" + filePath;
    this->Internal->FilePaths[row] = curFileName;
    }

  this->Internal->MetaData = table;

  this->CurrentMemberRange[1] = this->GetNumberOfMembers() > 0?
    (this->GetNumberOfMembers() - 1) : 0;
  return true;
}

//-----------------------------------------------------------------------------
void vtkEnsembleDataReader::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  // File name
  os << indent << "FileName: " << (this->FileName? this->FileName : "(none)") << endl;

  // Current member
  os << indent << "Current member: " << this->CurrentMember << endl;

  // Meta data
  os << indent << "MetaData: ";
  if (this->Internal->MetaData)
    {
    os << endl;
    this->Internal->MetaData->PrintSelf(os, indent.GetNextIndent());
    }
  else
    {
    os << "(none)" << endl;
    }
}
