// spritebake -- offline pre-compiler for JoeyLib sprite blit routines.
//
// Reads a cross-platform .spr cel bank (as tools/assetbake/assetbake.py
// produces), runs the SAME per-CPU emitters + staging the runtime JIT uses
// (src/codegen/spriteStage.c, linked with one src/*/spriteEmit*.c chosen at
// compile time), and writes a per-platform .spc holding the pre-emitted
// native routine images + slot-relative routineOffsets. The runtime loader
// (jlSpriteBankLoadPrecompiled) then skips jlSpriteCompile entirely.
//
// Built once per target by make/tools.mk with -DJOEYLIB_PLATFORM_<port> so
// JOEY_SPRITE_SHIFT_COUNT and the emitted machine code match that target,
// while running on the build host -- the emitters use only fixed/gated
// compile-time constants, so host output is byte-identical to the target JIT.
//
//   spritebake <in.spr> <out.spc>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "joey/tile.h"
#include "spriteInternal.h"
#include "spritePrecompiled.h"
#include "spriteStage.h"

#define SPR_MAGIC          "JSP1"
#define SPR_HEADER_BYTES   44
#define SPR_OFF_WIDTH       6
#define SPR_OFF_HEIGHT      7
#define SPR_OFF_CELLS       8
#define SPR_OFF_PALETTE     12
#define SPR_PALETTE_BYTES   32

#if defined(JOEYLIB_PLATFORM_AMIGA)
#define SPC_TARGET   1
#elif defined(JOEYLIB_PLATFORM_ATARIST)
#define SPC_TARGET   2
#elif defined(JOEYLIB_PLATFORM_DOS)
#define SPC_TARGET   3
#elif defined(JOEYLIB_PLATFORM_IIGS)
#define SPC_TARGET   4
#else
#error "spritebake: build with -DJOEYLIB_PLATFORM_<port>"
#endif


#if defined(JOEYLIB_PLATFORM_ATARIST)
// The ST emitter bakes the address of this walker into each shifted-DRAW
// thunk. On the host bake tool the value is don't-care (the .spc loader
// re-stamps the real target address at load), so a never-called stub just
// resolves the link -- the real walker is src/atarist/spriteWalk.s on target.
void stSpriteDrawShiftWalker(void) {}
#endif


static void putLE16(FILE *f, uint16_t v) {
    fputc((int)(v & 0xFFu), f);
    fputc((int)((v >> 8) & 0xFFu), f);
}


static void putLE32(FILE *f, uint32_t v) {
    fputc((int)(v & 0xFFu), f);
    fputc((int)((v >> 8) & 0xFFu), f);
    fputc((int)((v >> 16) & 0xFFu), f);
    fputc((int)((v >> 24) & 0xFFu), f);
}


