#include "config.h"
#include "theme.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>

static const char *FONT_KEY_NAMES[FONT_COUNT] = {
    "font_sans_regular", "font_sans_bold", "font_sans_italic", "font_sans_bold_italic",
    "font_mono_regular", "font_mono_bold", "font_mono_italic", "font_mono_bold_italic",
};

#define DEFAULT_LANGUAGE "sv"

static char *dup_str(const char *s) {
    size_t n = strlen(s);
    char *copy = malloc(n + 1);
    memcpy(copy, s, n + 1);
    return copy;
}

static void config_set_defaults(Config *cfg) {
    cfg->textColor = DEFAULT_COL_TEXT;
    cfg->bgColor = DEFAULT_COL_BG;
    for (int i = 0; i < FONT_COUNT; i++) cfg->fontPaths[i] = NULL;
    cfg->language = dup_str(DEFAULT_LANGUAGE);
    cfg->zoom = ZOOM_DEFAULT;
    keymap_set_defaults(cfg->keymap);
}

void config_free(Config *cfg) {
    for (int i = 0; i < FONT_COUNT; i++) {
        free(cfg->fontPaths[i]);
        cfg->fontPaths[i] = NULL;
    }
    free(cfg->language);
    cfg->language = NULL;
}

static const char *trim(const char *s, int len, int *outLen) {
    while (len > 0 && isspace((unsigned char)s[0])) { s++; len--; }
    while (len > 0 && isspace((unsigned char)s[len - 1])) len--;
    *outLen = len;
    return s;
}

static bool parse_hex_byte(const char *s, unsigned char *out) {
    unsigned char v = 0;
    for (int i = 0; i < 2; i++) {
        char c = s[i];
        v <<= 4;
        if (c >= '0' && c <= '9') v |= (unsigned char)(c - '0');
        else if (c >= 'a' && c <= 'f') v |= (unsigned char)(c - 'a' + 10);
        else if (c >= 'A' && c <= 'F') v |= (unsigned char)(c - 'A' + 10);
        else return false;
    }
    *out = v;
    return true;
}

static bool parse_hex_color(const char *v, int len, Clay_Color *out) {
    if (len > 0 && v[0] == '#') { v++; len--; }
    if (len != 6) return false;
    unsigned char r, g, b;
    if (!parse_hex_byte(v, &r) || !parse_hex_byte(v + 2, &g) || !parse_hex_byte(v + 4, &b)) return false;
    out->r = r; out->g = g; out->b = b; out->a = 255;
    return true;
}

static bool parse_float(const char *v, int len, float *out) {
    char buf[64];
    if (len <= 0 || len >= (int)sizeof buf) return false;
    memcpy(buf, v, (size_t)len);
    buf[len] = '\0';
    char *end;
    float f = strtof(buf, &end);
    if (end != buf + len) return false; /* trailing garbage, e.g. "1.0x" */
    *out = f;
    return true;
}

bool config_parse_line(const char *line, int len, Config *cfg) {
    int trimmedLen;
    const char *t = trim(line, len, &trimmedLen);
    if (trimmedLen == 0 || t[0] == '#') return false;

    const char *eq = memchr(t, '=', (size_t)trimmedLen);
    if (!eq) return false;

    int keyLen;
    const char *key = trim(t, (int)(eq - t), &keyLen);
    int valueLen;
    const char *value = trim(eq + 1, (int)(t + trimmedLen - (eq + 1)), &valueLen);
    if (keyLen == 0 || valueLen == 0) return false;

    if (keyLen == 10 && memcmp(key, "text_color", 10) == 0) return parse_hex_color(value, valueLen, &cfg->textColor);
    if (keyLen == 8 && memcmp(key, "bg_color", 8) == 0) return parse_hex_color(value, valueLen, &cfg->bgColor);
    if (keyLen == 8 && memcmp(key, "language", 8) == 0) {
        free(cfg->language);
        cfg->language = malloc((size_t)valueLen + 1);
        memcpy(cfg->language, value, (size_t)valueLen);
        cfg->language[valueLen] = '\0';
        return true;
    }
    if (keyLen == 4 && memcmp(key, "zoom", 4) == 0) {
        float z;
        if (!parse_float(value, valueLen, &z)) return false;
        if (z < ZOOM_MIN) z = ZOOM_MIN;
        if (z > ZOOM_MAX) z = ZOOM_MAX;
        cfg->zoom = z;
        return true;
    }

    for (int i = 0; i < FONT_COUNT; i++) {
        size_t nameLen = strlen(FONT_KEY_NAMES[i]);
        if ((size_t)keyLen == nameLen && memcmp(key, FONT_KEY_NAMES[i], nameLen) == 0) {
            free(cfg->fontPaths[i]);
            cfg->fontPaths[i] = malloc((size_t)valueLen + 1);
            memcpy(cfg->fontPaths[i], value, (size_t)valueLen);
            cfg->fontPaths[i][valueLen] = '\0';
            return true;
        }
    }

    if (keyLen > 4 && memcmp(key, "key.", 4) == 0) {
        return keymap_parse_config_line(key, keyLen, value, valueLen, cfg->keymap);
    }

    return false;
}

