/*
 * minifetch - tiny, fast system info for Linux.
 * Minimalist but pretty.
 *
 * Compile:
 *   gcc minifetch.c -o minifetch -O2
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/utsname.h>

/* ---- palette (ANSI truecolor) ---- */
#define RST   "\x1b[0m"
#define DIM   "\x1b[2m"
#define BOLD  "\x1b[1m"
#define TEAL  "\x1b[38;2;72;205;188m"   /* labels  */
#define SOFT  "\x1b[38;2;224;229;238m"  /* values  */
#define HEAD  "\x1b[1;38;2;108;236;216m"
#define USED  "\x1b[48;2;72;205;188m"   /* bar full */
#define EMPT  "\x1b[48;2;48;56;68m"     /* bar empty */

static int ansi_ok;

static void init_console(void)
{
    ansi_ok = isatty(STDOUT_FILENO);
}

static void out(const char *s) { if (ansi_ok) fputs(s, stdout); }

/* read a file and strip trailing whitespace */
static int read_file(const char *path, char *buf, size_t n)
{
    FILE *f = fopen(path, "r");
    size_t got;
    if (!f) return 0;
    got = fread(buf, 1, n - 1, f);
    buf[got] = 0;
    fclose(f);
    while (got && (buf[got - 1] == '\n' || buf[got - 1] == '\r' ||
                   buf[got - 1] == ' ' || buf[got - 1] == '\t'))
        buf[--got] = 0;
    return got > 0;
}

/* remove every occurrence of needle from s (in place, without overlap) */
static void strip(char *s, const char *needle)
{
    size_t nl = strlen(needle);
    char *w = s, *r = s;
    while (*r) {
        if (!strncmp(r, needle, nl)) r += nl;
        else *w++ = *r++;
    }
    *w = 0;
}

static void get_os(char *out, size_t n)
{
    char buf[512], *p, *q;
    struct utsname u;
    if (read_file("/etc/os-release", buf, sizeof buf)) {
        p = strstr(buf, "PRETTY_NAME=");
        if (p) {
            p += 12;
            if (*p == '"') {
                p++;
                q = strchr(p, '"');
                if (q) *q = 0;
            } else {
                q = strchr(p, '\n');
                if (q) *q = 0;
            }
            snprintf(out, n, "%s", p);
            if (uname(&u) == 0 && u.machine[0])
                snprintf(out + strlen(out), n - strlen(out), " %s",
                         strcmp(u.machine, "x86_64") ? u.machine : "x86_64");
            return;
        }
    }
    strncpy(out, "Linux", n);
}

static void get_host(char *out, size_t n)
{
    char pn[128] = "", bn[128] = "";
    read_file("/sys/class/dmi/id/product_name", pn, sizeof pn);
    read_file("/sys/class/dmi/id/board_name", bn, sizeof bn);
    if (!pn[0] || strcasestr(pn, "To be filled") || strstr(pn, "Default string") ||
        strcasestr(pn, "System Product Name") || strcasestr(pn, "O.E.M."))
        snprintf(out, n, "%s", bn[0] ? bn : "unknown");
    else
        snprintf(out, n, "%s", pn);
}

static void get_kernel(char *out, size_t n)
{
    struct utsname u;
    if (uname(&u) == 0)
        snprintf(out, n, "%s", u.release);
    else
        strncpy(out, "unknown", n);
    out[n - 1] = 0;
}

