//=========================================================================
//  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.
//=========================================================================

// Local includes
#include "smtk/simulation/ace3p/JobsManifest.h"
#include "smtk/simulation/ace3p/Metadata.h"
#include "smtk/simulation/ace3p/Project.h"
#include "smtk/simulation/ace3p/Registrar.h"
#include "smtk/simulation/ace3p/operations/Create.h"
#include "smtk/simulation/ace3p/operations/NewAnalysis.h"
#include "smtk/simulation/ace3p/operations/Write.h"
#include "smtk/simulation/ace3p/testing/cxx/randomJobCreator.h"
#include "smtk/simulation/ace3p/utility/AttributeUtils.h"

// SMTK includes
#include "smtk/attribute/Analyses.h"
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/Definition.h"
#include "smtk/attribute/DirectoryItem.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/GroupItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/Item.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"
#include "smtk/io/Logger.h"
#include "smtk/model/Registrar.h"
#include "smtk/model/Resource.h"
#include "smtk/operation/Manager.h"
#include "smtk/operation/Operation.h"
#include "smtk/project/Manager.h"
#include "smtk/project/Registrar.h"
#include "smtk/project/operators/Define.h"
#include "smtk/project/operators/Write.h"
#include "smtk/resource/Manager.h"
#include "smtk/session/vtk/Registrar.h"
#include "smtk/view/Configuration.h"

#include "smtk/common/testing/cxx/helpers.h" // smtkTest()

// boost includes
#include <boost/filesystem.hpp>

// stl includes
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

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

