// topfetch++ - live system monitor + fetch tool
// ISC licensed, see LICENSE.
// Logos adapted from pfetch (MIT), see THIRD-PARTY-NOTICES._
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <thread>
#include <vector>

// ---------- simple readers ----------

std::string readHostname() {
  std::ifstream f("/proc/sys/kernel/hostname");
  if (!f.is_open()) return "(none)";
  std::string s;
  std::getline(f, s);
  return s.empty() ? "(none)" : s;
}

std::string readVersion() {
  std::ifstream f("/proc/version");
  if (!f.is_open()) return "unknown";
  std::string w1, w2, version;
  f >> w1 >> w2 >> version;  // "Linux version 6.x.y ..."
  return version.empty() ? "unknown" : version;
}

long readUptime() {
  std::ifstream f("/proc/uptime");
  if (!f.is_open()) return -1;
  double seconds = 0;
  f >> seconds;
  return static_cast<long>(seconds);
}

struct Mem {
  long total = 0;      // kB
  long available = 0;  // kB
};

Mem readMeminfo() {
  Mem m;
  std::ifstream f("/proc/meminfo");
  if (!f.is_open()) return m;
  std::string line;
  while (std::getline(f, line)) {
    std::istringstream ss(line);
    std::string name;
    long value = 0;
    ss >> name >> value;
    if (name == "MemTotal:") m.total = value;
    else if (name == "MemAvailable:") m.available = value;
  }
  return m;
}

struct CpuTimes {
  long busy = 0;
  long total = 0;
};

CpuTimes readCpuTimes() {
  CpuTimes t;
  std::ifstream f("/proc/stat");
  if (!f.is_open()) return t;

  std::string label;
  f >> label;  // "cpu"

  long values[8] = {0};  // user nice system idle iowait irq softirq steal
  for (int i = 0; i < 8; i++) f >> values[i];

  long total = 0;
  for (int i = 0; i < 8; i++) total += values[i];

  long idle = values[3] + values[4];
  t.total = total;
  t.busy = total - idle;
  return t;
}

// ---------- distro ----------

struct Distro {
  std::string pretty = "Linux";
  std::string id;
  std::string idLike;
};

std::string unquote(const std::string& v) {
  if (v.size() >= 2 && (v.front() == '"' || v.front() == '\'') &&
      v.back() == v.front()) {
    return v.substr(1, v.size() - 2);
  }
  return v;
}

Distro readDistro() {
  Distro d;
  std::ifstream f("/etc/os-release");
  if (!f.is_open()) return d;

  std::string line;
  while (std::getline(f, line)) {
    size_t pos = line.find('=');
    if (pos == std::string::npos) continue;
    std::string key = line.substr(0, pos);
    std::string val = unquote(line.substr(pos + 1));
    if (key == "PRETTY_NAME" && !val.empty()) d.pretty = val;
    else if (key == "ID") d.id = val;
    else if (key == "ID_LIKE") d.idLike = val;
  }
  return d;
}

// ---------- logos ----------

struct Logo {
  std::vector<std::string> lines;
  std::string color;  // ANSI escape
};

// Logos adapted from pfetch (MIT)
// Copyright (c) 2016-2021 Dylan Araps
// Copyright (c) 2024-2025 Joey Reinhart
Logo pickLogo(const Distro& d, const std::string& override = "") {
  static const std::map<std::string, Logo> logos = {
    {"nixos", {{
      R"art(  \\  \\ //)art",
      R"art( ==\\__\\/ //)art",
      R"art(   //   \\//)art",
      R"art(==//     //==)art",
      R"art( //\\___//)art",
      R"art(// /\\  \\==)art",
      R"art(  // \\  \\)art",
    }, "\033[34m"}},
    {"arch", {{
      R"art(      /\)art",
      R"art(     /  \)art",
      R"art(    /\   \)art",
      R"art(   /      \)art",
      R"art(  /   ,,   \)art",
      R"art( /   |  |  -\)art",
      R"art(/_-''    ''-_\)art",
    }, "\033[36m"}},
        {"gentoo", {{
      R"art( _-----_)art",
      R"art((       \)art",
      R"art(\    0   \)art",
      R"art( \        ))art",
      R"art( /      _/)art",
      R"art((     _-)art",
      R"art(\____-)art",
    }, "\033[35m"}},
  };

  static const Logo fallback = {{
    R"art(    .--.)art",
    R"art(   |o_o |)art",
    R"art(   |:_/ |)art",
    R"art(  //   \ \)art",
    R"art( (|     | ))art",
    R"art(/'\_   _/`\)art",
    R"art(\___)=(___/)art",
  }, "\033[33m"};

  std::string id = override.empty() ? d.id : override;
  auto it = logos.find(id);
  if (it != logos.end()) return it->second;

  // ID_LIKE fallback (Manjaro -> arch, etc.)
  std::istringstream ss(d.idLike);
  std::string word;
  while (ss >> word) {
    it = logos.find(word);
    if (it != logos.end()) return it->second;
  }
  return fallback;
}

// ---------- main ----------

int main(int argc, char** argv) {
  std::string override;
  if (argc >= 3 && std::string(argv[1]) == "--logo") override = argv[2];


  const Distro distro = readDistro();
  const Logo logo = pickLogo(distro, override);
  const std::string hostname = readHostname();
  const std::string kernver = readVersion();

  size_t logoWidth = 0;
  for (const auto& line : logo.lines) logoWidth = std::max(logoWidth, line.size());

  CpuTimes prev = readCpuTimes();
  const double kbToGb = 1024.0 * 1024.0;

  while (true) {
    std::this_thread::sleep_for(std::chrono::seconds(1));

    Mem m = readMeminfo();
    long up = readUptime();
    CpuTimes now = readCpuTimes();

    long totalChange = now.total - prev.total;
    double cpuPercent = 0.0;
    if (totalChange > 0) cpuPercent = 100.0 * (now.busy - prev.busy) / totalChange;
    prev = now;
    std::string cpuColor;
    if (cpuPercent >= 80) {
      cpuColor = "\033[31m";
    }
    else if (cpuPercent >= 50) {
      cpuColor = "\033[33m";
    }
    else {
      cpuColor = "\033[32m";
    }



    std::ostringstream uptimeLine, memLine, cpuLine;
    uptimeLine << "Uptime:   ";
    if (up < 0) {
      uptimeLine << "Unknown";
    } else {
      uptimeLine << up / 86400 << "d " << (up % 86400) / 3600 << "h "
                 << (up % 3600) / 60 << "m";
    }

    memLine << std::fixed << std::setprecision(2) << "Memory:   ";
    if (m.total == 0) {
      memLine << "Unknown";
    } else {
      memLine << (m.total - m.available) / kbToGb << " GB / "
              << m.total / kbToGb << " GB";
    }

    cpuLine << std::fixed << std::setprecision(1)
            << "CPU:      " << cpuColor << cpuPercent << " %";

    std::vector<std::string> info = {
      "OS:       " + distro.pretty,
      "Hostname: " + hostname,
      "Kernel:   " + kernver,
      uptimeLine.str(),
      memLine.str(),
      cpuLine.str(),
    };

    size_t rows = std::max(logo.lines.size(), info.size());

    std::cout << "\033[H\033[2J";
    for (size_t i = 0; i < rows; i++) {
      std::string art = i < logo.lines.size() ? logo.lines[i] : "";
      std::string text = i < info.size() ? info[i] : "";
      std::cout << logo.color << std::left << std::setw(logoWidth) << art
                << "\033[0m" << "  " << text << '\n';
    }
    std::cout << std::flush;
  }
}
