/*
 * main.cpp
 * MontaukOS Power Manager - GUI equivalent of the `power` command.
 *
 * Polls the kernel's CPU power/thermal snapshot (SYS_POWERINFO) once per
 * second and plots package temperature, average active frequency, and the
 * thermal governor's frequency ceiling as scrolling line graphs, plus a
 * details panel with the HWP/EPP/idle configuration.
 * Copyright (c) 2026 Daniel Hammer
 */

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

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

using namespace gui;

static constexpr int INIT_W        = 640;
static constexpr int INIT_H        = 520;
static constexpr int PW_TOOLBAR_H  = 36;
static constexpr int PW_POLL_MS    = 1000;
static constexpr int PW_GRAPH_CAP  = 120;

static constexpr Color PW_TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
static constexpr Color PW_HEADER_BG  = Color::from_rgb(0xF0, 0xF0, 0xF0);
static constexpr Color PW_BORDER     = Color::from_rgb(0xD0, 0xD0, 0xD0);
static constexpr Color PW_DIM_TEXT   = Color::from_rgb(0x66, 0x66, 0x66);
static constexpr Color PW_SUCCESS    = Color::from_rgb(0x22, 0x88, 0x22);
static constexpr Color PW_WARNING    = Color::from_rgb(0xCC, 0x88, 0x00);
static constexpr Color PW_DANGER     = Color::from_rgb(0xCC, 0x33, 0x33);
static constexpr Color PW_INFO       = Color::from_rgb(0x33, 0x66, 0xCC);
static constexpr Color PW_CEILING    = Color::from_rgb(0x00, 0x88, 0x88);

struct PowerMgrState {
    montauk::abi::PowerInfo info;
    bool supported;
    int mouse_x;
    int mouse_y;
    uint64_t last_poll_ms;

    uint64_t temp_samples[PW_GRAPH_CAP];
    uint64_t freq_samples[PW_GRAPH_CAP];
    uint64_t ceiling_samples[PW_GRAPH_CAP];
    mtk::GraphHistory temp_history;
    mtk::GraphHistory freq_history;
    mtk::GraphHistory ceiling_history;
};

static WsWindow g_win;
static PowerMgrState g_pw = {};

static mtk::Theme pw_theme() {
    mtk::Theme theme = mtk::make_theme();
    theme.surface = PW_TOOLBAR_BG;
    theme.surface_alt = PW_HEADER_BG;
    theme.border = PW_BORDER;
    theme.text_muted = PW_DIM_TEXT;
    theme.text_subtle = PW_DIM_TEXT;
    return theme;
}

static void init_graph_histories() {
    mtk::graph_history_init(&g_pw.temp_history, g_pw.temp_samples, PW_GRAPH_CAP);
    mtk::graph_history_init(&g_pw.freq_history, g_pw.freq_samples, PW_GRAPH_CAP);
    mtk::graph_history_init(&g_pw.ceiling_history, g_pw.ceiling_samples, PW_GRAPH_CAP);
}

static bool read_info() {
    return montauk::syscall1(montauk::abi::SYS_POWERINFO, (uint64_t)&g_pw.info) == 0;
}

static bool refresh_state(bool force) {
    if (!g_pw.supported)
        return false;

    uint64_t now = montauk::get_milliseconds();
    if (!force && now - g_pw.last_poll_ms < PW_POLL_MS)
        return false;

    g_pw.last_poll_ms = now;
    if (!read_info())
        return false;

    if (g_pw.info.tempC != 0)
        mtk::graph_history_push(&g_pw.temp_history, g_pw.info.tempC);
    mtk::graph_history_push(&g_pw.freq_history, g_pw.info.effMHz);
    mtk::graph_history_push(&g_pw.ceiling_history, g_pw.info.curMaxPerf);
    return true;
}

static Rect toolbar_rect() {
    return {0, 0, g_win.width, PW_TOOLBAR_H};
}

static Rect refresh_button_rect() {
    int btn_h = 26;
    int btn_y = (PW_TOOLBAR_H - btn_h) / 2;
    return {8, btn_y, 82, btn_h};
}

static bool mouse_in_rect(const Rect& rect) {
    return rect.contains(g_pw.mouse_x, g_pw.mouse_y);
}

