//=========================================================================
//  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 "smtk/session/aeva/operators/Write.h"

#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"
#include "smtk/session/aeva/Write_xml.h"
#include "smtk/session/aeva/json/jsonResource.h"
#include "smtk/session/aeva/operators/Export.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/common/Paths.h"

#include "smtk/model/SessionIOJSON.h"
#include "smtk/model/Volume.h"

#include "vtkPolyData.h"
#include "vtkUnstructuredGrid.h"
#include "vtkXMLPolyDataWriter.h"
#include "vtkXMLUnstructuredGridWriter.h"

//force to use filesystem version 3
#define BOOST_FILESYSTEM_VERSION 3
#include <boost/filesystem.hpp>

#ifdef ERROR
#undef ERROR
#endif

namespace smtk
{
namespace session
{
namespace aeva
{
using json = nlohmann::json;

bool Write::writePoly(const smtk::model::EntityRef& cell,
  const smtk::session::aeva::Session::Ptr& session,
  vtkXMLUnstructuredGridWriter* uwriter,
  const std::string& fileDirectory,
  json& preservedUUIDs)
{
  auto polyComp = cell.component();
  smtk::common::UUID const& polyId = cell.entity();
  // check if this volume cell has been saved before, or is the result of 'import'
  std::string polyFile;
  std::string inFile;
  if (polyComp->properties().contains<std::string>("filename"))
  {
    inFile = polyComp->properties().at<std::string>("filename");
    // see if file is inside our dir.
    if (inFile.empty() || inFile.compare(0, fileDirectory.size(), fileDirectory) == 0)
    {
      polyFile = inFile;
    }
  }

  if (polyFile.empty() || !smtk::common::Paths::fileExists(polyFile))
  {
    auto* poly = vtkPolyData::SafeDownCast(session->findStorage(polyId));
    auto* ugrid = vtkUnstructuredGrid::SafeDownCast(session->findStorage(polyId));
    if (!(poly || ugrid))
    {
      // a rigid .med file will have a volume cell with no associated geometry
      // smtkWarningMacro(this->log(), "Cannot retrieve poly from model: " << polyId.toString());
      // empty volume will have a boundary child.
      smtk::model::EntityRefs all = cell.boundaryEntities(/*ofDimension = */ cell.dimension() - 1);
      for (const smtk::model::EntityRef& ref : all)
      {
        smtkWarningMacro(this->log(), "writing sub-poly: " << ref.entity().toString());
        this->writePoly(ref, session, uwriter, fileDirectory, preservedUUIDs);
      }
      return true;
    }

    if (polyFile.empty())
    {
      polyFile = fileDirectory + cell.entity().toString() + (poly ? ".vtp" : ".vtu");
    }

    int result = 0;
    if (poly)
    {
      vtkNew<vtkXMLPolyDataWriter> writer;
      writer->SetFileName(polyFile.c_str());
      writer->SetInputData(poly);
      result = writer->Write();
    }
    else if (ugrid)
    {
      uwriter->SetFileName(polyFile.c_str());
      uwriter->SetInputData(ugrid);
      result = uwriter->Write();
    }

    if (result != 1)
    {
      smtkErrorMacro(this->log(), "Cannot export file \"" << polyFile << "\".");
      return false;
    }
    // record the new filename
    polyComp->properties().get<std::string>()["filename"] = polyFile;
  }
  preservedUUIDs.push_back(
    { { "type", "poly" }, { "filename", polyFile }, { "uuid", polyId.toString() } });
  // some cells may have a boundary child.
  smtk::model::EntityRefs all = cell.boundaryEntities(/*ofDimension = */ cell.dimension() - 1);
  for (const smtk::model::EntityRef& ref : all)
  {
    // smtkWarningMacro(this->log(), "DBG writing sub-poly: " << ref.entity().toString());
    this->writePoly(ref, session, uwriter, fileDirectory, preservedUUIDs);
  }
  return true;
}

Write::Result Write::operateInternal()
{
  auto resourceItem = this->parameters()->associations();

  smtk::session::aeva::Resource::Ptr rsrc =
    std::dynamic_pointer_cast<smtk::session::aeva::Resource>(resourceItem->value());

  if (rsrc->location().empty())
  {
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  // map from model UUID string to json blob as a string.
  json preservedUUIDs = json::array();
  smtk::common::UUIDs modelIds = rsrc->entitiesMatchingFlags(smtk::model::MODEL_ENTITY);

  std::string fileDirectory = smtk::common::Paths::directory(rsrc->location()) + "/";

  for (auto const& id : modelIds)
  {
    smtk::model::Model dataset = smtk::model::Model(rsrc, id);
    auto modelComp = dataset.component();
    // datatype set by importer - "image" or "poly"
    std::string aevaDatatype = "image";
    if (modelComp->properties().contains<std::string>("aeva_datatype"))
    {
      aevaDatatype = modelComp->properties().at<std::string>("aeva_datatype");
    }
    // TODO need a "modified" indicator on the components of the resource,
    // not just the whole resource.
    // Assume anything outside our dir needs to be re-saved.

    if (aevaDatatype == "image")
    {
      if (dataset.cells().size() != 1)
      {
        smtkErrorMacro(
          log(), "Expected one volume cell in image model, got " << dataset.cells().size());
        return this->createResult(smtk::operation::Operation::Outcome::FAILED);
      }
      auto volume = dataset.cells()[0];
      auto volComp = volume.component();
      // check if this volume cell has been saved before, or is the result of 'import'
      std::string volFile;
      std::string inFile;
      if (volComp->properties().contains<std::string>("filename"))
      {
        inFile = volComp->properties().at<std::string>("filename");
        // see if file is inside our dir.
        if (inFile.empty() || inFile.compare(0, fileDirectory.size(), fileDirectory) == 0)
        {
          volFile = inFile;
        }
      }
      if (volFile.empty())
      {
        volFile = fileDirectory + volume.entity().toString() + ".vti";
      }

      smtk::common::UUID const& imageId = volume.entity();
      if (!smtk::common::Paths::fileExists(volFile))
      {
        Export::Ptr exportOp = Export::create();

        exportOp->parameters()->associate(dataset.entityRecord());
        exportOp->parameters()->findFile("filename")->setValue(volFile);
        Result exportOpResult = exportOp->operate(Key());

        if (exportOpResult->findInt("outcome")->value() != static_cast<int>(Outcome::SUCCEEDED))
        {
          smtkErrorMacro(log(), "Cannot export file \"" << volFile << "\".");
          return this->createResult(smtk::operation::Operation::Outcome::FAILED);
        }
        // record the new filename
        volComp->properties().get<std::string>()["filename"] = volFile;
      }
      preservedUUIDs.push_back(
        { { "type", aevaDatatype }, { "filename", volFile }, { "uuid", imageId.toString() } });
    }
    else if (aevaDatatype == "poly")
    {
      auto const& session = rsrc->session();
      vtkNew<vtkXMLUnstructuredGridWriter> writer;

      // each cell in .med file import may contain several cells with polydata.
      for (auto& cell : dataset.cells())
      {
        this->writePoly(cell, session, writer, fileDirectory, preservedUUIDs);
      }
    }
    else
    {
      smtkErrorMacro(log(), "Unknown model data type \"" << aevaDatatype << "\".");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
  }

  // Serialize resource into a set of JSON records:
  smtk::model::SessionIOJSON::json j = rsrc;
  // add UUIDs
  j["preservedUUIDs"] = preservedUUIDs;

  // Write JSON records to the specified URL:
  bool ok = smtk::model::SessionIOJSON::saveModelRecords(j, rsrc->location());

  return ok ? this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED)
            : this->createResult(smtk::operation::Operation::Outcome::FAILED);
}

const char* Write::xmlDescription() const
{
  return Write_xml;
}

void Write::markModifiedResources(Write::Result& /*unused*/)
{
  auto resourceItem = this->parameters()->associations();
  for (auto rit = resourceItem->begin(); rit != resourceItem->end(); ++rit)
  {
    auto resource = std::dynamic_pointer_cast<smtk::resource::Resource>(*rit);

    // Set the resource as unmodified from its persistent (i.e. on-disk) state
    resource->setClean(true);
  }
}

bool write(const smtk::resource::ResourcePtr& resource)
{
  Write::Ptr write = Write::create();
  write->parameters()->associate(resource);
  Write::Result result = write->operate();
  return (result->findInt("outcome")->value() == static_cast<int>(Write::Outcome::SUCCEEDED));
}

}
}
}
