//=========================================================================
//  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/NewStage.h"
#include "smtk/simulation/ace3p/operations/Write.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()

#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 TestNewStage(int /*argc*/, char* /*argv*/[])
{
  // Remove output folder
  std::stringstream ss;
  ss << SCRATCH_DIR << "/cxx/"
     << "new_analysis";
  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, "failed to create Create op");

    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), "meshPath not found: " << meshPath.string());
    params->findFile("analysis-mesh")->setIsEnabled(true);
    smtkTest(
      params->findFile("analysis-mesh")->setValue(meshPath.string()),
      "failed to set analysis-mesh item");

    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("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, "analysis attribute not found");

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

  // add a new analysis to the project that uses an existing mesh
  {
    auto newStageOp = operationManager->create<smtk::simulation::ace3p::NewStage>();
    smtkTest(newStageOp != nullptr, "failed to create NewStage op");
    smtk::attribute::AttributePtr params = newStageOp->parameters();
    smtkTest(params->associate(project), "NewAnalsys op failed to associate project");

    params->findString("stage-name")->setValue("UseExistingMesh");
    params->findString("assign-mesh")->setValue("link-existing-mesh");

    std::string htRole("analysis-mesh");
    auto mshSet = project->resources().findByRole<smtk::model::Resource>(htRole);
    smtkTest(
      mshSet.size() == 1,
      "Size of resource role \"" << htRole << "\" set should be 1 not " << mshSet.size());
    auto msh = *(mshSet.begin());
    smtkTest(msh != nullptr, "msh is null");

    auto mshResourceItem = params->findResource("existing-analysis-mesh");
    smtkTest(mshResourceItem != nullptr, "mshResourceItem is null");
    mshResourceItem->setValue(msh);

    auto result = newStageOp->operate();
    int outcome = result->findInt("outcome")->value();
    smtkTest(
      outcome == OP_SUCCEEDED, "NewStage op failed, log: " << newStageOp->log().convertToString());
  }

  // add a new analysis to the project that uses a new mesh loaded from file
  {
    auto newStageOp = operationManager->create<smtk::simulation::ace3p::NewStage>();
    smtkTest(newStageOp != nullptr, "NewStage op not created");
    smtk::attribute::AttributePtr params = newStageOp->parameters();
    smtkTest(params != nullptr, "NewStage parameters null");
    smtkTest(params->associate(project), "NewStage op failed to associate project");

    // to do. find a legitimate new mesh for this test to load 2 meshes
    boost::filesystem::path meshPath = dataPath / "model" / "3d" / "genesis" / "pillbox-rtop4.gen";
    smtkTest(boost::filesystem::exists(meshPath), "mesh file not found: " << meshPath.string());

    params->findString("stage-name")->setValue("UseNewMesh");
    params->findString("assign-mesh")->setValue("open-new-mesh");
    params->findFile("analysis-mesh-file")->setIsEnabled(true);
    params->findFile("analysis-mesh-file")->setValue(meshPath.string());

    auto result = newStageOp->operate();
    int outcome = result->findInt("outcome")->value();
    smtkTest(
      outcome == OP_SUCCEEDED, "NewStage op failed, log: " << newStageOp->log().convertToString());
  }

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

  return 0;
}
