#include <catch2/catch_all.hpp>

#include <Kokkos_Core.hpp>

#include <language/utils/OperatorRepository.hpp>
#include <mesh/DiamondDualConnectivityManager.hpp>
#include <mesh/DiamondDualMeshManager.hpp>
#include <mesh/MeshDataManager.hpp>
#include <mesh/SynchronizerManager.hpp>
#include <utils/Messenger.hpp>
#include <utils/PETScWrapper.hpp>
#include <utils/RandomEngine.hpp>
#include <utils/pugs_config.hpp>

#include <MeshDataBaseForTests.hpp>

#include <cstdlib>
#include <filesystem>

int
main(int argc, char* argv[])
{
  parallel::Messenger::create(argc, argv);
  Kokkos::initialize({4, -1, -1, true});

  PETScWrapper::initialize(argc, argv);

  const std::string output_base_name{"mpi_test_rank_"};

  std::filesystem::path parallel_output(std::string{PUGS_BINARY_DIR});

  std::filesystem::path gcov_prefix = [&]() -> std::filesystem::path {
    std::string template_temp_dir = std::filesystem::temp_directory_path() / "pugs_gcov_XXXXXX";
    return std::filesystem::path{mkdtemp(&template_temp_dir[0])};
  }();

  Catch::Session session;
  int result = session.applyCommandLine(argc, argv);

  if (result == 0) {
    const auto& config = session.config();
    if (config.listReporters() or config.listTags() or config.listTests()) {
      if (parallel::rank() == 0) {
        session.run();
      }
    } else {
      if (parallel::rank() != 0) {
        // Disable outputs for ranks != 0
        setenv("GCOV_PREFIX", gcov_prefix.string().c_str(), 1);
        parallel_output /= output_base_name + std::to_string(parallel::rank());

        Catch::ConfigData data{session.configData()};
        data.outputFilename = parallel_output.string();
        session.useConfigData(data);
      }

      // Disable outputs from tested classes to the standard output
      std::cout.setstate(std::ios::badbit);

      SynchronizerManager::create();
      RandomEngine::create();
      MeshDataManager::create();
      DiamondDualConnectivityManager::create();
      DiamondDualMeshManager::create();

      MeshDataBaseForTests::create();

      if (parallel::rank() == 0) {
        if (parallel::size() > 1) {
          session.config().stream() << rang::fgB::green << "Other rank outputs are stored in corresponding files"
                                    << rang::style::reset << '\n';

          for (size_t i_rank = 1; i_rank < parallel::size(); ++i_rank) {
            std::filesystem::path parallel_output(std::string{PUGS_BINARY_DIR});
            parallel_output /= output_base_name + std::to_string(i_rank);
            session.config().stream() << " - " << rang::fg::green << parallel_output.parent_path().string()
                                      << parallel_output.preferred_separator << rang::style::reset << rang::fgB::green
                                      << parallel_output.filename().string() << rang::style::reset << '\n';
          }
        }
      }

      OperatorRepository::create();

      result = session.run();

      OperatorRepository::destroy();

      MeshDataBaseForTests::destroy();

      DiamondDualMeshManager::destroy();
      DiamondDualConnectivityManager::destroy();
      MeshDataManager::destroy();
      RandomEngine::destroy();
      SynchronizerManager::destroy();
    }
  }

  PETScWrapper::finalize();

  Kokkos::finalize();
  parallel::Messenger::destroy();

  return result;
}