// Generic portable-C surface-service defaults -- the joey.c-equivalent reference
// for Category B structural ops (see genericDraw.c for the jlp<Op> dispatch
// model). A machine that does not override an op (no JL_HAS_<OP> in
// include/joey/platform.h) links these; the dispatch stanzas live in
// src/core/port.h. Always compiled into every target via the src/generic/*.c
// wildcard.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "surfaceInternal.h"   // full jlSurfaceT (s->pixels) + SURFACE_PIXELS_SIZE
#include "port.h"


// jlSurfaceCopy's chunky payload copy. Guarded so it is a no-op on planar
// ports (pixels == NULL, plane copy is jlpSurfaceCopyPlanes' job); chunky ports
// memcpy the whole buffer.
void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
    if (dst->pixels != NULL && src->pixels != NULL) {
        memcpy(dst->pixels, src->pixels, SURFACE_PIXELS_SIZE);
    }
}


// jlSurfaceCopyRect's chunky payload: same-position row segments. The
// core wrapper already clipped and group-snapped the rect, so x and w
// are multiples of 16 and the rect is on-surface. Guarded to a no-op
// on planar ports (pixels == NULL), whose JL_HAS_SURFACE_COPY_RECT
// override owns the plane work. The row offset is seeded once and
// walked by stride: SURFACE_ROW_OFFSET is a LUT read on the IIgs and
// a multiply elsewhere, and neither belongs in a row loop.
void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
    uint8_t       *d;
    const uint8_t *s;
    uint16_t       rowBytes;
    uint16_t       row;

    if (dst->pixels == NULL || src->pixels == NULL) {
        return;
    }
    d        = dst->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
    s        = src->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
    rowBytes = (uint16_t)(w >> 1);
    // NOTE (2026-07-12): a short-span inline word copy was tried here to
    // dodge the per-row memcpy CALL, but it REGRESSED DOS -12%
    // (gameFrameClean 195->172) -- x86's rep-movs memcpy beats an inline
    // word loop even for 8-byte rows. The IIgs (slow llvm-mos far-memcpy)
    // DOES want the inline copy, so it lives in an IIgs-specific
    // jlpSurfaceCopyRect override instead of here. Keep DOS on memcpy.
    for (row = 0; row < h; row++) {
        memcpy(d, s, rowBytes);
        d += SURFACE_BYTES_PER_ROW;
        s += SURFACE_BYTES_PER_ROW;
    }
}


// Release a per-surface chunky pixel buffer. Must mirror
// jlpGenericSurfaceAllocPixels' allocator exactly: handing a
// jlpBigAlloc pointer to free() corrupts the IIgs heap
// (BIG_ALLOC_HDR offset, finding #31). NULL is a no-op, so this
// stays safe on planar ports whose surfaces carry no chunky shadow.
void jlpGenericSurfaceFreePixels(uint8_t *pixels) {
    if (pixels != NULL) {
        jlpBigFree(pixels);
    }
}


// jlSurfaceCopy's planar dual-write hook. No-op default: chunky ports carry no
// bitplanes (the chunky payload is jlpSurfaceCopyChunky's job) and elide the
// call outright via port.h's chunky ((void)0) macros (PERF-AUDIT #42), so this
// body links only for a new planar port that has not provided its own yet.
// Planar ports (Amiga, ST) override via JL_HAS_SURFACE_COPY_PLANES.
void jlpGenericSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
    (void)dst;
    (void)src;
}


// No port currently surfaces a human-readable last-error string.
const char *jlpGenericLastError(void) {
    return NULL;
}


// --- Chunky surface readers (generic default; planar ports override) ---
// Bodies are the IIgs/DOS chunky implementations verbatim; amiga/ST decode
// their portData planes in their own overrides (JL_HAS_*).


uint8_t jlpGenericSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
    // SURFACE_READ_NIBBLE routes the row offset through
    // SURFACE_ROW_OFFSET, dropping the y * 160 software-multiply helper
    // this body used to pay on IIgs (PERF-AUDIT #14/#24). Identical
    // pixel result on every port.
    return SURFACE_READ_NIBBLE(s, x, y);
}


uint32_t jlpGenericSurfaceHash(const jlSurfaceT *s) {
    uint16_t        lo = 0xACE1u, hi = 0x1357u, blocks, n, v;
    const uint8_t  *p;
    const uint16_t *w;
    uint8_t         b;
    p      = s->pixels;
    blocks = (uint16_t)(SURFACE_PIXELS_SIZE / 8);
    do {
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        blocks--;
    } while (blocks > 0u);
    p = s->scb;
    for (n = 0; n < (uint16_t)SURFACE_HEIGHT; n++) {
        b = *p++;  SURFACE_HASH_MIX_BYTE(lo, hi, b);
    }
    w = &s->palette[0][0];
    for (n = 0; n < (uint16_t)SURFACE_PALETTE_ENTRIES; n++) {
        v  = *w++;
        b  = (uint8_t)((v >> 8) & 0xFFu);  SURFACE_HASH_MIX_BYTE(lo, hi, b);
        b  = (uint8_t)(v & 0xFFu);         SURFACE_HASH_MIX_BYTE(lo, hi, b);
    }
    return ((uint32_t)hi << 16) | (uint32_t)lo;
}


bool jlpGenericSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
    return fread(dst->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
}


bool jlpGenericSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
    return fwrite(src->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
}


// --- Allocation split by storage model (chunky generic defaults) ---
// Planar ports (Amiga/ST) override the per-surface allocators to manage their
// plane structs; IIgs overrides the stage allocators (pinned display memory).

// Stage back-buffer pixels: chunky default is a heap alloc.
uint8_t *jlpGenericStageAllocPixels(void) {
    return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
}


void jlpGenericStageFreePixels(uint8_t *pixels) {
    free(pixels);
}


// Per-surface chunky pixels, zero-filled; planar ports override to
// return NULL (storage is the portData planes). jlpBigAlloc, NOT
// calloc: the IIgs C heap is a few hundred bytes of bank-0 leftover
// and its calloc RETURNS 1 (not NULL) at exhaustion (LLVM816-ASKS
// item 4), so the old calloc here zero-filled 32000 bytes through
// pointer 1 and shredded bank 0 on the first OFFSCREEN
// jlSurfaceCreate -- the stage never hit it (pinned $01:2000 path),
// which is why UBER could pass while spacetaxi died in
// loadFontSheet. Other ports map jlpBigAlloc to malloc, so behavior
// is unchanged there (finding #31 pattern).
uint8_t *jlpGenericSurfaceAllocPixels(void) {
    uint8_t *pixels;

    pixels = (uint8_t *)jlpBigAlloc((uint32_t)SURFACE_PIXELS_SIZE);
    if (pixels == NULL) {
        return NULL;
    }
    memset(pixels, 0, SURFACE_PIXELS_SIZE);
    return pixels;
}


// Per-surface portData: NULL on chunky ports (unused); planar ports override.
void *jlpGenericSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
    (void)s;
    (void)isStage;
    return NULL;
}


void jlpGenericSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
    (void)s;
    (void)isStage;
    (void)portData;
}


