//=========================================================================
//  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/NewAnalysis.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/NewAnalysis_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 NewAnalysis::operateInternal()
{

  std::cout << "starting NewAnalysis::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);
  }

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

  // 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);
  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();

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

    // associate the mesh with the attributes
    attResource->associate(existingMesh->value());
  }
  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 nativeCopyLocation;
    if (copyNativeFiles)
    {
      boost::filesystem::path copyPath = assetsFolderPath / nativePath.filename();
      boost::filesystem::copy_file(nativePath, copyPath);
      nativeCopyLocation = 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 mesh file " << location);
      return this->createResult(smtk::operation::Operation::Outcome::FAILED);
    }
    auto modelResource = importOpResult->findResource("resource")->value(0);

    // add the mesh to the project
    std::string meshRole = analysisName + "-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);
  }

  auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
  return result;
}

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

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