// IIgs (65816) sprite codegen. Emits position-independent, C-ABI
// routines that jlSpriteCompile places in the codegen arena and the
// IIgs dispatchers in spriteCompile.c invoke through ordinary C
// function pointers -- the compiler emits the cross-bank JSL itself.
// There is NO self-modifying call stub and NO inline asm (that
// machinery was deleted in the #19 rewrite; see the note in
// spriteCompile.c).
//
// CALLING CONVENTION (llvm-mos w65816 cdecl; M=16, X=16 at entry and
// exit):
//
//   DRAW:  void draw(uint8_t *dstRow0);
//     dstRow0 (the sprite's top-left screen byte) is a 24-bit pointer
//     arriving as A = low 16 bits, X = bank. The emitted prologue
//     saves the caller's DBR (PHB), moves the offset into Y, drops to
//     M=8, and loads DBR with the destination bank so the body's
//     `sta abs,Y` stores land in the screen bank. The epilogue
//     restores M=16, pulls the caller's DBR back (PLB), and RTLs.
//
//   SAVE/RESTORE:  void copy(uint32_t packed);
//     packed = srcOff | (dstOff << 16), arriving as A = srcOff,
//     X = dstOff. The routine is an unrolled chain of MVN row copies;
//     the only runtime-dynamic bytes are the MVN bank operands, which
//     the dispatcher patches per call with a plain C pointer write
//     into the arena (patchMvnBanks in spriteCompile.c). The caller's
//     DBR is preserved via PHB/PLB (MVN leaves DBR = dst bank);
//     returns via RTL with M=16.
//
// DRAW body shape (see spriteEmitDrawIigs for the M=8/M=16 dual-path
// details):
//   opaque byte:  lda #imm; sta abs,Y
//   mixed byte:   lda abs,Y; and #~mask; ora #val; sta abs,Y
//   opaque run:   rep #$20; lda #imm16; sta abs,Y; ...; sep #$20
//
// Position-independent: the only abs operands are dest-byte offsets
// baked at emit time; Y carries the runtime dst offset.
//
// All multi-byte operands are little-endian, written low byte first
// into the output stream.

#include "joey/sprite.h"
#include "joey/surface.h"
#include "joey/tile.h"
#include "spriteEmitChunky.h"
#include "spriteEmitter.h"
#include "spriteInternal.h"


// ----- Constants -----

// Tile geometry comes from joey/tile.h; TRANSPARENT_NIBBLE from
// spriteInternal.h.


// ----- Prototypes -----

// shiftedByteAt / spriteSourceByte -- the transparency contract shared
// with the x86 emitter -- come from spriteEmitChunky.h.
static uint32_t emitMvnCabiRoutine(uint8_t *out, uint32_t cap, uint16_t heightPx, uint16_t copyBytes, bool advanceX);


// ----- Emit helpers -----

// writeLE16 was inlined at every call site. Inlining cuts a JSL/RTL
// per emitted 16-bit immediate (4 instructions per byte * 12 sites).

// Common backbone for save and restore. Both ops copy a byte-aligned
// rectangle row-by-row using MVN; only the operand banks (which buffer
// is source vs destination) and which index register (X or Y) needs
// per-row advance differ.
//
// The IIgs supplies a single MVN instruction that copies (count + 1)
// bytes from srcBank:X to dstBank:Y, advancing X and Y as it goes.
// We unroll the row loop:
//
//   row 0:                      row 1..H-1:
//     LDA #(copyBytes-1)          T_A         (TXA for save / TYA for restore)
//     MVN dstbk,srcbk             CLC
//                                 ADC #(rowStride - copyBytes)
//                                 TA_         (TAX for save / TAY for restore)
//                                 LDA #(copyBytes-1)
//                                 MVN dstbk,srcbk
//   end:
//     RTL
//
// The bank operand bytes are patched per call by
// spriteCompiledSaveUnder / spriteCompiledRestoreUnder so the routine
// works regardless of where the surface and backup happen to live in
// memory; the authoritative byte layout (including the patch offsets)
// is in the C-ABI comment below.
// Worst-case bytes emitted per MVN copy row: row 0 = 6 bytes, any
// later row = 12. Plus the prologue and the trailing PLB/RTL.
#define IIGS_MVN_MAX_BYTES_PER_ROW 12u

