/*
 * spranim — тест авто-анимаций спрайтового движка (§9г):
 * sprite_anim (LOOP/PINGPONG/ONCE) + sprite_moveto + статусы + стопы.
 *
 * Логика проверяется ПРОГРАММНО по полям sprite_t: спрайты невидимы,
 * тикер работает независимо от видимости, блитов нет — sprite_update
 * гоняется в текстовом режиме (скобка W3 безвредна).  Ожидания:
 *
 *   T1 LOOP 0..3 speed=2: смена раз в 2 тика: после 8 тиков frame
 *      снова 0 (0→1→2→3→0), src вернулся на кадр 0.
 *   T2 PINGPONG 0..3 speed=1: 1,2,3,2,1,0,1 — на тике 7 frame==1,
 *      разворот на границе без удвоения краёв.
 *   T3 ONCE 0..2 speed=1: после 2 тиков frame==2 и ANIM_ON ещё стоит,
 *      после 3-го — ON снят, DONE взведён, frame==2 (замер на last).
 *   T4 MOVETO (0,0)→(100,50) step=5 interval=4 (пример из §9г):
 *      тик 4: (5,3); тик 8: (10,5); тик 79: НЕ DONE; тик 80: (100,50),
 *      MOVE_ON снят, MOVE_DONE взведён.  Параллельно у спрайта идёт
 *      LOOP-анимация (одновременность) — frame успел смениться.
 *   T5 sprite_anim_stop(1): ON снят, frame==1, ось = 16.
 *      sprite_move_stop(at_target=1) на полпути: прыжок в цель + DONE.
 *   T7 Y-СОРТИРОВКА: gfx_sprite_ysort(1) + прямой вызов сортировщика:
 *      порядок по {layer, y}; персистентный resort после смены y;
 *      слой поверх y (layer=1 в хвосте при минимальном y).
 *
 * Кадры: RAM-лента 16×16×4 (getimage), кадр k залит k — содержимое
 * не важно (блитов нет), важен размер h=16 → an_step.
 */
#include <stdio.h>
#include <string.h>
#include <sprite.h>
#include "../../libbgi/_bgi.h"   /* T7: прямой доступ к сорт-таблице */

static uint8_t strip[4 + 16 * 16 * 4];
static sprite_t sp[4];

/* Адрес пикселей кадра k вертикальной ленты (stride 16, кадр 16 строк):
 * тикер двигает КЭШ src (sy не трогает — см. sprite.h), поэтому
 * положение кадра проверяем по нему. */
#define FRAME_SRC(k) ((const uint8_t *)strip + 4 + (uint16_t)(k) * 256)

static void build_strip(void)
{
    memset(strip, 0, sizeof strip);
    strip[0] = 16; strip[2] = 64;      /* w=16, h=64 (4 кадра) */
}

static void tick(sprite_t *arr, uint8_t n, uint16_t times)
{
    while (times--)
        sprite_update(arr, n);
}

