// Sharp X68000 per-surface planar storage.
//
// Step 1 of the storage-model conversion (see docs/x68000_port.md). Nothing
// includes this yet -- the port still ships the chunky-plus-expand path, which
// is hash-verified against the Apple IIgs golden reference. This header lands
// first so the conversion can proceed in verifiable increments instead of one
// unlandable change.
//
// WHY THIS IS ALMOST amigaPlanar.h
// -------------------------------
// A 320px-wide 4bpp surface is 40 bytes per row per plane on ANY planar
// machine, so the SURFACE layout here is byte-for-byte the Amiga's. That is
// not a coincidence worth exploiting cheaply -- it is why the 2,320 lines of
// planar primitives in src/amiga/hal.c can be adapted rather than rewritten:
// they read the stride out of this struct and never hardcode it
// (grep -c AMIGA_PLANE_STRIDE src/amiga/hal.c == 0).
//
// WHERE THE X68000 DIFFERS
// ------------------------
// The DISPLAY is not the surface. The text plane is four 1bpp planes of a
// 1024px-wide raster, 128 bytes per row, based 128 KB apart:
//
//     plane 0  $E00000    plane 1  $E20000
//     plane 2  $E40000    plane 3  $E60000
//     pixel(x,y) -> plane + y*128 + x/8, bit 7-(x&7), plane 0 = LSB
//
// so jlpPresent scatters the compact 40-byte rows into that 128-byte stride.
// That is the ONLY place the 128 appears, and it is where the win comes from:
// 4 planes x 200 rows x 40 bytes = 32,000 bytes per frame, against 128,000
// bytes of GVRAM word writes on the chunky path. Bus traffic is what the
// measured wait states punish (TVRAM 1.92 cycles/access, GVRAM 1.03 -- see
// docs/x68000_mame_crtc_timing.md), so a 4x traffic cut beats TVRAM's higher
// per-access cost by roughly 2x overall.
//
// Unlike the Amiga there is no Chip RAM constraint: planes are plain malloc,
// because nothing but the CPU ever reads them.
#ifndef JOEY_X68K_PLANAR_H
#define JOEY_X68K_PLANAR_H

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

#include "surfaceInternal.h"

#define X68K_BITPLANES        4
#define X68K_BYTES_PER_ROW    (SURFACE_WIDTH / 8)          // 40
#define X68K_PLANE_SIZE       (X68K_BYTES_PER_ROW * SURFACE_HEIGHT)

// Text-plane display geometry. Only jlpPresent uses these.
#define X68K_TVRAM_PLANE0     0xE00000UL
#define X68K_TVRAM_PLANE_STEP 0x020000UL                   // 128 KB apart
#define X68K_TVRAM_STRIDE     128u                         // 1024 px / 8

// The library surface is 320x200 and the machine has no such mode, so the
// closest is 512x512 (_iocs_crtmod 12) and the window is CENTRED in it rather
// than parked in the corner. X must land on a byte boundary in a 1bpp plane;
// 96/8 = 12 exactly, so no sub-byte shifting is needed.
#define X68K_DISPLAY_W        512u
#define X68K_DISPLAY_H        512u
#define X68K_ORIGIN_X         ((X68K_DISPLAY_W - SURFACE_WIDTH) / 2u)   // 96 px
#define X68K_ORIGIN_Y         ((X68K_DISPLAY_H - SURFACE_HEIGHT) / 2u)  // 156 rows
#define X68K_ORIGIN_BYTE      (X68K_ORIGIN_X / 8u)                      // 12 bytes
typedef char x68kOriginXIsByteAligned[((X68K_ORIGIN_X % 8u) == 0u) ? 1 : -1];

// Text/PCG palette: 16 entries the text plane indexes. NOT $E82000, which is
// the 256-entry GRAPHIC palette -- the text plane reads the PCG block.
#define X68K_TEXT_PALETTE     ((volatile uint16_t *)0xE82200L)

// Colour word is GGGGG RRRRR BBBBB I -- green and red SWAPPED versus the usual
// RGB555. Writing JoeyLib's $0RGB straight through puts red where green
// belongs, which reads as "greens turn blue, reds turn grey". Verified against
// MAME's own decoder (x68k_v.cpp GGGGGRRRRRBBBBBI):
//     i = raw & 1;  r = (raw >> 5) & 0x3e | i;
//     g = (raw >> 10) & 0x3e | i;  b = (raw >> 0) & 0x3e | i;
// so each channel is 6-bit: 5 bits from its field plus the SHARED intensity
// bit. 4-bit input expands to 5 as (v << 1) | (v >> 3) so 0 -> 0 and 15 -> 31.
static inline uint16_t x68kColorFromRgb12(uint16_t rgb12) {
    uint16_t r4 = (uint16_t)((rgb12 >> 8) & 0x0Fu);
    uint16_t g4 = (uint16_t)((rgb12 >> 4) & 0x0Fu);
    uint16_t b4 = (uint16_t)(rgb12 & 0x0Fu);
    uint16_t r5 = (uint16_t)((r4 << 1) | (r4 >> 3));
    uint16_t g5 = (uint16_t)((g4 << 1) | (g4 >> 3));
    uint16_t b5 = (uint16_t)((b4 << 1) | (b4 >> 3));
    // Intensity is shared, so it cannot be per-channel: set it for any
    // non-black colour, which keeps $000 pure black and lets $FFF reach full
    // white (31<<1 | 1 == 63) instead of topping out one step short.
    uint16_t i  = (uint16_t)(((r4 | g4 | b4) != 0u) ? 1u : 0u);
    return (uint16_t)((g5 << 11) | (r5 << 6) | (b5 << 1) | i);
}

// jlSurfaceT.portData points to this. Mirrors AmigaPlanarT field-for-field so
// the adapted primitives need no structural changes; direct fields rather than
// a union because the consumers are asm / inline C wanting minimal indirection
// in the inner loop.
typedef struct {
    uint8_t *planes[X68K_BITPLANES];
    uint16_t bytesPerRow;       // = X68K_BYTES_PER_ROW (40)
    uint16_t bytesPerPlane;     // = X68K_PLANE_SIZE (8000)
    bool     ownsPlanes;        // true = malloc'd (free at destroy)
} X68kPlanarT;


// NULL means "surface has no planar storage" -- callers use that as their
// route-to-generic gate, the same contract the Amiga side uses.
static inline X68kPlanarT *x68kSurfacePlanar(const jlSurfaceT *s) {
    return (X68kPlanarT *)s->portData;
}


// Byte offset of pixel (x,y) within one DISPLAY plane, and the bit within it.
// Split out so the present loop and any future asm agree on one definition.
static inline uint32_t x68kTvramOffset(uint16_t x, uint16_t y) {
    return ((uint32_t)y * X68K_TVRAM_STRIDE) + (uint32_t)(x >> 3);
}


static inline uint8_t x68kTvramBit(uint16_t x) {
    return (uint8_t)(0x80u >> (x & 7u));
}

#endif
