// Atari ST tile ops as always-inline functions (NATIVE-PERF Phase 2
// R4a). Bodies moved verbatim from src/atarist/hal.c so the public
// wrappers in src/core/tile.c inline them, deleting the second full
// cdecl call layer (~130-190 cycles/op on the 68000). The jlpTile*
// dispatch names are macro-aliased to these in port.h, which also
// #includes this header at its tail -- AFTER the jlpGenericTile*
// prototypes the portData == NULL fallbacks call. Do not include this
// header directly; it comes with port.h on the ST.
#ifndef JOEY_ST_TILE_H
#define JOEY_ST_TILE_H

#include <stdint.h>
#include <stdbool.h>

#include "joey/tile.h"
#include "stPlanar.h"

// 68k asm entry (surface68kSt.s side): fills one 8-px half-group in
// all 4 planes. Declared here because stTileFill is its only caller.
extern void surface68kStTileFill8x8(uint8_t *firstGroupPtr, uint16_t mask, uint8_t color);

// 68k asm entry (tileMap.s): whole-map paste with a movep.l row body
// (36 cyc/tile-row vs ~80 for compiled move.b). Requires an EVEN
// tileset pointer (move.l source alignment); stTileMapPaste guards
// and keeps the C pair walker as the odd-pointer fallback. Declared
// here because stTileMapPaste is its only caller.
extern void stTileMapPasteMovep(uint8_t *rowBase, const uint8_t *tiles, const uint8_t *map, uint16_t wTiles, uint16_t hTiles, uint16_t headOdd);

// Register-args jsr shims for the single-tile movep bodies in
// tileMap.s (NATIVE-PERF W3). The bodies take their pointers IN
// a0/a1 and use only the cdecl scratch set, so a call costs jsr+rts
// (~36 cyc) instead of a full cdecl frame (~100 cyc) -- the delta
// that lets per-call movep win where the Phase-10 walker asm lost.
// GNU local register variables pin the operands (the same register-
// args idea the Amiga SDI_compiler.h shim uses); "memory" orders the
// pixel traffic against surrounding C.
static inline __attribute__((always_inline)) void stTilePasteMovepCall(uint8_t *dstAddr, const uint8_t *tileSrc) {
    register const uint8_t *rSrc __asm__("a0") = tileSrc;
    register uint8_t       *rDst __asm__("a1") = dstAddr;

    __asm__ volatile ("jsr _stTilePasteMovepRegs" : "+a" (rSrc), "+a" (rDst) : : "d0", "cc", "memory");
}


static inline __attribute__((always_inline)) void stTileCopyMovepCall(uint8_t *dstAddr, const uint8_t *srcAddr) {
    register const uint8_t *rSrc __asm__("a0") = srcAddr;
    register uint8_t       *rDst __asm__("a1") = dstAddr;

    __asm__ volatile ("jsr _stTileCopyMovepRegs" : "+a" (rSrc), "+a" (rDst) : : "d0", "cc", "memory");
}


static inline __attribute__((always_inline)) void stTileSnapMovepCall(uint8_t *outTile, const uint8_t *srcAddr) {
    register const uint8_t *rSrc __asm__("a0") = srcAddr;
    register uint8_t       *rOut __asm__("a1") = outTile;

    __asm__ volatile ("jsr _stTileSnapMovepRegs" : "+a" (rSrc), "+a" (rOut) : : "d0", "cc", "memory");
}

// Phase 5 tile ops. 8x8 tiles at byte position (bx, by) start at
// pixel (bx*8, by*8). 8 pixels wide always covers exactly half a
// 16-pixel group: high half (bits 15..8) when bx is even, low half
// (bits 7..0) when bx is odd. Per-row work is 4 plane half-word RMWs.
static inline __attribute__((always_inline)) void stTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
    StPlanarT *pd;
    uint16_t   group;
    uint16_t   halfMask;
    uint8_t   *gp;

    // Core jlTileFill already proved s != NULL and clipped bx/by; only
    // the non-planar (offscreen, no portData) fallback needs handling.
    pd = (StPlanarT *)s->portData;
    if (pd == NULL) {
        jlpGenericTileFill(s, bx, by, colorIndex);
        return;
    }
    group    = (uint16_t)((uint16_t)bx >> 1);
    halfMask = ((bx & 1u) == 0u) ? 0xFF00u : 0x00FFu;
    gp = pd->base + (uint16_t)by * 8u * ST_BYTES_PER_ROW + group * ST_BYTES_PER_GROUP;
    /* Phase 10 final: specialized 8x8 unrolled tile-fill skips the
     * generic FRG_LOOP's per-row subq+bne overhead. */
    surface68kStTileFill8x8(gp, halfMask, colorIndex);
}


