/* calendar.c —— 常用小程序①：**日历**（第 4 层：`ui_*` 图形界面）
 * calendar.c -- Everyday utility #1: a **calendar** (layer 4: the `ui_*` graphical interface)
 *
 * 一扇窗口里的月历：**今天高亮**，`←`/`→` 翻月、`↑`/`↓` 翻年，**点某一天**也能选中它，
 * A month calendar in a window: **today is highlighted**; Left/Right change the month, Up/Down the year,
 * `Esc` 退出。手机上就是"点屏幕"，桌面上是键盘或鼠标 —— 同一份源码两个平台都跑。
 * and **tapping a day** selects it; Esc quits. On a phone that is a tap, on the desktop a key or a mouse --
 * one source file, both platforms.
 *
 * ## 第 4 层的三条规矩（不守就会"手机上行、桌面上歪"）
 * ## Three rules of layer 4 (break them and it works on the phone but drifts on the desktop)
 *
 * · **尺寸从宿主问**：`ui_scr_get_w()/_h()`，别写死。窗口是按宿主给的尺寸开的，
 * · **Ask the host for the size**: `ui_scr_get_w()/_h()`, never hard-code it. The window opens at the size
 *   手机上竖屏 ~395 宽、桌面上是另一套数，写死就一边挤一边空。
 *   the host gives; a phone in portrait is about 395 wide while the desktop is something else entirely.
 * · **颜色是真彩 `0xAARRGGBB`**（不是索引色）：`0xFF101018` = 不透明的深灰。
 * · **Colours are true colour `0xAARRGGBB`** (not palette indexes): `0xFF101018` is opaque near-black.
 * · **画完必须 `ui_2d_present()`**：它是"把这一帧交上去"那一步，漏了就是黑屏（宿主不猜你画完了没）。
 * · **Always finish with `ui_2d_present()`**: that is the "hand this frame over" step; skip it and you get a
 *   black screen (the host does not guess when you are done).
 *
 * ## 为什么"空闲 8 秒就自己退出"
 * ## Why it exits by itself after 8 seconds of idling
 *
 * 例程要能被**自动验收**（`scripts/vmlcli` 加 `--frame` 出一张图就够了），所以循环必须有界：
 * Samples have to be **automatically verifiable** (one `scripts/vmlcli` run with `--frame` is enough),
 * so the loop must be bounded:
 * 空闲超过 8 秒就退出。**写真正的程序时把 `IDLE_EXIT_MS` 去掉**，其余（画格子 / 读键 / 命中判定）就是完整写法。
 * it exits after 8 s of idling. **Drop `IDLE_EXIT_MS` when you write a real program**; everything else
 * (drawing the grid, reading keys, hit testing) is the complete pattern.
 *
 * 界面文字跟着宿主语言走（`ui_scr_get_language()`），与 `demo_ui.c` 同一套做法。
 * The on-screen text follows the host language (`ui_scr_get_language()`), the same approach `demo_ui.c` uses.
 */

#include <vml_ui.h>
#include <stdlib.h>   /* itoa --- digits to text via the library */
#include <time.h>         /* time / localtime（`tm_t` 与 C 的 `struct tm` 同 layout） */
                          /* time / localtime (`tm_t` has the same layout as C's `struct tm`) */

#define IDLE_EXIT_MS 8000

static int SW;            /* 画布宽 */
/* canvas width */
static int SH;            /* 画布高 */
/* canvas height */
/* 界面语言：0 = 中文，1 = English */
/* UI language: 0 = Chinese, 1 = English */
static int lang;

/* 配色（真彩 AA RRGGBB）*/
/* Colours (true colour AA RRGGBB) */
#define C_BG      0xFF101018
#define C_CARD    0xFF1A1A24
#define C_TITLE   0xFFE8E8F0
#define C_DIM     0xFF9AA0B0
#define C_ACCENT  0xFF4A90D9
#define C_TODAY   0xFFFFC64A
#define C_WEEKEND 0xFFE06C50
#define C_DAY     0xFFD8DCE8

/* 是不是闰年 */
/* Is it a leap year */
static int is_leap(int y) {
    if (y % 400 == 0) return 1;
    if (y % 100 == 0) return 0;
    return (y % 4 == 0) ? 1 : 0;
}

/* 这个月有几天 */
/* How many days this month has */
static int days_in_month(int y, int m) {
    if (m == 2) return is_leap(y) ? 29 : 28;
    if (m == 4 || m == 6 || m == 9 || m == 11) return 30;
    return 31;
}

