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

  Program:   Visualization Toolkit
  Module:    vtkXMLHierarchicalBoxDataFileConverter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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 "vtkXMLHierarchicalBoxDataFileConverter.h"

#include "vtkBoundingBox.h"
#include "vtkImageData.h"
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLDataParser.h"
#include "vtkXMLImageDataReader.h"
#include "vtkStructuredData.h"
#include "vtksys/SystemTools.hxx"
#include "vtkExecutive.h"
#include "vtkInformation.h"

#include <string>
#include <set>
#include <map>
#include <vector>
#include <cassert>

vtkStandardNewMacro(vtkXMLHierarchicalBoxDataFileConverter);
//----------------------------------------------------------------------------
vtkXMLHierarchicalBoxDataFileConverter::vtkXMLHierarchicalBoxDataFileConverter()
{
  this->InputFileName = nullptr;
  this->OutputFileName = nullptr;
  this->FilePath = nullptr;
}

//----------------------------------------------------------------------------
vtkXMLHierarchicalBoxDataFileConverter::~vtkXMLHierarchicalBoxDataFileConverter()
{
  this->SetInputFileName(nullptr);
  this->SetOutputFileName(nullptr);
  this->SetFilePath(nullptr);
}

//----------------------------------------------------------------------------
bool vtkXMLHierarchicalBoxDataFileConverter::Convert()
{
  if (!this->InputFileName)
  {
    vtkErrorMacro("Missing InputFileName.");
    return false;
  }

  if (!this->OutputFileName)
  {
    vtkErrorMacro("Missing OutputFileName.");
    return false;
  }

  vtkSmartPointer<vtkXMLDataElement> dom;
  dom.TakeReference(this->ParseXML(this->InputFileName));
  if (!dom)
  {
    return false;
  }

  // Ensure this file we can convert.
  if (dom->GetName() == nullptr ||
    strcmp(dom->GetName(), "VTKFile") != 0 ||
    dom->GetAttribute("type") == nullptr ||
    strcmp(dom->GetAttribute("type"), "vtkHierarchicalBoxDataSet") != 0 ||
    dom->GetAttribute("version") == nullptr ||
    strcmp(dom->GetAttribute("version"), "1.0") != 0)
  {
    vtkErrorMacro("Cannot convert the input file: " << this->InputFileName);
    return false;
  }

  dom->SetAttribute("version", "1.1");
  dom->SetAttribute("type", "vtkOverlappingAMR");

  // locate primary element.
  vtkXMLDataElement* ePrimary =
    dom->FindNestedElementWithName("vtkHierarchicalBoxDataSet");
  if (!ePrimary)
  {
    vtkErrorMacro("Failed to locate primary element.");
    return false;
  }

  ePrimary->SetName("vtkOverlappingAMR");

  // Find the path to this file in case the internal files are
  // specified as relative paths.
  std::string filePath = this->InputFileName;
  std::string::size_type pos = filePath.find_last_of("/\\");
  if (pos != filePath.npos)
  {
    filePath = filePath.substr(0, pos);
  }
  else
  {
    filePath = "";
  }
  this->SetFilePath(filePath.c_str());

  // We need origin for level 0, and spacing for all levels.
  double origin[3];
  double *spacing = nullptr;

  int gridDescription = this->GetOriginAndSpacing(
    ePrimary, origin, spacing);
  if (gridDescription < VTK_XY_PLANE || gridDescription > VTK_XYZ_GRID)
  {
    delete [] spacing;
    vtkErrorMacro("Failed to determine origin/spacing/grid description.");
    return false;
  }

//  cout << "Origin: " << origin[0] <<", " << origin[1] << ", " << origin[2]
//    << endl;
//  cout << "Spacing: " << spacing[0] << ", " << spacing[1] << ", " << spacing[2]
//    << endl;

  const char* grid_description = "XYZ";
  switch (gridDescription)
  {
  case VTK_XY_PLANE:
    grid_description = "XY";
    break;
  case VTK_XZ_PLANE:
    grid_description = "XZ";
    break;
  case VTK_YZ_PLANE:
    grid_description = "YZ";
    break;
  }

  ePrimary->SetAttribute("grid_description", grid_description);
  ePrimary->SetVectorAttribute("origin", 3, origin);

  // Now iterate over all "<Block>" elements and update them.
  for (int cc=0; cc < ePrimary->GetNumberOfNestedElements(); cc++)
  {
    int level = 0;
    vtkXMLDataElement* block = ePrimary->GetNestedElement(cc);
    // iterate over all <DataSet> inside the current block
    // and replace the folder for the file attribute.
    for (int i = 0; i < block->GetNumberOfNestedElements(); ++i)
    {
      vtkXMLDataElement* dataset = block->GetNestedElement(i);
      std::string file(dataset->GetAttribute("file"));
      std::string fileNoDir (vtksys::SystemTools::GetFilenameName(file));
      std::string dir (
        vtksys::SystemTools::GetFilenameWithoutLastExtension(
          this->OutputFileName));
      dataset->SetAttribute("file", (dir + '/' + fileNoDir).c_str());
    }
    if (block && block->GetName() &&
      strcmp(block->GetName(), "Block") == 0 &&
      block->GetScalarAttribute("level", level) &&
      level >= 0)
    {
    }
    else
    {
      continue;
    }
    block->SetVectorAttribute("spacing", 3, &spacing[3*level]);
    block->RemoveAttribute("refinement_ratio");
  }
  delete [] spacing;

  // now save the xml out.
  dom->PrintXML(this->OutputFileName);
  return true;
}

