//=========================================================================
//  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/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/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/common/testing/cxx/helpers.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 <boost/filesystem.hpp>

#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>

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

int TestCreateProject(int /*argc*/, char* /*argv*/[])
{
  // Remove output folder
  std::stringstream ss;
  ss << SCRATCH_DIR << "/cxx/"
     << "create_project";
  std::string outputFolder = ss.str();
  if (boost::filesystem::is_directory(outputFolder))
  {
    boost::filesystem::remove_all(outputFolder);
  }

  // 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, "failed to create Create operation");

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

    // Data directory is set by CMake (target_compile_definitions)
    boost::filesystem::path dataPath(DATA_DIR);
    boost::filesystem::path meshPath = dataPath / "model" / "3d" / "genesis" / "pillbox4.gen";
    std::cout << "mesh path: " << meshPath.string() << std::endl;
    smtkTest(
      boost::filesystem::exists(meshPath), "failed to find mesh file at " << meshPath.string());
    auto fileItem = params->findFile("analysis-mesh");
    smtkTest(fileItem != nullptr, "Create operation missing \"analysis-mesh\" file item");
    fileItem->setIsEnabled(true);
    fileItem->setValue(meshPath.string());

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

    smtk::attribute::ResourceItemPtr projectItem = result->findResource("resource");
    auto resource = projectItem->value();
    smtkTest(resource != nullptr, "No resource found Create operation's result.");
    std::cout << "Created resource type " << resource->typeName() << std::endl;
    project = std::dynamic_pointer_cast<smtk::simulation::ace3p::Project>(resource);
    smtkTest(project != nullptr, "Create operation returned non-project resource");

    // Check that default att resource was created
    std::string analysisRole("attributes");
    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 HT 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;
  }

  {
    // Create instanced attributes
    smtk::simulation::ace3p::AttributeUtils attUtils;

    // Set analysis type
    auto analysisAtt = attUtils.getAnalysisAtt(attResource);
    smtkTest(analysisAtt != nullptr, "failed to find analysis attribute.");

    auto analysisItem = analysisAtt->findAs<smtk::attribute::StringItem>(
      "Analysis", smtk::attribute::SearchStyle::IMMEDIATE_ACTIVE);
    smtkTest(analysisItem != nullptr, "failed to find analysis item");
    bool setAnalysis = analysisItem->setValue("Omega3P");
    smtkTest(setAnalysis, "failed to set analysis to Omega3P");
  }

  // add some job data to round out the contents
  project->addJobRecord(randomJob(), 0);
  project->addJobRecord(randomJob(), 0);
  project->addJobRecord(randomJob(), 0);

  {
    // 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, "failed to create writer for ace3p project.");
    bool associateOK = writeOp->parameters()->associate(project);
    smtkTest(associateOK, "failed to associate project to Write operation.");

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

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

  return 0;
}
