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

#include "smtk/simulation/ace3p/JobsManifest.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/SearchStyle.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/ValueItem.h"
#include "smtk/io/AttributeWriter.h"
#include "smtk/io/Logger.h"
#include "smtk/operation/Operation.h"

#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>

#include <iostream>
#include <string>

namespace
{
  // Macro to call method on instance. Used for setting fields in JobRecordGenerator
  #define AsLambdaMacro(instance, method) [&instance](const std::string& s) { instance.method(s); }

  // Copy from smtk ValueItem to job record
  void toJobRecord(const smtk::attribute::AttributePtr att, const std::string& itemName, std::function <void(const std::string)> setField, smtk::io::Logger& logger)
  {
    const auto valueItem = att->findAs<smtk::attribute::ValueItem>(itemName, smtk::attribute::SearchStyle::RECURSIVE_ACTIVE);
    if (valueItem == nullptr)
    {
      smtkWarningMacro(logger, "Warning: Did not find attribute ValueItem \"" << itemName << "\".");
    }
    else if (valueItem->isEnabled() && valueItem->isSet())
    {
      setField(valueItem->valueAsString());
    }
  }

} // namespace

namespace smtk
{
namespace simulation
{
namespace ace3p
{

Project::Project() {}

bool Project::onJobSubmit(smtk::attribute::AttributePtr exportParameters,
  smtk::attribute::AttributePtr exportResult,
  smtk::io::Logger& logger)
{
  // Get cumulus job id
  auto cumulusJobIdItem = exportResult->findString("CumulusJobId");
  if (!cumulusJobIdItem || !cumulusJobIdItem->isSet() || !cumulusJobIdItem->isValid())
  {
    smtkErrorMacro(logger, "Internal Error: CumulusJobId item missing or not set");
    return false;
  }
  std::string cumulusJobId = cumulusJobIdItem->value();

  // Create job folder
  boost::filesystem::path projectFilePath(this->location());
  boost::filesystem::path projectPath = projectFilePath.parent_path();
  boost::filesystem::path jobPath = projectPath / "jobs" / cumulusJobId;

  boost::system::error_code boost_errcode;
  boost::filesystem::create_directories(jobPath, boost_errcode);
  if (boost_errcode != boost::system::errc::success)
  {
    smtkErrorMacro(logger, "Error creating job folder " << jobPath.string() << ": " << boost_errcode.message());
    return false;
  }
  std::cout << "Created local job folder at " << jobPath.string() << std::endl;

  // Copy the ACE3P command file to the job folder
  auto fileItem = exportResult->findFile("OutputFile");
  if (!fileItem || !fileItem->isSet() || !fileItem->isValid())
  {
    smtkErrorMacro(logger, "Internal Error: OutputFile item missing or not set");
    return false;
  }
  boost::filesystem::path ace3pFilePath(fileItem->value());
  if (!boost::filesystem::exists(ace3pFilePath))
  {
    smtkErrorMacro(
      logger, "Internal Error: ace3p input file not found at " << ace3pFilePath.string());
    return false;
  }
  boost::filesystem::path toFilePath = jobPath / ace3pFilePath.filename();
  boost::filesystem::copy_file(ace3pFilePath, toFilePath, boost_errcode);
  if (boost_errcode != boost::system::errc::success)
  {
    smtkErrorMacro(logger, "Error copying input file " << ace3pFilePath.string() << ": " << boost_errcode.message());
    return false;
  }

  // Write the export spec to the job folder
  boost::filesystem::path specPath = jobPath / "export-spec.smtk";
  smtk::io::AttributeWriter attWriter;
  bool writeErr = attWriter.write(exportParameters->attributeResource(), specPath.string(), logger);
  if (writeErr)
  {
    smtkErrorMacro(logger, "Error writing export parameter file.");
    return false;
  }

  // Create job record
  auto jrg = JobRecordGenerator();
  jrg.slurmID("000000");  // future
  jrg.cumulusID(cumulusJobId);
  jrg.localJobFolder(jobPath.string());

  ::toJobRecord(exportParameters, "JobName", AsLambdaMacro(jrg, jobName),logger);
  ::toJobRecord(exportParameters, "Machine", AsLambdaMacro(jrg, machine), logger);

  smtk::resource::ResourcePtr resource = exportParameters->findResource("analysis")->value();
  jrg.analysisID(resource->id().toString());

  smtk::simulation::ace3p::AttributeUtils attUtils;
  auto attResource = std::dynamic_pointer_cast<smtk::attribute::Resource>(resource);
  auto analysisAtt = attUtils.getAnalysisAtt(attResource);
  if (analysisAtt)
  {
    ::toJobRecord(analysisAtt, "Analysis", AsLambdaMacro(jrg, analysis), logger);
  }
  else
  {
    smtkWarningMacro(logger, "Failed to find analysis item");
  }

  ::toJobRecord(exportParameters, "JobNotes", AsLambdaMacro(jrg, notes), logger);
  ::toJobRecord(exportResult, "NerscJobFolder", AsLambdaMacro(jrg, runtimeJobFolder), logger);
  ::toJobRecord(exportResult, "NerscInputFolder", AsLambdaMacro(jrg, runtimeInputFolder), logger);
  ::toJobRecord(exportParameters, "NumberOfNodes", AsLambdaMacro(jrg, nodes), logger);
  ::toJobRecord(exportParameters, "NumberOfTasks", AsLambdaMacro(jrg, processes), logger);

  // update this continuously via cumulus
  jrg.runtime(0);
  jrg.submissionTime("0");

  this->addJobRecord(jrg.get());
  this->setClean(false);
  return true;
}

//-----------------------------------------------------------------------------
void Project::addJobRecord(nlohmann::json job)
{
  m_jobsManifest.addJobRecord(job);
}

void Project::setJobRecordField(int idx, std::string key, std::string value)
{
  m_jobsManifest.setField(idx, key, value);
}

void Project::getJobRecordField(int idx, std::string key, std::string& value)
{
  m_jobsManifest.getField(idx, key, value);
}

std::string Project::jobData(int idx, std::string key)
{
  std::string value;
  m_jobsManifest.getField(idx, key, value);
  return value;
}

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