#include <utils/PugsUtils.hpp>

#include <utils/BacktraceManager.hpp>
#include <utils/BuildInfo.hpp>
#include <utils/ConsoleManager.hpp>
#include <utils/FPEManager.hpp>
#include <utils/Messenger.hpp>
#include <utils/PETScWrapper.hpp>
#include <utils/RevisionInfo.hpp>
#include <utils/SLEPcWrapper.hpp>
#include <utils/SignalManager.hpp>
#include <utils/pugs_build_info.hpp>

#ifdef PUGS_HAS_PETSC
#include <petsc.h>
#endif   // PUGS_HAS_PETSC
#include <rang.hpp>

#include <Kokkos_Core.hpp>

#include <CLI/CLI.hpp>

#include <iostream>

std::string
pugsVersion()
{
  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 << "  (";

  // LCOV_EXCL_START Cannot cover both situations at same time
  if (RevisionInfo::gitIsClean()) {
    os << rang::fgB::green << "clean" << rang::fg::reset;
  } else {
    os << rang::fgB::red << "dirty" << rang::fg::reset;
  }
  // LCOV_EXCL_STOP
  os << ")\n";
  os << "-------------------------------------------------------";

  return os.str();
}

std::string
pugsBuildInfo()
{
  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 << "-------------------------------------------------------";

  return os.str();
}

void
setDefaultOMPEnvironment()
{
  if constexpr (std::string_view{PUGS_BUILD_KOKKOS_DEVICES}.find("OpenMP") != std::string_view::npos) {
    setenv("OMP_PROC_BIND", "spread,close", 0);
    setenv("OMP_PLACES", "threads", 0);
  }
}

// LCOV_EXCL_START

// This function cannot be unit-tested: run once when pugs starts

std::string
initialize(int& argc, char* argv[])
{
  parallel::Messenger::create(argc, argv);
  bool enable_fpe     = true;
  bool enable_signals = true;

  std::string filename;
  {
    CLI::App app{"pugs help"};

    app.add_option("filename", filename, "pugs script file")->check(CLI::ExistingFile)->required();

    app.set_version_flag("-v,--version", []() {
      ConsoleManager::init(true);
      std::stringstream os;
      os << pugsVersion() << '\n' << pugsBuildInfo();
      return os.str();
    });

    int threads = -1;
    app.add_option("--threads", threads, "Number of Kokkos threads")
      ->check(CLI::Range(1, std::numeric_limits<decltype(threads)>::max()));

    bool enable_color = true;
    app.add_flag("--color,!--no-color", enable_color, "Colorize console output [default: true]");

    app.add_flag("--fpe,!--no-fpe", enable_fpe, "Trap floating point exceptions [default: true]");

    bool show_preamble = true;
    app.add_flag("--preamble,!--no-preamble", show_preamble, "Show execution info preamble [default: true]");

    bool show_backtrace = false;
    app.add_flag("-b,--backtrace,!--no-backtrace", show_backtrace, "Show backtrace on failure [default: false]");

    app.add_flag("--signal,!--no-signal", enable_signals, "Catches signals [default: true]");

    bool pause_on_error = false;
    app.add_flag("-p,--pause-on-error", pause_on_error, "Pause for debugging on unexpected error [default: false]");

    std::atexit([]() { std::cout << rang::style::reset; });
    try {
      app.parse(argc, argv);
    }
    catch (const CLI::ParseError& e) {
      parallel::Messenger::destroy();
      std::exit(app.exit(e, std::cout, std::cerr));
    }

    BacktraceManager::setShow(show_backtrace);
    ConsoleManager::setShowPreamble(show_preamble);
    ConsoleManager::init(enable_color);
    SignalManager::setPauseForDebug(pause_on_error);
  }

#ifdef PUGS_HAS_PETSC
  PETSC_COMM_WORLD = parallel::Messenger::getInstance().comm();
#endif   // PUGS_HAS_PETSC

  PETScWrapper::initialize(argc, argv);
  SLEPcWrapper::initialize(argc, argv);

  FPEManager::init(enable_fpe);
  SignalManager::init(enable_signals);

  setDefaultOMPEnvironment();
  Kokkos::initialize(argc, argv);

  if (ConsoleManager::showPreamble()) {
    std::cout << "----------------- " << rang::fg::green << "pugs exec info" << rang::fg::reset
              << " ----------------------" << '\n';

    std::cout << rang::style::bold;
#ifdef PUGS_HAS_MPI
    std::cout << "MPI number of ranks " << parallel::size() << '\n';
#else    // PUGS_HAS_MPI
    std::cout << "Sequential build\n";
#endif   // PUGS_HAS_MPI
    Kokkos::DefaultExecutionSpace::print_configuration(std::cout);
    std::cout << rang::style::reset;
    std::cout << "-------------------------------------------------------\n";
  }

  return filename;
}

// LCOV_EXCL_STOP

// LCOV_EXCL_START

// This function cannot be unit-tested: run once when pugs stops

void
finalize()
{
  Kokkos::finalize();
  SLEPcWrapper::finalize();
  PETScWrapper::finalize();
  parallel::Messenger::destroy();
}

// LCOV_EXCL_STOP