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

  Program:   ParaView
  Module:    vtkPlot3DMetaReader.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkPlot3DMetaReader.h"

#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkMultiBlockPLOT3DReader.h"
#include "vtkObjectFactory.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include <vtksys/SystemTools.hxx>

#include <map>
#include <string>
#include <vector>

#include "vtk_jsoncpp.h"

#define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember))

vtkStandardNewMacro(vtkPlot3DMetaReader);

typedef void (vtkPlot3DMetaReader::*Plot3DFunction)(Json::Value* val);

struct Plot3DTimeStep
{
  double Time;
  std::string XYZFile;
  std::string QFile;
  std::string FunctionFile;
};

struct vtkPlot3DMetaReaderInternals
{
  std::map<std::string, Plot3DFunction> FunctionMap;
  std::vector<Plot3DTimeStep> TimeSteps;

  std::string ResolveFileName(const std::string& metaFileName,
                              std::string fileName)
  {
      if (vtksys::SystemTools::FileIsFullPath(fileName.c_str()))
      {
        return fileName;
      }
      else
      {
        std::string path = vtksys::SystemTools::GetFilenamePath(metaFileName);
        std::vector<std::string> components;
        components.push_back(path + "/");
        components.push_back(fileName);
        return vtksys::SystemTools::JoinPath(components);
      }
  }
};

//-----------------------------------------------------------------------------
vtkPlot3DMetaReader::vtkPlot3DMetaReader()
{
  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(1);

  this->Reader = vtkMultiBlockPLOT3DReader::New();
  this->Reader->AutoDetectFormatOn();

  this->FileName = nullptr;

  this->Internal = new vtkPlot3DMetaReaderInternals;

  this->Internal->FunctionMap["auto-detect-format"] =
    &vtkPlot3DMetaReader::SetAutoDetectFormat;
  this->Internal->FunctionMap["byte-order"] =
    &vtkPlot3DMetaReader::SetByteOrder;
  this->Internal->FunctionMap["precision"] =
    &vtkPlot3DMetaReader::SetPrecision;
  this->Internal->FunctionMap["multi-grid"] =
    &vtkPlot3DMetaReader::SetMultiGrid;
  this->Internal->FunctionMap["format"] =
    &vtkPlot3DMetaReader::SetFormat;
  this->Internal->FunctionMap["blanking"] =
    &vtkPlot3DMetaReader::SetBlanking;
  this->Internal->FunctionMap["language"] =
    &vtkPlot3DMetaReader::SetLanguage;
  this->Internal->FunctionMap["2D"] =
    &vtkPlot3DMetaReader::Set2D;
  this->Internal->FunctionMap["R"] =
    &vtkPlot3DMetaReader::SetR;
  this->Internal->FunctionMap["gamma"] =
    &vtkPlot3DMetaReader::SetGamma;
  this->Internal->FunctionMap["filenames"] =
    &vtkPlot3DMetaReader::SetFileNames;
  this->Internal->FunctionMap["functions"] =
    &vtkPlot3DMetaReader::AddFunctions;
  this->Internal->FunctionMap["function-names"] =
    &vtkPlot3DMetaReader::SetFunctionNames;
}

