// Generic portable-C tile op defaults (chunky 4bpp). The compile-time jlp<Op>
// dispatch in src/core/port.h routes to these when a machine declares no
// JL_HAS_<OP> for the tile op; machine overrides (IIgs asm macros, Amiga/ST
// planar functions) live in the machine folders.

#include <string.h>

#include "joey/tile.h"
#include "surfaceInternal.h"
#include "port.h"


// Derive the chunky row-0 pointer for tile block (bx,by) within a surface.
#define GENERIC_TILE_ROW0(_pix, _bx, _by) \
    (&(_pix)[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)])

// Copy TILE_PIXELS_PER_SIDE rows of TILE_BYTES_PER_ROW bytes from _s to _d,
// advancing each pointer by its own stride -- the one row-copy inner loop
// shared by TileCopy / TilePaste / TileSnap (PERF-AUDIT #50). memcpy on
// purpose: the previous byte-at-a-time form let gcc assume d/s alias, which
// blocked store merging; memcpy asserts non-overlap and compiles to one
// 32-bit move per row on DOS (DJGPP). Only DOS/BLANK run these generics --
// IIgs uses asm macros and Amiga/ST override every tile op -- so the IIgs
// byte-loop-libc memcpy caveat (PERF-AUDIT #79) does not apply here.
#define GENERIC_TILE_COPY_ROWS(_d, _dStride, _s, _sStride) do { \
    uint8_t copyRow_; \
    for (copyRow_ = 0; copyRow_ < TILE_PIXELS_PER_SIDE; copyRow_++) { \
        memcpy((_d), (_s), TILE_BYTES_PER_ROW); \
        (_d) += (_dStride); \
        (_s) += (_sStride); \
    } \
} while (0)


void jlpGenericTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
    uint8_t       *d;
    const uint8_t *s;

    if (dst->pixels == NULL || src->pixels == NULL) {
        return;
    }
    d = GENERIC_TILE_ROW0(dst->pixels, dstBx, dstBy);
    s = GENERIC_TILE_ROW0(src->pixels, srcBx, srcBy);
    GENERIC_TILE_COPY_ROWS(d, SURFACE_BYTES_PER_ROW, s, SURFACE_BYTES_PER_ROW);
}


void jlpGenericTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
    uint8_t       *d;
    const uint8_t *s;
    uint8_t        row;
    uint8_t        col;
    uint8_t        srcByte;
    uint8_t        transHi = (uint8_t)((transparent & 0x0F) << 4);
    uint8_t        transLo = (uint8_t)(transparent & 0x0F);
    uint8_t        srcHi;
    uint8_t        srcLo;

    if (dst->pixels == NULL || src->pixels == NULL) {
        return;
    }
    d = GENERIC_TILE_ROW0(dst->pixels, dstBx, dstBy);
    s = GENERIC_TILE_ROW0(src->pixels, srcBx, srcBy);
    for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
        for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
            srcByte = s[col];
            srcHi   = (uint8_t)(srcByte & 0xF0);
            srcLo   = (uint8_t)(srcByte & 0x0F);
            if (srcHi != transHi) {
                if (srcLo != transLo) {
                    // Both nibbles opaque (the common case in solid glyph
                    // strokes / tileset art): the merge result is exactly
                    // srcByte, so skip the destination read (PERF-AUDIT #51).
                    d[col] = srcByte;
                } else {
                    d[col] = (uint8_t)((d[col] & 0x0F) | srcHi);
                }
            } else if (srcLo != transLo) {
                d[col] = (uint8_t)((d[col] & 0xF0) | srcLo);
            }
        }
        d += SURFACE_BYTES_PER_ROW;
        s += SURFACE_BYTES_PER_ROW;
    }
}


// Colorize a 1bpp-ish mono shape (non-zero nibble -> fg, zero -> bg) into a
// chunky tile, then paste via the jlpTilePaste op (IIgs asm / DOS generic). The
// caller (jlTilePasteMono) marks the rect dirty, so this does not.
// PRECONDITION: fgColor/bgColor arrive already masked to 4 bits -- the public
// jlTilePasteMono wrapper owns the & 0x0F (PERF-AUDIT #69; contract documented
// at the jlpTilePasteMono dispatch stanza in port.h). No re-mask here.
void jlpGenericTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
    jlTileT colored;
    uint8_t i;
    uint8_t srcByte;
    uint8_t hi;
    uint8_t lo;

    for (i = 0u; i < TILE_BYTES; i++) {
        srcByte = monoTile[i];
        hi = (uint8_t)((srcByte >> 4) ? fgColor : bgColor);
        lo = (uint8_t)((srcByte & 0x0Fu) ? fgColor : bgColor);
        colored.pixels[i] = (uint8_t)((hi << 4) | lo);
    }
    jlpTilePaste(dst, bx, by, &colored.pixels[0]);
}


// Paste a chunky 8x8 tile (32 bytes, 4-bytes/row) into surface block (bx,by).
void jlpGenericTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
    uint8_t       *d;
    const uint8_t *s = chunkyTile;

    if (dst->pixels == NULL) {
        return;
    }
    d = GENERIC_TILE_ROW0(dst->pixels, bx, by);
    GENERIC_TILE_COPY_ROWS(d, SURFACE_BYTES_PER_ROW, s, TILE_BYTES_PER_ROW);
}


// Read surface block (bx,by) into a chunky 8x8 tile buffer (32 bytes).
void jlpGenericTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
    const uint8_t *s;
    uint8_t       *d = chunkyOut;

    if (src->pixels == NULL) {
        return;
    }
    s = GENERIC_TILE_ROW0(src->pixels, bx, by);
    GENERIC_TILE_COPY_ROWS(d, TILE_BYTES_PER_ROW, s, SURFACE_BYTES_PER_ROW);
}


void jlpGenericTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
    uint8_t  doubled = (uint8_t)(((colorIndex & 0x0F) << 4) | (colorIndex & 0x0F));
    uint8_t *row;
    uint8_t  i;

    if (s->pixels == NULL) {
        return;
    }
    row = GENERIC_TILE_ROW0(s->pixels, bx, by);
    for (i = 0; i < TILE_PIXELS_PER_SIDE; i++) {
        row[0] = doubled;
        row[1] = doubled;
        row[2] = doubled;
        row[3] = doubled;
        row += SURFACE_BYTES_PER_ROW;
    }
}
