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

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

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

// SMTK includes
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DirectoryItem.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/io/AttributeReader.h"
#include "smtk/io/Logger.h"
#include "smtk/project/Manager.h"
#include "smtk/resource/Manager.h"

#include <boost/filesystem.hpp>

#include <cassert>
#include <iostream>
#include <string>

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

namespace smtk
{
namespace simulation
{
namespace ace3p
{

smtk::operation::Operation::Result Create::operateInternal()
{
  auto& logger = this->log();

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

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

  // Initialize the project directory
  std::string projectFolder = this->parameters()->findDirectory("location")->value();
  boost::filesystem::path projectPath(projectFolder);

  // Clear any existing directory contents
  if (boost::filesystem::exists(projectPath) && !boost::filesystem::is_empty(projectPath))
  {
    bool overwrite = this->parameters()->find("overwrite")->isEnabled();
    if (!overwrite)
    {
      smtkErrorMacro(
        logger,
        "Error: Cannot create project in existing directory ("
          << projectPath.string() << ") unless overwrite flag is set.");
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
    boost::filesystem::remove_all(projectPath);
  }

  if (!boost::filesystem::exists(projectPath))
  {
    boost::filesystem::create_directories(projectPath);
  }

  // Create ace3p project
  auto project = smtk::simulation::ace3p::Project::create();
  if (!project)
  {
    smtkErrorMacro(
      this->log(), "Cannot create project type \"" << Metadata::PROJECT_TYPENAME << "\"");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }
  // Set the project name and location
  std::string projectName = projectPath.filename().string();
  project->setName(projectName);
  std::string filename = projectFolder + "/" + projectName + ".project.smtk";
  project->setLocation(filename);

  // Must set project operations manager to avoid seg fault writing project.
  // Todo is this a workaround for something missing elsewhere?
  project->operations().setManager(m_manager);

  // Create assets folder
  boost::filesystem::path assetsFolderPath = projectPath / "assets";
  if (!boost::filesystem::create_directories(assetsFolderPath))
  {
    smtkErrorMacro(
      this->log(),
      "Failed to create project assets directory: " << assetsFolderPath.string() << ".");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  // Initialize metadata instance for project-specific info
  Metadata metadataInstance;

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

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

  // Use analysis name as the role
  std::string analysisName = this->parameters()->findString("analysis-name")->value();
  attResource->setName(analysisName);
  std::string role = analysisName;
  project->resources().add(attResource, role);

  // Get copy flag
  bool copyNativeFiles = this->parameters()->find("copy-file")->isEnabled();

  // Load mesh files (if any)
  std::vector<std::string> modelRoles = { Metadata::ANALYSIS_MESH_ROLE };
  for (auto role : modelRoles)
  {
    auto fileItem = this->parameters()->findFile(role);
    if (!fileItem->isEnabled() || !fileItem->isSet())
    {
      continue;
    }

    std::string location = fileItem->value();
    boost::filesystem::path nativePath(location);
    if (!boost::filesystem::exists(nativePath))
    {
      smtkErrorMacro(logger, "Error import file not found " << location);
      continue;
    }

    std::string nativeFileLocation = location;
    if (copyNativeFiles)
    {
      // Copy the mesh file to project directory
      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(location);
    auto importOpResult = importOp->operate();
    int outcome = importOpResult->findInt("outcome")->value(0);
    if (outcome != OP_SUCCEEDED)
    {
      smtkErrorMacro(logger, "Error importing file " << location);
      continue;
    }
    auto modelResource = importOpResult->findResource("resource")->value(0);
    if (!project->resources().add(modelResource, role))
    {
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }

    if (role == Metadata::ANALYSIS_MESH_ROLE)
    {
      attResource->associate(modelResource);
      metadataInstance.NativeAnalysisMeshLocation = nativeFileLocation;
    }
  } // for (role)

  // Add metadata instance to the project properties
  metadataInstance.putToResource(project);

  auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
  {
    smtk::attribute::ResourceItem::Ptr created = result->findResource("resource");
    bool ok = created->setValue(project);
    assert(ok);
  }

  return result;
}

smtk::operation::Operation::Specification Create::createSpecification()
{
  Specification spec = this->smtk::operation::XMLOperation::createSpecification();
  //std::cout << "Create Op:\n" << this->xmlDescription() << std::endl;
  return spec;
}

const char* Create::xmlDescription() const
{
  return Create_xml;
}
} // namespace ace3p
} // namespace simulation
} // namespace smtk