// Phase 10: tile paste/snap reuse the asm sprite save/restore
// helpers -- identical per-row work patterns at byte-aligned
// positions. Width 8 = single tile column = single half-group
// write per plane. The asm walker handles 8 rows just as well
// as a sprite's variable height.
static inline __attribute__((always_inline)) void stTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
    StPlanarT *pd;
    uint16_t   group;
    uint8_t   *dstAddr;

    if (dst->portData == NULL) {
        jlpGenericTilePaste(dst, bx, by, chunkyTile);
        return;
    }
    pd = (StPlanarT *)dst->portData;
    /* Phase 10.5: jlTileT.pixels holds plane-major bytes (4 plane bytes
     * per row * 8 rows = 32 bytes). Direct byte copy to the planar
     * buffer; no chunky <-> planar conversion. */
    group   = (uint16_t)((uint16_t)bx >> 1);
    dstAddr = pd->base
            + (uint16_t)by * 8u * ST_BYTES_PER_ROW
            + group * ST_BYTES_PER_GROUP
            + (uint16_t)(bx & 1u);
    if (((uintptr_t)chunkyTile & 1u) == 0u) {
        // W3: movep row body (tileMap.s), 36 cyc/tile-row vs ~80 for
        // the byte copy below.
        stTilePasteMovepCall(dstAddr, chunkyTile);
        return;
    }
    // Odd tile source: move.l needs an even src -- same contract and
    // fallback as the whole-map walker's odd-tileset arm.
#define ST_TILE_PASTE_ROW                                                   \
    do {                                                                    \
        dstAddr[0] = chunkyTile[0];                                         \
        dstAddr[2] = chunkyTile[1];                                         \
        dstAddr[4] = chunkyTile[2];                                         \
        dstAddr[6] = chunkyTile[3];                                         \
        dstAddr    += ST_BYTES_PER_ROW;                                     \
        chunkyTile += TILE_BYTES_PER_ROW;                                   \
    } while (0)
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
    ST_TILE_PASTE_ROW;
#undef ST_TILE_PASTE_ROW
}


// Whole-map paste engine (NATIVE-PERF Phase 4). jlTileMapPaste's ST
// arm calls this ONCE per map: the portData check, the row base
// derivation, and the group walk are hoisted out of the per-cell
// loop -- the generic loop re-derives base + by*1280 + (bx>>1)*8 +
// (bx&1) and reloads portData per cell, ~250 of its ~980 cyc/tile.
// Cells advance in even/odd PAIRS sharing one group pointer (+8 per
// pair, constant displacements select the half-group), so an odd bx
// contributes a head cell and an odd width a tail cell. Pixel output
// is byte-identical to per-cell stTilePaste.
#define ST_MAP_CELL_ROW(_o, _r)                                             \
    do {                                                                    \
        gp[(_o) + (_r) * ST_BYTES_PER_ROW + 0] = src[(_r) * TILE_BYTES_PER_ROW + 0]; \
        gp[(_o) + (_r) * ST_BYTES_PER_ROW + 2] = src[(_r) * TILE_BYTES_PER_ROW + 1]; \
        gp[(_o) + (_r) * ST_BYTES_PER_ROW + 4] = src[(_r) * TILE_BYTES_PER_ROW + 2]; \
        gp[(_o) + (_r) * ST_BYTES_PER_ROW + 6] = src[(_r) * TILE_BYTES_PER_ROW + 3]; \
    } while (0)

