/* ── Stack Animation ── 栈的 push/pop 动画演示 ──
 *
 * 工作原理（动画系统）：
 *
 * 1. 通过 canvas_start_timer(canvas, 60, on_timer, NULL) 启动定时器，
 *    on_timer 每秒回调 60 次（60 FPS）。
 *
 * 2. anim_state 控制当前状态：IDLE / PUSHING / POPPING。
 *    - IDLE: 无动画，等待用户操作
 *    - PUSHING: 新元素从上方飞入栈（持续 ANIM_FRAMES 帧）
 *    - POPPING: 栈顶元素飞出消失（持续 ANIM_FRAMES 帧）
 *
 * 3. 每帧 on_timer 递增 anim_frame，当 anim_frame >= ANIM_FRAMES（20帧）
 *    时动画结束，真正操作栈数据（top++ 或 top--），回到 IDLE 状态。
 *
 * 4. 动画期间的视觉过渡在 redraw() 中实时绘制，不修改栈数据，
 *    数据修改只在动画完成的最后一帧进行——典型的"先动画后数据"模式。
 */

#include "eduwin.h"

/* ── 常量 ── */
#define MAX_STACK 8           /* 栈最大容量 */
#define BOX_W 120             /* 盒子宽度 */
#define BOX_H 44              /* 盒子高度 */
#define BOX_GAP 8             /* 盒子间距 */
#define STACK_X 90            /* 栈起始 X 坐标 */
#define STACK_BASE_Y 530      /* 栈底 Y 坐标（从底部向上堆叠） */
#define ANIM_FPS 60           /* 动画帧率 */
#define ANIM_FRAMES 20        /* 每次动画持续帧数（约 0.33 秒） */

/* ── 栈数据结构 ── */
static int stack[MAX_STACK];
static int top = -1;          /* -1 表示空栈 */

/* ── 控件句柄 ── */
static HWND canvas;
static HWND btn_push;
static HWND btn_pop;
static HWND btn_reset;
static HWND label_status;

/* ── 动画状态机 ──
 *  IDLE   : 空闲，等待 Push/Pop 操作
 *  PUSHING: 新元素飞入动画中
 *  POPPING: 栈顶元素飞出动画中
 */
typedef enum { IDLE, PUSHING, POPPING } AnimState;
static AnimState anim_state = IDLE;
static int anim_frame = 0;            /* 当前动画帧计数（0 ~ ANIM_FRAMES-1） */
static int anim_value = 0;            /* 正在入栈或出栈的值 */

/* 根据栈状态和动画状态，启用/禁用 Push/Pop 按钮 */
static void update_buttons(void)
{
    set_enable(btn_push, top < MAX_STACK - 1 && anim_state == IDLE);
    set_enable(btn_pop, top >= 0 && anim_state == IDLE);
}

/* ── 核心绘制函数 ──
 *
 * 每次调用都会完全重绘画布。分三层绘制：
 *
 * 1. 栈底 "BASE" 标识
 * 2. 栈中所有已存在的元素（从索引 0 到 top），从下往上绘制
 * 3. 动画中的元素（PUSHING 或 POPPING），叠加在现有元素之上
 *
 * 栈的视觉布局：栈底在底部（STACK_BASE_Y），元素从下往上叠放。
 * 第 i 个元素（索引 i）的顶部 Y = STACK_BASE_Y - (i+1) * (BOX_H + BOX_GAP)
 */
