// Amiga tile ops as always-inline functions (NATIVE-PERF Phase 2
// R4a). Bodies moved verbatim from src/amiga/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 Amiga.
#ifndef JOEY_AMIGA_TILE_H
#define JOEY_AMIGA_TILE_H

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

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

// 8-row unrolled tile access (NATIVE-PERF Phase 2 R8): row offsets
// are compile-time constants row * 40, so each row is one d16(An)
// access with no pointer stepping and no loop control. gcc-amigaos
// -O2 keeps the equivalent row loops rolled as dbf (objdump-verified,
// ~18 cycles/row of pure loop overhead). Only the row dimension
// unrolls -- the 4-plane loop keeps real per-plane setup and stays
// register-light per the m68k-gcc-unroll-backfire lesson.
#define AMIGA_TILE_ROW_OFF(_r) ((uint16_t)((_r) * AMIGA_BYTES_PER_ROW))

// Phase 5 planar dual-write for tile ops, fully planar after Phase 9
// dropped the chunky shadow. All tiles are 8-pixel aligned (8x8 blocks
// at multiples of 8), so plane writes are byte-aligned -- one plane
// byte per row, 8 rows per tile, no edge masks. Stride between rows
// in a plane is AMIGA_BYTES_PER_ROW (40).

static inline __attribute__((always_inline)) void amigaTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
    AmigaPlanarT *pd;
    uint16_t      plane;
    uint8_t       fillByte;
    uint8_t      *p;

    pd = (AmigaPlanarT *)s->portData;
    if (pd == NULL) {
        jlpGenericTileFill(s, bx, by, colorIndex);
        return;
    }
    for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
        fillByte = ((colorIndex >> plane) & 1u) ? 0xFFu : 0x00u;
        p = pd->planes[plane] + (uint16_t)by * 8u * AMIGA_BYTES_PER_ROW + bx;
        p[AMIGA_TILE_ROW_OFF(0)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(1)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(2)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(3)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(4)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(5)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(6)] = fillByte;
        p[AMIGA_TILE_ROW_OFF(7)] = fillByte;
    }
}


static inline __attribute__((always_inline)) void amigaTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
    AmigaPlanarT *dstPd;
    AmigaPlanarT *srcPd;
    uint16_t      plane;
    uint8_t      *dp;
    const uint8_t *sp;

    dstPd = (AmigaPlanarT *)dst->portData;
    srcPd = (AmigaPlanarT *)src->portData;
    if (dstPd == NULL || srcPd == NULL) {
        jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
        return;
    }
    for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
        dp = dstPd->planes[plane] + (uint16_t)dstBy * 8u * AMIGA_BYTES_PER_ROW + dstBx;
        sp = srcPd->planes[plane] + (uint16_t)srcBy * 8u * AMIGA_BYTES_PER_ROW + srcBx;
        dp[AMIGA_TILE_ROW_OFF(0)] = sp[AMIGA_TILE_ROW_OFF(0)];
        dp[AMIGA_TILE_ROW_OFF(1)] = sp[AMIGA_TILE_ROW_OFF(1)];
        dp[AMIGA_TILE_ROW_OFF(2)] = sp[AMIGA_TILE_ROW_OFF(2)];
        dp[AMIGA_TILE_ROW_OFF(3)] = sp[AMIGA_TILE_ROW_OFF(3)];
        dp[AMIGA_TILE_ROW_OFF(4)] = sp[AMIGA_TILE_ROW_OFF(4)];
        dp[AMIGA_TILE_ROW_OFF(5)] = sp[AMIGA_TILE_ROW_OFF(5)];
        dp[AMIGA_TILE_ROW_OFF(6)] = sp[AMIGA_TILE_ROW_OFF(6)];
        dp[AMIGA_TILE_ROW_OFF(7)] = sp[AMIGA_TILE_ROW_OFF(7)];
    }
}