/* 某月 1 号是星期几（0=周日）。蔡勒公式，编号与 `localtime` 的 `tm_wday` 一致。
 * Weekday of the 1st of a month (0 = Sunday). Zeller's congruence, numbered like `localtime`'s `tm_wday`. */
static int weekday_of_first(int y, int m) {
    int yy = y;
    int mm = m;
    int k;
    int j;
    int h;
    if (mm < 3) { mm = mm + 12; yy = yy - 1; }   /* 1、2 月算作上一年的 13、14 月 */
                                                 /* Jan and Feb count as months 13 and 14 of the year before */
    k = yy % 100;
    j = yy / 100;
    h = (1 + (13 * (mm + 1)) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;   /* h: 0=周六 */
    /* 0 = Saturday */
    return (h + 6) % 7;                                              /* 换成 0=周日 */
    /* to 0 = Sunday */
}

static char *month_en(int m) {
    if (m == 1) return "January";
    if (m == 2) return "February";
    if (m == 3) return "March";
    if (m == 4) return "April";
    if (m == 5) return "May";
    if (m == 6) return "June";
    if (m == 7) return "July";
    if (m == 8) return "August";
    if (m == 9) return "September";
    if (m == 10) return "October";
    if (m == 11) return "November";
    return "December";
}

/* 整数 → 十进制串（`ui_2d_text` 只收字符串；第 4 层没有 `print_int` 那种东西）
 * Integer to a decimal string (`ui_2d_text` takes strings only; layer 4 has no `print_int`). */
static char numbuf[16];
static char *numstr(int v) {
    itoa(v, numbuf);
    return numbuf;
}

/* 画两位日号（补零，7×7 的格子里对齐才好看）
 * A two-digit day number (zero padded, which keeps the 7x7 grid aligned) */
static char daybuf[4];
static char *daystr(int d) {
    daybuf[0] = (char)('0' + (d / 10) % 10);
    daybuf[1] = (char)('0' + d % 10);
    daybuf[2] = 0;
    return daybuf;
}

/* 当前显示的月份 */
/* The month currently on screen */
static int cur_y;
static int cur_m;
/* 选中/高亮的日号，0 = 没有 */
/* The selected (highlighted) day, 0 = none */
static int sel_d;
static int today_y;
static int today_m;
static int today_d;

/* 棋盘几何（画与命中判定**必须用同一份**，见 `hit_day`）
 * Grid geometry (drawing and hit testing **must share this one copy**, see `hit_day`) */
static int g_pad;
static int g_cell;
static int g_top;

static void layout(void) {
    int grid_h;
    int block_h;
    SW = ui_scr_get_w();
    SH = ui_scr_get_h();
    if (SW <= 0) SW = 360;
    if (SH <= 0) SH = 620;
    g_pad = SW / 18;
    if (g_pad < 6) g_pad = 6;
    g_cell = (SW - g_pad * 2) / 7;      /* 7 列等宽；余数留在右边不动 */
                                        /* 7 equal columns; any remainder stays on the right */
    if (g_cell < 18) g_cell = 18;
    /* ⚠ **行数固定 6**：月历最长 6 行（31 天且 1 号在周六时）。按"这个月实际几行"算的话，
     * ⚠ **Always six rows**: a month spans at most six rows (31 days with the 1st on a Saturday).
     *   翻月时整块会上下跳（实测过：5 行与 6 行来回切，日历像在抖）。
     *   Sizing by the actual row count makes the whole block jump when you change month (measured:
     *   flipping between five and six rows makes the calendar visibly shake).
     */
    grid_h = 6 * g_cell;
    block_h = 64 + grid_h;              /* 标题 + 表头（64）+ 网格 */
    /* title + header (64) + grid */
    g_top = (SH - block_h) / 2 + 64;    /* 整块垂直居中 */
    /* centre the whole block vertically */
    if (g_top < 80) g_top = 80;         /* 矮屏兜底：别把标题顶出画布 */
                                        /* short screens: keep the title on the canvas */
}

/* 第 d 天那一格的左上角 */
/* Top-left corner of the cell for day `d` */
static int cell_x(int idx) { return g_pad + (idx % 7) * g_cell; }
static int cell_y(int idx) { return g_top + (idx / 7) * g_cell; }

static void draw(void) {
    int first = weekday_of_first(cur_y, cur_m);
    int n = days_in_month(cur_y, cur_m);
    int cx = SW / 2;
    int i;
    int d;
    int idx;
    int x;
    int y;

    ui_2d_clear(C_BG);

    /* 标题 */
     /* Title */
     /* ⚠ **中文一律走字符串字面量拼接，绝不用 `buf[k] = '年'` 这种逐字节赋值** —— 一个 `char` 只装得下 */
     /* ⚠ **Append Chinese text as a string literal, never as a byte-by-byte assignment** -- one `char` holds */
     /*   一个字节，而一个汉字在 UTF-8 里是 3 个字节，写进去就是截断（实测：标题显示成 `2026 t 10 □`）。*/
     /*   a single byte while one CJK glyph is 3 bytes in UTF-8, so it truncates (measured: `2026 t 10 □`).*/
    {
        char buf[48];
        buf[0] = 0;
        if (lang == 0) {
            strcat(buf, numstr(cur_y));
            strcat(buf, " 年 ");
            strcat(buf, numstr(cur_m));
            strcat(buf, " 月");
        } else {
            strcat(buf, month_en(cur_m));
            strcat(buf, " ");
            strcat(buf, numstr(cur_y));
        }
        ui_2d_text_styled(cx, g_top - 64, buf, C_TITLE, 18, VML_ANCHOR_CENTER, VML_FONT_BOLD);
    }

    /* 星期表头：一格一个词，位置与下面的格子**同一套算式**
     * Weekday header: one word per cell, positioned by the **same formula** the cells below use */
    for (i = 0; i < 7; i++) {
        char *w;
        if (lang == 0) {
            w = (i == 0) ? "日" : (i == 1) ? "一" : (i == 2) ? "二" : (i == 3) ? "三"
              : (i == 4) ? "四" : (i == 5) ? "五" : "六";
        } else {
            w = (i == 0) ? "Su" : (i == 1) ? "Mo" : (i == 2) ? "Tu" : (i == 3) ? "We"
              : (i == 4) ? "Th" : (i == 5) ? "Fr" : "Sa";
        }
        ui_2d_text(g_pad + i * g_cell + g_cell / 2, g_top - 28, w,
                   (i == 0 || i == 6) ? C_WEEKEND : C_DIM, 13, VML_ANCHOR_CENTER);
    }

    /* 日期格：先铺底，再写日号 */
    /* Day cells: background first, then the number */
    for (d = 1; d <= n; d++) {
        int is_today;
        int is_sel;
        idx = first + d - 1;
        x = cell_x(idx);
        y = cell_y(idx);
        is_today = (cur_y == today_y && cur_m == today_m && d == today_d);
        is_sel = (d == sel_d);
        if (is_sel) {
            ui_2d_rect(x + 3, y + 3, g_cell - 6, g_cell - 6, C_ACCENT, 1, 0, g_cell / 4);
        } else if (is_today) {
            ui_2d_rect(x + 3, y + 3, g_cell - 6, g_cell - 6, C_TODAY, 1, 0, g_cell / 4);
        } else {
            ui_2d_rect(x + 3, y + 3, g_cell - 6, g_cell - 6, C_CARD, 1, 0, g_cell / 4);
        }
        ui_2d_text(x + g_cell / 2, y + g_cell / 2 - 7, daystr(d),
                   (is_sel || is_today) ? 0xFF101018 : ((idx % 7 == 0 || idx % 7 == 6) ? C_WEEKEND : C_DAY),
                   15, VML_ANCHOR_CENTER);
    }

    /* 底部：高亮说明 + 操作提示 */
    /* Bottom: what is highlighted, and the controls */
    {
        int by = g_top + ((first + n - 1) / 7 + 1) * g_cell + 12;
        if (by > SH - 40) by = SH - 40;
        if (sel_d > 0) {
            ui_2d_text(cx, by, lang == 0 ? "已选中（点一天 / 用方向键翻月）" : "selected (tap a day / arrows change month)",
                       C_ACCENT, 12, VML_ANCHOR_CENTER);
        } else {
            ui_2d_text(cx, by, lang == 0 ? "黄底 = 今天" : "yellow = today", C_TODAY, 12, VML_ANCHOR_CENTER);
        }
        ui_2d_text(cx, by + 22, lang == 0 ? "← → 翻月   ↑ ↓ 翻年   点一天选中   Esc 退出"
                                          : "Left/Right month   Up/Down year   tap a day   Esc quits",
                   C_DIM, 11, VML_ANCHOR_CENTER);
    }

    ui_2d_present();      /* ⚠ 不调这句 = 黑屏 */
    /* without this call you get a black screen */
}

/* 点在某一天的格子里？返回日号，没点中返回 0。
 * Did the tap land in a day cell? Returns the day number, or 0 when it missed.
 * ⚠ **必须与 `draw` 用同一套几何**（`cell_x/cell_y`）：各算一套就会出现"看着点在 7 号、
 * ⚠ **It must use the same geometry as `draw`** (`cell_x/cell_y`): two sets of arithmetic produce
 *   实际选中 8 号"这种"意图在变、位置不动"的老毛病。
 *   the classic "it looks like you tapped the 7th but the 8th got selected" bug.
 */
static int hit_day(int px, int py) {
    int first = weekday_of_first(cur_y, cur_m);
    int n = days_in_month(cur_y, cur_m);
    int d;
    int idx;
    int x;
    int y;
    for (d = 1; d <= n; d++) {
        idx = first + d - 1;
        x = cell_x(idx);
        y = cell_y(idx);
        if (px >= x && px < x + g_cell && py >= y && py < y + g_cell) return d;
    }
    return 0;
}

static void shift_month(int delta) {
    cur_m = cur_m + delta;
    if (cur_m < 1) { cur_m = 12; cur_y = cur_y - 1; }
    if (cur_m > 12) { cur_m = 1; cur_y = cur_y + 1; }
    sel_d = 0;
}

int main(void) {
    int msg[4];
    int t;
    int t0;
    int done;

    lang = ui_scr_get_language();

    /* 今天：`time`/`localtime` 来自 `util`，`tm_t` 的字段与 C 的 `struct tm` 一致 */
    /* Today: `time`/`localtime` come from `util` and `tm_t` matches C's `struct tm` */
    {
        time_t now = time(0);
        tm_t *tm = localtime(&now);
        cur_y = tm->tm_year + 1900;
        cur_m = tm->tm_mon + 1;
        today_y = cur_y;
        today_m = cur_m;
        today_d = tm->tm_mday;
    }
    sel_d = 0;

    /* 开窗：**传 `0, 0`（尺寸交给宿主），开窗之后**再取一次尺寸 —— 那一次才是**真实画布**，
     * Open the window: **pass `0, 0` (let the host size it) and take the size again after the open** -- that pass is the **real canvas**,
     * 窗口与画布才是一套坐标系（开窗前的 `layout()` 只是先算一版）。
     * so window and canvas share one coordinate space (the pre-open `layout()` is just a first pass).
     * 竖屏 + 不要手柄区（日历全靠点，用不到摇杆）。可旋转。
     * Portrait plus no gamepad area (a calendar is all tapping, no stick needed). Rotatable. */
    layout();
    ui_win_open_ex("calendar.c", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);
    layout();
    draw();

    t0 = ui_time_get_ms();
    done = 0;
    while (!done && ui_win_is_closed() == 0) {
        t = ui_msg_wait(msg, 200);
        if (t == VML_MSG_NONE) {
            if (ui_time_get_ms() - t0 > IDLE_EXIT_MS) done = 1;   /* 空闲超时（验收用）*/
                                                          /* idle timeout (for automated checks) */
            continue;
        }
        t0 = ui_time_get_ms();
        if (t == VML_MSG_KEYDOWN) {
            int k = msg[1];
            if (k == VML_KEY_ESCAPE || k == 'q' || k == 'Q') done = 1;
            else if (k == VML_KEY_LEFT) { shift_month(-1); draw(); }
            else if (k == VML_KEY_RIGHT) { shift_month(1); draw(); }
            else if (k == VML_KEY_UP) { cur_y = cur_y - 1; sel_d = 0; draw(); }
            else if (k == VML_KEY_DOWN) { cur_y = cur_y + 1; sel_d = 0; draw(); }
            continue;
        }
        if (t == VML_MSG_TOUCHDOWN || t == VML_MSG_MOUSEDOWN) {
            int d = hit_day(msg[1], msg[2]);
            if (d > 0) { sel_d = d; draw(); }
            continue;
        }
        if (t == VML_MSG_WINDOWORIENT || t == VML_MSG_WINDOWRESIZE) {
            layout();
            draw();
            continue;
        }
        if (t == VML_MSG_WINDOWCLOSE) done = 1;
    }
    return 0;
}
