// Palette accessors.
//
// Library contract: color 0 of every palette is forced to black ($000).
// jlPaletteSet silently masks that entry regardless of what the caller
// provides.

#include <stddef.h>
#include <string.h>

#include "joey/palette.h"
#include "surfaceInternal.h"

// jlPaletteSet / jlPaletteGet reach palette row N via byte-pointer math
// + a single `<< 5` shift (= 32 bytes per row) to avoid a per-access
// multiply helper. That stride is only correct while a palette row is
// exactly SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t) bytes; pin the
// relationship so a future palette-width change fails to compile here
// rather than silently reading the wrong row.
typedef char paletteRowStrideMatchesShift[((1u << 5) == (SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t))) ? 1 : -1];


// Standard 16-color EGA palette in IIgs $0RGB format. Used as the
// per-surface default at allocation time (paletteInitDefault) so a
// program that draws without first calling jlPaletteSet still gets
// recognizable colors instead of an all-black palette. EGA index 6
// is the canonical "brown" hack ($0A50, half-green) so CGA monitors
// rendered the third primary as brown rather than dark yellow.
static const uint16_t kDefaultPaletteEga[SURFACE_COLORS_PER_PALETTE] = {
    0x0000,  //  0: Black
    0x000A,  //  1: Blue
    0x00A0,  //  2: Green
    0x00AA,  //  3: Cyan
    0x0A00,  //  4: Red
    0x0A0A,  //  5: Magenta
    0x0A50,  //  6: Brown
    0x0AAA,  //  7: Light Gray
    0x0555,  //  8: Dark Gray
    0x055F,  //  9: Light Blue
    0x05F5,  // 10: Light Green
    0x05FF,  // 11: Light Cyan
    0x0F55,  // 12: Light Red
    0x0F5F,  // 13: Light Magenta
    0x0FF5,  // 14: Yellow
    0x0FFF   // 15: White
};


// ----- Internal API -----

void paletteInitDefault(jlSurfaceT *s) {
    uint8_t i;

    if (s == NULL) {
        return;
    }
    // kDefaultPaletteEga is already valid $0RGB with color 0 == 0x0000,
    // so copy it directly into each palette row instead of routing
    // through jlPaletteSet 16 times (which would re-mask every entry
    // and re-run the stage-dirty check on a surface that is not yet
    // the stage). gStagePaletteDirty already defaults to true so the
    // default palette is still uploaded on first present.
    for (i = 0; i < SURFACE_PALETTE_COUNT; i++) {
        memcpy(s->palette[i], kDefaultPaletteEga, sizeof(kDefaultPaletteEga));
    }
}


// ----- Public API (alphabetical) -----

void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16) {
    const uint16_t *row;

    if (s == NULL || out16 == NULL) {
        return;
    }
    if (paletteIndex >= SURFACE_PALETTE_COUNT) {
        return;
    }
    /* Byte-pointer math + shift to skip the multiply helper -- see
     * jlPaletteSet for the reasoning. */
    row = (const uint16_t *)((const uint8_t *)s->palette + ((uint16_t)paletteIndex << 5));
    memcpy(out16, row, SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t));
}


void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16) {
    uint16_t *row;

    if (s == NULL || colors16 == NULL) {
        return;
    }
    if (paletteIndex >= SURFACE_PALETTE_COUNT) {
        return;
    }

    // Compute the row pointer via byte-pointer math + a single shift
    // (16 entries * 2 bytes = 32 = 1 << 5) so the 2D-array indexing
    // avoids a multiply helper. The caller contract (joey/palette.h)
    // guarantees $0RGB input, so copy the row wholesale and force
    // color 0 to black -- the only documented silent rewrite. The copy
    // is an unrolled sequence of word assignments, NOT memcpy: the
    // clang IIgs libc memcpy is a ~30 cyc/byte byte loop (finding #79)
    // and measurably REGRESSED this op when tried (1443 -> 603
    // ops/sec); 15 direct word stores beat both that and the old
    // masked loop on every port.
    row     = (uint16_t *)((uint8_t *)s->palette + ((uint16_t)paletteIndex << 5));
    row[0]  = 0x0000;
    row[1]  = colors16[1];
    row[2]  = colors16[2];
    row[3]  = colors16[3];
    row[4]  = colors16[4];
    row[5]  = colors16[5];
    row[6]  = colors16[6];
    row[7]  = colors16[7];
    row[8]  = colors16[8];
    row[9]  = colors16[9];
    row[10] = colors16[10];
    row[11] = colors16[11];
    row[12] = colors16[12];
    row[13] = colors16[13];
    row[14] = colors16[14];
    row[15] = colors16[15];
    if (s == gStage) {
        gStagePaletteDirty = true;
    }
}