int main(int argc, char **argv) {
    FILE          *in;
    FILE          *out;
    uint8_t        header[SPR_HEADER_BYTES];
    uint8_t        widthTiles;
    uint8_t        heightTiles;
    uint16_t       cellCount;
    uint32_t       tileBytes;
    uint8_t       *staging;
    uint16_t       cel;

    if (argc != 3) {
        fprintf(stderr, "usage: spritebake <in.spr> <out.spc>\n");
        return 2;
    }
    in = fopen(argv[1], "rb");
    if (in == NULL) {
        fprintf(stderr, "spritebake: cannot open %s\n", argv[1]);
        return 1;
    }
    if (fread(header, 1, SPR_HEADER_BYTES, in) != SPR_HEADER_BYTES ||
        memcmp(header, SPR_MAGIC, 4) != 0 || header[SPC_OFF_TARGET] != 0u) {
        fprintf(stderr, "spritebake: %s is not a JSP1 cross-target .spr\n", argv[1]);
        fclose(in);
        return 1;
    }
    widthTiles  = header[SPR_OFF_WIDTH];
    heightTiles = header[SPR_OFF_HEIGHT];
    cellCount   = (uint16_t)(header[SPR_OFF_CELLS] | (header[SPR_OFF_CELLS + 1] << 8));
    if (widthTiles == 0u || heightTiles == 0u || cellCount == 0u) {
        fprintf(stderr, "spritebake: degenerate geometry in %s\n", argv[1]);
        fclose(in);
        return 1;
    }
    tileBytes = (uint32_t)widthTiles * heightTiles * TILE_BYTES;

    staging = (uint8_t *)malloc(SPRITE_STAGING_BYTES);
    if (staging == NULL) {
        fclose(in);
        return 1;
    }

    out = fopen(argv[2], "wb");
    if (out == NULL) {
        fprintf(stderr, "spritebake: cannot write %s\n", argv[2]);
        free(staging);
        fclose(in);
        return 1;
    }

    // .spc header: same 44-byte prefix, but JSC1 + per-machine target byte,
    // plus the baked shift count at byte 10 for the loader to validate.
    fwrite(SPC_MAGIC, 1, 4, out);
    fputc(SPC_TARGET, out);
    fputc(header[SPC_OFF_HAS_PALETTE], out);
    fputc(widthTiles, out);
    fputc(heightTiles, out);
    putLE16(out, cellCount);
    fputc((int)JOEY_SPRITE_SHIFT_COUNT, out);
    fputc(0, out);   // reserved
    fwrite(header + SPR_OFF_PALETTE, 1, SPR_PALETTE_BYTES, out);

    for (cel = 0; cel < cellCount; cel++) {
        uint8_t   *tileData;
        jlSpriteT  sp;
        uint32_t   total;
        uint16_t   relocOffsets[JOEY_SPRITE_SHIFT_COUNT];
        uint16_t   relocCount;
        uint8_t    shift;
        uint8_t    op;

        tileData = (uint8_t *)malloc(tileBytes);
        if (tileData == NULL) {
            fprintf(stderr, "spritebake: OOM cel %u\n", (unsigned)cel);
            goto fail;
        }
        if (fread(tileData, 1, tileBytes, in) != tileBytes) {
            fprintf(stderr, "spritebake: %s truncated at cel %u\n", argv[1], (unsigned)cel);
            free(tileData);
            goto fail;
        }

        // Run the identical staging the runtime JIT would: same emitters,
        // same tier order, same offsets -> byte-identical image.
        memset(&sp, 0, sizeof(sp));
        sp.tileData    = tileData;
        sp.widthTiles  = widthTiles;
        sp.heightTiles = heightTiles;
        sp.widthPx     = (uint16_t)(widthTiles * TILE_PIXELS_PER_SIDE);
        sp.heightPx    = (uint16_t)(heightTiles * TILE_PIXELS_PER_SIDE);
        total = spriteStageAndPlan(staging, &sp, false);

        // ST shifted-phase DRAW thunks bake an absolute jmp to the shared
        // walker; record its patch site so the loader re-stamps the runtime
        // address. Every other target/op is fully position-independent.
        relocCount = 0u;
#if defined(JOEYLIB_PLATFORM_ATARIST)
        for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
            uint16_t drawOff = sp.routineOffsets[shift][SPRITE_OP_DRAW];

            if ((shift & 7u) != 0u && drawOff != SPRITE_NOT_COMPILED) {
                relocOffsets[relocCount] = (uint16_t)(drawOff + SPC_ST_WALKER_JMP_OFFSET);
                relocCount++;
            }
        }
#endif

        // celMeta: imageSize, routineOffsets[shift][op], relocCount.
        putLE32(out, total);
        for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
            for (op = 0; op < SPRITE_OP_COUNT; op++) {
                putLE16(out, sp.routineOffsets[shift][op]);
            }
        }
        putLE16(out, relocCount);

        // Raw tile blob (kept for the interpreter/clip fallback + dims), then
        // the pre-emitted routine image, then the reloc site list.
        fwrite(tileData, 1, tileBytes, out);
        if (total != 0u) {
            fwrite(staging, 1, total, out);
        }
        for (uint16_t r = 0; r < relocCount; r++) {
            putLE16(out, relocOffsets[r]);
        }

        free(tileData);
    }

    if (fclose(out) != 0) {
        fprintf(stderr, "spritebake: write error on %s\n", argv[2]);
        free(staging);
        fclose(in);
        return 1;
    }
    free(staging);
    fclose(in);
    printf("spritebake: %s -> %s (%u cels, target %d, %d shifts)\n",
           argv[1], argv[2], (unsigned)cellCount, SPC_TARGET, (int)JOEY_SPRITE_SHIFT_COUNT);
    return 0;

fail:
    fclose(out);
    free(staging);
    fclose(in);
    return 1;
}