static void get_cpu(char *out, size_t n)
{
    FILE *f = fopen("/proc/cpuinfo", "r");
    char line[512], raw[256] = "CPU";
    int nproc = 0, found = 0;
    float mhz = 0.0f;
    if (f) {
        while (fgets(line, sizeof line, f)) {
            if (!strncmp(line, "processor", 9)) {
                nproc++;
            } else if (!found && !strncmp(line, "model name", 10)) {
                char *p = strchr(line, ':');
                if (p) {
                    char *e;
                    p++;
                    while (*p == ' ' || *p == '\t') p++;
                    e = p;
                    while (*e && *e != '\n' && *e != '@') e++;
                    while (e > p && (e[-1] == ' ' || e[-1] == '\t')) e--;
                    *e = 0;
                    strncpy(raw, p, sizeof raw - 1);
                    found = 1;
                }
            } else if (!strncmp(line, "cpu MHz", 7)) {
                char *p = strchr(line, ':');
                if (p) {
                    float v = (float)atof(p + 1);
                    if (v > mhz) mhz = v;
                }
            }
        }
        fclose(f);
    }

    {
        char buf[32];
        if (read_file("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
                      buf, sizeof buf)) {
            long khz = atol(buf);
            if (khz > 0 && khz / 1000.0f > mhz) mhz = khz / 1000.0f;
        }
    }

    if (found) {
        char brand[256];
        strncpy(brand, raw, sizeof brand - 1);
        brand[sizeof brand - 1] = 0;
        strip(brand, "(R)");   /* Intel(R) Xeon(R) ... -> Intel Xeon ... */
        strip(brand, " CPU");  /* Intel Xeon CPU E5-...  -> Intel Xeon E5-... */
        {
            char *w = brand, *r = brand;
            int last_space = 0;
            while (*r) {
                if (*r != ' ' || !last_space) *w++ = *r;
                last_space = (*r == ' ');
                r++;
            }
            *w = 0;
        }
        if (mhz > 0.0f)
            snprintf(out, n, "%s (%d) @ %.3fGHz", brand, nproc, mhz / 1000.0f);
        else
            snprintf(out, n, "%s (%d)", brand, nproc);
    } else if (nproc) {
        snprintf(out, n, "%d threads", nproc);
    } else {
        strncpy(out, "unknown CPU", n);
    }
    out[n - 1] = 0;
}

/* first VGA/3D/Display controller model, via lspci */
static void get_gpu(char *out, size_t n)
{
    FILE *p = popen("lspci -mm 2>/dev/null", "r");
    char line[512];
    int found = 0;
    if (!p) goto fail;
    while (fgets(line, sizeof line, p)) {
        char *tok[3];
        const char *vend;
        int ti = 0;
        char *s = line;
        while (ti < 3) {
            s = strchr(s, '"');
            if (!s) break;
            s++;
            tok[ti] = s;
            s = strchr(s, '"');
            if (!s) break;
            *s = 0;
            s++;
            ti++;
        }
        if (ti < 3) continue;
        if (strcmp(tok[0], "VGA compatible controller") &&
            strcmp(tok[0], "3D controller") &&
            strcmp(tok[0], "Display controller"))
            continue;

        /* model: prefer the [bracket] part of the device name */
        {
            char *m = strchr(tok[2], '[');
            char *e = m ? strchr(m + 1, ']') : NULL;
            if (e) { m++; *e = 0; tok[2] = m; }
            else { e = strchr(tok[2], '('); if (e) { *e = 0; m = e - 1;
                   while (m > tok[2] && *m == ' ') *m-- = 0; } }
        }
        /* short vendor name */
        if (strcasestr(tok[1], "nvidia")) vend = "NVIDIA";
        else if (strcasestr(tok[1], "amd") || strcasestr(tok[1], "advanced"))
            vend = "AMD";
        else if (strcasestr(tok[1], "intel")) vend = "Intel";
        else vend = tok[1];

        if (strstr(tok[2], vend))
            snprintf(out, n, "%s", tok[2]);
        else
            snprintf(out, n, "%s %s", vend, tok[2]);
        found = 1;
        break;
    }
    pclose(p);
    if (found) { out[n - 1] = 0; return; }
fail:
    strncpy(out, "unknown", n);
}

static long mem_kb(const char *key)
{
    FILE *f = fopen("/proc/meminfo", "r");
    char line[256];
    long v = -1;
    size_t kl = strlen(key);
    if (!f) return -1;
    while (fgets(line, sizeof line, f)) {
        if (!strncmp(line, key, kl)) {
            char *p = line + kl;
            while (*p == ' ' || *p == '\t' || *p == ':') p++;
            v = atol(p);
            break;
        }
    }
    fclose(f);
    return v;
}

static long read_uptime(void)
{
    char buf[64];
    if (read_file("/proc/uptime", buf, sizeof buf))
        return (long)strtod(buf, NULL);
    return -1;
}