// C-ABI MVN copy routine. Called via a normal C function pointer:
//   void copy(uint32_t packed);   packed = srcOff | (dstOff << 16)
// so arg0 arrives as A = srcOff, X = dstOff (llvm-mos w65816 cdecl). The
// routine reaches MVN's required state (M=16, X=16 -- the C ABI entry state --
// with X = src offset, Y = dst offset) via a tiny fixed prologue, runs the
// unrolled MVN rows, then restores the caller's DBR (MVN leaves DBR = dst bank)
// and returns. No self-modifying call stub, no inline asm.
//
// The MVN bank operand bytes are still patched per call (MVN's banks are
// immediate operands and the screen/backup banks are runtime-dynamic), but the
// patch is a plain C pointer write into the arena from the dispatcher -- the
// dst bank for row R is at routine offset (12*R + 8), src bank at (12*R + 9)
// (the 4-byte prologue shifts the old +4/+5 offsets by 4).
//
// Layout:  prologue (4): 8B DA AA 7A   (PHB; PHX; TAX; PLY)
//          row 0   (6):  A9 lo hi 54 db sb
//          row R   (12): 8A/98 18 69 lo hi AA/A8 A9 lo hi 54 db sb
//          epilogue(2):  AB 6B          (PLB; RTL)
static uint32_t emitMvnCabiRoutine(uint8_t *out, uint32_t cap, uint16_t heightPx, uint16_t copyBytes, bool advanceX) {
    uint32_t cursor;
    uint16_t advance;
    uint16_t row;

    // A zero copyBytes would emit LDA #0xFFFF and MVN a full 64 KB -- guard it.
    if (copyBytes == 0u) {
        return 0u;
    }

    cursor  = 0;
    advance = (uint16_t)(SURFACE_BYTES_PER_ROW - copyBytes);

    // C-ABI prologue: A = srcOff, X = dstOff -> X = srcOff, Y = dstOff.
    if (cursor + 4u > cap) {
        return SPRITE_EMIT_OVERFLOW;
    }
    out[cursor++] = 0x8B;                                  // PHB  ; save caller DBR
    out[cursor++] = 0xDA;                                  // PHX  ; push dstOff
    out[cursor++] = 0xAA;                                  // TAX  ; X = A = srcOff
    out[cursor++] = 0x7A;                                  // PLY  ; Y = dstOff

    for (row = 0; row < heightPx; row++) {
        if (cursor + IIGS_MVN_MAX_BYTES_PER_ROW + 2u > cap) {
            return SPRITE_EMIT_OVERFLOW;
        }
        if (row > 0) {
            out[cursor++] = advanceX ? 0x8A : 0x98;        // TXA / TYA
            out[cursor++] = 0x18;                          // CLC
            out[cursor++] = 0x69;                          // ADC #imm (M=16)
            out[cursor++] = (uint8_t)(advance & 0xFFu);
            out[cursor++] = (uint8_t)((advance >> 8) & 0xFFu);
            out[cursor++] = advanceX ? 0xAA : 0xA8;        // TAX / TAY
        }
        out[cursor++] = 0xA9;                              // LDA #imm (M=16)
        out[cursor++] = (uint8_t)((copyBytes - 1) & 0xFFu);
        out[cursor++] = (uint8_t)(((copyBytes - 1) >> 8) & 0xFFu);
        out[cursor++] = 0x54;                              // MVN
        out[cursor++] = 0x00;                              // dstbk -- patched per call
        out[cursor++] = 0x00;                              // srcbk -- patched per call
    }

    // C-ABI epilogue: restore caller DBR (MVN left it = dst bank), return.
    if (cursor + 2u > cap) {
        return SPRITE_EMIT_OVERFLOW;
    }
    out[cursor++] = 0xAB;                                  // PLB
    out[cursor++] = 0x6B;                                  // RTL
    return cursor;
}


// SAVE (screen -> backup). The prologue leaves X = screen low offset
// (the source), Y = backup low offset (the destination); MVN advances
// both by copyBytes per row. Backup rows are contiguous in memory so Y
// is already correct for the next row; screen rows are
// SURFACE_BYTES_PER_ROW apart so X needs an explicit ADC between rows.
uint32_t spriteEmitSaveIigs(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
    uint16_t heightPx;
    uint16_t spriteBytesPerRow;
    uint16_t copyBytes;

    if (shift > 1u) {
        return 0u;
    }

    heightPx          = (uint16_t)(sp->heightTiles * TILE_PIXELS_PER_SIDE);
    spriteBytesPerRow = (uint16_t)(sp->widthTiles * TILE_BYTES_PER_ROW);
    copyBytes         = (uint16_t)(spriteBytesPerRow + shift);  /* shift is 0 or 1 */

    // SAVE: src = screen (X advances SURFACE_BYTES_PER_ROW per row), dst =
    // backup (Y, contiguous). The dispatcher packs srcOff=screen, dstOff=backup.
    return emitMvnCabiRoutine(out, cap, heightPx, copyBytes, /*advanceX=*/true);
}