#define ST_MAP_CELL(_o)                                                     \
    do {                                                                    \
        src = tileBytes + ((uint16_t)idx << 5);                             \
        ST_MAP_CELL_ROW(_o, 0);                                             \
        ST_MAP_CELL_ROW(_o, 1);                                             \
        ST_MAP_CELL_ROW(_o, 2);                                             \
        ST_MAP_CELL_ROW(_o, 3);                                             \
        ST_MAP_CELL_ROW(_o, 4);                                             \
        ST_MAP_CELL_ROW(_o, 5);                                             \
        ST_MAP_CELL_ROW(_o, 6);                                             \
        ST_MAP_CELL_ROW(_o, 7);                                             \
    } while (0)

static inline __attribute__((always_inline)) void stTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
    StPlanarT     *pd;
    const uint8_t *tileBytes;
    const uint8_t *src;
    uint8_t       *rowBase;
    uint8_t       *gp;
    uint8_t        idx;
    uint8_t        ty;
    uint8_t        tx;
    uint8_t        pairs;
    uint8_t        headOdd;
    uint8_t        tail;

    tileBytes = (const uint8_t *)tiles;
    pd        = (StPlanarT *)dst->portData;
    if (pd == NULL) {
        for (ty = 0; ty < hTiles; ty++) {
            for (tx = 0; tx < wTiles; tx++) {
                idx = map[tx];
                if (idx != TILE_MAP_SKIP) {
                    jlpGenericTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), tileBytes + ((uint16_t)idx << 5));
                }
            }
            map += wTiles;
        }
        return;
    }
    rowBase = pd->base + (uint16_t)by * 8u * ST_BYTES_PER_ROW + ((uint16_t)bx >> 1) * ST_BYTES_PER_GROUP;
    headOdd = (uint8_t)(bx & 1u);
    if (((uintptr_t)tileBytes & 1u) == 0u) {
        stTileMapPasteMovep(rowBase, tileBytes, map, wTiles, hTiles, headOdd);
        return;
    }
    pairs = (uint8_t)((uint8_t)(wTiles - headOdd) >> 1);
    tail  = (uint8_t)((uint8_t)(wTiles - headOdd) & 1u);
    for (ty = 0; ty < hTiles; ty++) {
        gp = rowBase;
        if (headOdd != 0u) {
            idx = *map++;
            if (idx != TILE_MAP_SKIP) {
                ST_MAP_CELL(1);
            }
            gp += ST_BYTES_PER_GROUP;
        }
        for (tx = pairs; tx != 0u; tx--) {
            idx = *map++;
            if (idx != TILE_MAP_SKIP) {
                ST_MAP_CELL(0);
            }
            idx = *map++;
            if (idx != TILE_MAP_SKIP) {
                ST_MAP_CELL(1);
            }
            gp += ST_BYTES_PER_GROUP;
        }
        if (tail != 0u) {
            idx = *map++;
            if (idx != TILE_MAP_SKIP) {
                ST_MAP_CELL(0);
            }
        }
        rowBase += 8u * ST_BYTES_PER_ROW;
    }
}
#undef ST_MAP_CELL
#undef ST_MAP_CELL_ROW