static void fmt_uptime(long s, char *out, size_t n)
{
    long d = s / 86400, h = s % 86400 / 3600, m = s % 3600 / 60, x = s % 60;
    if (d) snprintf(out, n, "%ld days, %ld hours, %ld mins", d, h, m);
    else if (h) snprintf(out, n, "%ld hours, %ld mins", h, m);
    else if (m) snprintf(out, n, "%ld mins, %ld secs", m, x);
    else snprintf(out, n, "%ld secs", x);
}

static void get_shell(char *out, size_t n)
{
    const char *sh = getenv("SHELL");
    const char *b;
    char cmd[320], ver[128] = "";
    FILE *p;
    if (!sh || !*sh) { strncpy(out, "unknown", n); return; }
    b = strrchr(sh, '/');
    b = b ? b + 1 : sh;

    /* $SHELL does not export its version -> ask the binary itself */
    snprintf(cmd, sizeof cmd, "'%s' --version 2>/dev/null | head -1", sh);
    p = popen(cmd, "r");
    if (p) {
        if (fgets(ver, sizeof ver, p)) {
            char *v = strcasestr(ver, "version");
            char *t;
            if (v) {
                v += 7;                       /* "version 5.2.21(1)-release" */
            } else {
                t = strchr(ver, ' ');         /* zsh: "zsh 5.9 (x86_64...)" */
                v = t && *t ? t + 1 : NULL;
            }
            if (v) {
                while (*v == ' ' || *v == '\t') v++;
                t = v;
                while (*t && *t != '\n' && *t != ' ' &&
                       *t != '(' && *t != ',') t++;
                *t = 0;
                if (!*v) v = NULL;
            }
            if (!v) ver[0] = 0;
            else {
                char tmp[128];
                strncpy(tmp, v, sizeof tmp - 1);
                tmp[sizeof tmp - 1] = 0;
                strncpy(ver, tmp, sizeof ver - 1);
                ver[sizeof ver - 1] = 0;
            }
        }
        pclose(p);
    }
    if (ver[0])
        snprintf(out, n, "%s %s", b, ver);
    else
        strncpy(out, b, n - 1);
    out[n - 1] = 0;
}

static long count_lines(const char *cmd)
{
    FILE *p = popen(cmd, "r");
    char buf[512];
    long k = 0;
    if (!p) return 0;
    while (fgets(buf, sizeof buf, p)) k++;
    pclose(p);
    return k;
}

/* number of installed snaps; --all also counts disabled ones, like
   neofetch does; header may be missing or indented when piped */
static long count_snaps(void)
{
    FILE *p = popen("snap list --all 2>/dev/null", "r");
    char buf[512];
    long rows = 0;
    int header = 0;
    if (!p) return 0;
    while (fgets(buf, sizeof buf, p)) {
        char *s = buf;
        while (*s == ' ' || *s == '\t' || *s == '\n') s++;
        if (!*s) continue;   /* skip blank lines */
        rows++;
        if (s == buf && !header &&
            !strncmp(s, "Name", 4) &&
            (s[4] == ' ' || s[4] == '\t' || s[4] == '\n'))
            header = 1;
    }
    pclose(p);
    if (rows - header < 0) return 0;
    return rows - header;
}

/* packages -> "1234 (dpkg), 8 (snap)" ; returns 0 if none found */
static int get_packages(char *out, size_t n)
{
    long c;
    char tag[8] = "dpkg";
    c = count_lines("dpkg-query -W -f='${Package}\\n' 2>/dev/null");
    if (c <= 0) {
        c = count_lines("pacman -Qq 2>/dev/null");
        if (c > 0) strcpy(tag, "pacman");
        else { c = count_lines("rpm -qa 2>/dev/null"); if (c > 0) strcpy(tag, "rpm"); }
    }
    if (c <= 0) return 0;
    snprintf(out, n, "%ld (%s)", c, tag);

    if (!strcmp(tag, "dpkg")) {
        long s = count_snaps();
        if (s > 0) {
            size_t l = strlen(out);
            snprintf(out + l, n - l, ", %ld (snap)", s);
        }
    }
    return 1;
}