// Pure-planar masked copy. For each row of the 8x8 tile, read 4 src
// plane bytes; compute a per-pixel "non-transparent" mask via XOR
// against the transparent index's per-plane bit pattern (a pixel
// matches transparent IFF all 4 plane bits match transparent's 4
// bits = OR of 4 XOR'd bytes is 0 in that bit). Then for each plane,
// dst = (dst & ~mask) | (src & mask) -- copy src bits at mask-set
// positions, preserve dst bits elsewhere.
static inline __attribute__((always_inline)) void amigaTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
    AmigaPlanarT *dstPd;
    AmigaPlanarT *srcPd;
    uint8_t       transBitByte[AMIGA_BITPLANES];
    uint16_t      i;
    uint8_t       row;
    uint16_t      srcByteOff;
    uint16_t      dstByteOff;
    uint8_t       srcPlaneBytes[AMIGA_BITPLANES];
    uint8_t       maskByte;

    dstPd = (AmigaPlanarT *)dst->portData;
    srcPd = (AmigaPlanarT *)src->portData;
    if (dstPd == NULL || srcPd == NULL) {
        jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
        return;
    }
    transparent = (uint8_t)(transparent & 0x0Fu);
    /* Per-plane "all bits set if transparent's bit at this plane is 1
     * else all 0" -- so XOR gives bit set where pixel differs from
     * transparent in that plane. */
    for (i = 0; i < AMIGA_BITPLANES; i++) {
        transBitByte[i] = ((transparent >> i) & 1u) ? 0xFFu : 0x00u;
    }

    for (row = 0; row < 8u; row++) {
        srcByteOff = (uint16_t)((uint16_t)srcBy * 8u + row) * AMIGA_BYTES_PER_ROW + srcBx;
        dstByteOff = (uint16_t)((uint16_t)dstBy * 8u + row) * AMIGA_BYTES_PER_ROW + dstBx;
        srcPlaneBytes[0] = srcPd->planes[0][srcByteOff];
        srcPlaneBytes[1] = srcPd->planes[1][srcByteOff];
        srcPlaneBytes[2] = srcPd->planes[2][srcByteOff];
        srcPlaneBytes[3] = srcPd->planes[3][srcByteOff];
        /* maskByte: bit set where pixel differs from transparent in
         * ANY plane -- i.e., where the pixel is non-transparent. */
        maskByte = (uint8_t)((srcPlaneBytes[0] ^ transBitByte[0])
                           | (srcPlaneBytes[1] ^ transBitByte[1])
                           | (srcPlaneBytes[2] ^ transBitByte[2])
                           | (srcPlaneBytes[3] ^ transBitByte[3]));
        for (i = 0; i < AMIGA_BITPLANES; i++) {
            uint8_t existing = dstPd->planes[i][dstByteOff];
            dstPd->planes[i][dstByteOff] = (uint8_t)((existing & (uint8_t)~maskByte)
                                                  | (srcPlaneBytes[i] & maskByte));
        }
    }
}