// Planar monochrome paste. monoTile follows the cross-port mono
// contract (include/joey/tile.h + jlpGenericTilePasteMono): 32 chunky
// nibble-pair bytes, row-major, TILE_BYTES_PER_ROW bytes per row, two
// pixels per byte. A pixel renders fgColor when its source nibble is
// nonzero, bgColor when zero; the HIGH nibble is the LEFT pixel. Each
// row's 4 source bytes fold into an 8-bit shape mask (bit 7 = leftmost
// pixel, matching the planar bit order), then each plane k writes
//   outPlaneK = (shape & maskFgK) | (~shape & maskBgK)
// at dstAddr + k*2 inside the 16-pixel group (4 plane words = 8 bytes
// per group). bx odd selects the second byte of each plane word (the
// low / right 8 pixels of the group).
static inline __attribute__((always_inline)) void stTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
    StPlanarT *pd;
    uint16_t   group;
    uint8_t   *dstAddr;
    uint8_t    row;
    uint8_t    col;
    uint8_t    plane;
    uint8_t    masksFg[4];
    uint8_t    masksBg[4];

    if (dst->portData == NULL) {
        jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
        return;
    }
    pd = (StPlanarT *)dst->portData;
    for (plane = 0u; plane < 4u; plane++) {
        masksFg[plane] = (uint8_t)((fgColor & (1u << plane)) ? 0xFFu : 0x00u);
        masksBg[plane] = (uint8_t)((bgColor & (1u << plane)) ? 0xFFu : 0x00u);
    }
    group   = (uint16_t)((uint16_t)bx >> 1);
    dstAddr = pd->base
            + (uint16_t)by * 8u * ST_BYTES_PER_ROW
            + group * ST_BYTES_PER_GROUP
            + (uint16_t)(bx & 1u);
    for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
        uint8_t shape = 0u;

        for (col = 0u; col < TILE_BYTES_PER_ROW; col++) {
            uint8_t srcByte = *monoTile++;

            shape = (uint8_t)(shape << 2);
            if (srcByte & 0xF0u) {
                shape = (uint8_t)(shape | 0x02u);
            }
            if (srcByte & 0x0Fu) {
                shape = (uint8_t)(shape | 0x01u);
            }
        }
        dstAddr[0] = (uint8_t)((uint8_t)(shape & masksFg[0]) |
                               (uint8_t)((uint8_t)(~shape) & masksBg[0]));
        dstAddr[2] = (uint8_t)((uint8_t)(shape & masksFg[1]) |
                               (uint8_t)((uint8_t)(~shape) & masksBg[1]));
        dstAddr[4] = (uint8_t)((uint8_t)(shape & masksFg[2]) |
                               (uint8_t)((uint8_t)(~shape) & masksBg[2]));
        dstAddr[6] = (uint8_t)((uint8_t)(shape & masksFg[3]) |
                               (uint8_t)((uint8_t)(~shape) & masksBg[3]));
        dstAddr += ST_BYTES_PER_ROW;
    }
}


static inline __attribute__((always_inline)) void stTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
    const StPlanarT *pd;
    uint16_t         group;
    const uint8_t   *srcAddr;

    if (src->portData == NULL) {
        jlpGenericTileSnap(src, bx, by, chunkyOut);
        return;
    }
    pd = (const StPlanarT *)src->portData;
    /* Phase 10.5: write plane-major bytes to jlTileT (4 per row * 8 rows). */
    group   = (uint16_t)((uint16_t)bx >> 1);
    srcAddr = pd->base
            + (uint16_t)by * 8u * ST_BYTES_PER_ROW
            + group * ST_BYTES_PER_GROUP
            + (uint16_t)(bx & 1u);
    if (((uintptr_t)chunkyOut & 1u) == 0u) {
        // W3: movep gather -> sequential move.l stores (36 cyc/row).
        // The screen side is byte-lane (any parity, incl. odd bx);
        // only the OUT buffer must be even for the move.l stores.
        stTileSnapMovepCall(chunkyOut, srcAddr);
        return;
    }
    // Odd out pointer: keep the byte-copy rows as the cold fallback.
#define ST_TILE_SNAP_ROW                                                    \
    do {                                                                    \
        chunkyOut[0] = srcAddr[0];                                          \
        chunkyOut[1] = srcAddr[2];                                          \
        chunkyOut[2] = srcAddr[4];                                          \
        chunkyOut[3] = srcAddr[6];                                          \
        srcAddr   += ST_BYTES_PER_ROW;                                      \
        chunkyOut += TILE_BYTES_PER_ROW;                                    \
    } while (0)
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
    ST_TILE_SNAP_ROW;
#undef ST_TILE_SNAP_ROW
}


