//=========================================================================
//  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 "vtkSceneGenVegetationClusterReader.h"

#include "vtkCellArray.h"
#include "vtkCharArray.h"
#include "vtkDoubleArray.h"
#include "vtkErrorCode.h"
#include "vtkGlyph3D.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLongArray.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkStringArray.h"
#include "vtkStripper.h"
#include "vtkTransform.h"
#include "vtkTransformPolyDataFilter.h"
#
#include "smtk/extension/vtk/reader/vtkCMBGeometryReader.h"
#include <sys/stat.h>
#include <sys/types.h>

vtkStandardNewMacro(vtkSceneGenVegetationClusterReader);

struct ModelInstance
{
  std::string ID;
  double Scale;
  double ZRotation;
  double Translation[3];
};

vtkSceneGenVegetationClusterReader::vtkSceneGenVegetationClusterReader()
{
  this->FileName = 0;
  this->SetNumberOfInputPorts(0);
  this->MetWindHeight = -1;
  this->StartSimTime = -1;
  this->EndSimTime = -1;
  this->InputFluxFile = -1;
}

vtkSceneGenVegetationClusterReader::~vtkSceneGenVegetationClusterReader()
{
  this->SetFileName(0);
  this->ClearModel();
}

int vtkSceneGenVegetationClusterReader::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

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

  ifstream fin(this->FileName);
  if (!fin)
  {
    vtkErrorMacro(<< "File " << this->FileName << " not found");
    return 0;
  }

  char buffer[2048];
  this->ClearModel();

  std::map<std::string, VegetationModel>::iterator iterModel;

  std::string cmd, vegID;
  std::vector<ModelInstance> instances;
  while (!fin.eof())
  {
    fin >> cmd;

    if (cmd[0] == '#')
    {
      fin.getline(buffer, 2047); // finish reading the current line
    }

    if (cmd == "MODELS")
    {
      std::string fileName;
      int numModels;
      fin >> numModels;
      for (int i = 0; i < numModels; i++)
      {
        fin >> vegID >> fileName;
        iterModel = this->Models.find(vegID);
        if (iterModel != this->Models.end())
        {
          // leaf was specified 1st??? probably don't need to do this check!
          iterModel->second.FileName = fileName;
        }
        else
        {
          VegetationModel vegModel;
          vegModel.FileName = fileName;
          this->Models[vegID] = vegModel;
        }
      }
    }

    if (cmd == "LEAFSIZE" || cmd == "LEAF_SIZE")
    {
      double leafSize;
      int numLeafSizes;
      fin >> numLeafSizes;
      for (int i = 0; i < numLeafSizes; i++)
      {
        fin >> vegID >> leafSize;
        iterModel = this->Models.find(vegID);
        if (iterModel != this->Models.end())
        {
          iterModel->second.LeafSize = leafSize;
        }
        else
        {
          VegetationModel vegModel;
          vegModel.LeafSize = leafSize;
          this->Models[vegID] = vegModel;
        }
      }
    }

    if (cmd == "MATERIALCODE" || cmd == "MATERIAL_CODE")
    {
      long material;
      int numMaterials;
      fin >> numMaterials;
      for (int i = 0; i < numMaterials; i++)
      {
        fin >> vegID >> material;
        iterModel = this->Models.find(vegID);
        if (iterModel != this->Models.end())
        {
          iterModel->second.Material = material;
        }
        else
        {
          VegetationModel vegModel;
          vegModel.Material = material;
          this->Models[vegID] = vegModel;
        }
      }
    }

    if (cmd == "NODEFILE")
    {
      fin >> this->NodeFile;
    }

    if (cmd == "ENSIGHT_NODEFILE")
    {
      fin >> this->EnsightNodeFile;
    }

    if (cmd == "ENSIGHT_STOMATAL")
    {
      std::string name;
      this->EnsightStomatal.push_back(name);
    }

    if (cmd == "MET_FILE")
    {
      fin >> this->MetFile;
    }

    if (cmd == "START_SIM_TIME")
    {
      fin >> this->StartSimTime;
    }

    if (cmd == "END_SIM_TIME")
    {
      fin >> this->EndSimTime;
    }

    if (cmd == "MET_WIND_HEIGHT")
    {
      fin >> this->MetWindHeight;
    }

    if (cmd == "OUTPUT_MESH")
    {
      fin >> this->OutputMesh;
    }

    if (cmd == "ENSIGHT_OUTPUT_MESH")
    {
      fin >> this->EnsightOutputMesh;
    }

    if (cmd == "INPUT_FLUX_FILE")
    {
      fin >> this->InputFluxFile;
    }

    if (cmd == "INSTANCE")
    {
      int numInstances;
      fin >> numInstances;

      for (int i = 0; i < numInstances; i++)
      {
        ModelInstance instance;
        fin >> instance.ID >> instance.Scale >> instance.ZRotation >> instance.Translation[0] >>
          instance.Translation[1] >> instance.Translation[2];
        instances.push_back(instance);
      }
    }
    cmd.clear();
  }
  fin.close();

  // add field data that applies to all blocks
  if (this->NodeFile.size())
  {
    vtkNew<vtkStringArray> nodefileFD;
    nodefileFD->SetName("NodeFile");
    nodefileFD->InsertNextValue(this->NodeFile);
    output->GetFieldData()->AddArray(nodefileFD.GetPointer());
  }
  if (this->EnsightNodeFile.size())
  {
    vtkNew<vtkStringArray> nodefileFD;
    nodefileFD->SetName("EnsightNodeFile");
    nodefileFD->InsertNextValue(this->EnsightNodeFile);
    output->GetFieldData()->AddArray(nodefileFD.GetPointer());
  }
  if (this->MetFile.size())
  {
    vtkNew<vtkStringArray> metFileFD;
    metFileFD->SetName("MetFile");
    metFileFD->InsertNextValue(this->MetFile);
    output->GetFieldData()->AddArray(metFileFD.GetPointer());
  }
  if (this->OutputMesh.size())
  {
    vtkNew<vtkStringArray> meshFD;
    meshFD->SetName("OutputMesh");
    meshFD->InsertNextValue(this->OutputMesh);
    output->GetFieldData()->AddArray(meshFD.GetPointer());
  }
  if (this->EnsightOutputMesh.size())
  {
    vtkNew<vtkStringArray> meshFD;
    meshFD->SetName("EnsightOutputMesh");
    meshFD->InsertNextValue(this->EnsightOutputMesh);
    output->GetFieldData()->AddArray(meshFD.GetPointer());
  }
  if (this->MetWindHeight >= 0)
  {
    vtkNew<vtkDoubleArray> metWindHeightFD;
    metWindHeightFD->SetName("MetWindHeight");
    metWindHeightFD->InsertNextValue(this->MetWindHeight);
    output->GetFieldData()->AddArray(metWindHeightFD.GetPointer());
  }
  if (this->StartSimTime >= 0)
  {
    vtkNew<vtkLongArray> startSimTimeFD;
    startSimTimeFD->SetName("StartSimTime");
    startSimTimeFD->InsertNextValue(this->StartSimTime);
    output->GetFieldData()->AddArray(startSimTimeFD.GetPointer());
  }
  if (this->EndSimTime >= 0)
  {
    vtkNew<vtkLongArray> endSimTimeFD;
    endSimTimeFD->SetName("EndSimTime");
    endSimTimeFD->InsertNextValue(this->EndSimTime);
    output->GetFieldData()->AddArray(endSimTimeFD.GetPointer());
  }
  if (this->InputFluxFile >= 0)
  {
    vtkNew<vtkLongArray> inputFluxFileFD;
    inputFluxFileFD->SetName("InputFluxFile");
    inputFluxFileFD->InsertNextValue(this->InputFluxFile);
    output->GetFieldData()->AddArray(inputFluxFileFD.GetPointer());
  }
  if (this->EnsightStomatal.size())
  {
    // I've had some iffy expereiences with vtkStringArray when more than one
    // string; thus using vtkCharArray, which is less elegant
    vtkNew<vtkCharArray> ensightStomatalFD;
    ensightStomatalFD->SetName("ensightStomatalFD");
    size_t maxSize = 0;
    for (size_t i = 0; i < this->EnsightStomatal.size(); i++)
    {
      if (this->EnsightStomatal[i].size() > maxSize)
      {
        maxSize = this->EnsightStomatal[i].size();
      }
    }
    ensightStomatalFD->SetNumberOfComponents(static_cast<int>(maxSize + 1));
    ensightStomatalFD->SetNumberOfTuples(this->EnsightStomatal.size());
    for (int i = 0; i < static_cast<int>(this->EnsightStomatal.size()); i++)
    {
      ensightStomatalFD->SetTypedTuple(i, this->EnsightStomatal[i].c_str());
    }
    output->GetFieldData()->AddArray(ensightStomatalFD.GetPointer());
  }

  // now add a block for each instance
  double color[3] = { 0.2, 0.4, 0.2 };
  double pnt[3];
  for (std::vector<ModelInstance>::iterator i = instances.begin(); i != instances.end(); i++)
  {
    iterModel = this->Models.find(i->ID);
    if (iterModel == this->Models.end())
    {
      vtkErrorMacro("Veg instance not found: " << i->ID);
      continue;
    }
    // Is the model loaded?
    if (!iterModel->second.Dataset)
    {
      vtkNew<vtkCMBGeometryReader> reader;
      reader->SetFileName(iterModel->second.FileName.c_str());
      reader->Update();
      iterModel->second.Dataset = vtkPolyData::New();
      if (reader->GetErrorCode() == vtkErrorCode::NoError)
      {
        iterModel->second.Dataset->ShallowCopy(reader->GetOutput());
      }
      iterModel->second.Plants = vtkPolyData::New();
      vtkPoints* points = vtkPoints::New();
      points->SetDataTypeToDouble();
      iterModel->second.Plants->SetPoints(points);
      points->Delete();
    }
    iterModel->second.Scale += i->Scale;
    pnt[0] = i->Translation[0];
    pnt[1] = i->Translation[1];
    pnt[2] = i->Translation[2];
    iterModel->second.Plants->GetPoints()->InsertNextPoint(pnt);
  }

  for (std::map<std::string, VegetationModel>::iterator iter = this->Models.begin();
       iter != this->Models.end(); iter++)
  {
    if (iter->second.Dataset)
    {
      this->AddBlock(output, iter->second, color);
    }
  }
  return 1;
}

