//=========================================================================
//  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/Registrar.h"
#include "smtk/simulation/ace3p/Project.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/helpers.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/VoidItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.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/Write.h"
#include "smtk/project/operators/Define.h"
#include "smtk/resource/Manager.h"
#include "smtk/session/vtk/Registrar.h"
#include "smtk/view/Configuration.h"

#include <boost/filesystem.hpp>

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

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

int TestNewAnalysis(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>();
    assert(createOp != nullptr);

    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;
    assert(boost::filesystem::exists(meshPath));
    params->findFile("analysis-mesh")->setIsEnabled(true);
    assert(params->findFile("analysis-mesh")->setValue(meshPath.string()));

    auto result = createOp->operate();
    int outcome = result->findInt("outcome")->value();
    std::cout << "Create Outcome: " << outcome << std::endl;
    assert(outcome == OP_SUCCEEDED);

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

    // Check that default att resource was created
    std::string analysisRole("Analysis1");
    attResource = project->resources().getByRole<smtk::attribute::Resource>(analysisRole);
    assert(attResource != nullptr);
    std::cout << "Project contains attribute resource with role name \"" << analysisRole << "\"\n";

    // Check for HT mesh
    std::string htRole("analysis-mesh");
    modelResource = project->resources().getByRole<smtk::model::Resource>(htRole);
    assert(modelResource != nullptr);
    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);
    assert(analysisAtt != nullptr);

    auto analysisItem = analysisAtt->findAs<smtk::attribute::StringItem>("Analysis", smtk::attribute::SearchStyle::IMMEDIATE_ACTIVE);
    assert(analysisItem != nullptr);
    assert(analysisItem->setValue("Omega3P"));
  }



  // add a new analysis to the project that uses an existing mesh
  {
    auto newAnalysisOp = operationManager->create<smtk::simulation::ace3p::NewAnalysis>();
    assert(newAnalysisOp != nullptr);
    smtk::attribute::AttributePtr params = newAnalysisOp->parameters();
    assert(params != nullptr);
    assert(params->associate(project));


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

    auto msh =
      project->resources().getByRole<smtk::model::Resource>("analysis-mesh");
    assert(msh != nullptr);

    auto mshResourceItem = params->findResource("existing-analysis-mesh");
    assert(mshResourceItem != nullptr);
    mshResourceItem->setValue(msh);

    auto result = newAnalysisOp->operate();
    int outcome = result->findInt("outcome")->value();
    assert(outcome == OP_SUCCEEDED);
  }


  // add a new analysis to the project that uses a new mesh loaded from file
  {
    auto newAnalysisOp = operationManager->create<smtk::simulation::ace3p::NewAnalysis>();
    assert(newAnalysisOp != nullptr);
    smtk::attribute::AttributePtr params = newAnalysisOp->parameters();
    assert(params != nullptr);
    assert(params->associate(project));

    // to do. find a legitimate new mesh for this test to load 2 meshes
    boost::filesystem::path meshPath = dataPath / "model" / "3d" / "genesis" / "pillbox42.gen";
    assert(boost::filesystem::exists(meshPath));

    params->findString("analysis-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 = newAnalysisOp->operate();
    int outcome = result->findInt("outcome")->value();
    assert(outcome == OP_SUCCEEDED);
  }



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

    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;
    }
    assert(outcome == OP_SUCCEEDED);

    // Resources should not be marked modified
    assert(project->clean());
    assert(attResource->clean());
    assert(modelResource->clean());
  }

  return 0;
}
