// Internal interface for per-CPU sprite emitters.
//
// Each per-CPU emitter file (src/dos/spriteEmitX86.c,
// src/iigs/spriteEmitIigs.c, src/m68k/spriteEmitPlanar68k.c,
// src/m68k/spriteEmitInterleaved68k.c) implements its own emit
// functions. spriteCompile.c picks the right one at compile time
// (via #ifdef on JOEYLIB_PLATFORM_*) for the runtime build.
//
// Each emit function takes the sprite + shift variant and a capacity
// `cap` (bytes available at `out`), and writes position-independent
// draw-routine bytes into `out`. It returns the number of bytes
// written (uint32_t -- a single pathological large routine can exceed
// 64 KB, so a 16-bit count would truncate and mis-size the arena
// slot). The output bytes follow the target CPU's calling convention
// as documented in the per-CPU file's header comment, so the bytes
// can be called via a C function pointer cast once placed in
// executable memory.
//
// Return-value contract:
//   0                     -- op not implemented for this (shift, op)
//                            on this platform; dispatcher falls back
//                            to the interpreted path.
//   SPRITE_EMIT_OVERFLOW  -- the routine would not fit in `cap`. The
//                            sizing pass and the real emit pass both
//                            propagate this and jlSpriteCompile bails
//                            cleanly (no arena alloc, sp->slot NULL).
//   any other value       -- bytes written.

#ifndef JOEYLIB_SPRITE_EMITTER_H
#define JOEYLIB_SPRITE_EMITTER_H

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

// Overflow sentinel returned by an emitter whose output would exceed
// the supplied capacity. Distinct from a real byte count and from the
// 0 "not implemented" return.
#define SPRITE_EMIT_OVERFLOW 0xFFFFFFFFul

// Worst-case emitted bytes per destination byte, per target. Used to
// size the emit scratch and to derive the create-time dimension cap so
// the scratch, the cap, and the per-emitter bound all share one fact:
//   68k mixed RMW : move.b d16(a0),d0; andi.b; ori.b; move.b d0,d16(a0)
//                   = 4 instructions * 4 bytes                  = 16
//   x86 mixed RMW : mov al,[esi+d]; and al; or al; mov [esi+d],al = 10
//   IIgs mixed RMW: lda abs,Y; and #; ora #; sta abs,Y           = 9
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_68K  16u
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86  10u
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS 9u

#if defined(JOEYLIB_PLATFORM_DOS)
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86
#elif defined(JOEYLIB_PLATFORM_IIGS)
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS
#else
// The planar (Amiga) and interleaved (ST) emitters share the same
// worst-case mixed-RMW shape (4 instructions * 4 bytes per dest byte).
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_68K
#endif

uint32_t spriteEmitDrawX86 (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitDrawIigs(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);

// Save-under and restore-under emitters. Both copy a byte-aligned
// rectangle between the destination surface and a backup buffer. The
// rectangle's width and start position depend on the shift: for
// shift=0 (even x) it covers exactly the sprite's bytes per row;
// for shift=1 (odd x) it covers one extra byte (left edge nibble).
// Per-CPU emitters return 0 to mean "not implemented" -- the runtime
// dispatch falls back to the interpreted path in that case.
//
// x86 emits a plain cdecl `void copy(const uint8_t *src, uint8_t *dst)`
// where the caller swaps args between SAVE (screen->backup) and
// RESTORE (backup->screen). IIgs emits a C-ABI
// `void copy(uint32_t packed)` (packed = srcOff | (dstOff << 16)) of
// unrolled MVN rows whose bank operand bytes the dispatcher patches
// per call -- see spriteEmitIigs.c's header for the full contract.
uint32_t spriteEmitSaveIigs   (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitRestoreIigs(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitSaveX86    (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitRestoreX86 (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);

// Planar 68k emitters (Amiga). Destination addressing is across 4
// separate bitplane buffers, not a single packed-pixel surface.
// Calling convention for the emitted DRAW bytes (cdecl):
//   void draw(uint8_t *p0, uint8_t *p1, uint8_t *p2, uint8_t *p3);
// Each pi is plane_base + byteOff (= y*40 + x/8 already added by the
// dispatcher). DRAW returns 0 for shifts not yet implemented (today
// only shift 0 == byte-aligned x is emitted; shifts 1..7 fall back to
// the cross-platform interpreter). SAVE/RESTORE always return 0 --
// not compiled on the Amiga yet; the interpreted jlpSpriteSavePlanes /
// jlpSpriteRestorePlanes path handles them.
uint32_t spriteEmitDrawPlanar68k    (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitSavePlanar68k    (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitRestorePlanar68k (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);

// Word-interleaved planar 68k emitter (ST). Calling convention for
// the emitted bytes:
//   void draw(uint8_t *groupBase);
// where groupBase = pd->base + y*160 + (x>>4)*8. DRAW shifts 0 and 1
// emit real bytes (x mod 16 == 0 for shift 0, x mod 16 == 8 for
// shift 1); other shifts return 0 so the cross-platform dispatcher
// falls back to jlpSpriteDrawPlanes. SAVE/RESTORE always return 0 --
// not compiled on the ST; the interpreted planes path handles them.
uint32_t spriteEmitDrawInterleaved68k    (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitSaveInterleaved68k    (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
uint32_t spriteEmitRestoreInterleaved68k (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);

#endif
