// Sprite demo: bounces a 16x16 ball using the jlSpriteT API. The ball
// art is authored as a 16x16 4bpp packed image in this file (one row
// per source line) and converted at startup into the 2x2 tile layout
// jlSpriteT expects. jlSpriteDraw handles transparent-color-0; we use
// jlSpriteSaveUnder/RestoreUnder to undraw the previous frame's ball
// without redrawing the whole screen.

#include <stdio.h>

#include <joey/joey.h>

#define BALL_W            16
#define BALL_H            16
#define BALL_TILES_X      (BALL_W / 8)
#define BALL_TILES_Y      (BALL_H / 8)

#define BALL_TILE_BYTES   (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES)
// SaveUnder rounds x down to the platform's storage alignment: 2 px
// for chunky 4bpp (1 extra byte/row worst case), 8 px for planar
// 4-plane (4 extra bytes/row worst case -- one per plane). The +4
// covers the planar case and is a no-op overhead on chunky.
// Sized by the library-wide worst case (16-px-group window on the
// planar ports); see JOEY_SPRITE_BACKUP_BYTES in joey/sprite.h.
#define BALL_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(BALL_W / 8, BALL_H / 8)

#define BALL_PALETTE_IDX  0

#define COLOR_BG          0

// Authored layout: 16 pixels wide x 16 rows = 8 bytes/row x 16 rows.
//   0 = transparent
//   2 = ball body (yellow)
//   3 = highlight (white)
// High nibble of each byte is the LEFT pixel.
static const uint8_t gBallAuthored[BALL_H * (BALL_W / 2)] = {
    0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00,  // row  0
    0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00,  // row  1
    0x02, 0x22, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20,  // row  2
    0x02, 0x23, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20,  // row  3
    0x22, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  4
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  5
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  6
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  7
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  8
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row  9
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  // row 10
    0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20,  // row 11
    0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20,  // row 12
    0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00,  // row 13
    0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00,  // row 14
    0x00, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0x00   // row 15
};

static uint8_t       gBallTiles[BALL_TILE_BYTES];
static uint8_t       gBallBackup[BALL_BACKUP_BYTES] __attribute__((aligned(2)));


// Repack from authored "wide row" layout to tile-major jlSpriteT
// layout: tile (0,0), tile (1,0), tile (0,1), tile (1,1), each tile
// internally stored row-by-row at 4 bytes per row.
static void repackBallTiles(void) {
    uint16_t tx;
    uint16_t ty;
    uint16_t row;
    uint16_t b;
    uint8_t *dst;

    for (ty = 0; ty < BALL_TILES_Y; ty++) {
        for (tx = 0; tx < BALL_TILES_X; tx++) {
            dst = &gBallTiles[(ty * BALL_TILES_X + tx) * TILE_BYTES];
            for (row = 0; row < 8; row++) {
                for (b = 0; b < 4; b++) {
                    dst[row * 4 + b] =
                        gBallAuthored[((ty * 8) + row) * (BALL_W / 2) +
                                      (tx * 4) + b];
                }
            }
        }
    }
}


static void buildPalette(jlSurfaceT *screen) {
    uint16_t colors[16];
    uint16_t i;

    for (i = 0; i < 16; i++) {
        colors[i] = 0x0000;
    }
    colors[2] = 0x0FF0;  // yellow body
    colors[3] = 0x0FFF;  // white highlight
    jlPaletteSet(screen, BALL_PALETTE_IDX, colors);
}


int main(void) {
    jlConfigT    config;
    jlSurfaceT      *screen;
    jlSpriteT       *ball;
    jlSpriteBackupT  backup;
    int16_t        x;
    int16_t        y;
    int16_t        vx;
    int16_t        vy;
    bool           haveBackup;

    /* Amiga planar emits 8 pre-shifted DRAW variants per sprite (one
     * per x % 8 alignment) so the codegen arena needs roughly 8x what
     * the chunky two-shift case asks for. 32 KB fits a 16x16 ball
     * with all variants. */
    config.codegenBytes = 32UL * 1024;
    config.audioBytes   = 64UL * 1024;

    if (!jlInit(&config)) {
        fprintf(stderr, "jlInit failed: %s\n", jlLastError());
        return 1;
    }

    screen = jlStageGet();
    if (screen == NULL) {
        fprintf(stderr, "jlStageGet returned NULL\n");
        jlShutdown();
        return 1;
    }

    repackBallTiles();
    ball = jlSpriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y);
    if (ball == NULL) {
        fprintf(stderr, "jlSpriteCreate failed\n");
        jlShutdown();
        return 1;
    }
    // Compile draw routines into the codegen arena. Returns false on
    // platforms that don't have a real emitter or if the arena is
    // full -- either way the demo still runs via the interpreter
    // path in jlSpriteDraw.
    (void)jlSpriteCompile(ball);

    buildPalette(screen);
    jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
    jlSurfaceClear(screen, COLOR_BG);
    jlStagePresent();

    backup.bytes = gBallBackup;

    x  = 40;
    y  = 30;
    vx = 2;
    vy = 1;
    haveBackup = false;

    jlSpriteSaveAndDraw(screen, ball, x, y, &backup);
    jlStagePresent();
    haveBackup = true;

    for (;;) {
        jlInputPoll();
        if (jlKeyPressed(KEY_ESCAPE)) {
            break;
        }

        // Do all off-screen work first (restore + move + draw), then
        // ONE jlStagePresent flushes the union of dirty bands set by
        // restoreUnder + draw. Add a jlWaitVBL() before the present
        // to land it inside the VBL window so the CRT never sees a
        // half-updated framebuffer (matters most on single-buffered
        // chunky targets like IIgs SHR; on planar c2p platforms it
        // also avoids c2p racing the raster). VBL wait is omitted
        // here so the demo runs at the sprite pipeline's native
        // throughput -- expect tearing on the ball.
        if (haveBackup) {
            jlSpriteRestoreUnder(screen, &backup);
        }

        x = (int16_t)(x + vx);
        y = (int16_t)(y + vy);
        if (x <= 0)                       { x = 0;                       vx = (int16_t)-vx; }
        if (x >= SURFACE_WIDTH  - BALL_W) { x = SURFACE_WIDTH  - BALL_W; vx = (int16_t)-vx; }
        if (y <= 0)                       { y = 0;                       vy = (int16_t)-vy; }
        if (y >= SURFACE_HEIGHT - BALL_H) { y = SURFACE_HEIGHT - BALL_H; vy = (int16_t)-vy; }

        jlSpriteSaveAndDraw(screen, ball, x, y, &backup);

        jlStagePresent();
        haveBackup = true;
    }

    jlSpriteDestroy(ball);
    jlShutdown();
    return 0;
}