static void render_toolbar(Canvas& c, const mtk::Theme& theme, int fh) {
    Rect bar = toolbar_rect();
    c.fill_rect(bar.x, bar.y, bar.w, bar.h, theme.surface);
    c.hline(bar.x, bar.y + bar.h - 1, bar.w, theme.border);

    Rect refresh_rect = refresh_button_rect();
    mtk::draw_button(c, refresh_rect, "Refresh", mtk::BUTTON_TONAL,
                     mtk::widget_state(false, mouse_in_rect(refresh_rect),
                                       g_pw.supported), theme);

    char status[96];
    if (!g_pw.supported) {
        snprintf(status, sizeof(status), "No power management");
    } else if (g_pw.info.throttling) {
        snprintf(status, sizeof(status), "%u C, avg %u MHz - THROTTLING",
                 (unsigned)g_pw.info.tempC, (unsigned)g_pw.info.effMHz);
    } else if (g_pw.info.tempC != 0) {
        snprintf(status, sizeof(status), "%u C, avg %u MHz",
                 (unsigned)g_pw.info.tempC, (unsigned)g_pw.info.effMHz);
    } else {
        snprintf(status, sizeof(status), "avg %u MHz", (unsigned)g_pw.info.effMHz);
    }

    int status_w = text_width(status);
    Color status_color = (g_pw.supported && g_pw.info.throttling)
        ? PW_DANGER : theme.text;
    c.text(g_win.width - status_w - 12, (PW_TOOLBAR_H - fh) / 2, status, status_color);
}

static void render_graph(Canvas& c,
                         const Rect& rect,
                         const mtk::GraphHistory& history,
                         uint64_t max_value,
                         const char* title,
                         const char* value,
                         Color line,
                         const mtk::Theme& theme) {
    mtk::GraphStyle style = mtk::make_graph_style(theme, line);
    style.background = theme.window_bg;
    style.plot_bg = Color::from_rgb(0xFA, 0xFA, 0xFA);
    style.grid = mtk::mix(style.plot_bg, line, 16);
    style.fill = mtk::lighten(line, 225);
    mtk::draw_line_graph(c, rect, history, max_value, title, value, theme, style);
}

static constexpr int PW_DETAIL_ROWS = 4;

static int details_row_h(int fh) {
    return fh + 10;
}

static int details_panel_h(int fh) {
    // Mirrors the graph cards: padding, title line, 7px gap, then rows.
    return 8 * 2 + fh + 7 + PW_DETAIL_ROWS * details_row_h(fh);
}

static void render_details(Canvas& c, const Rect& rect,
                           const mtk::Theme& theme, int fh) {
    mtk::draw_rounded_frame(c, rect, theme.radius_md, theme.window_bg, theme.border);

    const montauk::abi::PowerInfo& info = g_pw.info;
    int pad = 8;
    int x = rect.x + pad;
    int y = rect.y + pad;
    int w = rect.w - pad * 2;
    int row_h = details_row_h(fh);
    char buf[96];

    c.text(x, y, "Details", theme.text_subtle);
    y += fh + 7;

    struct Row {
        const char* label;
        const char* value;
        Color color;
    } rows[PW_DETAIL_ROWS];

    char hwp[64];
    if (info.hwpActive) {
        snprintf(hwp, sizeof(hwp), "active (EPP 0x%02X, perf range %u-%u)",
                 (unsigned)info.epp, (unsigned)info.lowestPerf,
                 (unsigned)info.highestPerf);
    } else {
        snprintf(hwp, sizeof(hwp), "not active");
    }
    rows[0] = {"HWP", hwp, theme.text};

    snprintf(buf, sizeof(buf), "base %u MHz, turbo %u MHz",
             (unsigned)info.baseMHz, (unsigned)info.maxMHz);
    rows[1] = {"Frequency", buf, theme.text};

    char gov[64];
    Color gov_color = theme.text;
    if (!info.hwpActive) {
        snprintf(gov, sizeof(gov), "inactive");
    } else if (info.throttling) {
        snprintf(gov, sizeof(gov), "THROTTLING - ceiling %u of %u",
                 (unsigned)info.curMaxPerf, (unsigned)info.highestPerf);
        gov_color = PW_DANGER;
    } else {
        snprintf(gov, sizeof(gov), "not throttling (ceiling %u)",
                 (unsigned)info.curMaxPerf);
        gov_color = PW_SUCCESS;
    }
    rows[2] = {"Governor", gov, gov_color};

    char idle[48];
    snprintf(idle, sizeof(idle), "MWAIT hint 0x%02X%s",
             (unsigned)(info.apIdleHint & 0xFF),
             info.apIdleHint == 0 ? " (C1 only)" : "");
    rows[3] = {"AP idle", idle, theme.text};

    for (int i = 0; i < PW_DETAIL_ROWS; i++) {
        c.text(x, y, rows[i].label, theme.text);
        int value_w = text_width(rows[i].value);
        c.text(x + w - value_w, y, rows[i].value, rows[i].color);
        y += row_h;
        if (i < PW_DETAIL_ROWS - 1)
            mtk::draw_separator(c, x, y - 5, w, theme);
    }
}