/* resolution from the first DRM connector that has modes */
static int get_res(char *out, size_t n)
{
    DIR *d = opendir("/sys/class/drm");
    struct dirent *e;
    char buf[64];
    if (!d) return 0;
    while ((e = readdir(d))) {
        char path[512];
        FILE *f;
        if (strncmp(e->d_name, "card", 4)) continue;
        snprintf(path, sizeof path, "/sys/class/drm/%s/modes", e->d_name);
        f = fopen(path, "r");
        if (f) {
            if (fgets(buf, sizeof buf, f)) {
                buf[strcspn(buf, "\n")] = 0;
                if (strchr(buf, 'x')) {
                    snprintf(out, n, "%s", buf);
                    fclose(f);
                    closedir(d);
                    return 1;
                }
            }
            fclose(f);
        }
    }
    closedir(d);
    return 0;
}

/* aligned label + value */
static void field(const char *label, const char *value)
{
    printf("  ");
    out(TEAL);
    printf("%-9s", label);
    out(RST);
    printf("  ");
    out(SOFT);
    printf("%s", value);
    out(RST);
    printf("\n");
}

int main(void)
{
    char user[64] = "user", host[256] = "host";
    char os[256], hst[128], kern[128], cpu[512], gpu[256], shell[256];
    char upt[64], res[64], term[128] = "", mem[64], pk[128];
    char *ttyv = ttyname(STDIN_FILENO);
    long total = mem_kb("MemTotal");
    long avail = mem_kb("MemAvailable");
    int i;

    init_console();

    {
        const char *u2 = getenv("USER");
        if (u2 && *u2) strncpy(user, u2, sizeof user - 1);
        user[sizeof user - 1] = 0;
    }
    if (gethostname(host, sizeof host) != 0)
        strncpy(host, "host", sizeof host - 1);
    host[sizeof host - 1] = 0;

    get_os(os, sizeof os);
    get_host(hst, sizeof hst);
    get_kernel(kern, sizeof kern);
    get_cpu(cpu, sizeof cpu);
    get_gpu(gpu, sizeof gpu);
    get_shell(shell, sizeof shell);
    fmt_uptime(read_uptime(), upt, sizeof upt);

    if (avail < 0) avail = mem_kb("MemFree");
    if (total > 0) {
        double g = 1024.0 * 1024.0;
        snprintf(mem, sizeof mem, "%.1f / %.1f GiB",
                 (total - avail) / g, total / g);
    } else {
        strncpy(mem, "unknown", sizeof mem);
    }

    if (ttyv)
        snprintf(term, sizeof term, "%s", ttyv);
    else if (getenv("TERM"))
        snprintf(term, sizeof term, "%s", getenv("TERM"));

    {
        double pct = total > 0
            ? 100.0 * (double)(total - avail) / (double)total : 0.0;
        int wid = 12;
        int fill = (int)(pct / 100.0 * wid + 0.5);
        int has_res = get_res(res, sizeof res);
        int has_pk = get_packages(pk, sizeof pk);

        printf("\n  ");
        out(HEAD);
        printf("%s@%s", user, host);
        out(RST);
        printf("\n");
        out(DIM);
        for (i = 0; i < 42; i++) printf("-");
        out(RST);
        printf("\n");

        field("os", os);
        field("host", hst);
        field("kernel", kern);
        field("uptime", upt);
        if (has_pk) field("packages", pk);
        field("shell", shell);
        if (has_res) field("res", res);
        if (term[0]) field("term", term);
        out("\n");
        field("cpu", cpu);
        field("gpu", gpu);
        out("\n");
        /* memory with inline bar */
        printf("  ");
        out(TEAL);
        printf("%-9s", "mem");
        out(RST);
        printf("  ");
        out(SOFT);
        printf("%s  (%.0f%%)  ", mem, pct);
        for (i = 0; i < wid; i++) {
            if (i < fill) { out(USED); printf(" "); }
            else { out(EMPT); printf(" "); }
        }
        out(RST);
        printf("\n\n");
    }
    return 0;
}