static void redraw(void)
{
    canvas_clear(canvas, 28, 28, 35, 255);

    int base_y = STACK_BASE_Y;
    int cw = canvas_get_width(canvas);

    canvas_draw_text(canvas, L"Stack Animation - 栈演示动画", 10, 8, 180, 200, 220, 255, 20);

    /* 绘制栈底的 "BASE" 标记 */
    canvas_set_brush(canvas, 55, 55, 65, 255);
    canvas_fill_rect(canvas, STACK_X - 14, base_y - 6, BOX_W + 28, 12);
    canvas_set_pen(canvas, 100, 100, 120, 255, 1);
    canvas_draw_rect(canvas, STACK_X - 14, base_y - 6, BOX_W + 28, 12);
    canvas_draw_text(canvas, L"BASE", STACK_X + BOX_W + 28, base_y - 8, 140, 140, 160, 255, 13);

    /* 确定当前需要绘制的元素个数（POPPING 时不绘制要弹出的那个） */
    int draw_count = top + 1;
    float pop_offset = 0;
    float pop_progress = 0;
    int pop_index = -1;

    if (anim_state == POPPING)
    {
        pop_index = top;
        pop_progress = (float)anim_frame / ANIM_FRAMES;
        /* 出栈动画：元素向上飞出，使用二次函数加速效果 */
        pop_offset = -pop_progress * pop_progress * (BOX_H + BOX_GAP) * 3.5f;
        draw_count = top;
    }

    /* 绘制栈中所有静态元素 */
    for (int i = 0; i < draw_count; i++)
    {
        int y = base_y - (i + 1) * (BOX_H + BOX_GAP);

        /* 颜色渐变：越靠上颜色越亮 */
        int r = 40 + i * 18;
        int g = 90 + i * 12;
        int b = 170 - i * 8;
        if (r > 190) r = 190;
        if (g > 180) g = 180;
        if (b < 100) b = 100;

        canvas_set_brush(canvas, r, g, b, 255);
        canvas_fill_rect(canvas, STACK_X, y, BOX_W, BOX_H);

        canvas_set_pen(canvas, 200, 200, 220, 255, 1);
        canvas_draw_rect(canvas, STACK_X, y, BOX_W, BOX_H);

        /* 显示元素值 */
        WCHAR buf[16];
        str_concat(buf, 16, itow(stack[i]), NULL);
        canvas_draw_text(canvas, buf, STACK_X + BOX_W / 2 - 14, y + 10, 255, 255, 255, 255, 20);

        /* 显示索引 */
        WCHAR idx[16];
        str_concat(idx, 16, L"[", itow(i), L"]", NULL);
        canvas_draw_text(canvas, idx, STACK_X + BOX_W + 10, y + 12, 130, 130, 150, 255, 12);
    }

    /* ── POPPING 动画：栈顶元素向上飞出并淡出 ──
     *
     * pop_offset 从 0 到 -(BOX_H+BOX_GAP)*3.5，产生向上飞出的效果。
     * 借助二次函数 (t^2) 让元素越飞越快，视觉上更自然。
     * alpha 从 1.0 到 0.0，元素逐渐消失。
     */
    if (anim_state == POPPING && pop_index >= 0)
    {
        int py = base_y - (pop_index + 1) * (BOX_H + BOX_GAP) + (int)pop_offset;
        float alpha = 1.0f - pop_progress;

        int br = (int)(220 * alpha);
        int bg = (int)(160 * alpha);
        int bb = (int)(60 * alpha);
        canvas_set_brush(canvas, br, bg, bb, 255);
        canvas_fill_rect(canvas, STACK_X, py, BOX_W, BOX_H);

        WCHAR buf[16];
        str_concat(buf, 16, itow(stack[pop_index]), NULL);
        int tr = 255 - (int)((255 - 28) * (1.0f - alpha));
        int tg = 255 - (int)((255 - 28) * (1.0f - alpha));
        int tb = 255 - (int)((255 - 35) * (1.0f - alpha));
        canvas_draw_text(canvas, buf, STACK_X + BOX_W / 2 - 14, py + 10, tr, tg, tb, 255, 20);

        canvas_draw_text(canvas, L"<- POP", STACK_X + BOX_W + 50, py + 10, 255, 180, 80, 255, 14);
    }

    /* ── PUSHING 动画：新元素从上方飞入栈顶 ──
     *
     * 使用 ease-out 缓动公式 ease = 1 - (1-t)^2，让元素
     * 开始时速度较快，接近目标时减速停止，模拟物理惯性效果。
     * start_y 在目标位置上方 3 个盒子高度处，cur_y 从 start_y
     * 逐渐逼近 target_y。
     */
    if (anim_state == PUSHING)
    {
        float t = (float)anim_frame / ANIM_FRAMES;
        float ease = 1.0f - (1.0f - t) * (1.0f - t);
        int target_y = base_y - (top + 2) * (BOX_H + BOX_GAP);
        int start_y = target_y - (BOX_H + BOX_GAP) * 3;
        int cur_y = start_y + (int)((target_y - start_y) * ease);

        canvas_set_brush(canvas, 255, 200, 50, 255);
        canvas_fill_rect(canvas, STACK_X, cur_y, BOX_W, BOX_H);
        canvas_set_pen(canvas, 255, 230, 100, 255, 2);
        canvas_draw_rect(canvas, STACK_X, cur_y, BOX_W, BOX_H);

        WCHAR buf[16];
        str_concat(buf, 16, itow(anim_value), NULL);
        canvas_draw_text(canvas, buf, STACK_X + BOX_W / 2 - 14, cur_y + 10, 255, 255, 255, 255, 20);

        WCHAR idx[16];
        str_concat(idx, 16, L"[", itow(top + 1), L"]", NULL);
        canvas_draw_text(canvas, idx, STACK_X + BOX_W + 10, cur_y + 12, 220, 200, 100, 255, 12);

        canvas_draw_text(canvas, L"<- PUSH", STACK_X + BOX_W + 50, cur_y + 10, 255, 200, 50, 255, 14);
    }

    /* 绘制 "TOP" 箭头标记（指向栈顶元素） */
    if (top >= 0 && anim_state != PUSHING)
    {
        int top_y = base_y - (top + 1) * (BOX_H + BOX_GAP);

        canvas_set_pen(canvas, 255, 120, 100, 255, 2);
        canvas_draw_line(canvas, STACK_X + BOX_W + 40, top_y + BOX_H / 2, STACK_X + BOX_W + 55, top_y + BOX_H / 2);
        canvas_draw_line(canvas, STACK_X + BOX_W + 48, top_y + BOX_H / 2 - 5, STACK_X + BOX_W + 55, top_y + BOX_H / 2);
        canvas_draw_line(canvas, STACK_X + BOX_W + 48, top_y + BOX_H / 2 + 5, STACK_X + BOX_W + 55, top_y + BOX_H / 2);

        canvas_draw_text(canvas, L"TOP", STACK_X + BOX_W + 60, top_y + 10, 255, 120, 100, 255, 14);
    }

    if (top < 0 && anim_state == IDLE)
    {
        canvas_draw_text(canvas, L"栈为空，请点击 \"Push\" 添加元素", 50, base_y - 60, 160, 160, 180, 255, 16);
    }
}

