/*
 * main.cpp
 * MontaukOS Network configuration applet
 * Copyright (c) 2026 Daniel Hammer
 */

#include <montauk/syscall.h>
#include <montauk/string.h>
#include <gui/mtk.hpp>
#include <gui/mtk/settings.hpp>
#include <gui/standalone.hpp>

extern "C" {
#include <stdio.h>
#include <string.h>
}

using namespace gui;

static constexpr int WIN_W = 560;
static constexpr int WIN_H = 420;
static constexpr int TAB_H = 36;
static constexpr int FOOTER_H = 44;
static constexpr int PAD = 16;
static constexpr int GAP = 12;
static constexpr int BUTTON_H = 30;
static constexpr int BUTTON_W = 90;
static constexpr int FIELD_COUNT = 4;
static constexpr int FIELD_TEXT_CAP = 16;
static constexpr int FIELD_H = 32;

enum Tab {
    TAB_STATUS = 0,
    TAB_CONFIG = 1,
    TAB_COUNT = 2,
};

struct Field {
    const char* label;
    char value[FIELD_TEXT_CAP];
    mtk::TextInputState input;
};

static const char* const kTabLabels[TAB_COUNT] = {
    "Status",
    "Configure",
};

static WsWindow g_win;
static Tab g_tab = TAB_STATUS;
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static int g_focus_field = -1;
static Montauk::NetCfg g_cfg = {};
static Montauk::NetStatus g_net = {};
static Field g_fields[FIELD_COUNT] = {
    {"IP Address", {}, {}},
    {"Subnet Mask", {}, {}},
    {"Gateway", {}, {}},
    {"DNS Server", {}, {}},
};
static bool g_dirty = false;
static char g_status[128] = {};
static uint64_t g_status_time = 0;
static uint64_t g_last_refresh = 0;
static Color g_accent = colors::ACCENT;

static mtk::Theme app_theme() {
    mtk::Theme theme = mtk::make_theme(g_accent);
    theme.danger_soft = Color::from_rgb(0xF8, 0xE0, 0xE0);
    return theme;
}

static void load_accent() {
    g_accent = mtk::load_system_accent();
}

static Rect tab_bar_rect() {
    return {0, 0, g_win.width, TAB_H};
}

static Rect footer_rect() {
    return {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H};
}

static bool status_visible() {
    return g_status[0] && (montauk::get_milliseconds() - g_status_time < 5000);
}

static void set_status(const char* msg) {
    snprintf(g_status, sizeof(g_status), "%s", msg ? msg : "");
    g_status_time = montauk::get_milliseconds();
}

static bool mouse_in_rect(const Rect& rect) {
    return rect.contains(g_mouse_x, g_mouse_y);
}

static mtk::WidgetState button_state(const Rect& rect, bool enabled = true) {
    return mtk::widget_state(false, mouse_in_rect(rect), enabled);
}

static int str_len(const char* s) {
    return s ? montauk::slen(s) : 0;
}

static bool is_ipv4_input_char(char ch) {
    return (ch >= '0' && ch <= '9') || ch == '.';
}

static bool accept_ipv4_input_char(char ch, void* userdata) {
    (void)userdata;
    return is_ipv4_input_char(ch);
}

static void focus_field(int index, int cursor = -1) {
    if (index < 0) index = 0;
    if (index >= FIELD_COUNT) index = FIELD_COUNT - 1;

    Field& field = g_fields[index];
    int len = str_len(field.value);
    if (cursor < 0) cursor = len;
    if (cursor > len) cursor = len;

    g_focus_field = index;
    for (int i = 0; i < FIELD_COUNT; i++) {
        if (i != index) mtk::context_menu_close(g_fields[i].input.context);
    }
    mtk::text_input_reset(field.input, cursor);
}

static bool mac_is_zero(const uint8_t* mac) {
    for (int i = 0; i < 6; i++) {
        if (mac[i] != 0) return false;
    }
    return true;
}

