/**
 * \brief  Implementation of class Simulation
 * \file
 *
 * \copyright
 * Copyright (c) 2012-2022, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 *
 */

#include "Simulation.h"

#include <spdlog/spdlog.h>

#include "Applications/ApplicationsLib/LinearSolverLibrarySetup.h"
#include "Applications/ApplicationsLib/ProjectData.h"
#include "Applications/ApplicationsLib/TestDefinition.h"
#include "Applications/InSituLib/Adaptor.h"
#include "BaseLib/ConfigTreeUtil.h"
#include "BaseLib/FileTools.h"
#include "BaseLib/PrjProcessing.h"
#include "NumLib/NumericsConfig.h"
#include "ProcessLib/TimeLoop.h"

Simulation::Simulation(int argc, char* argv[])
    : linear_solver_library_setup(argc, argv),
#if defined(USE_PETSC)
      controller(vtkSmartPointer<vtkMPIController>::New()),
#endif
      test_definition{std::nullopt}
{
#if defined(USE_PETSC)
    controller->Initialize(&argc, &argv, 1);
    vtkMPIController::SetGlobalController(controller);

    {  // Can be called only after MPI_INIT.
        int mpi_rank;
        MPI_Comm_rank(PETSC_COMM_WORLD, &mpi_rank);
        spdlog::set_pattern(fmt::format("[{}] %^%l:%$ %v", mpi_rank));
    }
#endif
}

void Simulation::initializeDataStructures(
    std::string const& project,
    std::vector<std::string> const& xml_patch_file_names,
    bool const reference_path_is_set, std::string const& reference_path,
    bool const nonfatal, std::string const& outdir, std::string const& mesh_dir,
    bool const write_prj)
{
    INFO("Reading project file {}.",
         std::filesystem::absolute(project).string());

    std::stringstream prj_stream;
    BaseLib::prepareProjectFile(prj_stream, project, xml_patch_file_names,
                                write_prj, outdir);
    auto project_config = BaseLib::makeConfigTree(
        project, !nonfatal, "OpenGeoSysProject", prj_stream);

    if (!reference_path_is_set)
    {  // Ignore the test_definition section.
        project_config.ignoreConfigParameter("test_definition");
    }
    else
    {
        test_definition = ApplicationsLib::TestDefinition(
            //! \ogs_file_param{prj__test_definition}
            project_config.getConfigSubtree("test_definition"), reference_path,
            outdir);
        if (test_definition->numberOfTests() == 0)
        {
            OGS_FATAL(
                "No tests were constructed from the test definitions, "
                "but reference solutions path was given.");
        }

        INFO("Cleanup possible output files before running ogs.");
        BaseLib::removeFiles(test_definition->getOutputFiles());
    }
#ifdef OGS_USE_INSITU
    //! \ogs_file_param{prj__insitu}
    if (auto t = project_config.getConfigSubtreeOptional("insitu"))
    {
        InSituLib::Initialize(
            //! \ogs_file_param{prj__insitu__scripts}
            t->getConfigSubtree("scripts"),
            BaseLib::extractPath(project));
        isInsituConfigured = true;
    }
#else
    project_config.ignoreConfigParameter("insitu");
#endif

    project_data = std::make_unique<ProjectData>(
        project_config, BaseLib::getProjectDirectory(), outdir, mesh_dir);

    INFO("Initialize processes.");
    for (auto& p : project_data->getProcesses())
    {
        p->initialize();
    }

    // Check intermediately that config parsing went fine.
    checkAndInvalidate(project_config);
    BaseLib::ConfigTree::assertNoSwallowedErrors();

    auto& time_loop = project_data->getTimeLoop();
    time_loop.initialize();
}

double Simulation::currentTime() const
{
    auto const& time_loop = project_data->getTimeLoop();
    return time_loop.currentTime();
}

double Simulation::endTime() const
{
    auto const& time_loop = project_data->getTimeLoop();
    return time_loop.endTime();
}

bool Simulation::executeTimeStep()
{
    auto& time_loop = project_data->getTimeLoop();
    if (time_loop.currentTime() < time_loop.endTime())
    {
        return time_loop.executeTimeStep();
    }
    return false;
}

bool Simulation::executeSimulation()
{
    INFO("Solve processes.");
    auto& time_loop = project_data->getTimeLoop();
    while (time_loop.currentTime() < time_loop.endTime())
    {
        time_loop.executeTimeStep();
        if (!time_loop.calculateNextTimeStep())
        {
            break;
        }
    }

    return time_loop.successful_time_step;
}

void Simulation::outputLastTimeStep() const
{
    auto const& time_loop = project_data->getTimeLoop();
    time_loop.outputLastTimeStep();
}

std::optional<ApplicationsLib::TestDefinition> Simulation::getTestDefinition()
    const
{
    return test_definition;
}

Simulation::~Simulation()
{
#ifdef OGS_USE_INSITU
    if (isInsituConfigured)
    {
        InSituLib::Finalize();
    }
#endif
#if defined(USE_PETSC)
    controller->Finalize(1);
#endif
}