//----------------------------------------------------------------------------
vtkXMLDataElement* vtkXMLHierarchicalBoxDataFileConverter::ParseXML(
  const char* fname)
{
  assert(fname);

  vtkNew<vtkXMLDataParser> parser;
  parser->SetFileName(fname);
  if (!parser->Parse())
  {
    vtkErrorMacro("Failed to parse input XML: " << fname);
    return nullptr;
  }

  vtkXMLDataElement* element = parser->GetRootElement();
  element->Register(this);
  return element;
}

//----------------------------------------------------------------------------
int vtkXMLHierarchicalBoxDataFileConverter::GetOriginAndSpacing(
  vtkXMLDataElement* ePrimary, double origin[3], double* &spacing)
{
  // Build list of filenames for all levels.
  std::map<int, std::set<std::string> > filenames;

  for (int cc=0; cc < ePrimary->GetNumberOfNestedElements(); cc++)
  {
    int level = 0;

    vtkXMLDataElement* child = ePrimary->GetNestedElement(cc);
    if (child && child->GetName() &&
      strcmp(child->GetName(), "Block") == 0 &&
      child->GetScalarAttribute("level", level) &&
      level >= 0)
    {
    }
    else
    {
      continue;
    }

    for (int kk=0; kk < child->GetNumberOfNestedElements(); kk++)
    {
      vtkXMLDataElement* dsElement = child->GetNestedElement(cc);
      if (dsElement && dsElement->GetName() &&
        strcmp(dsElement->GetName(), "DataSet") == 0 &&
        dsElement->GetAttribute("file") != nullptr)
      {
        std::string file = dsElement->GetAttribute("file");
        if (file.c_str()[0] != '/' && file.c_str()[1] != ':')
        {
          std::string prefix = this->FilePath;
          if (prefix.length())
          {
            prefix += "/";
          }
          file = prefix + file;
        }
        filenames[level].insert(file);
      }
    }
  }

  vtkBoundingBox bbox;
  int gridDescription = VTK_UNCHANGED;
  spacing = new double[3* filenames.size() + 1];
  memset(spacing, 0, (3*filenames.size() + 1)*sizeof(double));

  // Now read all the datasets at level 0.
  for (std::set<std::string>::iterator iter = filenames[0].begin();
    iter != filenames[0].end(); ++iter)
  {
    vtkNew<vtkXMLImageDataReader> imageReader;
    imageReader->SetFileName((*iter).c_str());
    imageReader->Update();

    vtkImageData* image = imageReader->GetOutput();
    if (image && vtkMath::AreBoundsInitialized(image->GetBounds()))
    {
      if (!bbox.IsValid())
      {
        gridDescription = vtkStructuredData::GetDataDescription(
          image->GetDimensions());
      }
      bbox.AddBounds(image->GetBounds());
    }
  }

  if (bbox.IsValid())
  {
    bbox.GetMinPoint(origin[0], origin[1], origin[2]);
  }

  // Read 1 dataset from each level to get information about spacing.
  for (std::map<int, std::set<std::string> >::iterator iter =
    filenames.begin(); iter != filenames.end(); ++iter)
  {
    if (iter->second.empty())
    {
      continue;
    }

    std::string filename = (*iter->second.begin());
    vtkNew<vtkXMLImageDataReader> imageReader;
    imageReader->SetFileName(filename.c_str());
    imageReader->UpdateInformation();
    vtkInformation* outInfo =
      imageReader->GetExecutive()->GetOutputInformation(0);
    if (outInfo->Has(vtkDataObject::SPACING()))
    {
      assert(outInfo->Length(vtkDataObject::SPACING()) == 3);
      outInfo->Get(vtkDataObject::SPACING(), &spacing[3*iter->first]);
    }
  }

  return gridDescription;
}

//----------------------------------------------------------------------------
void vtkXMLHierarchicalBoxDataFileConverter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "InputFileName: "
    << (this->InputFileName? this->InputFileName : "(none)") << endl;
  os << indent << "OutputFileName: "
    << (this->OutputFileName?  this->OutputFileName : "(none)")<< endl;
}