//-----------------------------------------------------------------------------
vtkPlot3DMetaReader::~vtkPlot3DMetaReader()
{
  this->Reader->Delete();

  delete this->Internal;

  delete[] this->FileName;
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetAutoDetectFormat(Json::Value* val)
{
  bool value = val->asBool();
  if (value)
  {
    this->Reader->AutoDetectFormatOn();
  }
  else
  {
    this->Reader->AutoDetectFormatOff();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetByteOrder(Json::Value* val)
{
  std::string value = val->asString();
  if (value == "little")
  {
    this->Reader->SetByteOrderToLittleEndian();
  }
  else if (value == "big")
  {
    this->Reader->SetByteOrderToBigEndian();
  }
  else
  {
    vtkErrorMacro("Unrecognized byte order: " <<
                  value.c_str()<< ". Valid options are \"little\" and \"big\"."
                  " Setting to little endian");
    this->Reader->SetByteOrderToLittleEndian();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetLanguage(Json::Value* val)
{
  std::string value = val->asString();
  if (value == "fortran")
  {
    this->Reader->HasByteCountOn();
  }
  else if (value == "C")
  {
    this->Reader->HasByteCountOff();
  }
  else
  {
    vtkErrorMacro("Unrecognized language: " <<
                  value.c_str()<< ". Valid options are \"fortran\" and \"C\"."
                  " Setting to little fortran");
    this->Reader->HasByteCountOn();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetPrecision(Json::Value* val)
{
  int value = val->asInt();
  if (value == 32)
  {
    this->Reader->DoublePrecisionOff();
  }
  else if (value == 64)
  {
    this->Reader->DoublePrecisionOn();
  }
  else
  {
    vtkErrorMacro("Unrecognized precision: " <<
                  value << ". Valid options are 32 and 64 (bits)."
                  " Setting to 32 bits");
    this->Reader->DoublePrecisionOff();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetMultiGrid(Json::Value* val)
{
  bool value = val->asBool();
  if (value)
  {
    this->Reader->MultiGridOn();
  }
  else
  {
    this->Reader->MultiGridOff();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetFormat(Json::Value* val)
{
  std::string value = val->asString();
  if (value == "binary")
  {
    this->Reader->BinaryFileOn();
  }
  else if (value == "ascii")
  {
    this->Reader->BinaryFileOff();
  }
  else
  {
    vtkErrorMacro("Unrecognized file type: " <<
                  value.c_str()<< ". Valid options are \"binary\" and \"ascii\"."
                  " Setting to binary");
    this->Reader->BinaryFileOn();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetBlanking(Json::Value* val)
{
  bool value = val->asBool();
  if (value)
  {
    this->Reader->IBlankingOn();
  }
  else
  {
    this->Reader->IBlankingOff();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::Set2D(Json::Value* val)
{
  bool value = val->asBool();
  if (value)
  {
    this->Reader->TwoDimensionalGeometryOn();
  }
  else
  {
    this->Reader->TwoDimensionalGeometryOff();
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetR(Json::Value* val)
{
  double R = val->asDouble();
  this->Reader->SetR(R);
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetGamma(Json::Value* val)
{
  double gamma = val->asDouble();
  this->Reader->SetGamma(gamma);
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::AddFunctions(Json::Value* val)
{
  const Json::Value& functions = *val;
  for ( size_t index = 0; index < functions.size(); ++index )
  {
    this->Reader->AddFunction(functions[(int)index].asInt());
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetFileNames(Json::Value* val)
{
  const Json::Value& filenames = *val;
  for ( size_t index = 0; index < filenames.size(); ++index )
  {
    const Json::Value& astep = filenames[(int)index];
    bool doAdd = true;
    Plot3DTimeStep aTime;
    if (!astep.isMember("time"))
    {
      vtkErrorMacro("Missing time value in timestep " << index);
      doAdd = false;
    }
    else
    {
      aTime.Time = astep["time"].asDouble();
    }

    if (!astep.isMember("xyz"))
    {
      vtkErrorMacro("Missing xyz filename in timestep " << index);
      doAdd = false;
    }
    else
    {
      std::string xyzfile = astep["xyz"].asString();
      aTime.XYZFile = this->Internal->ResolveFileName(this->FileName,
                                                      xyzfile);
    }

    if (astep.isMember("q"))
    {
      std::string qfile = astep["q"].asString();
      aTime.QFile = this->Internal->ResolveFileName(this->FileName,
                                                    qfile);
    }

    if (astep.isMember("function"))
    {
      std::string functionfile = astep["function"].asString();
      aTime.FunctionFile = this->Internal->ResolveFileName(this->FileName,
                                                           functionfile);
    }

    if (doAdd)
    {
      this->Internal->TimeSteps.push_back(aTime);
    }
  }
}

//-----------------------------------------------------------------------------
void vtkPlot3DMetaReader::SetFunctionNames(Json::Value* val)
{
  const Json::Value& functionNames = *val;
  for ( size_t index = 0; index < functionNames.size(); ++index )
  {
    this->Reader->AddFunctionName(functionNames[(int)index].asString());
  }
}

//----------------------------------------------------------------------------
int vtkPlot3DMetaReader::RequestInformation(
  vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector),
  vtkInformationVector* outputVector)
{
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  outInfo->Set(vtkAlgorithm::CAN_HANDLE_PIECE_REQUEST(), 1);

  this->Internal->TimeSteps.clear();

  this->Reader->RemoveAllFunctions();

  if (!this->FileName)
  {
    vtkErrorMacro("No file name was specified. Cannot execute.");
    return 0;
  }

  ifstream file(this->FileName);

  Json::CharReaderBuilder rbuilder;
  rbuilder["collectComments"] = true;

  Json::Value root;
  std::string formattedErrorMessages;

  bool parsingSuccessful = Json::parseFromStream(rbuilder, file, &root, &formattedErrorMessages);

  if (!parsingSuccessful)
  {
    // report to the user the failure and their locations in the document.
    vtkErrorMacro("Failed to parse configuration\n"
                  << formattedErrorMessages);
    return 0;
  }

  Json::Value::Members members = root.getMemberNames();
  Json::Value::Members::iterator memberIterator;
  for (memberIterator = members.begin();
       memberIterator != members.end();
       ++memberIterator)
  {
    std::map<std::string, Plot3DFunction>::iterator iter =
      this->Internal->FunctionMap.find(*memberIterator);
    if (iter != this->Internal->FunctionMap.end())
    {
      Json::Value val = root[*memberIterator];
      Plot3DFunction func = iter->second;
      CALL_MEMBER_FN(*this, func)(&val);
    }
    else
    {
      vtkErrorMacro("Syntax error in file. Option \""
                    << memberIterator->c_str()
                    << "\" is not valid.");
    }
  }

  std::vector<Plot3DTimeStep>::iterator iter;
  std::vector<double> timeValues;
  for (iter  = this->Internal->TimeSteps.begin();
       iter != this->Internal->TimeSteps.end();
       ++iter)
  {
    timeValues.push_back(iter->Time);
  }

  size_t numSteps = timeValues.size();
  if (numSteps > 0)
  {
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(),
                 &timeValues[0],
                 (int)numSteps);

    double timeRange[2];
    timeRange[0] = timeValues[0];
    timeRange[1] = timeValues[numSteps - 1];
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(),
                 timeRange,
                 2);
  }

  return 1;
}

//----------------------------------------------------------------------------
int vtkPlot3DMetaReader::RequestData(
  vtkInformation*,
  vtkInformationVector**,
  vtkInformationVector* outputVector)
{
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkMultiBlockDataSet* output = vtkMultiBlockDataSet::GetData(
    outputVector->GetInformationObject(0));

  double timeValue = 0;
  if (outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()))
  {
    // Get the requested time step. We only support requests of a single time
    // step in this reader right now
    timeValue =
      outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP());
  }

  int tsLength =
    outInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
  double *steps =
    outInfo->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS());

  if (tsLength < 1)
  {
    vtkErrorMacro("No timesteps were found. Please specify at least one "
                  "filenames entry in the input file.");
    return 0;
  }


  // find the first time value larger than requested time value
  // this logic could be improved
  int cnt = 0;
  while (cnt < tsLength-1 && steps[cnt] < timeValue)
  {
    cnt++;
  }

  int updateTime = cnt;

  if (updateTime >= tsLength)
  {
    updateTime = tsLength - 1;
  }

  if (tsLength > updateTime)
  {
    this->Reader->SetXYZFileName(
      this->Internal->TimeSteps[updateTime].XYZFile.c_str());
    const char* qname = this->Internal->TimeSteps[updateTime].QFile.c_str();
    if (strlen(qname) > 0)
    {
      this->Reader->SetQFileName(qname);
    }
    else
    {
      this->Reader->SetQFileName(nullptr);
    }
    const char* fname =
      this->Internal->TimeSteps[updateTime].FunctionFile.c_str();
    if (strlen(fname) > 0)
    {
      this->Reader->SetFunctionFileName(fname);
    }
    else
    {
      this->Reader->SetFunctionFileName(nullptr);
    }
    this->Reader->UpdatePiece(
      outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()),
      outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()),
      outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS())
      );
    vtkDataObject* ioutput = this->Reader->GetOutput();
    output->ShallowCopy(ioutput);
    output->GetInformation()->Set(
      vtkDataObject::DATA_NUMBER_OF_GHOST_LEVELS(),
      ioutput->GetInformation()->Get(
        vtkDataObject::DATA_NUMBER_OF_GHOST_LEVELS()));
  }
  else
  {
    vtkErrorMacro("Time step " << updateTime << " was not found.");
    return 0;
  }

  return 1;
}

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