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

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

// Project includes
#include "smtk/simulation/ace3p/Metadata.h"
#include "smtk/simulation/ace3p/Project.h"
#include "smtk/simulation/ace3p/Stage.h"
#include "smtk/simulation/ace3p/utility/AttributeUtils.h"

// SMTK includes
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/common/UUID.h"
#include "smtk/model/Resource.h"
#include "smtk/project/Manager.h"

#include <boost/filesystem.hpp>

#include <sstream>

namespace
{
const int OP_SUCCEEDED = static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED);
}

namespace smtk
{
namespace simulation
{
namespace ace3p
{

smtk::operation::Operation::Result NewStage::operateInternal()
{
  std::cout << "starting NewStage::operateInternal\n";
  auto& logger = this->log();

  // Make sure the attribute template (ace3p.sbt) exists.
  if (Metadata::WORKFLOWS_DIRECTORY.empty())
  {
    std::cout << "Internal Error: Metadata::WORKFLOWS_DIRECTORY not defined.\n";
    smtkErrorMacro(logger, "Internal Error: Metadata::WORKFLOWS_DIRECTORY not defined.");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  //////////////////////////////////////////////////////////////////////////////
  // Configure the operation
  auto refItem = this->parameters()->associations();
  auto project = refItem->valueAs<smtk::simulation::ace3p::Project>();

  boost::filesystem::path workflowDir(Metadata::WORKFLOWS_DIRECTORY);
  boost::filesystem::path sbtPath = workflowDir / "ACE3P.sbt";
  if (!boost::filesystem::exists(sbtPath))
  {
    std::cout << "Error: expecting sbt file at " << sbtPath.string() << "\n";
    smtkErrorMacro(logger, "Error: expecting sbt file at " << sbtPath.string());
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  auto op = this->manager()->create("smtk::attribute::Import");
  assert(op != nullptr);
  op->parameters()->findFile("filename")->setValue(sbtPath.string());
  auto opResult = op->operate();
  int outcome = opResult->findInt("outcome")->value(0);
  if (outcome != OP_SUCCEEDED)
  {
    std::cout << "Error importing template file " << sbtPath.string() << "\n";
    smtkErrorMacro(logger, "Error importing template file " << sbtPath.string());
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  auto res = opResult->findResource("resource")->value(0);
  smtk::attribute::ResourcePtr attResource =
    std::dynamic_pointer_cast<smtk::attribute::Resource>(res);
  assert(attResource != nullptr);

  // Add smtk's automatic-display property and set to off (false)
  attResource->properties().insert<bool>("smtk.attribute_panel.display_hint", false);

  // Populate instanced attributes
  AttributeUtils attUtils;
  attUtils.createInstancedAtts(attResource);

  // Set filesystem location to unique path
  std::stringstream ss;
  ss << "attributes." << attResource->id() << ".smtk";
  std::string filename = ss.str();
  boost::filesystem::path directoryPath(project->resourcesDirectory());
  boost::filesystem::path resourcePath = directoryPath / filename;
  attResource->setLocation(resourcePath.string());

  // Set attribute resource name and add to project
  std::string stageName = this->parameters()->findString("stage-name")->value();
  attResource->setName(stageName);
  std::string role = "attributes";
  project->resources().add(attResource, role);
  std::cout << "project Attribute Resource added\n";

  //////////////////////////////////////////////////////////////////////////////
  // deal with a mesh association on the new Attribute resource

  // get the use-existing-mesh item
  std::string meshMode = this->parameters()->findString("assign-mesh")->value();

  smtk::model::ResourcePtr modelRecast = nullptr;
  if (meshMode == "link-existing-mesh")
  {
    std::cout << "using existing mesh..." << std::endl;
    auto existingMesh = this->parameters()->findResource("existing-analysis-mesh")->value();
    if (existingMesh == nullptr)
    {
      smtkErrorMacro(logger, "No analysis mesh name set!");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }

    modelRecast = dynamic_pointer_cast<smtk::model::Resource>(existingMesh);

    // associate the mesh with the attributes
    attResource->associate(existingMesh);
  }
  else
  {
    std::cout << "loading new mesh..." << std::endl;
    bool copyNativeFiles = this->parameters()->find("copy-file")->isEnabled();

    auto fileItem = this->parameters()->findFile("analysis-mesh-file");

    std::string location = fileItem->value();
    boost::filesystem::path nativePath(location);
    if (!boost::filesystem::exists(nativePath))
    {
      std::cout << "Error import file not found " << location << "\n";
      smtkErrorMacro(logger, "Error import file not found " << location);
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }

    // make sure we can copy the mesh asset into the project path folder
    boost::filesystem::path assetsFolderPath =
      boost::filesystem::path(project->location()).parent_path() / "assets";
    if (
      !boost::filesystem::exists(assetsFolderPath) &&
      !boost::filesystem::create_directories(assetsFolderPath))
    {
      smtkErrorMacro(
        this->log(),
        "Failed to create project assets directory: " << assetsFolderPath.string() << ".");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }

    // Copy the mesh file to project directory
    std::string nativeFileLocation = nativePath.string();
    if (copyNativeFiles)
    {
      boost::filesystem::path copyPath = assetsFolderPath / nativePath.filename();
      boost::filesystem::copy_file(nativePath, copyPath);
      nativeFileLocation = copyPath.string();
    }

    // Create import operator
    auto importOp = this->manager()->create("smtk::session::vtk::Import");
    assert(importOp != nullptr);
    importOp->parameters()->findFile("filename")->setValue(nativeFileLocation);
    auto importOpResult = importOp->operate();
    int outcome = importOpResult->findInt("outcome")->value(0);
    if (outcome != OP_SUCCEEDED)
    {
      smtkErrorMacro(logger, "Error importing mesh file " << location);
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
    smtk::resource::ResourcePtr modelResource = importOpResult->findResource("resource")->value(0);
    modelRecast = std::dynamic_pointer_cast<smtk::model::Resource>(modelResource);

    // Store native file locally
    modelResource->properties().insert<std::string>(
      Metadata::METADATA_PROPERTY_KEY, nativeFileLocation);

    // Set filesystem location to unique path
    std::stringstream ss;
    ss << "model." << modelResource->id() << ".smtk";
    std::string filename = ss.str();
    boost::filesystem::path directoryPath(project->resourcesDirectory());
    boost::filesystem::path resourcePath = directoryPath / filename;
    modelResource->setLocation(resourcePath.string());

    // Add resource to the project
    std::string meshRole = "analysis-mesh";
    if (!project->resources().add(modelResource, meshRole))
    {
      std::cout << "error adding the mesh to the project" << std::endl;
      smtkErrorMacro(logger, "error adding the mesh to the project");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
    attResource->associate(modelResource);
  }

  Stage stage = Stage(attResource, modelRecast, project->jobsManifest(), stageName);
  std::shared_ptr<Stage> stagePtr = std::make_shared<Stage>(stage);
  project->addStage(stagePtr);

  auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
  result->findResource("resource")->setValue(project);
  return result;
}

smtk::operation::Operation::Specification NewStage::createSpecification()
{
  Specification spec = this->smtk::operation::XMLOperation::createSpecification();
  return spec;
}

const char* NewStage::xmlDescription() const
{
  return NewStage_xml;
}

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