int main(void)
{
    uint8_t ok;

    puts("SPRANIM: frame/tween auto-animation");
    build_strip();

    /* ---- T1: LOOP ---------------------------------------------------- */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;
    sprite_anim(&sp[0], 0, 3, 2, ANIM_LOOP);
    tick(sp, 1, 2);
    ok = (sprite_anim_frame(&sp[0]) == 1 && sp[0].src == FRAME_SRC(1));
    tick(sp, 1, 4);                     /* всего 6: 0→1→2→3      */
    ok = ok && (sprite_anim_frame(&sp[0]) == 3 && sp[0].src == FRAME_SRC(3));
    tick(sp, 1, 2);                     /* всего 8: wrap → 0     */
    ok = ok && (sprite_anim_frame(&sp[0]) == 0 && sp[0].src == FRAME_SRC(0));
    printf("T1 LOOP:     %s\n", ok ? "PASS" : "FAIL");

    /* ---- T2: PINGPONG ------------------------------------------------ */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;
    sprite_anim(&sp[0], 0, 3, 1, ANIM_PINGPONG);
    tick(sp, 1, 3);                     /* 1,2,3                 */
    ok = (sprite_anim_frame(&sp[0]) == 3);
    tick(sp, 1, 1);                     /* разворот: 2           */
    ok = ok && (sprite_anim_frame(&sp[0]) == 2 && sp[0].src == FRAME_SRC(2));
    tick(sp, 1, 2);                     /* 1,0                   */
    ok = ok && (sprite_anim_frame(&sp[0]) == 0);
    tick(sp, 1, 1);                     /* разворот: 1           */
    ok = ok && (sprite_anim_frame(&sp[0]) == 1 && sp[0].src == FRAME_SRC(1));
    printf("T2 PINGPONG: %s\n", ok ? "PASS" : "FAIL");

    /* ---- T3: ONCE ---------------------------------------------------- */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;
    sprite_anim(&sp[0], 0, 2, 1, ANIM_ONCE);
    tick(sp, 1, 2);                     /* 1,2 — дошли до last   */
    ok = (sprite_anim_frame(&sp[0]) == 2 &&
          (sprite_anim_status(&sp[0]) & SPR_ANIM_ON) &&
          !(sprite_anim_status(&sp[0]) & SPR_ANIM_DONE));
    tick(sp, 1, 1);                     /* фиксация: ON→off,DONE */
    ok = ok && (sprite_anim_frame(&sp[0]) == 2 && sp[0].src == FRAME_SRC(2) &&
                !(sprite_anim_status(&sp[0]) & SPR_ANIM_ON) &&
                (sprite_anim_status(&sp[0]) & SPR_ANIM_DONE));
    tick(sp, 1, 5);                     /* стоит на месте        */
    ok = ok && (sprite_anim_frame(&sp[0]) == 2);
    printf("T3 ONCE:     %s\n", ok ? "PASS" : "FAIL");

    /* ---- T4: MOVETO + одновременная кадровая ------------------------- */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;         /* x=y=0 после init      */
    sprite_anim(&sp[0], 0, 3, 3, ANIM_LOOP);
    sprite_moveto(&sp[0], 100, 50, 5, 4);
    ok = (sprite_moving(&sp[0]) != 0);
    tick(sp, 1, 4);
    ok = ok && (sp[0].x == 5 && sp[0].y == 3);   /* Y: 3/2/3/2 */
    tick(sp, 1, 4);
    ok = ok && (sp[0].x == 10 && sp[0].y == 5);
    tick(sp, 1, 71);                    /* всего 79 — ещё едет   */
    ok = ok && sprite_moving(&sp[0]) &&
         !(sprite_anim_status(&sp[0]) & SPR_MOVE_DONE);
    tick(sp, 1, 1);                     /* 80: приехали          */
    ok = ok && (sp[0].x == 100 && sp[0].y == 50 &&
                !sprite_moving(&sp[0]) &&
                (sprite_anim_status(&sp[0]) & SPR_MOVE_DONE));
    ok = ok && (sprite_anim_frame(&sp[0]) != 0 ||
                sp[0].an_timer != 3);   /* кадровая тоже тикала  */
    printf("T4 MOVETO:   %s (x=%d y=%d)\n",
           ok ? "PASS" : "FAIL", sp[0].x, sp[0].y);

    /* ---- T5: стопы ---------------------------------------------------- */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;
    sprite_anim(&sp[0], 0, 3, 1, ANIM_LOOP);
    tick(sp, 1, 2);                     /* frame 2               */
    sprite_anim_stop(&sp[0], 1);        /* остановить на кадре 1 */
    ok = (!(sprite_anim_status(&sp[0]) & SPR_ANIM_ON) &&
          sprite_anim_frame(&sp[0]) == 1 && sp[0].sy == 16 &&
          sp[0].src == FRAME_SRC(1));
    tick(sp, 1, 4);                     /* не двигается          */
    ok = ok && (sprite_anim_frame(&sp[0]) == 1);

    sprite_moveto(&sp[0], 60, 60, 3, 1);
    tick(sp, 1, 5);                     /* на полпути            */
    sprite_move_stop(&sp[0], 1);        /* добить в цель         */
    ok = ok && (sp[0].x == 60 && sp[0].y == 60 &&
                !sprite_moving(&sp[0]) &&
                (sprite_anim_status(&sp[0]) & SPR_MOVE_DONE));

    sprite_moveto(&sp[0], 0, 0, 3, 1);
    tick(sp, 1, 5);
    sprite_move_stop(&sp[0], 0);        /* замереть где есть     */
    ok = ok && (sp[0].x != 0 && !sprite_moving(&sp[0]) &&
                !(sprite_anim_status(&sp[0]) & SPR_MOVE_DONE));
    printf("T5 STOPS:    %s\n", ok ? "PASS" : "FAIL");

    /* ---- T6: sprite_frame ПОСЛЕ тикера ------------------------------- *
     * Тикер двигает src, оставляя sx/sy как есть — sprite_frame обязан
     * сверять КАДР по src, а не по (sx,sy): просьба вернуть кадр (0,0)
     * при формально не изменившихся sx/sy должна вернуть src. */
    sprite_init(&sp[0], strip);
    sp[0].w = 16; sp[0].h = 16;
    sprite_anim(&sp[0], 0, 3, 1, ANIM_LOOP);
    tick(sp, 1, 2);                     /* src уехал на кадр 2   */
    sprite_anim_stop(&sp[0], -1);       /* замереть на текущем   */
    ok = (sp[0].src == FRAME_SRC(2) && sp[0].sy == 0);
    sprite_frame(&sp[0], 0, 0);         /* sx/sy те же, src ушёл */
    ok = ok && (sp[0].src == FRAME_SRC(0));
    printf("T6 REFRAME:  %s\n", ok ? "PASS" : "FAIL");

    /* ---- T7: Y-сортировка (слои + персистентность) -------------------- *
     * Проверяем через возвращаемую таблицу (записи {key16, ptr16} по 4
     * байта, ptr по +2): порядок указателей = порядок отрисовки. */
    {
        uint8_t *tab;
        uint8_t i;
        sprite_t *o0, *o1, *o2, *o3;
#define YS_PTR(t, i) (*(sprite_t **)((t) + (i) * 4 + 2))

        for (i = 0; i < 4; i++) {
            sprite_init(&sp[i], strip);
            sp[i].w = 16; sp[i].h = 16;
        }
        sp[0].y = 50;  sp[1].y = 10;  sp[2].y = 30;
        sp[3].y = 0;   sp[3].layer = 1;      /* слой поверх всех */

        gfx_sprite_ysort(YSORT_Y | YSORT_LAYER);   /* полный порядок */
        tab = (*_spr_ysort_fn)(sp, 4);
        o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1);
        o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3);
        /* ожидание: y10, y30, y50, затем layer1 (несмотря на y=0) */
        ok = (o0 == &sp[1] && o1 == &sp[2] && o2 == &sp[0] && o3 == &sp[3]);

        sp[1].y = 60;                        /* обгон: y10 → y60 */
        tab = (*_spr_ysort_fn)(sp, 4);       /* персистентный resort */
        o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1);
        o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3);
        ok = ok && (o0 == &sp[2] && o1 == &sp[0] && o2 == &sp[1] &&
                    o3 == &sp[3]);

        /* LAYER-only: y замаскирован — слой 0 в порядке МАССИВА
         * (сеттер форсит rebuild), sp[3] (layer 1) в хвосте. */
        gfx_sprite_ysort(YSORT_LAYER);
        tab = (*_spr_ysort_fn)(sp, 4);
        o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1);
        o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3);
        ok = ok && (o0 == &sp[0] && o1 == &sp[1] && o2 == &sp[2] &&
                    o3 == &sp[3]);
        gfx_sprite_ysort(0);
        printf("T7 YSORT:    %s\n", ok ? "PASS" : "FAIL");
    }

    puts("done");
    return 0;
}