// Phase 9 plane-to-chunky derivation for jlTileSnap. Reads 8 plane
// bytes (1 byte per row x 8 rows) from each of 4 planes for the
// 8-pixel-aligned tile column at bx, then assembles 32 chunky bytes
// (4 per row x 8 rows, packed 2 px/byte high-then-low nibble) into
// On Amiga, jlTileT.pixels is opaque port-specific storage (cross-
// platform tile.c never reads it directly when s->pixels is NULL).
// We use those 32 bytes as 4 planes x 8 rows, plane-major:
//   bytes [0..7]   = plane 0, rows 0..7
//   bytes [8..15]  = plane 1, rows 0..7
//   bytes [16..23] = plane 2, rows 0..7
//   bytes [24..31] = plane 3, rows 0..7
// snap/paste then become 32 plain byte loads + stores -- no chunky
// <-> planar conversion at all. The previous c2p-based path paid
// 4 KB LUT lookups + bit shuffling per pixel; this is ~50x cheaper.
#define AMIGA_TILE_PLANE_STRIDE  8
static inline __attribute__((always_inline)) void amigaTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
    AmigaPlanarT *pd;
    uint16_t      rowBase;
    uint8_t       plane;

    pd = (AmigaPlanarT *)src->portData;
    if (pd == NULL) {
        jlpGenericTileSnap(src, bx, by, chunkyOut);
        return;
    }
    rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
    for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
        const uint8_t *p = pd->planes[plane] + rowBase;
        uint8_t       *q = chunkyOut + plane * AMIGA_TILE_PLANE_STRIDE;
        q[0] = p[AMIGA_TILE_ROW_OFF(0)];
        q[1] = p[AMIGA_TILE_ROW_OFF(1)];
        q[2] = p[AMIGA_TILE_ROW_OFF(2)];
        q[3] = p[AMIGA_TILE_ROW_OFF(3)];
        q[4] = p[AMIGA_TILE_ROW_OFF(4)];
        q[5] = p[AMIGA_TILE_ROW_OFF(5)];
        q[6] = p[AMIGA_TILE_ROW_OFF(6)];
        q[7] = p[AMIGA_TILE_ROW_OFF(7)];
    }
}


static inline __attribute__((always_inline)) void amigaTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
    AmigaPlanarT *pd;
    uint8_t       plane;
    uint16_t      rowBase;

    pd = (AmigaPlanarT *)dst->portData;
    if (pd == NULL) {
        jlpGenericTilePaste(dst, bx, by, chunkyTile);
        return;
    }
    /* jlTileT.pixels[] holds plane-major planar bytes (see
     * jlpTileSnap header for layout). Paste = 32 byte stores
     * with no chunky -> planar conversion. */
    rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
    for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
        const uint8_t *q = chunkyTile + plane * AMIGA_TILE_PLANE_STRIDE;
        uint8_t       *p = pd->planes[plane] + rowBase;
        p[AMIGA_TILE_ROW_OFF(0)] = q[0];
        p[AMIGA_TILE_ROW_OFF(1)] = q[1];
        p[AMIGA_TILE_ROW_OFF(2)] = q[2];
        p[AMIGA_TILE_ROW_OFF(3)] = q[3];
        p[AMIGA_TILE_ROW_OFF(4)] = q[4];
        p[AMIGA_TILE_ROW_OFF(5)] = q[5];
        p[AMIGA_TILE_ROW_OFF(6)] = q[6];
        p[AMIGA_TILE_ROW_OFF(7)] = q[7];
    }
}


// Whole-map paste engine (NATIVE-PERF W3 item 9). jlTileMapPaste's
// shared whole-map arm calls this ONCE per map: the portData load +
// NULL test, the by*320 row-base derive, and the four plane base
// pointers are hoisted out of the per-cell loop -- the per-cell
// amigaTilePaste route reloads portData, rebuilds the cell offset,
// and re-reads pd->planes[0..3] from the struct every cell (~172 of
// its ~776 cyc/cell, objdump-measured). The four dst pointers stay
// resident in a0-a3 (one byte per column, one row stride per tile
// row), so each cell is 32 move.b off compile-time displacements
// with zero per-plane setup. Plane order, row order, and written
// values are identical to per-cell amigaTilePaste -- pixel output is
// byte-identical. movep is unusable here (plane bytes sit 8000 bytes
// apart across separate AllocMem planes, vs the ST's interleaved +2
// layout), so C is the right weapon on this port.
#define AMIGA_TILE_MAP_ROW_STRIDE ((uint16_t)(TILE_PIXELS_PER_SIDE * AMIGA_BYTES_PER_ROW))