// RESTORE (backup -> screen). The prologue leaves X = backup low
// offset, Y = screen low offset. Backup is contiguous so X advances
// correctly via MVN; screen needs explicit advance, so Y is the one
// we ADC.
uint32_t spriteEmitRestoreIigs(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
    uint16_t heightPx;
    uint16_t spriteBytesPerRow;
    uint16_t copyBytes;

    if (shift > 1u) {
        return 0u;
    }

    heightPx          = (uint16_t)(sp->heightTiles * TILE_PIXELS_PER_SIDE);
    spriteBytesPerRow = (uint16_t)(sp->widthTiles * TILE_BYTES_PER_ROW);
    copyBytes         = (uint16_t)(spriteBytesPerRow + shift);  /* shift is 0 or 1 */

    // RESTORE: src = backup (X, contiguous), dst = screen (Y advances
    // SURFACE_BYTES_PER_ROW per row). The dispatcher packs srcOff=backup,
    // dstOff=screen.
    return emitMvnCabiRoutine(out, cap, heightPx, copyBytes, /*advanceX=*/false);
}


// 65816 draw emit. Returns bytes written.
//
// Two emission paths share the body:
//
//   * M=8 byte path (default; the emitted prologue enters M=8):
//       opaque:  A9 vv         LDA #vv             ;  2c
//                99 lo hi      STA abs,Y           ;  5c
//                                                  ; 7c / 5 bytes per byte
//       mixed:   B9 lo hi      LDA abs,Y           ;  5c
//                29 mm         AND #~mask          ;  2c
//                09 vv         ORA #val            ;  2c
//                99 lo hi      STA abs,Y           ;  5c
//                                                  ;14c / 9 bytes per byte
//
//   * M=16 word path (entered around runs of >= 2 consecutive
//     fully-opaque bytes). Each word write covers 2 dest bytes:
//       prologue: C2 20         REP #$20            ;  3c
//       per pair: A9 lo hi      LDA #imm16          ;  3c
//                 99 lo hi      STA abs,Y           ;  6c
//                                                   ;  9c / 6 bytes per pair
//       epilogue: E2 20         SEP #$20            ;  3c
//
//     vs. M=8 path doing the same 2 bytes: 14c / 10 bytes. Per-pair
//     savings are 5c / 4 bytes; the 6c/4-byte REP+SEP transition is
//     amortized once per opaque run, so the path is profitable for
//     runs of 2 pairs (4 consecutive opaque bytes) or longer. For
//     isolated pairs we still take the M=16 path -- the 1-cycle loss
//     vs. M=8 is dwarfed by the typical-sprite opaque-run length.
//
// Mixed bytes always run on the M=8 path because the AND/ORA in
// M=16 would clobber the adjacent byte.
uint32_t spriteEmitDrawIigs(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
    uint32_t cursor;
    uint16_t row;
    uint16_t col;
    uint16_t heightPx;
    uint16_t spriteBytesPerRow;
    uint16_t destBytesPerRow;
    uint16_t absOffset;
    uint8_t  value;
    uint8_t  opaqueMask;
    uint8_t  nextValue;
    uint8_t  nextOpaqueMask;
    bool     wide;

    if (shift > 1u) {
        return 0u;
    }

    cursor             = 0;
    heightPx           = (uint16_t)(sp->heightTiles * TILE_PIXELS_PER_SIDE);
    spriteBytesPerRow  = (uint16_t)(sp->widthTiles * TILE_BYTES_PER_ROW);
    destBytesPerRow    = (uint16_t)(spriteBytesPerRow + shift);  /* shift is 0 or 1 */
    wide               = false;

    // C-ABI prologue. The runtime calls this routine via a normal C
    // function pointer -- void draw(uint8_t *dstRow0) -- so the compiler
    // emits the JSL and there is no self-modifying stub. The 24-bit
    // dstRow0 pointer arrives in A = low 16, X = bank (llvm-mos w65816
    // cdecl). Establish the draw body's expected entry state:
    //   M = 8, X = 16, Y = dstRow0 offset, DBR = dstRow0 bank
    // and preserve the caller's DBR.
    if (cursor + 7u > cap) {
        return SPRITE_EMIT_OVERFLOW;
    }
    out[cursor++] = 0x8B;        // PHB        ; save caller DBR
    out[cursor++] = 0xA8;        // TAY        ; Y = A = dstRow0 offset (M=16)
    out[cursor++] = 0xE2;        // SEP #$20   ; M=8
    out[cursor++] = 0x20;
    out[cursor++] = 0x8A;        // TXA        ; A = X low byte = dstRow0 bank
    out[cursor++] = 0x48;        // PHA
    out[cursor++] = 0xAB;        // PLB        ; DBR = dstRow0 bank

    for (row = 0; row < heightPx; row++) {
        for (col = 0; col < destBytesPerRow; col++) {
            shiftedByteAt(sp, row, col, shift, spriteBytesPerRow, &value, &opaqueMask);
            if (opaqueMask == 0x00) {
                continue;
            }
            // Reserve the worst case for one dest byte: the 9-byte
            // mixed RMW, plus a possible REP/SEP mode transition (2),
            // plus the trailing SEP+RTL (3) at routine end.
            if (cursor + SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS + 2u + 3u > cap) {
                return SPRITE_EMIT_OVERFLOW;
            }
            // Compute the dest-byte offset in a 32-bit intermediate so
            // row*160 cannot wrap a 16-bit int used for plain
            // int arithmetic (row up to ~199, 199*160 = 31840 is in
            // range for legal sprites, but a clamp-bypassing caller
            // must not silently wrap). The result still fits the 16-bit
            // abs,Y operand for any on-surface sprite.
            absOffset = (uint16_t)((uint32_t)row * SURFACE_BYTES_PER_ROW + col);

            if (opaqueMask == 0xFFu && (col + 1) < destBytesPerRow) {
                // Look ahead: if (col, col+1) are both fully opaque
                // we can pair them as a single M=16 word write.
                shiftedByteAt(sp, row, (uint16_t)(col + 1), shift,
                              spriteBytesPerRow, &nextValue, &nextOpaqueMask);
                if (nextOpaqueMask == 0xFFu) {
                    if (!wide) {
                        out[cursor++] = 0xC2;            // REP #$20 -- M=16
                        out[cursor++] = 0x20;
                        wide = true;
                    }
                    {
                        uint16_t pair = (uint16_t)(((uint16_t)nextValue << 8) | value);
                        out[cursor++] = 0xA9;            // LDA #imm16
                        out[cursor++] = (uint8_t)(pair & 0xFFu);
                        out[cursor++] = (uint8_t)((pair >> 8) & 0xFFu);
                        out[cursor++] = 0x99;            // STA abs,Y
                        out[cursor++] = (uint8_t)(absOffset & 0xFFu);
                        out[cursor++] = (uint8_t)((absOffset >> 8) & 0xFFu);
                    }
                    col++;                               // consumed col+1
                    continue;
                }
            }

            // Falls through here for: isolated opaque (no pair), mixed,
            // or the trailing odd byte at the right edge. All on M=8.
            if (wide) {
                out[cursor++] = 0xE2;                    // SEP #$20 -- back to M=8
                out[cursor++] = 0x20;
                wide = false;
            }

            if (opaqueMask == 0xFFu) {
                out[cursor++] = 0xA9;
                out[cursor++] = value;
                out[cursor++] = 0x99;
                out[cursor++] = (uint8_t)(absOffset & 0xFFu);
                out[cursor++] = (uint8_t)((absOffset >> 8) & 0xFFu);
            } else {
                out[cursor++] = 0xB9;
                out[cursor++] = (uint8_t)(absOffset & 0xFFu);
                out[cursor++] = (uint8_t)((absOffset >> 8) & 0xFFu);
                out[cursor++] = 0x29;
                out[cursor++] = (uint8_t)(~opaqueMask & 0xFFu);
                out[cursor++] = 0x09;
                out[cursor++] = value;
                out[cursor++] = 0x99;
                out[cursor++] = (uint8_t)(absOffset & 0xFFu);
                out[cursor++] = (uint8_t)((absOffset >> 8) & 0xFFu);
            }
        }
    }

    // C-ABI epilogue: normalize to M=16 (REP clears M whether the body left
    // it 8 or 16 after a word run), restore the caller's DBR, return. X was
    // never touched by the body, so it is still 16-bit for the caller.
    if (cursor + 4u > cap) {
        return SPRITE_EMIT_OVERFLOW;
    }
    out[cursor++] = 0xC2;        // REP #$20   ; M=16
    out[cursor++] = 0x20;
    out[cursor++] = 0xAB;        // PLB        ; restore caller DBR
    out[cursor++] = 0x6B;        // RTL
    return cursor;
}