static void render_unsupported(Canvas& c, const mtk::Theme& theme, int fh) {
    const char* line1 = "No CPU power management available";
    const char* line2 = "(non-Intel CPU or running under emulation)";
    int y = gui_max((g_win.height - fh * 2 - 8) / 2, PW_TOOLBAR_H + 12);
    c.text(gui_max((g_win.width - text_width(line1)) / 2, 12), y,
           line1, theme.text);
    c.text(gui_max((g_win.width - text_width(line2)) / 2, 12), y + fh + 8,
           line2, theme.text_muted);
}

static void render_body(Canvas& c, const mtk::Theme& theme, int fh) {
    const montauk::abi::PowerInfo& info = g_pw.info;

    Rect body = {0, PW_TOOLBAR_H, g_win.width,
                 gui_max(g_win.height - PW_TOOLBAR_H, 0)};
    int margin = 12;
    int gap = 10;
    int details_h = details_panel_h(fh);
    int graph_area_h = gui_max(body.h - margin * 2 - gap * 2 - details_h, 96);
    int temp_h = (graph_area_h * 3) / 5;
    int row_h = graph_area_h - temp_h - gap;
    int col_w = gui_max((body.w - margin * 2 - gap) / 2, 80);
    int x0 = body.x + margin;
    int y0 = body.y + margin;
    int y1 = y0 + temp_h + gap;
    int y2 = y1 + row_h + gap;

    Rect temp_rect = {x0, y0, body.w - margin * 2, temp_h};
    Rect freq_rect = {x0, y1, col_w, row_h};
    Rect ceiling_rect = {x0 + col_w + gap, y1, col_w, row_h};
    Rect details_rect = {x0, y2, body.w - margin * 2, details_h};

    // Scale the plot to TjMax so the top edge is the hardware throttle
    // point; the line turns red while the governor is actively limiting.
    char temp_value[48];
    if (info.tempC != 0) {
        snprintf(temp_value, sizeof(temp_value), "%u C (throttle at %u C)",
                 (unsigned)info.tempC, (unsigned)info.tjMaxC);
    } else {
        snprintf(temp_value, sizeof(temp_value), "unavailable");
    }
    render_graph(c, temp_rect, g_pw.temp_history,
                 info.tjMaxC > 0 ? info.tjMaxC : 100,
                 "Package Temperature", temp_value,
                 info.throttling ? PW_DANGER : PW_WARNING, theme);

    char freq_value[32];
    snprintf(freq_value, sizeof(freq_value), "%u MHz", (unsigned)info.effMHz);
    render_graph(c, freq_rect, g_pw.freq_history,
                 info.maxMHz > 0 ? info.maxMHz : 1,
                 "Avg Frequency", freq_value, PW_INFO, theme);

    char ceiling_value[32];
    snprintf(ceiling_value, sizeof(ceiling_value), "%u / %u",
             (unsigned)info.curMaxPerf, (unsigned)info.highestPerf);
    render_graph(c, ceiling_rect, g_pw.ceiling_history,
                 info.highestPerf > 0 ? info.highestPerf : 1,
                 "Frequency Ceiling", ceiling_value, PW_CEILING, theme);

    render_details(c, details_rect, theme, fh);
}

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

    int fh = system_font_height();
    render_toolbar(c, theme, fh);

    if (g_pw.supported)
        render_body(c, theme, fh);
    else
        render_unsupported(c, theme, fh);
}

static bool handle_mouse(const montauk::abi::WinEvent& ev) {
    bool refresh_hover_before = mouse_in_rect(refresh_button_rect());

    g_pw.mouse_x = ev.mouse.x;
    g_pw.mouse_y = ev.mouse.y;

    bool redraw = refresh_hover_before != mouse_in_rect(refresh_button_rect());

    bool left_pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
    if (!left_pressed)
        return redraw;

    if (refresh_button_rect().contains(ev.mouse.x, ev.mouse.y))
        return refresh_state(true) || redraw;
    return redraw;
}

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

    if (!g_win.create("Power", INIT_W, INIT_H))
        montauk::exit(1);

    montauk::memset(&g_pw, 0, sizeof(g_pw));
    init_graph_histories();
    g_pw.mouse_x = -1;
    g_pw.mouse_y = -1;
    g_pw.supported = read_info();

    refresh_state(true);
    render();
    g_win.present();

    while (!g_win.closed) {
        bool redraw = refresh_state(false);

        montauk::abi::WinEvent ev;
        int r = g_win.poll(&ev);
        if (r < 0) break;

        if (r == 0) {
            if (redraw) {
                render();
                g_win.present();
            }
            montauk::sleep_ms(16);
            continue;
        }

        if (ev.type == 3)
            break;

        if (ev.type == 1) redraw |= handle_mouse(ev);
        else if (ev.type == 2 || ev.type == 4) redraw = true;

        if (redraw) {
            render();
            g_win.present();
        }
    }

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