// Phase 10: direct planar->planar tile copy. Each tile occupies one
// half-byte of one plane word per plane per row (8 rows total).
// We just byte-copy 4 plane bytes per row -- no chunky scratch, no
// bit transpose, no LUT. ~640 cyc per tile vs ~5000 cyc for the
// snap+paste path.
static inline __attribute__((always_inline)) void stTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
    StPlanarT     *dstPd;
    const StPlanarT *srcPd;
    uint8_t        *dstAddr;
    const uint8_t  *srcAddr;
    uint16_t        srcGroup;
    uint16_t        dstGroup;

    if (dst->portData == NULL || src->portData == NULL) {
        jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
        return;
    }
    dstPd = (StPlanarT *)dst->portData;
    srcPd = (const StPlanarT *)src->portData;
    srcGroup = (uint16_t)((uint16_t)srcBx >> 1);
    dstGroup = (uint16_t)((uint16_t)dstBx >> 1);
    srcAddr = srcPd->base
            + (uint16_t)srcBy * 8u * ST_BYTES_PER_ROW
            + srcGroup * ST_BYTES_PER_GROUP
            + (uint16_t)(srcBx & 1u);
    dstAddr = dstPd->base
            + (uint16_t)dstBy * 8u * ST_BYTES_PER_ROW
            + dstGroup * ST_BYTES_PER_GROUP
            + (uint16_t)(dstBx & 1u);
    // W3: movep gather + scatter (tileMap.s), 48 cyc/tile-row vs ~80.
    // Byte-lane both sides -- legal at either parity, so no guard and
    // no fallback.
    stTileCopyMovepCall(dstAddr, srcAddr);
}


static inline __attribute__((always_inline)) void stTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
    StPlanarT *dstPd;
    // aligned(2): keeps the snap arm's even-out guard cold -- an odd
    // stack frame offset would silently drop every copyMasked/drawText
    // snap to the byte-copy fallback (correct, slow). Audit W3-A2.
    uint8_t    scratch[TILE_BYTES] __attribute__((aligned(2)));
    int16_t    row;
    uint16_t   dstX0;
    uint16_t   dstY0;
    uint16_t   group;
    uint16_t   halfOff;
    uint8_t   *dstByte;
    uint8_t    p0;
    uint8_t    p1;
    uint8_t    p2;
    uint8_t    p3;
    uint8_t    xK0;
    uint8_t    xK1;
    uint8_t    xK2;
    uint8_t    xK3;
    uint8_t    mask;
    uint8_t    notMask;

    if (dst->portData == NULL) {
        jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
        return;
    }
    dstPd = (StPlanarT *)dst->portData;
    /* Phase 10.5: bulk-plane fast path. scratch holds plane-major bytes
     * (4 plane bytes per row * 8 rows). For each row, build a "non-
     * transparent" mask = OR of (plane_byte XOR replicated transparent
     * bit) -- 1s where the source pixel != transparent. Then 4 byte
     * RMWs (one per plane) write the row at byte-aligned dst.
     *
     * For transparent=0 this collapses to mask = p0|p1|p2|p3.
     * Replaces the prior 64-iteration per-pixel SetPixel walker. */
    stTileSnap(src, srcBx, srcBy, scratch);
    dstX0 = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
    dstY0 = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
    group   = (uint16_t)(dstX0 >> 4);
    halfOff = (uint16_t)((dstX0 & 8u) >> 3u);
    dstByte = dstPd->base + dstY0 * ST_BYTES_PER_ROW
            + group * ST_BYTES_PER_GROUP + halfOff;

    xK0 = (transparent & 1u) ? 0xFFu : 0u;
    xK1 = (transparent & 2u) ? 0xFFu : 0u;
    xK2 = (transparent & 4u) ? 0xFFu : 0u;
    xK3 = (transparent & 8u) ? 0xFFu : 0u;

    for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
        p0 = scratch[row * 4 + 0];
        p1 = scratch[row * 4 + 1];
        p2 = scratch[row * 4 + 2];
        p3 = scratch[row * 4 + 3];
        mask = (uint8_t)((p0 ^ xK0) | (p1 ^ xK1) | (p2 ^ xK2) | (p3 ^ xK3));
        if (mask != 0u) {
            notMask = (uint8_t)~mask;
            dstByte[0] = (uint8_t)((dstByte[0] & notMask) | (p0 & mask));
            dstByte[2] = (uint8_t)((dstByte[2] & notMask) | (p1 & mask));
            dstByte[4] = (uint8_t)((dstByte[4] & notMask) | (p2 & mask));
            dstByte[6] = (uint8_t)((dstByte[6] & notMask) | (p3 & mask));
        }
        dstByte += ST_BYTES_PER_ROW;
    }
}

#endif
