//=========================================================================
//  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/simulation/ace3p/operations/Write.h"
#include "smtk/simulation/ace3p/Project.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/Definition.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/ResourceItemDefinition.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/StringItemDefinition.h"

#include "smtk/io/Logger.h"

#include "smtk/operation/operators/WriteResource.h"

#include "smtk/project/Manager.h"

#include "smtk/project/json/jsonProject.h"

#include "smtk/simulation/ace3p/Write_xml.h"

#include <iostream>

SMTK_THIRDPARTY_PRE_INCLUDE
#include "boost/filesystem.hpp"
#include "boost/system/error_code.hpp"
SMTK_THIRDPARTY_POST_INCLUDE

namespace smtk
{
namespace simulation
{
namespace ace3p
{
Write::Result Write::operateInternal()
{
  // Access the project to write.
  smtk::attribute::ReferenceItem::Ptr projectItem = this->parameters()->associations();
  smtk::simulation::ace3p::Project::Ptr project =
    projectItem->valueAs<smtk::simulation::ace3p::Project>();

  // Get the project file (path) and setup folders
  std::string outputFile = project->location();
  if (outputFile.empty())
  {
    smtkErrorMacro(this->log(), "Error Cannot write project because location not specified.");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }
  boost::filesystem::path outputFilePath(outputFile);
  boost::filesystem::path projectFolderPath = outputFilePath.parent_path();
  boost::filesystem::path resourcesFolderPath = projectFolderPath / "resources";

  // Construct a WriteResource operation to write all of the project's resources.
  smtk::operation::WriteResource::Ptr write =
    project->operations().manager()->create<smtk::operation::WriteResource>();
  if (!write)
  {
    smtkErrorMacro(this->log(), "Cannot create WriteResource operation.");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  // Create project and project/resources folders if needed
  if (!boost::filesystem::exists(resourcesFolderPath))
  {
    if (!boost::filesystem::create_directories(resourcesFolderPath))
    {
      smtkErrorMacro(
        this->log(),
        "Failed to create project resources directory: " << resourcesFolderPath.string() << ".");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
  }
  else if (!boost::filesystem::is_directory(resourcesFolderPath))
  {
    smtkErrorMacro(
      this->log(), "Resource path is not a folder: " << resourcesFolderPath.string() << ".");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  std::map<std::string, std::string> resourceDictionary;

  // Write the modified resources.
  for (auto& resource : project->resources())
  {
    const std::string& role = project::detail::role(resource);

    if (!resource->clean())
    {
      // Reset the write operation's associations.
      write->parameters()->associations()->reset();

      if (resource->location().empty())
      {
        std::string filename = role + ".smtk";
        boost::filesystem::path location = resourcesFolderPath / filename;
        resource->setLocation(location.string());
      }

      write->parameters()->associate(resource);
      smtk::operation::Operation::Result writeResult = write->operate();
      if (
        writeResult->findInt("outcome")->value() !=
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
      {
        // An error message should already enter the logger from the local operation.
        return this->createResult(smtk::operation::Operation::Outcome::FAILED);
      }
    }

    resourceDictionary[resource->location()] = role + ".smtk";
  }

  // We now write the project's smtk file.
  {
    nlohmann::json j = project;
    if (j.is_null())
    {
      smtkErrorMacro(log(), "Unable to serialize project to json object.");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }

    std::string fileContents = j.dump(2);
    std::ofstream file(outputFile);
    file << fileContents;
    file.close();
  }

  // write the Jobs Manifest to file
  if (!project->writeJobsManifest())
  {
    smtkErrorMacro(log(), "Unable to write Jobs Manifest.");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  // Reset the project's clean flag
  project->setClean(true);

  // Construct a result object.
  return this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
}

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

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));
}

} // namespace ace3p
} // namespace simulation
} // namespace smtk