static void format_ip(char* buf, int size, uint32_t ip) {
    snprintf(buf, size, "%u.%u.%u.%u",
             (unsigned)(ip & 0xFF),
             (unsigned)((ip >> 8) & 0xFF),
             (unsigned)((ip >> 16) & 0xFF),
             (unsigned)((ip >> 24) & 0xFF));
}

static void format_mac(char* buf, int size, const uint8_t* mac) {
    if (!mac || mac_is_zero(mac)) {
        snprintf(buf, size, "Unavailable");
        return;
    }
    snprintf(buf, size, "%02X:%02X:%02X:%02X:%02X:%02X",
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

static void format_u64(char* buf, int size, uint64_t value) {
    if (size <= 0) return;
    char tmp[24];
    int n = 0;
    if (value == 0) {
        tmp[n++] = '0';
    } else {
        while (value && n < (int)sizeof(tmp)) {
            tmp[n++] = (char)('0' + (value % 10));
            value /= 10;
        }
    }

    int out = 0;
    while (n > 0 && out < size - 1) {
        buf[out++] = tmp[--n];
    }
    buf[out] = '\0';
}

static bool parse_ip(const char* s, uint32_t* out) {
    if (!s || !out) return false;
    uint32_t octets[4] = {};
    int idx = 0;
    uint32_t value = 0;
    bool has_digit = false;

    for (int i = 0; ; i++) {
        char c = s[i];
        if (c >= '0' && c <= '9') {
            value = value * 10 + (uint32_t)(c - '0');
            if (value > 255) return false;
            has_digit = true;
        } else if (c == '.' || c == '\0') {
            if (!has_digit || idx >= 4) return false;
            octets[idx++] = value;
            value = 0;
            has_digit = false;
            if (c == '\0') break;
        } else {
            return false;
        }
    }

    if (idx != 4) return false;
    *out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
    return true;
}

static void fit_text(char* out, int out_cap, const char* text, int max_w) {
    if (!out || out_cap <= 0) return;
    out[0] = '\0';
    if (!text || !text[0] || max_w <= 0) return;
    if (text_width(text) <= max_w) {
        snprintf(out, out_cap, "%s", text);
        return;
    }

    const char* ellipsis = "...";
    int ell_w = text_width(ellipsis);
    if (ell_w > max_w) return;

    int out_len = 0;
    while (text[out_len] && out_len < out_cap - 4) {
        out[out_len] = text[out_len];
        out[out_len + 1] = '\0';
        if (text_width(out) + ell_w > max_w)
            break;
        out_len++;
    }
    out[out_len] = '.';
    out[out_len + 1] = '.';
    out[out_len + 2] = '.';
    out[out_len + 3] = '\0';
}

static void draw_text_fit(Canvas& c, int x, int y, const char* text, int max_w, Color color) {
    char fitted[128];
    fit_text(fitted, sizeof(fitted), text, max_w);
    c.text(x, y, fitted, color);
}

static void copy_cfg_to_fields() {
    format_ip(g_fields[0].value, sizeof(g_fields[0].value), g_cfg.ipAddress);
    format_ip(g_fields[1].value, sizeof(g_fields[1].value), g_cfg.subnetMask);
    format_ip(g_fields[2].value, sizeof(g_fields[2].value), g_cfg.gateway);
    format_ip(g_fields[3].value, sizeof(g_fields[3].value), g_cfg.dnsServer);
    for (int i = 0; i < FIELD_COUNT; i++) {
        mtk::text_input_reset(g_fields[i].input, str_len(g_fields[i].value));
    }
    g_dirty = false;
}

static void refresh_state(bool update_fields) {
    montauk::get_netcfg(&g_cfg);
    if (montauk::net_status(&g_net) < 0) {
        montauk::memset(&g_net, 0, sizeof(g_net));
        snprintf(g_net.driver, sizeof(g_net.driver), "Unavailable");
    }
    if (update_fields) copy_cfg_to_fields();
    g_last_refresh = montauk::get_milliseconds();
}

static Rect status_refresh_button() {
    Rect foot = footer_rect();
    return {g_win.width - PAD - BUTTON_W, foot.y + 7, BUTTON_W, BUTTON_H};
}

static Rect status_dhcp_button() {
    Rect refresh = status_refresh_button();
    return {refresh.x - GAP - BUTTON_W, refresh.y, BUTTON_W, BUTTON_H};
}

static Rect status_clear_button() {
    Rect dhcp = status_dhcp_button();
    return {dhcp.x - GAP - BUTTON_W, dhcp.y, BUTTON_W, BUTTON_H};
}

static Rect config_apply_button() {
    Rect foot = footer_rect();
    return {g_win.width - PAD - BUTTON_W, foot.y + 7, BUTTON_W, BUTTON_H};
}

static Rect config_revert_button() {
    Rect apply = config_apply_button();
    return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, BUTTON_H};
}

static Rect config_dhcp_button() {
    Rect revert = config_revert_button();
    return {revert.x - GAP - BUTTON_W, revert.y, BUTTON_W, BUTTON_H};
}

static bool config_two_columns() {
    return g_win.width - PAD * 2 >= 470;
}

static int config_header_y() {
    return TAB_H + 18;
}

static int config_field_base_y() {
    return config_header_y() + system_font_height() + 18;
}

static int config_field_row_step() {
    return system_font_height() + FIELD_H + 16;
}

static Rect field_input_rect(int index) {
    int available = g_win.width - PAD * 2;
    bool two_cols = config_two_columns();
    int col_w = two_cols ? (available - GAP) / 2 : available;
    int col = two_cols ? (index % 2) : 0;
    int row = two_cols ? (index / 2) : index;
    int x = PAD + col * (col_w + GAP);
    int y = config_field_base_y() + row * config_field_row_step();
    return mtk::labeled_text_input_rect(x, y, col_w, app_theme(), FIELD_H);
}

static int field_label_y(int index) {
    int row = config_two_columns() ? (index / 2) : index;
    return config_field_base_y() + row * config_field_row_step();
}

static int field_x(int index) {
    int available = g_win.width - PAD * 2;
    bool two_cols = config_two_columns();
    int col_w = two_cols ? (available - GAP) / 2 : available;
    int col = two_cols ? (index % 2) : 0;
    return PAD + col * (col_w + GAP);
}

static int field_w(int index) {
    (void)index;
    int available = g_win.width - PAD * 2;
    return config_two_columns() ? (available - GAP) / 2 : available;
}

static void draw_kv(Canvas& c, int* y, const char* key, const char* value,
                    const mtk::Theme& theme) {
    int fh = system_font_height();
    int label_w = 128;
    c.text(PAD, *y, key, theme.text_muted);
    draw_text_fit(c, PAD + label_w, *y, value,
                  g_win.width - PAD * 2 - label_w, theme.text);
    *y += fh + 10;
}

static void draw_status_tab(Canvas& c, const mtk::Theme& theme) {
    int y = TAB_H + 20;
    int fh = system_font_height();
    bool adapter = g_net.initialized != 0;
    bool online = adapter && g_net.linkUp != 0 && g_cfg.ipAddress != 0;

    Color dot = adapter
        ? (g_net.linkUp ? Color::from_rgb(0x27, 0xA0, 0x58) : Color::from_rgb(0xD0, 0x9A, 0x2E))
        : theme.danger;
    fill_circle(c, PAD + 8, y + 8, 6, dot);

    const char* title = adapter
        ? (online ? "Online" : (g_net.linkUp ? "Link up, no address" : "Link down"))
        : "No network adapter";
    c.text(PAD + 24, y, title, adapter ? theme.text : theme.danger);

    char driver[80];
    snprintf(driver, sizeof(driver), "%s%s",
             g_net.driver[0] ? g_net.driver : "Unknown driver",
             g_net.polling ? " (polling)" : "");
    draw_text_fit(c, PAD + 24, y + fh + 3, driver,
                  g_win.width - PAD * 2 - 24, theme.text_subtle);

    y += fh * 2 + 24;
    mtk::draw_separator(c, PAD, y, g_win.width - PAD * 2, theme);
    y += 16;

    char ip[32], mask[32], gw[32], dns[32], mac[40], rx[32], tx[32];
    format_ip(ip, sizeof(ip), g_cfg.ipAddress);
    format_ip(mask, sizeof(mask), g_cfg.subnetMask);
    format_ip(gw, sizeof(gw), g_cfg.gateway);
    format_ip(dns, sizeof(dns), g_cfg.dnsServer);
    format_mac(mac, sizeof(mac), g_cfg.macAddress);
    format_u64(rx, sizeof(rx), g_net.rxPackets);
    format_u64(tx, sizeof(tx), g_net.txPackets);

    draw_kv(c, &y, "MAC Address", mac, theme);
    draw_kv(c, &y, "IP Address", ip, theme);
    draw_kv(c, &y, "Subnet Mask", mask, theme);
    draw_kv(c, &y, "Gateway", gw, theme);
    draw_kv(c, &y, "DNS Server", dns, theme);
    draw_kv(c, &y, "RX Packets", rx, theme);
    draw_kv(c, &y, "TX Packets", tx, theme);
}

static void draw_config_tab(Canvas& c, const mtk::Theme& theme) {
    int y = config_header_y();
    c.text(PAD, y, "IPv4 Configuration", theme.text);

    for (int i = 0; i < FIELD_COUNT; i++) {
        mtk::draw_labeled_text_field(c, field_x(i), field_label_y(i), field_w(i),
                                     g_fields[i].label, g_fields[i].value,
                                     g_fields[i].input.cursor, g_focus_field == i,
                                     false, theme, FIELD_H,
                                     g_fields[i].input.selection_anchor);
    }
}

static void draw_config_context_menus(Canvas& c, const mtk::Theme& theme) {
    for (int i = 0; i < FIELD_COUNT; i++) {
        mtk::draw_text_input_context_menu(
            c, g_fields[i].input, theme,
            mtk::text_input_has_selection(g_fields[i].input,
                                          g_fields[i].value,
                                          (int)sizeof(g_fields[i].value)));
    }
}

static void draw_footer(Canvas& c, const mtk::Theme& theme) {
    Rect foot = footer_rect();
    c.fill_rect(foot.x, foot.y, foot.w, foot.h, theme.surface);
    mtk::draw_separator(c, 0, foot.y, g_win.width, theme);

    const char* msg = status_visible()
        ? g_status
        : (g_dirty ? "Unsaved static IPv4 changes" : "Network settings ready");
    int text_right = (g_tab == TAB_STATUS ? status_clear_button().x : config_dhcp_button().x) - GAP;
    draw_text_fit(c, PAD, foot.y + (FOOTER_H - system_font_height()) / 2,
                  msg, text_right - PAD, g_dirty ? theme.text : theme.text_subtle);

    if (g_tab == TAB_STATUS) {
        Rect clear = status_clear_button();
        Rect dhcp = status_dhcp_button();
        Rect refresh = status_refresh_button();
        mtk::draw_button(c, clear, "Clear", mtk::BUTTON_SECONDARY,
                         button_state(clear), theme);
        mtk::draw_button(c, dhcp, "DHCP", mtk::BUTTON_PRIMARY,
                         button_state(dhcp), theme);
        mtk::draw_button(c, refresh, "Refresh", mtk::BUTTON_SECONDARY,
                         button_state(refresh), theme);
    } else {
        Rect dhcp = config_dhcp_button();
        Rect revert = config_revert_button();
        Rect apply = config_apply_button();
        mtk::draw_button(c, dhcp, "DHCP", mtk::BUTTON_SECONDARY,
                         button_state(dhcp), theme);
        mtk::draw_button(c, revert, "Revert", mtk::BUTTON_SECONDARY,
                         button_state(revert, g_dirty), theme);
        mtk::draw_button(c, apply, "Apply", mtk::BUTTON_PRIMARY,
                         button_state(apply, g_dirty), theme);
    }
}

static void render() {
    mtk::StandaloneHost host(&g_win);
    Canvas c = host.canvas();
    mtk::Theme theme = app_theme();

    c.fill(theme.window_bg);
    mtk::draw_tab_bar(c, tab_bar_rect(), kTabLabels, TAB_COUNT, (int)g_tab, theme);

    if (g_tab == TAB_STATUS) {
        draw_status_tab(c, theme);
    } else {
        draw_config_tab(c, theme);
    }
    draw_footer(c, theme);
    if (g_tab == TAB_CONFIG)
        draw_config_context_menus(c, theme);

    host.present();
}

static void launch_dhcp() {
    int pid = montauk::spawn("0:/os/dhcp.elf");
    if (pid >= 0) {
        set_status("DHCP client started");
    } else {
        set_status("Could not start DHCP client");
    }
}

static void clear_config() {
    Montauk::NetCfg cfg = g_cfg;
    cfg.ipAddress = 0;
    cfg.subnetMask = 0;
    cfg.gateway = 0;
    cfg.dnsServer = 0;
    if (montauk::set_netcfg(&cfg) < 0) {
        set_status("Could not clear network configuration");
        return;
    }
    refresh_state(true);
    set_status("Network configuration cleared");
}

static bool apply_config() {
    uint32_t values[FIELD_COUNT];
    for (int i = 0; i < FIELD_COUNT; i++) {
        if (!parse_ip(g_fields[i].value, &values[i])) {
            char msg[96];
            snprintf(msg, sizeof(msg), "Invalid %s", g_fields[i].label);
            set_status(msg);
            g_focus_field = i;
            return false;
        }
    }

    Montauk::NetCfg cfg = g_cfg;
    cfg.ipAddress = values[0];
    cfg.subnetMask = values[1];
    cfg.gateway = values[2];
    cfg.dnsServer = values[3];

    if (montauk::set_netcfg(&cfg) < 0) {
        set_status("Could not apply network configuration");
        return false;
    }

    refresh_state(true);
    set_status("Network configuration applied");
    return true;
}

static void set_tab(Tab tab) {
    if (g_tab == tab) {
        if (tab == TAB_CONFIG && (g_focus_field < 0 || g_focus_field >= FIELD_COUNT))
            focus_field(0);
        return;
    }
    g_tab = tab;
    if (tab == TAB_CONFIG) {
        focus_field(0);
    } else {
        g_focus_field = -1;
        for (int i = 0; i < FIELD_COUNT; i++) {
            g_fields[i].input.dragging = false;
            mtk::context_menu_close(g_fields[i].input.context);
        }
    }
}

static bool handle_mouse(int mx, int my, uint8_t buttons, uint8_t prev_buttons) {
    bool left_pressed = (buttons & 1) && !(prev_buttons & 1);
    bool right_pressed = (buttons & 2) && !(prev_buttons & 2);

    if (left_pressed) {
        int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
        if (tab >= 0) {
            set_tab((Tab)tab);
            return true;
        }
    }

    if (g_tab == TAB_STATUS) {
        if (!left_pressed) return false;
        if (status_refresh_button().contains(mx, my)) {
            refresh_state(!g_dirty);
            set_status("Network status refreshed");
            return true;
        }
        if (status_dhcp_button().contains(mx, my)) {
            launch_dhcp();
            return true;
        }
        if (status_clear_button().contains(mx, my)) {
            clear_config();
            return true;
        }
        return false;
    }

    for (int i = 0; i < FIELD_COUNT; i++) {
        if (!g_fields[i].input.context.open && !g_fields[i].input.dragging) continue;
        int result = mtk::text_input_handle_mouse(g_fields[i].input,
                                                  field_input_rect(i),
                                                  g_fields[i].value,
                                                  (int)sizeof(g_fields[i].value),
                                                  mx, my, buttons, prev_buttons,
                                                  g_win.width, g_win.height,
                                                  g_focus_field == i,
                                                  false, accept_ipv4_input_char);
        if (result & mtk::TEXT_INPUT_CHANGED)
            g_dirty = true;
        return result != mtk::TEXT_INPUT_NONE;
    }

    if (!left_pressed && !right_pressed) return false;

    for (int i = 0; i < FIELD_COUNT; i++) {
        Rect input = field_input_rect(i);
        if (input.contains(mx, my)) {
            g_focus_field = i;
            for (int j = 0; j < FIELD_COUNT; j++) {
                if (j != i) mtk::context_menu_close(g_fields[j].input.context);
            }
            int result = mtk::text_input_handle_mouse(g_fields[i].input, input,
                                                      g_fields[i].value,
                                                      (int)sizeof(g_fields[i].value),
                                                      mx, my, buttons, prev_buttons,
                                                      g_win.width, g_win.height,
                                                      true, false,
                                                      accept_ipv4_input_char);
            if (result & mtk::TEXT_INPUT_CHANGED)
                g_dirty = true;
            return true;
        }
    }

    if (!left_pressed) return false;

    if (config_dhcp_button().contains(mx, my)) {
        launch_dhcp();
        return true;
    }
    if (config_revert_button().contains(mx, my)) {
        if (g_dirty) {
            copy_cfg_to_fields();
            set_status("Changes reverted");
        }
        return true;
    }
    if (config_apply_button().contains(mx, my)) {
        if (g_dirty) apply_config();
        return true;
    }

    g_focus_field = -1;
    return true;
}

static bool handle_key(const Montauk::KeyEvent& key) {
    if (!key.pressed) return false;

    if (key.scancode == 0x01) {
        g_win.closed = true;
        return true;
    }

    if (key.ascii == '\t') {
        if (g_tab != TAB_CONFIG) {
            set_tab(TAB_CONFIG);
        } else {
            int next = (g_focus_field >= 0 && g_focus_field < FIELD_COUNT)
                ? (g_focus_field + 1) % FIELD_COUNT
                : 0;
            focus_field(next);
        }
        return true;
    }

    if (g_tab != TAB_CONFIG) {
        if (key.ascii == 'r' || key.ascii == 'R') {
            refresh_state(!g_dirty);
            set_status("Network status refreshed");
            return true;
        }
        if (key.ascii == 'd' || key.ascii == 'D') {
            launch_dhcp();
            return true;
        }
        return false;
    }

    if (key.ascii == '\n' || key.ascii == '\r') {
        if (g_dirty) apply_config();
        return true;
    }
    if (key.ascii == '\033') {
        if (g_dirty) copy_cfg_to_fields();
        return true;
    }

    if (g_focus_field < 0 || g_focus_field >= FIELD_COUNT) {
        if (!is_ipv4_input_char(key.ascii)) return false;
        focus_field(0);
    }

    Field* field = &g_fields[g_focus_field];
    int result = mtk::text_input_key(field->input, field->value,
                                     (int)sizeof(field->value), key,
                                     accept_ipv4_input_char);
    if (result & mtk::TEXT_INPUT_CHANGED)
        g_dirty = true;
    return (result & mtk::TEXT_INPUT_CONSUMED) != 0;
}

extern "C" void _start() {
    if (!fonts::init()) {
        montauk::exit(1);
    }

    load_accent();

    if (!g_win.create("Network", WIN_W, WIN_H)) {
        montauk::exit(1);
    }

    refresh_state(true);
    render();

    while (g_win.id >= 0 && !g_win.closed) {
        Montauk::WinEvent ev;
        int r = g_win.poll(&ev);
        bool redraw = false;

        if (r < 0) break;
        if (r == 0) {
            uint64_t now = montauk::get_milliseconds();
            if (now - g_last_refresh >= 3000) {
                refresh_state(!g_dirty);
                redraw = true;
            }
            if (status_visible()) redraw = true;
            if (redraw) render();
            montauk::sleep_ms(16);
            continue;
        }

        if (ev.type == 3) break;
        if (ev.type == 2 || ev.type == 4) {
            redraw = true;
        } else if (ev.type == 1) {
            g_mouse_x = ev.mouse.x;
            g_mouse_y = ev.mouse.y;
            redraw = true;
            if (handle_mouse(ev.mouse.x, ev.mouse.y,
                             ev.mouse.buttons, ev.mouse.prev_buttons)) {
                redraw = true;
            }
        } else if (ev.type == 0) {
            redraw = handle_key(ev.key);
        }

        if (redraw) render();
    }

    g_win.destroy();
    montauk::exit(0);
}