static bool build_config_path(char *buf, size_t bufSize) {
    const char *xdg = getenv("XDG_CONFIG_HOME");
    if (xdg && xdg[0]) { snprintf(buf, bufSize, "%s/hush/config", xdg); return true; }
    const char *home = getenv("HOME");
    if (!home || !home[0]) return false;
    snprintf(buf, bufSize, "%s/.config/hush/config", home);
    return true;
}

/* Creates every directory component of `path` (a file path; only the directory portion is
   created). Tolerates components that already exist. */
static void mkdir_parents(const char *path) {
    char buf[1024];
    size_t n = strlen(path);
    if (n >= sizeof buf) return;
    memcpy(buf, path, n + 1);

    for (size_t i = 1; i < n; i++) {
        if (buf[i] != '/') continue;
        buf[i] = '\0';
        mkdir(buf, 0755);
        buf[i] = '/';
    }
}

static void write_default_config(const char *path) {
    mkdir_parents(path);
    FILE *f = fopen(path, "wb");
    if (!f) return;
    fprintf(f,
        "# hush config file\n"
        "# Uncomment and edit any line below to override the default. Unknown or malformed\n"
        "# lines are ignored, so it's safe to experiment.\n"
        "\n"
        "# Language for the status bar and the quit/shortcuts dialogs. See assets/lang/ next to\n"
        "# the hush binary for available codes (or add your own -- copy en.lang, translate it,\n"
        "# name it <code>.lang, and set that code here).\n"
        "# language = " DEFAULT_LANGUAGE "\n"
        "\n"
        "# text_color = #%02X%02X%02X\n"
        "# bg_color = #%02X%02X%02X\n"
        "\n"
        "# Starting zoom level (Ctrl++/Ctrl+- still adjust it during the session; Ctrl+0 always\n"
        "# resets to 100%% regardless of this). Range 0.5-3.0.\n"
        "# zoom = 1.0\n"
        "\n"
        "# Font overrides (paths to .ttf files). Relative paths are resolved relative to the\n"
        "# directory hush is launched from. Leave commented out to use the bundled DejaVu fonts.\n"
        "# font_sans_regular = /path/to/font.ttf\n"
        "# font_sans_bold = /path/to/font-bold.ttf\n"
        "# font_sans_italic = /path/to/font-italic.ttf\n"
        "# font_sans_bold_italic = /path/to/font-bold-italic.ttf\n"
        "# font_mono_regular = /path/to/mono.ttf\n"
        "# font_mono_bold = /path/to/mono-bold.ttf\n"
        "# font_mono_italic = /path/to/mono-italic.ttf\n"
        "# font_mono_bold_italic = /path/to/mono-bold-italic.ttf\n"
        "\n"
        "# Keyboard shortcuts. Each is \"modifier+modifier+key\", where modifiers are any of\n"
        "# ctrl/shift/alt (in any order) and key is a single letter or digit, f1-f12, punctuation\n"
        "# (e.g. \"/\" or \"slash\"), or a name like enter/escape/tab/space/home/end/up/down/left/\n"
        "# right/pageup/pagedown/insert. A numpad fallback (for zoom) and F1 (for help) always\n"
        "# work too, regardless of what's set here, so there's no way to lock yourself out.\n"
        "# key.save = ctrl+s\n"
        "# key.open = ctrl+o\n"
        "# key.copy = ctrl+c\n"
        "# key.paste = ctrl+v\n"
        "# key.undo = ctrl+z\n"
        "# key.redo = ctrl+shift+z\n"
        "# key.quit = ctrl+x\n"
        "# key.help = ctrl+/\n"
        "# key.source_view = ctrl+m\n"
        "# key.zoom_in = ctrl++\n"
        "# key.zoom_out = ctrl+-\n"
        "# key.zoom_reset = ctrl+0\n",
        (unsigned)DEFAULT_COL_TEXT.r, (unsigned)DEFAULT_COL_TEXT.g, (unsigned)DEFAULT_COL_TEXT.b,
        (unsigned)DEFAULT_COL_BG.r, (unsigned)DEFAULT_COL_BG.g, (unsigned)DEFAULT_COL_BG.b);
    fclose(f);
}

bool config_load(Config *out) {
    config_set_defaults(out);

    char path[1024];
    if (!build_config_path(path, sizeof path)) return true;

    FILE *f = fopen(path, "rb");
    if (!f) {
        write_default_config(path);
        return true;
    }

    char line[1024];
    while (fgets(line, sizeof line, f)) {
        int len = (int)strlen(line);
        while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) len--;
        config_parse_line(line, len, out);
    }
    fclose(f);
    return true;
}