int TestAddJob(int, char*[])
{
  // Remove output folder
  std::stringstream ss;
  ss << SCRATCH_DIR << "/cxx/"
     << "new_job";
  std::string outputFolder = ss.str();
  if (boost::filesystem::is_directory(outputFolder))
  {
    boost::filesystem::remove_all(outputFolder);
  }
  // Data directory is set by CMake (target_compile_definitions)
  boost::filesystem::path dataPath(DATA_DIR);

  // Create sundry managers
  smtk::resource::ManagerPtr resourceManager = smtk::resource::Manager::create();
  smtk::attribute::Registrar::registerTo(resourceManager);
  smtk::model::Registrar::registerTo(resourceManager);
  smtk::session::vtk::Registrar::registerTo(resourceManager);

  smtk::operation::ManagerPtr operationManager = smtk::operation::Manager::create();
  smtk::operation::Registrar::registerTo(operationManager);
  smtk::attribute::Registrar::registerTo(operationManager);
  smtk::session::vtk::Registrar::registerTo(operationManager);

  smtk::project::ManagerPtr projectManager =
    smtk::project::Manager::create(resourceManager, operationManager);
  smtk::project::Registrar::registerTo(projectManager);
  smtk::simulation::ace3p::Registrar::registerTo(projectManager);

  // Todo Next line is currently a no-op. Is it needed?
  smtk::project::Registrar::registerTo(operationManager);
  operationManager->registerResourceManager(resourceManager);

// Application must set workflows directory
#ifndef WORKFLOWS_SOURCE_DIR
#error WORKFLOWS_SOURCE_DIR must be defined
#endif
  smtk::simulation::ace3p::Metadata::WORKFLOWS_DIRECTORY = WORKFLOWS_SOURCE_DIR;

  // Setup and run the create-ace3p-project operator
  std::shared_ptr<smtk::simulation::ace3p::Project> project;
  smtk::attribute::ResourcePtr attResource;
  smtk::model::ResourcePtr modelResource;
  {
    auto createOp = operationManager->create<smtk::simulation::ace3p::Create>();
    smtkTest(createOp != nullptr, "Create op is null");

    smtk::attribute::AttributePtr params = createOp->parameters();
    params->findDirectory("location")->setValue(outputFolder);

    boost::filesystem::path meshPath = dataPath / "model" / "3d" / "genesis" / "pillbox4.gen";
    std::cout << "mesh path: " << meshPath.string() << std::endl;
    smtkTest(boost::filesystem::exists(meshPath), "mesh file not found: " << meshPath.string());
    params->findFile("analysis-mesh")->setIsEnabled(true);
    smtkTest(
      params->findFile("analysis-mesh")->setValue(meshPath.string()),
      "failed to set analysis-mesh");

    auto result = createOp->operate();
    int outcome = result->findInt("outcome")->value();
    std::cout << "Create Outcome: " << outcome << std::endl;
    smtkTest(
      outcome == OP_SUCCEEDED,
      "Create op did not succeed, log: " << createOp->log().convertToString());

    smtk::attribute::ResourceItemPtr projectItem = result->findResource("resource");
    auto resource = projectItem->value();
    smtkTest(resource != nullptr, "resource is null");
    std::cout << "Created resource type " << resource->typeName() << std::endl;
    project = std::dynamic_pointer_cast<smtk::simulation::ace3p::Project>(resource);
    smtkTest(project != nullptr, "project is null");

    // Check that default att resource was created
    std::string analysisRole("Analysis1");
    auto attSet = project->resources().findByRole<smtk::attribute::Resource>(analysisRole);
    smtkTest(
      attSet.size() == 1,
      "Size of resource role \"" << analysisRole << "\" set should be 1 not " << attSet.size());
    attResource = *(attSet.begin());
    std::cout << "Project contains attribute resource with role name \"" << analysisRole << "\"\n";

    // Check for analysis mesh
    std::string htRole("analysis-mesh");
    auto modelSet = project->resources().findByRole<smtk::model::Resource>(htRole);
    smtkTest(
      modelSet.size() == 1,
      "Size of resource role \"" << htRole << "\" set should be 1 not " << modelSet.size());
    modelResource = *(modelSet.begin());
    std::cout << "Project contains model resource with role name \"" << htRole << "\"" << std::endl;
  }

  nlohmann::json job = randomJob();
  project->addJobRecord(job);

  // read the Job Record and check that it matches
  {
    smtkTest(job["slurm_id"] == project->jobData(0, "slurm_id"), "slurm_id wrong value");
    smtkTest(job["cumulus_id"] == project->jobData(0, "cumulus_id"), " wrong value");
    smtkTest(job["cumulus_folder_id"] == project->jobData(0, "cumulus_folder_id"), " wrong value");
    smtkTest(job["job_name"] == project->jobData(0, "job_name"), "job_name wrong value");
    smtkTest(job["machine"] == project->jobData(0, "machine"), "machine wrong value");
    smtkTest(job["analysis_id"] == project->jobData(0, "analysis_id"), "analysis_id wrong value");
    smtkTest(job["analysis"] == project->jobData(0, "analysis"), "analysis wrong value");
    smtkTest(job["nodes"] == project->jobData(0, "nodes"), "nodes wrong value");
    smtkTest(job["processes"] == project->jobData(0, "processes"), "processes wrong value");
    smtkTest(job["runtime"] == project->jobData(0, "runtime"), "runtime wrong value");
    smtkTest(
      job["submission_time"] == project->jobData(0, "submission_time"),
      "submission_time wrong value");
    smtkTest(job["notes"] == project->jobData(0, "notes"), "notes wrong value");
    smtkTest(
      job["runtime_job_folder"] == project->jobData(0, "runtime_job_folder"),
      "runtime_job_folder wrong value");
    smtkTest(
      job["local_job_folder"] == project->jobData(0, "local_job_folder"),
      "local_job_folder wrong value");
    smtkTest(
      job["runtime_input_folder"] == project->jobData(0, "runtime_input_folder"),
      "runtime_input_folder wrong value");
  }

  {
    // Write project to file system
    std::cout << "Write project to " << project->location() << " ..." << std::endl;
    auto writeOp = operationManager->create<smtk::simulation::ace3p::Write>();
    smtkTest(writeOp != nullptr, "Write op not created");
    smtkTest(writeOp->parameters()->associate(project), "Write op associate project failed");

    auto result = writeOp->operate();
    int outcome = result->findInt("outcome")->value();
    std::cout << "Write Outcome: " << outcome << std::endl;
    smtkTest(
      outcome == OP_SUCCEEDED,
      "Write operation did not succeed, log: " << writeOp->log().convertToString());

    // Resources should not be marked modified
    smtkTest(project->clean(), "project marked as modified");
    smtkTest(attResource->clean(), "attribute resource marked as modified");
    smtkTest(modelResource->clean(), "model resource marked as modified");
  }

  // read the JobsManifest from file and check it still matches

  return 0;
}