int vtkSceneGenVegetationClusterReader::AddBlock(
  vtkMultiBlockDataSet* output, VegetationModel& model, double color[3])
{
  // create the transform of the plant - note we need to do an average for the scale
  double scale = model.Scale / static_cast<double>(model.Plants->GetPoints()->GetNumberOfPoints());
  vtkNew<vtkTransform> transform;
  transform->PreMultiply();
  transform->RotateX(90);
  transform->Scale(scale, scale, scale);
  vtkNew<vtkTransformPolyDataFilter> trans;
  trans->SetInputData(model.Dataset);
  trans->SetTransform(transform.GetPointer());

  // Create TriStrips of the plant
  vtkNew<vtkStripper> stripper;
  stripper->SetInputConnection(trans->GetOutputPort());
  // Lets create a glyph filter for the instances (of the same geometry)
  vtkNew<vtkGlyph3D> glyph;
  glyph->SetInputData(model.Plants);
  glyph->SetSourceConnection(stripper->GetOutputPort());
  glyph->Update();
  vtkNew<vtkPolyData> block;
  // may resuse the geometry, but the field data (transformation) will be different
  block->ShallowCopy(glyph->GetOutput());
  output->SetBlock(output->GetNumberOfBlocks(), block.GetPointer());
  double dummy[3];
  dummy[0] = dummy[1] = dummy[2] = 0.0;
  vtkNew<vtkDoubleArray> translationFD;
  translationFD->SetName("Translation");
  translationFD->SetNumberOfComponents(3);
  translationFD->InsertNextTuple(dummy);
  block->GetFieldData()->AddArray(translationFD.GetPointer());

  vtkNew<vtkDoubleArray> rotationFD;
  rotationFD->SetName("Rotation");
  rotationFD->SetNumberOfComponents(3);
  rotationFD->InsertNextTuple3(0, 0, 0);
  block->GetFieldData()->AddArray(rotationFD.GetPointer());

  //orientoccluder[ito].RotationEuler(0.0, 0.0, 90.0);  //plants are sideways
  vtkNew<vtkDoubleArray> scaleFD;
  scaleFD->SetName("Scale");
  scaleFD->SetNumberOfComponents(1);
  scaleFD->InsertNextValue(1.0);
  block->GetFieldData()->AddArray(scaleFD.GetPointer());

  vtkNew<vtkDoubleArray> colorFD;
  colorFD->SetName("Color");
  colorFD->SetNumberOfComponents(3);
  colorFD->InsertNextTuple(color);
  block->GetFieldData()->AddArray(colorFD.GetPointer());

  vtkNew<vtkDoubleArray> transformFD;
  transformFD->SetName("Transformation");
  transformFD->SetNumberOfComponents(16);
  transform->Identity();
  double transformData[16];
  vtkMatrix4x4::DeepCopy(transformData, transform->GetMatrix());
  transformFD->InsertNextTuple(transformData);
  block->GetFieldData()->AddArray(transformFD.GetPointer());

  return VTK_OK;
}

void vtkSceneGenVegetationClusterReader::ClearModel()
{
  for (std::map<std::string, VegetationModel>::iterator i = this->Models.begin();
       i != this->Models.end(); i++)
  {
    if (i->second.Dataset)
    {
      i->second.Dataset->Delete();
      i->second.Dataset = 0;
    }
    if (i->second.Plants)
    {
      i->second.Plants->Delete();
      i->second.Plants = 0;
    }
  }
  this->Models.clear();
  this->EnsightStomatal.clear();
}

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

  os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << "\n";
}

int vtkSceneGenVegetationClusterReader::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* vtkNotUsed(outputVector))
{
  if (!this->FileName)
  {
    vtkErrorMacro("FileName has to be specified!");
    return 0;
  }

  return 1;
}
