#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include <utils/BuildInfo.hpp>
#include <utils/PugsUtils.hpp>
#include <utils/RevisionInfo.hpp>
#include <utils/pugs_build_info.hpp>

#include <rang.hpp>

#include <string>

// clazy:excludeall=non-pod-global-static

TEST_CASE("PugsUtils", "[utils]")
{
  SECTION("checking infos")
  {
    const std::string pugs_version = [] {
      std::stringstream os;

      os << "pugs version: " << rang::style::bold << RevisionInfo::version() << rang::style::reset << '\n';

      os << "-------------------- " << rang::fg::green << "git info" << rang::fg::reset << " -------------------------"
         << '\n';
      os << "tag:  " << rang::style::bold << RevisionInfo::gitTag() << rang::style::reset << '\n';
      os << "HEAD: " << rang::style::bold << RevisionInfo::gitHead() << rang::style::reset << '\n';
      os << "hash: " << rang::style::bold << RevisionInfo::gitHash() << rang::style::reset << "  (";

      if (RevisionInfo::gitIsClean()) {
        os << rang::fgB::green << "clean" << rang::fg::reset;
      } else {
        os << rang::fgB::red << "dirty" << rang::fg::reset;
      }
      os << ")\n";
      os << "-------------------------------------------------------";

      return os.str();
    }();

    REQUIRE(pugsVersion() == pugs_version);

    const std::string build_info = [] {
      std::ostringstream os;

      os << "-------------------- " << rang::fg::green << "build info" << rang::fg::reset << " -----------------------"
         << '\n';
      os << "type:     " << rang::style::bold << BuildInfo::type() << rang::style::reset << '\n';
      os << "compiler: " << rang::style::bold << BuildInfo::compiler() << rang::style::reset << '\n';
      os << "kokkos:   " << rang::style::bold << BuildInfo::kokkosDevices() << rang::style::reset << '\n';
      os << "MPI:      " << rang::style::bold << BuildInfo::mpiLibrary() << rang::style::reset << '\n';
      os << "PETSc:    " << rang::style::bold << BuildInfo::petscLibrary() << rang::style::reset << '\n';
      os << "SLEPc:    " << rang::style::bold << BuildInfo::slepcLibrary() << rang::style::reset << '\n';
      os << "HDF5:     " << rang::style::bold << BuildInfo::hdf5Library() << rang::style::reset << '\n';
      os << "SLURM:    " << rang::style::bold << BuildInfo::slurmLibrary() << rang::style::reset << '\n';
      os << "-------------------------------------------------------";

      return os.str();
    }();

    REQUIRE(pugsBuildInfo() == build_info);
  }

  SECTION("checking OMP environment setting")
  {
    if constexpr (std::string_view{PUGS_BUILD_KOKKOS_DEVICES} == std::string_view{"OpenMP"}) {
      const std::string saved_omp_proc_bind = []() {
        char* value = getenv("OMP_PROC_BIND");
        if (value != nullptr) {
          return std::string{value};
        } else {
          return std::string{};
        }
      }();

      const std::string saved_omp_places = []() {
        char* value = getenv("OMP_PLACES");
        if (value != nullptr) {
          return std::string{value};
        } else {
          return std::string{};
        }
      }();

      unsetenv("OMP_PROC_BIND");
      unsetenv("OMP_PLACES");

      setDefaultOMPEnvironment();
      REQUIRE(std::string{getenv("OMP_PROC_BIND")} == std::string{"spread,close"});
      REQUIRE(std::string{getenv("OMP_PLACES")} == std::string{"threads"});

      unsetenv("OMP_PROC_BIND");
      unsetenv("OMP_PLACES");

      setenv("OMP_PROC_BIND", "foo", 1);
      setenv("OMP_PLACES", "bar", 1);

      setDefaultOMPEnvironment();
      REQUIRE(std::string{getenv("OMP_PROC_BIND")} == std::string{"foo"});
      REQUIRE(std::string{getenv("OMP_PLACES")} == std::string{"bar"});

      unsetenv("OMP_PROC_BIND");
      unsetenv("OMP_PLACES");

      if (saved_omp_proc_bind.size() != 0) {
        setenv("OMP_PROC_BIND", saved_omp_proc_bind.c_str(), 1);
      }

      if (saved_omp_places.size() != 0) {
        setenv("OMP_PLACES", saved_omp_places.c_str(), 1);
      }
    }
  }
}