//
// Created by aviallon on 20/04/2021.
//

#include "config.hpp"
#include "core/priority.hpp"

#include <chrono> // for system_clock::now
#include <fstream>

#include <fmt/chrono.h>
#include <fmt/format.h>

#include <spdlog/spdlog.h>

/* clang-format off */
const Config::ConfigInit &&Config::default_config = {
    {"apply_nice", "true"},   {"apply_sched", "true"},
    {"apply_ionice", "true"}, {"apply_oom_score_adj", "true"},
    {"apply_latnice", "true"}, {"log_applied_rule", "false"},
    {"cgroup_load", "true"},  {"type_load", "true"},
    {"rule_load", "true"},    {"cgroup_realtime_workaround", "true"},
    {"apply_cpuset", "true"}, {"x3d_mode", "auto"},
    {"loglevel", "info"},     {"check_freq", "60"}};
/* clang-format on */

static std::string trim(const std::string &str) {
  const std::size_t first = str.find_first_not_of(' ');
  if (std::string::npos == first) {
    return str;
  }
  const std::size_t last = str.find_last_not_of(' ');
  return str.substr(first, (last - first + 1));
}

Config::Config(const std::filesystem::path &config_path)
    : m_config_path(config_path) {
  config = {};

  for (const auto &item : default_config) {
    config[std::string(item.first)] = item.second;
  }

  const auto latnice_support_status = priority::test_latnice_support();

  // Testing latency nice support.
  config["apply_latnice"] = latnice_support_status;

  if (!exists(config_path)) {
    spdlog::error(fmt::format("{}: file {} does not exist", __func__,
                              std::string_view{config_path.c_str()}));

    std::string config_string = static_cast<std::string>(*this);
    spdlog::info("{}: Default config:\n{:s}", __func__, config_string);

    spdlog::info(fmt::format("{}: Writing default config to {}", __func__,
                             std::string_view{config_path.c_str()}));

    std::ofstream config_file(config_path);
    config_file << config_string << std::flush;

    if (!config_file) {
      spdlog::error(fmt::format("{}: Cannot write config to {}", __func__,
                                std::string_view{config_path.c_str()}));
    }

    return;
  }

  this->reload();
}

void Config::reload() noexcept {
  const auto latnice_support_status = priority::test_latnice_support();

  std::ifstream is_file{m_config_path};

  std::string line;
  while (std::getline(is_file, line)) {
    std::istringstream is_line(line);
    std::string        key;
    if (std::getline(is_line, key, '=')) {
      std::string value;
      if (std::getline(is_line, value)) {
        config[trim(key)] = trim(value);
      }
    }
  }

  // Disable latency nice if not supported by the kernel.
  const bool is_latnice_supported = latnice_support_status == "true";
  if (!is_latnice_supported) {
    config["apply_latnice"] = latnice_support_status;
  }
}

void Config::show() {
  for (const auto &item : config) {
    spdlog::info("Config {}: {}", item.first, item.second);
  }
}

void Config::set(const std::string &key, const std::string &value) {
  config[key] = value;
}

std::optional<std::string> Config::get(const std::string &key) const {
  if (config.contains(key))
    return config.at(key);

  return {};
}

Config::operator std::string() const {
  std::string config_string;
  config_string.reserve(config.size() * 10);

  fmt::format_to(std::back_inserter(config_string),
                 "# Generated by ananicy-cpp at {}\n",
                 std::chrono::system_clock::now());
  for (const auto &item : config) {
    fmt::format_to(std::back_inserter(config_string), "{:s} = {}\n", item.first,
                   item.second);
  }
  return config_string;
}

std::chrono::duration<std::uint32_t, std::ratio<1, 1>> Config::check_freq() {
  std::uint32_t frequency = 60;
  if (config.contains("check_freq"))
    frequency = static_cast<uint32_t>(std::stoul(config.at("check_freq")));

  spdlog::trace("{}: frequency = {}", __func__, frequency);

  std::chrono::duration<uint32_t, std::ratio<1, 1>> duration(frequency);
  return duration;
}
