// Shared sprite-codegen staging (see spriteStage.h). Emits every included
// (shift, op) routine once into the staging image and records slot-relative
// routineOffsets. Split out of spriteCompile.c so the offline bake tool can
// link the exact same emitter-driving code without pulling in the runtime
// arena/heap machinery -- guaranteeing a baked .spc image is byte-identical
// to the JIT.

// Deliberately host-clean: NO port.h / hal / surfaceInternal includes, so
// this TU compiles unchanged on the build host (tools/spritebake) as well as
// every cross target. It needs only the emitter decls + the sprite struct.
#include <string.h>

#include "joey/sprite.h"
#include "spriteEmitter.h"
#include "spriteInternal.h"
#include "spriteStage.h"


// Compile-time selection of the per-CPU emitter. One per-CPU
// spriteEmit*.c file (src/dos, src/iigs, src/m68k) is built per
// platform, but the dispatch lives in this file so jlSpriteCompile +
// spriteCompiledDraw aren't duplicated per port.
static uint32_t emitDrawForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
#if defined(JOEYLIB_PLATFORM_DOS)
    return spriteEmitDrawX86(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
    return spriteEmitDrawPlanar68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_ATARIST)
    return spriteEmitDrawInterleaved68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_IIGS)
    return spriteEmitDrawIigs(out, cap, sp, shift);
#else
#  error "jlSpriteCompile: no emitter selected for this platform"
#endif
}


// Save-under and restore-under emit dispatch. Each per-CPU pair
// produces row-by-row copy bytes; the runtime dispatch in
// src/core/sprite.c gates on routineOffsets[shift][SPRITE_OP_SAVE]
// != SPRITE_NOT_COMPILED and falls back to the interpreted memcpy
// path otherwise.
static uint32_t emitSaveForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
#if defined(JOEYLIB_PLATFORM_DOS)
    return spriteEmitSaveX86(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
    return spriteEmitSavePlanar68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_ATARIST)
    return spriteEmitSaveInterleaved68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_IIGS)
    return spriteEmitSaveIigs(out, cap, sp, shift);
#else
    (void)out; (void)cap; (void)sp; (void)shift;
    return 0;
#endif
}


static uint32_t emitRestoreForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
#if defined(JOEYLIB_PLATFORM_DOS)
    return spriteEmitRestoreX86(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
    return spriteEmitRestorePlanar68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_ATARIST)
    return spriteEmitRestoreInterleaved68k(out, cap, sp, shift);
#elif defined(JOEYLIB_PLATFORM_IIGS)
    return spriteEmitRestoreIigs(out, cap, sp, shift);
#else
    (void)out; (void)cap; (void)sp; (void)shift;
    return 0;
#endif
}


// Emit one (shift, op) routine into the staging image at byte offset
// `total`, honoring the per-routine scratch cap AND the remaining uint16
// routine-offset budget (no routine may start at or past
// SPRITE_NOT_COMPILED, and the cap keeps its end within the 64 KB image).
// Returns the bytes written, or 0 when the routine doesn't fit, isn't
// implemented for this port, or (68k) is odd-sized -- in every 0 case that
// (shift, op) falls to the interpreter instead of rejecting the sprite.
// The emitter (and its per-pixel analysis) runs exactly ONCE per variant
// here; there is no separate measure pass.
static uint32_t spriteStageOne(uint8_t *staging, uint32_t total, const jlSpriteT *sp, uint8_t shift, uint8_t op) {
    uint32_t cap;
    uint32_t written;

    if (total >= SPRITE_NOT_COMPILED) {
        return 0;
    }
    cap = SPRITE_EMIT_SCRATCH_BYTES;
    if (cap > (uint32_t)SPRITE_NOT_COMPILED - total) {
        cap = (uint32_t)SPRITE_NOT_COMPILED - total;
    }
    switch (op) {
        case SPRITE_OP_DRAW:    written = emitDrawForTarget   (staging + total, cap, sp, shift); break;
        case SPRITE_OP_SAVE:    written = emitSaveForTarget   (staging + total, cap, sp, shift); break;
        case SPRITE_OP_RESTORE: written = emitRestoreForTarget(staging + total, cap, sp, shift); break;
        default:                written = 0;                                                     break;
    }
    if (written == SPRITE_EMIT_OVERFLOW || written == 0u || written > cap) {
        return 0;
    }
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_X68000)
    if ((written & 1u) != 0u) {
        // 68k call targets must stay word-aligned: one odd routine would
        // misalign every subsequent routine in the slot (address error on
        // the fetch). The 68k emitters only produce even sizes, so this is
        // a defensive skip. 65816/x86 code has NO even-length invariant --
        // the guard must not run there (it silently demoted odd-sized
        // IIgs/DOS routines to the interpreter when it did).
        return 0;
    }
#endif
    return written;
}


// Single greedy pass, replacing the old measure-then-re-emit two-pass:
// each included variant is emitted ONCE straight into the staging image and
// its slot-relative offset recorded in sp->routineOffsets. Tier 1 = DRAW
// shifts ascending (the Phase-1 parity-gate metric); tier 2 = SAVE+RESTORE
// as ATOMIC pairs per shift (a compiled save whose restore is absent would
// strand a compiled-format backup with an interpreted restore expecting the
// other format -- silent plane scramble; both-or-neither, always). When
// alignedOnly is set (the arena-full degrade tier) tier 1 keeps only the
// cheap byte-aligned phases in SPRITE_DEGRADE_DRAW_MASK. Returns the total
// image size in bytes (0 == nothing compiled). Inclusion order and caps
// match the old spritePlanBuild exactly, so the staged bytes and the chosen
// variant set are identical by construction (PERF-AUDIT #74).
uint32_t spriteStageAndPlan(uint8_t *staging, jlSpriteT *sp, bool alignedOnly) {
    uint32_t total;
    uint8_t  shift;

    memset(sp->routineOffsets, 0xFF, sizeof(sp->routineOffsets));
    total = 0;

    for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
        uint32_t written;

        if (alignedOnly && ((SPRITE_DEGRADE_DRAW_MASK >> shift) & 1u) == 0u) {
            continue;
        }
        written = spriteStageOne(staging, total, sp, shift, SPRITE_OP_DRAW);
        if (written != 0u) {
            sp->routineOffsets[shift][SPRITE_OP_DRAW] = (uint16_t)total;
            total += written;
        }
    }

    for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
        uint32_t saveOff;
        uint32_t wSave;
        uint32_t wRest;

        saveOff = total;
        wSave   = spriteStageOne(staging, total, sp, shift, SPRITE_OP_SAVE);
        if (wSave == 0u) {
            continue;
        }
        // Place restore right after save; commit the pair only if it also
        // fits (atomic -- a lone compiled save would mismatch its restore).
        wRest = spriteStageOne(staging, total + wSave, sp, shift, SPRITE_OP_RESTORE);
        if (wRest == 0u) {
            continue;   // save's bytes stay at saveOff, overwritten next iter
        }
        sp->routineOffsets[shift][SPRITE_OP_SAVE]    = (uint16_t)saveOff;
        sp->routineOffsets[shift][SPRITE_OP_RESTORE] = (uint16_t)(saveOff + wSave);
        total += wSave + wRest;
    }
    return total;
}