#define AMIGA_MAP_CELL_PLANE(_dp, _plane) \
    do { \
        (_dp)[AMIGA_TILE_ROW_OFF(0)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 0]; \
        (_dp)[AMIGA_TILE_ROW_OFF(1)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 1]; \
        (_dp)[AMIGA_TILE_ROW_OFF(2)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 2]; \
        (_dp)[AMIGA_TILE_ROW_OFF(3)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 3]; \
        (_dp)[AMIGA_TILE_ROW_OFF(4)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 4]; \
        (_dp)[AMIGA_TILE_ROW_OFF(5)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 5]; \
        (_dp)[AMIGA_TILE_ROW_OFF(6)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 6]; \
        (_dp)[AMIGA_TILE_ROW_OFF(7)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 7]; \
    } while (0)

static inline __attribute__((always_inline)) void amigaTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
    AmigaPlanarT  *pd;
    const uint8_t *tileBytes;
    const uint8_t *src;
    uint8_t       *p0;
    uint8_t       *p1;
    uint8_t       *p2;
    uint8_t       *p3;
    uint16_t       rowBase;
    uint16_t       rewind;
    uint8_t        idx;
    uint8_t        ty;
    uint8_t        tx;

    tileBytes = (const uint8_t *)tiles;
    pd        = (AmigaPlanarT *)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 = (uint16_t)((uint16_t)by * AMIGA_TILE_MAP_ROW_STRIDE + bx);
    rewind  = (uint16_t)(AMIGA_TILE_MAP_ROW_STRIDE - wTiles);
    p0 = pd->planes[0] + rowBase;
    p1 = pd->planes[1] + rowBase;
    p2 = pd->planes[2] + rowBase;
    p3 = pd->planes[3] + rowBase;
    for (ty = hTiles; ty != 0u; ty--) {
        for (tx = wTiles; tx != 0u; tx--) {
            idx = *map++;
            if (idx != TILE_MAP_SKIP) {
                src = tileBytes + ((uint16_t)idx << 5);
                AMIGA_MAP_CELL_PLANE(p0, 0);
                AMIGA_MAP_CELL_PLANE(p1, 1);
                AMIGA_MAP_CELL_PLANE(p2, 2);
                AMIGA_MAP_CELL_PLANE(p3, 3);
            }
            p0++;
            p1++;
            p2++;
            p3++;
        }
        p0 += rewind;
        p1 += rewind;
        p2 += rewind;
        p3 += rewind;
    }
}
#undef AMIGA_MAP_CELL_PLANE
#undef AMIGA_TILE_MAP_ROW_STRIDE


// 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)
// where maskXK = $FF if (X & (1 << k)) else $00.
static inline __attribute__((always_inline)) void amigaTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
    AmigaPlanarT *pd;
    uint8_t       shape[TILE_PIXELS_PER_SIDE];
    uint8_t       plane;
    uint8_t       row;
    uint8_t       col;
    uint16_t      rowBase;

    pd = (AmigaPlanarT *)dst->portData;
    if (pd == NULL) {
        jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
        return;
    }
    // Fold each chunky nibble-pair row into one 1bpp shape byte.
    for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
        uint8_t bits = 0u;
        for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
            uint8_t srcByte = *monoTile++;
            bits = (uint8_t)(bits << 2);
            if (srcByte & 0xF0u) {
                bits = (uint8_t)(bits | 0x02u);
            }
            if (srcByte & 0x0Fu) {
                bits = (uint8_t)(bits | 0x01u);
            }
        }
        shape[row] = bits;
    }
    rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
    for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
        uint8_t  maskFg = (uint8_t)((fgColor & (1u << plane)) ? 0xFFu : 0x00u);
        uint8_t  maskBg = (uint8_t)((bgColor & (1u << plane)) ? 0xFFu : 0x00u);
        uint8_t *p      = pd->planes[plane] + rowBase;
        for (row = 0; row < 8u; row++) {
            uint8_t s = shape[row];
            p[row * AMIGA_BYTES_PER_ROW] = (uint8_t)((uint8_t)(s & maskFg) | (uint8_t)((uint8_t)(~s) & maskBg));
        }
    }
}

#endif
