//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt 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 "vtkCMBICMReader.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCellType.h"
#include "vtkErrorCode.h"
#include "vtkFieldData.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLine.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringArray.h"
#include "vtkUnstructuredGrid.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <vtksys/SystemTools.hxx>
#include "smtk/extension/vtk/reader/vtkCMBReaderHelperFunctions.h"


using namespace ReaderHelperFunctions;

vtkStandardNewMacro(vtkCMBICMReader);
//=============================================================================
vtkCMBICMReader::vtkCMBICMReader()
{
  //By default data is to be read in long/lat and
  //East will be facing the positive direction
  this->DataIsLatLong = false;
  this->OldDataIsLatLong = true;

  this->DataIsPositiveEast = true;
  this->OldDataIsPositiveEast = false;

  this->FileName = NULL;
  this->DataFileName = NULL;
  this->OldFileName = NULL;
  this->OldDataFileName = NULL;
  this->SetNumberOfInputPorts(0);
  this->NumberOfTimeSteps = 0;
  this->TimeSteps = NULL;
  this->points = NULL;
  this->vectors = NULL;
  this->HexCells = NULL;
}
//-----------------------------------------------------------------------------
vtkCMBICMReader::~vtkCMBICMReader()
{
  this->SetFileName(0);
  this->SetDataFileName(0);
  if(this->points)
    {
    this->points->Delete();
    }
  if(this->vectors)
    {
    this->vectors->Delete();
    }
  if(this->HexCells)
    {
    this->HexCells->Delete();
    }
  if (this->TimeSteps)
    {
    delete [] this->TimeSteps;
    this->TimeSteps = 0;
    this->NumberOfTimeSteps = 0;
    }
}
//-----------------------------------------------------------------------------
int vtkCMBICMReader::ReadTemporalData(vtkInformationVector* vtkNotUsed(outputVector))
{
  //Help the user get the full path to the temporal data
  if (!this->DataFileName || strcmp(this->DataFileName,"") == 0)
    {
    //If no data file is specified use a default name
    this->SetDataFileName((vtksys::SystemTools::GetFilenamePath(this->FileName)+
          "/"+vtksys::SystemTools::GetFilenameWithoutExtension(this->FileName)+
          "-timedata.dat").c_str());
    }
  if(vtksys::SystemTools::GetFilenameExtension(this->DataFileName)=="")
    {
    this->SetDataFileName((std::string(this->DataFileName)+".dat").c_str());
    }
  if(vtksys::SystemTools::GetFilenamePath(this->DataFileName) == "")
    {
    this->SetDataFileName((vtksys::SystemTools::GetFilenamePath(this->FileName)+"/"+this->DataFileName).c_str());
    }
  std::string dataFileNameStr = this->DataFileName;
  struct stat dfs;
  if (stat(dataFileNameStr.c_str(), &dfs) != 0)
    {
    vtkErrorMacro("Unable to open file: "<< dataFileNameStr);
    this->SetErrorCode( vtkErrorCode::CannotOpenFileError );
    return 0;
    }
  /*Setup variables*/
  std::ifstream file(dataFileNameStr.c_str(), std::ios::in | std::ios::binary );
  std::stringstream line(std::stringstream::in | std::stringstream::out);

  //Read in data
  if(!file)
    {
    vtkErrorMacro("Unable to open file: " << dataFileNameStr);
    return 0;
    }

  this->UpdateProgress(0);
  file.seekg(0,ios::end);
  double numBytes = file.tellg();
  file.clear();
  file.seekg(0,ios::beg);

  this->TimeStepRange[0] = 0;
  std::vector<double> TimeSteps_vector;

  while(readNextLine(file,line))
    {
    float current_time;
    line >> current_time;
    TimeSteps_vector.push_back(current_time);
    vtkSmartPointer<vtkFloatArray> cellData = vtkSmartPointer<vtkFloatArray>::New();
    cellData->SetNumberOfComponents(1);
    cellData->Allocate((this->NumberOfCells*50),(this->NumberOfCells));

    //each timestep should have numPoints/8 in it
    for(int i = 0; i < (this->NumberOfCells); i++)
      {
      if(!readNextLine(file,line))
        {
        vtkErrorMacro("Incorrect number of lines in " << dataFileNameStr << "\n  num_lines must be multiple of: " << (this->NumberOfCells)+1 );
        }
      float val;
      line >> val;
      cellData->InsertTuple1(i,val);
      }


    cellData->SetName(vtksys::SystemTools::GetFilenameWithoutExtension(this->DataFileName).c_str());
    this->CellData.push_back(cellData);
    this->UpdateProgress(static_cast<double>(file.tellg())/static_cast<double>(numBytes));
    }
  this->NumberOfTimeSteps = static_cast<int>(TimeSteps_vector.size());
  if(this->TimeSteps)
    {
    delete []this->TimeSteps;
    }
  this->TimeSteps = new double[this->NumberOfTimeSteps];
  std::copy(TimeSteps_vector.begin(), TimeSteps_vector.end(), this->TimeSteps);
  this->TimeStepRange[0] = this->TimeSteps[0];
  this->TimeStepRange[1] = this->TimeSteps[this->NumberOfTimeSteps-1];
  this->TimeStep = 0;
  this->TimeValue = this->TimeSteps[0];
  file.close();
  return 1;
}
//-----------------------------------------------------------------------------
int vtkCMBICMReader::ReadGeometryData(vtkInformationVector *vtkNotUsed(outputVector))
{
  std::string fileNameStr = this->FileName;
  struct stat fs;
  if (stat(fileNameStr.c_str(), &fs) != 0)
    {
    vtkErrorMacro("Unable to open file: "<< fileNameStr);
    this->SetErrorCode( vtkErrorCode::CannotOpenFileError );
    return 0;
    }
  /*Setup variables*/
  std::ifstream file(fileNameStr.c_str(), ios::in | ios::binary );
  std::stringstream line(std::stringstream::in | std::stringstream::out);
  if(!file)
    {
    vtkErrorMacro("Unable to open file: " << fileNameStr);
    return 1;
    }

  this->UpdateProgress(0);
  file.seekg(0,ios::end);
  double numBytes = file.tellg();
  double numBytes2 = numBytes * 2;//*2 because the file will be read twice
  file.clear();
  file.seekg(0,ios::beg);
  //Read File

  //Starting First Pass
  // This file is an ide followed by 8
  // pieces of point data
  // idnum
  // id1 lon lat z lonj lati k
  // id2 lon lat z lonj lati k
  // id3 lon lat z lonj lati k
  // id4 lon lat z lonj lati k
  // id5 lon lat z lonj lati k
  // id6 lon lat z lonj lati k
  // id7 lon lat z lonj lati k
  // id8 lon lat z lonj lati k
  while(readNextLine(file,line))
    {
    for(int count = 0; count < 8 ; count++)
      {
      if(!readNextLine(file,line)){vtkErrorMacro("Incorrect file format: " << fileNameStr);}
      this->NumberOfPoints++;
      }
    this->UpdateProgress(static_cast<double>(file.tellg())/static_cast<double>(numBytes2));
    }
  this->NumberOfCells = this->NumberOfPoints/8;
  file.clear();
  file.seekg(0,ios::beg);
  if(!file)
    {
    vtkErrorMacro("Unable to open file: " << fileNameStr);
    return 1;
    }

  //Start Second Pass
  this->points->SetNumberOfPoints(this->NumberOfPoints);
  this->vectors->SetNumberOfComponents(3);
  this->vectors->SetNumberOfTuples(this->NumberOfPoints);
  this->HexCells->Allocate(this->NumberOfCells);

  vtkIdType pointId = 0;
  while(readNextLine(file,line))
    {
    //skip over box number
    vtkSmartPointer<vtkHexahedron> hex = vtkSmartPointer<vtkHexahedron>::New();
    for(int count = 0; count < 8 ; count++)
      {
      //By default read in long/lat data
      float lon, lat, z;
      float lonj,lati,k;
      int unused;
      if(!readNextLine(file,line)){ vtkErrorMacro("Incorrect file format: " << fileNameStr);}
      line >> unused >> lon >> lat >> z >> lonj >> lati >> k;
      if(this->DataIsLatLong)
        {
        //If the data being passed in is lat/long swap values
        std::swap(lat, lon);
        std::swap(lati,lonj);
        }
      if(!this->DataIsPositiveEast)
        {
        //Longitude will be negated if positive is west
        lon = -lon;
        lonj = -lonj;
        }

      this->points->SetPoint(pointId,lon,lat,z);
      hex->GetPointIds()->SetId(count,pointId);
      this->vectors->SetTuple3(pointId,lonj,lati,k);
      pointId++;
      }
    this->HexCells->InsertNextCell(hex);
    //Numbytes2 is twice as big as the acutal number of bytes in the file
    //This is the second time reading the file so add an offset
    this->UpdateProgress((static_cast<double>(file.tellg())+numBytes)/static_cast<double>(numBytes2));
    }
  //Clean up pass 2
  file.close();
  return 1;
}
//-----------------------------------------------------------------------------
  int vtkCMBICMReader::RequestData(
      vtkInformation *vtkNotUsed(request),
      vtkInformationVector **vtkNotUsed(inputVector),
      vtkInformationVector *outputVector)
{
  // get the info object
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the ouptut
  vtkUnstructuredGrid *output = vtkUnstructuredGrid::SafeDownCast(
      outInfo->Get(vtkDataObject::DATA_OBJECT()));

  if(!output)
    {
    return 0;
    }

  //Set up geometry and read in timesteps if
  //it hasn't been done yet or the file names
  //changed
  if(!this->OldFileName ||
      strcmp(this->FileName, this->OldFileName) != 0 ||
      this->OldDataIsLatLong != this->DataIsLatLong ||
      this->OldDataIsPositiveEast != this->DataIsPositiveEast )
    {
    //Clean up old data
    if(this->points)
      {
      this->points->Delete();
      }
    if(this->vectors)
      {
      this->vectors->Delete();
      }
    if(this->HexCells)
      {
      this->HexCells->Delete();
      }

    this->NumberOfPoints = 0;
    this->NumberOfCells = 0;
    this->points = vtkPoints::New();
    this->vectors = vtkFloatArray::New();
    this->vectors->SetName(vtksys::SystemTools::GetFilenameWithoutExtension(this->FileName).c_str());
    this->HexCells = vtkCellArray::New();

    if(!ReadGeometryData(outputVector))
      {
      return 0;
      }
    //Don't reread geometry unless the filename changed
    this->SetOldFileName(this->FileName);
    this->OldDataIsLatLong = this->DataIsLatLong;
    this->OldDataIsPositiveEast = this->DataIsPositiveEast;
    //force rereading of temporal data
    this->OldDataFileName = 0;
    }
  if(!this->OldDataFileName || strcmp(this->DataFileName, this->OldDataFileName) != 0)
    {
    this->CellData.clear();
    if (this->TimeSteps)
      {
      delete [] this->TimeSteps;
      }
    this->TimeSteps = NULL;
    this->TimeStep = 0;
    this->NumberOfTimeSteps = 0;
    this->TimeStepRange[0] = 0;
    this->TimeStepRange[1] = 0;
    if(!ReadTemporalData(outputVector))
      {
      return 0;
      }
    this->SetOldDataFileName(this->DataFileName);
    }

  if(outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()))
    {
    this->TimeValue = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP());
    //convert TimeValue to TimeIndex
    for(int i = 0; i < this->NumberOfTimeSteps; i++)
      {
      if(this->TimeSteps[i] == this->TimeValue)
        {
        this->TimeStep = i;
        break;
        }
      }
    }
  //Setup out info
  outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), this->TimeSteps, this->NumberOfTimeSteps);
  outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(),this->TimeStepRange,2);
  //Apply geometry to output
  output->Allocate(this->NumberOfCells,1024);
  output->SetPoints(this->points);
  output->GetPointData()->SetVectors(this->vectors);
  output->SetCells(VTK_HEXAHEDRON,this->HexCells);
  //apply timestep to geometry
  output->GetCellData()->SetScalars(this->CellData[this->TimeStep]);
  return 1;
}
//-----------------------------------------------------------------------------
void vtkCMBICMReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  os << indent << "File Name: "
    << (this->FileName ? this->FileName : "(none)") << "\n";
}
//----------------------------------------------------------------------------
int vtkCMBICMReader::RequestInformation(
    vtkInformation * /*request*/,
    vtkInformationVector ** /*inputVector*/,
    vtkInformationVector *outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  if(this->TimeSteps)
    {
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), this->TimeSteps, this->NumberOfTimeSteps);
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(),this->TimeStepRange,2);
    }
  else
    {
    //Create fake timesteps if none exist yet
    //This makes sure paraview knows that this class has time fields
    this->TimeSteps = new double[1];
    this->NumberOfTimeSteps = 1;
    this->TimeSteps[0] = 0;
    this->TimeStepRange[0] = 0;
    this->TimeStepRange[1] = 0;
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), this->TimeSteps, this->NumberOfTimeSteps);
    outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(),this->TimeStepRange,2);
    }
  if (!this->FileName)
    {
    vtkErrorMacro("FileName has to be specified!");
    return 0;
    }
  return 1;
}