/* ── 定时器回调（动画驱动核心） ──
 *
 * 每帧（约 1/60 秒）调用一次：
 * 1. anim_frame 递增
 * 2. 当达到 ANIM_FRAMES 时动画结束：
 *    - PUSHING: 真正将 anim_value 入栈（top++; stack[top]=anim_value）
 *    - POPPING: 真正将栈顶弹出（top--）
 * 3. 回到 IDLE 状态，停止定时器
 * 4. 每帧都调用 redraw() 重绘，产生连续动画效果
 *
 * 关键设计：动画期间仅绘制视觉过渡效果，不修改栈数据。
 * 栈数据的实际修改推迟到动画结束的那一刻，保证数据一致性。
 */
static void on_timer(void *data)
{
    anim_frame++;

    if (anim_frame >= ANIM_FRAMES)
    {
        if (anim_state == PUSHING)
        {
            top++;
            stack[top] = anim_value;
        }
        else if (anim_state == POPPING)
        {
            top--;
        }
        anim_state = IDLE;
        canvas_stop_timer(canvas);
        update_buttons();

        if (top < 0)
        {
            set_text(label_status, L"栈已清空");
        }
        else
        {
            WCHAR buf[64];
            str_concat(buf, 64, L"栈大小: ", itow(top + 1), L", 栈顶: ", itow(stack[top]), NULL);
            set_text(label_status, buf);
        }
    }

    redraw();
}

/* ── Push 按钮回调 ──
 *
 * 生成随机值（10~99），启动 PUSHING 动画。
 * 动画结束后在 on_timer 中真正入栈。
 */
static void on_push(void *data)
{
    if (top >= MAX_STACK - 1 || anim_state != IDLE) return;

    anim_value = rand_int(10, 99);
    anim_state = PUSHING;
    anim_frame = 0;
    update_buttons();

    WCHAR buf[64];
    str_concat(buf, 64, L"Push ", itow(anim_value), L" 入栈中...", NULL);
    set_text(label_status, buf);

    canvas_start_timer(canvas, ANIM_FPS, on_timer, NULL);
}

/* ── Pop 按钮回调 ──
 *
 * 获取栈顶值，启动 POPPING 动画。
 * 动画结束后在 on_timer 中真正出栈。
 */
static void on_pop(void *data)
{
    if (top < 0 || anim_state != IDLE) return;

    anim_value = stack[top];
    anim_state = POPPING;
    anim_frame = 0;
    update_buttons();

    WCHAR buf[64];
    str_concat(buf, 64, L"Pop ", itow(anim_value), L" 出栈中...", NULL);
    set_text(label_status, buf);

    canvas_start_timer(canvas, ANIM_FPS, on_timer, NULL);
}

/* ── Reset 按钮回调 ── */
static void on_reset(void *data)
{
    if (anim_state != IDLE) return;
    top = -1;
    update_buttons();
    set_text(label_status, L"栈已重置");
    redraw();
}

/* ── 键盘快捷键 ──
 *  P / Enter = Push    O / Backspace = Pop    R = Reset
 */
static void on_key_down(int vkey)
{
    if (vkey == 'P' || vkey == VK_RETURN)
    {
        if (top < MAX_STACK - 1 && anim_state == IDLE)
            on_push(NULL);
    }
    else if (vkey == 'O' || vkey == VK_BACK)
    {
        if (top >= 0 && anim_state == IDLE)
            on_pop(NULL);
    }
    else if (vkey == 'R')
    {
        on_reset(NULL);
    }
}

int main(void)
{
    if (init_app() != 0)
    {
        return 1;
    }

    HWND win = create_window(L"Stack Animation - 栈演示动画", 520, 680);
    if (!win)
    {
        return 1;
    }

    canvas = create_canvas(win, 10, 5, 500, 545);

    btn_push = create_button(win, L"Push (入栈)", 20, 558, 110, 30);
    on_button_click(btn_push, on_push, NULL);

    btn_pop = create_button(win, L"Pop (出栈)", 145, 558, 110, 30);
    on_button_click(btn_pop, on_pop, NULL);

    btn_reset = create_button(win, L"Reset (重置)", 270, 558, 110, 30);
    on_button_click(btn_reset, on_reset, NULL);

    label_status = create_label(win, L"栈为空，请点击 Push 开始演示", 20, 596, 480, 20);

    canvas_on_key(canvas, on_key_down, NULL);

    update_buttons();

    redraw();
    show_window(win);

    run_app();
    return 0;
}
