// AGI v2 text plane: status line, display() rows, print() modal.
//
// Renders directly to the stage with an embedded 8x8 ASCII bitmap
// font; bypasses jlDrawText so glyph foreground/background can vary
// per-call without rebuilding a font surface for every color combo.
//
// Layout matches AGI's 40x25 text grid:
//   Row 0       : status line (when statusLineOn)
//   Rows 1..21  : picture (handled by agiPicBlit, not here)
//   Rows 22..24 : message overlay area
//
// The print() modal centers a window over the picture region with
// a 1-character border and waits for user ack (HALT_PRINT_PENDING).

#include "agi.h"

#include "joey/draw.h"
#include "joey/surface.h"
#include "surfaceInternal.h"

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


#ifdef JOEYLIB_PLATFORM_IIGS
#endif


#define GLYPH_W             8u
#define GLYPH_H             8u
#define FIRST_CHAR          0x20u   // space
#define LAST_CHAR           0x7Eu   // tilde
#define GLYPH_COUNT         (LAST_CHAR - FIRST_CHAR + 1u)

#define STAGE_W             SURFACE_WIDTH
#define STAGE_H             SURFACE_HEIGHT


// ----- Prototypes -----

static void  drawChar(jlSurfaceT *stage, int16_t x, int16_t y, char ch, uint8_t fg, uint8_t bg);
static void  drawCharTransparent(jlSurfaceT *stage, int16_t x, int16_t y, char ch, uint8_t fg);
static void  drawString(jlSurfaceT *stage, int16_t x, int16_t y, const char *s, uint8_t fg, uint8_t bg);
static void  drawWindow(jlSurfaceT *stage, int16_t x, int16_t y, uint8_t cols, uint8_t rows, uint8_t bg, uint8_t border);
static void  fillRow(jlSurfaceT *stage, uint8_t row, uint8_t bg);


// 8x8 bitmap font for printable ASCII (0x20 = space .. 0x7E = tilde).
// Each glyph is 8 bytes; bit 7 is the leftmost pixel of each row.
// Lower-case letters map to upper-case via the ASCII normalizer in
// drawChar so the table only needs the upper half.
//
// Hand-pixeled to be readable rather than authentic. Each row is
// commented with the visual pattern (X = on, . = off).
static const uint8_t kFont[GLYPH_COUNT][GLYPH_H] = {
    /* 0x20 ' ' */ {0,0,0,0,0,0,0,0},
    /* 0x21 '!' */ {0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00},
    /* 0x22 '"' */ {0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00},
    /* 0x23 '#' */ {0x36,0x36,0x7F,0x36,0x7F,0x36,0x36,0x00},
    /* 0x24 '$' */ {0x18,0x3E,0x60,0x3C,0x06,0x7C,0x18,0x00},
    /* 0x25 '%' */ {0x66,0x66,0x0C,0x18,0x30,0x66,0x66,0x00},
    /* 0x26 '&' */ {0x38,0x6C,0x38,0x76,0xDC,0xCC,0x76,0x00},
    /* 0x27 '\''*/ {0x18,0x18,0x10,0x00,0x00,0x00,0x00,0x00},
    /* 0x28 '(' */ {0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00},
    /* 0x29 ')' */ {0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00},
    /* 0x2A '*' */ {0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00},
    /* 0x2B '+' */ {0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00},
    /* 0x2C ',' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30},
    /* 0x2D '-' */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00},
    /* 0x2E '.' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00},
    /* 0x2F '/' */ {0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00},
    /* 0x30 '0' */ {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00},
    /* 0x31 '1' */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
    /* 0x32 '2' */ {0x3C,0x66,0x06,0x1C,0x30,0x66,0x7E,0x00},
    /* 0x33 '3' */ {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00},
    /* 0x34 '4' */ {0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00},
    /* 0x35 '5' */ {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00},
    /* 0x36 '6' */ {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00},
    /* 0x37 '7' */ {0x7E,0x66,0x0C,0x18,0x18,0x18,0x18,0x00},
    /* 0x38 '8' */ {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00},
    /* 0x39 '9' */ {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00},
    /* 0x3A ':' */ {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00},
    /* 0x3B ';' */ {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30},
    /* 0x3C '<' */ {0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x00},
    /* 0x3D '=' */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00},
    /* 0x3E '>' */ {0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x00},
    /* 0x3F '?' */ {0x3C,0x66,0x06,0x0C,0x18,0x00,0x18,0x00},
    /* 0x40 '@' */ {0x3C,0x66,0x6E,0x6E,0x60,0x62,0x3C,0x00},
    /* 0x41 'A' */ {0x18,0x3C,0x66,0x66,0x7E,0x66,0x66,0x00},
    /* 0x42 'B' */ {0x7C,0x66,0x66,0x7C,0x66,0x66,0x7C,0x00},
    /* 0x43 'C' */ {0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00},
    /* 0x44 'D' */ {0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00},
    /* 0x45 'E' */ {0x7E,0x60,0x60,0x78,0x60,0x60,0x7E,0x00},
    /* 0x46 'F' */ {0x7E,0x60,0x60,0x78,0x60,0x60,0x60,0x00},
    /* 0x47 'G' */ {0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00},
    /* 0x48 'H' */ {0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00},
    /* 0x49 'I' */ {0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00},
    /* 0x4A 'J' */ {0x1E,0x0C,0x0C,0x0C,0x0C,0x6C,0x38,0x00},
    /* 0x4B 'K' */ {0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00},
    /* 0x4C 'L' */ {0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00},
    /* 0x4D 'M' */ {0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00},
    /* 0x4E 'N' */ {0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00},
    /* 0x4F 'O' */ {0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00},
    /* 0x50 'P' */ {0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00},
    /* 0x51 'Q' */ {0x3C,0x66,0x66,0x66,0x6A,0x6C,0x36,0x00},
    /* 0x52 'R' */ {0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00},
    /* 0x53 'S' */ {0x3C,0x66,0x60,0x3C,0x06,0x66,0x3C,0x00},
    /* 0x54 'T' */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00},
    /* 0x55 'U' */ {0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00},
    /* 0x56 'V' */ {0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00},
    /* 0x57 'W' */ {0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00},
    /* 0x58 'X' */ {0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00},
    /* 0x59 'Y' */ {0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00},
    /* 0x5A 'Z' */ {0x7E,0x06,0x0C,0x18,0x30,0x60,0x7E,0x00},
    /* 0x5B '[' */ {0x3C,0x30,0x30,0x30,0x30,0x30,0x3C,0x00},
    /* 0x5C '\\'*/ {0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00},
    /* 0x5D ']' */ {0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00},
    /* 0x5E '^' */ {0x18,0x3C,0x66,0x00,0x00,0x00,0x00,0x00},
    /* 0x5F '_' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF},
    /* 0x60 '`' */ {0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00},
    /* 0x61 'a' */ {0x00,0x00,0x3C,0x06,0x3E,0x66,0x3E,0x00},
    /* 0x62 'b' */ {0x60,0x60,0x7C,0x66,0x66,0x66,0x7C,0x00},
    /* 0x63 'c' */ {0x00,0x00,0x3C,0x66,0x60,0x66,0x3C,0x00},
    /* 0x64 'd' */ {0x06,0x06,0x3E,0x66,0x66,0x66,0x3E,0x00},
    /* 0x65 'e' */ {0x00,0x00,0x3C,0x66,0x7E,0x60,0x3C,0x00},
    /* 0x66 'f' */ {0x1C,0x36,0x30,0x78,0x30,0x30,0x30,0x00},
    /* 0x67 'g' */ {0x00,0x00,0x3E,0x66,0x66,0x3E,0x06,0x3C},
    /* 0x68 'h' */ {0x60,0x60,0x7C,0x66,0x66,0x66,0x66,0x00},
    /* 0x69 'i' */ {0x18,0x00,0x38,0x18,0x18,0x18,0x3C,0x00},
    /* 0x6A 'j' */ {0x06,0x00,0x06,0x06,0x06,0x06,0x66,0x3C},
    /* 0x6B 'k' */ {0x60,0x60,0x66,0x6C,0x78,0x6C,0x66,0x00},
    /* 0x6C 'l' */ {0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00},
    /* 0x6D 'm' */ {0x00,0x00,0x66,0x7F,0x7F,0x6B,0x63,0x00},
    /* 0x6E 'n' */ {0x00,0x00,0x7C,0x66,0x66,0x66,0x66,0x00},
    /* 0x6F 'o' */ {0x00,0x00,0x3C,0x66,0x66,0x66,0x3C,0x00},
    /* 0x70 'p' */ {0x00,0x00,0x7C,0x66,0x66,0x7C,0x60,0x60},
    /* 0x71 'q' */ {0x00,0x00,0x3E,0x66,0x66,0x3E,0x06,0x06},
    /* 0x72 'r' */ {0x00,0x00,0x7C,0x66,0x60,0x60,0x60,0x00},
    /* 0x73 's' */ {0x00,0x00,0x3E,0x60,0x3C,0x06,0x7C,0x00},
    /* 0x74 't' */ {0x18,0x18,0x7E,0x18,0x18,0x18,0x0E,0x00},
    /* 0x75 'u' */ {0x00,0x00,0x66,0x66,0x66,0x66,0x3E,0x00},
    /* 0x76 'v' */ {0x00,0x00,0x66,0x66,0x66,0x3C,0x18,0x00},
    /* 0x77 'w' */ {0x00,0x00,0x63,0x6B,0x7F,0x7F,0x36,0x00},
    /* 0x78 'x' */ {0x00,0x00,0x66,0x3C,0x18,0x3C,0x66,0x00},
    /* 0x79 'y' */ {0x00,0x00,0x66,0x66,0x66,0x3E,0x06,0x3C},
    /* 0x7A 'z' */ {0x00,0x00,0x7E,0x0C,0x18,0x30,0x7E,0x00},
    /* 0x7B '{' */ {0x0E,0x18,0x18,0x70,0x18,0x18,0x0E,0x00},
    /* 0x7C '|' */ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00},
    /* 0x7D '}' */ {0x70,0x18,0x18,0x0E,0x18,0x18,0x70,0x00},
    /* 0x7E '~' */ {0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00}
};


// ----- Internal helpers (alphabetical) -----

static void drawChar(jlSurfaceT *stage, int16_t x, int16_t y, char ch, uint8_t fg, uint8_t bg) {
    const uint8_t  *glyph;
    uint8_t         row;
    uint8_t         col;
    uint8_t         bits;
    uint8_t         c;
    uint8_t        *stagePixels;

    c = (uint8_t)ch;
    if (c < FIRST_CHAR || c > LAST_CHAR) {
        c = FIRST_CHAR;     // unknown -> space
    }
    glyph = kFont[c - FIRST_CHAR];

    // Chunky fast path: each glyph row is 8 bits == 4 stage bytes
    // (each byte = 2 pixels in 4bpp packed). Build each byte by
    // checking 2 bits at a time. Bypasses jlDrawPixel's per-pixel
    // overhead which dominates text rendering on slow targets.
    stagePixels = (stage != NULL) ? stage->pixels : NULL;
    if (stagePixels != NULL && x >= 0 && y >= 0 &&
        x + (int16_t)GLYPH_W <= (int16_t)SURFACE_WIDTH &&
        y + (int16_t)GLYPH_H <= (int16_t)SURFACE_HEIGHT &&
        (x & 1) == 0) {
        uint16_t  rowOff;
        uint16_t  byteX;
        uint8_t   bytePair;
        uint8_t   left;
        uint8_t   right;

        byteX = (uint16_t)(x >> 1);
        for (row = 0u; row < GLYPH_H; row++) {
            bits = glyph[row];
            rowOff = (uint16_t)((y + row) * SURFACE_BYTES_PER_ROW + byteX);
            for (col = 0u; col < GLYPH_W; col += 2u) {
                left  = (uint8_t)((bits & (uint8_t)(0x80u >> col))     ? fg : bg);
                right = (uint8_t)((bits & (uint8_t)(0x80u >> (col + 1u))) ? fg : bg);
                bytePair = (uint8_t)((left << 4) | right);
                stagePixels[rowOff + (col >> 1)] = bytePair;
            }
        }
        surfaceMarkDirtyRect(stage, x, y, (int16_t)GLYPH_W, (int16_t)GLYPH_H);
        return;
    }

    // Planar / out-of-bounds slow fallback.
    for (row = 0u; row < GLYPH_H; row++) {
        bits = glyph[row];
        for (col = 0u; col < GLYPH_W; col++) {
            uint8_t  pixel;

            pixel = ((bits >> (uint8_t)(7u - col)) & 0x01u) ? fg : bg;
            jlDrawPixel(stage, (int16_t)(x + (int16_t)col), (int16_t)(y + (int16_t)row), pixel);
        }
    }
}


// Draw a char with transparent background: only foreground bits are
// written, the surface pixels underneath show through. Chunky fast
// path reads each stage byte, modifies set nibbles, writes back.
static void drawCharTransparent(jlSurfaceT *stage, int16_t x, int16_t y, char ch, uint8_t fg) {
    const uint8_t  *glyph;
    uint8_t         row;
    uint8_t         col;
    uint8_t         bits;
    uint8_t         c;
    uint8_t        *stagePixels;

    c = (uint8_t)ch;
    if (c < FIRST_CHAR || c > LAST_CHAR) {
        return;             // unknown -> nothing to draw (transparent)
    }
    glyph = kFont[c - FIRST_CHAR];
    stagePixels = (stage != NULL) ? stage->pixels : NULL;
    if (stagePixels != NULL && x >= 0 && y >= 0 &&
        x + (int16_t)GLYPH_W <= (int16_t)SURFACE_WIDTH &&
        y + (int16_t)GLYPH_H <= (int16_t)SURFACE_HEIGHT &&
        (x & 1) == 0) {
        uint16_t  rowOff;
        uint16_t  byteX;
        uint8_t   existing;
        uint8_t   leftMask;
        uint8_t   rightMask;
        uint8_t   nibbleFg;

        byteX    = (uint16_t)(x >> 1);
        nibbleFg = (uint8_t)(fg & 0x0Fu);
        for (row = 0u; row < GLYPH_H; row++) {
            bits = glyph[row];
            if (bits == 0u) { continue; }    // entire row transparent
            rowOff = (uint16_t)((y + row) * SURFACE_BYTES_PER_ROW + byteX);
            for (col = 0u; col < GLYPH_W; col += 2u) {
                leftMask  = (uint8_t)(bits & (uint8_t)(0x80u >> col));
                rightMask = (uint8_t)(bits & (uint8_t)(0x80u >> (col + 1u)));
                if (leftMask == 0u && rightMask == 0u) { continue; }
                existing = stagePixels[rowOff + (col >> 1)];
                if (leftMask)  { existing = (uint8_t)((existing & 0x0Fu) | (nibbleFg << 4)); }
                if (rightMask) { existing = (uint8_t)((existing & 0xF0u) | nibbleFg); }
                stagePixels[rowOff + (col >> 1)] = existing;
            }
        }
        surfaceMarkDirtyRect(stage, x, y, (int16_t)GLYPH_W, (int16_t)GLYPH_H);
        return;
    }

    // Slow fallback (planar or out-of-bounds).
    for (row = 0u; row < GLYPH_H; row++) {
        bits = glyph[row];
        for (col = 0u; col < GLYPH_W; col++) {
            if (bits & (uint8_t)(0x80u >> col)) {
                jlDrawPixel(stage, (int16_t)(x + (int16_t)col), (int16_t)(y + (int16_t)row), fg);
            }
        }
    }
}


static void drawString(jlSurfaceT *stage, int16_t x, int16_t y, const char *s, uint8_t fg, uint8_t bg) {
    int16_t  cur;

    if (s == NULL) {
        return;
    }
    cur = x;
    while (*s != '\0') {
        if (cur >= (int16_t)STAGE_W) {
            return;
        }
        drawChar(stage, cur, y, *s, fg, bg);
        cur = (int16_t)(cur + (int16_t)GLYPH_W);
        s++;
    }
}


static void drawWindow(jlSurfaceT *stage, int16_t x, int16_t y, uint8_t cols, uint8_t rows, uint8_t bg, uint8_t border) {
    int16_t  w;
    int16_t  h;

    w = (int16_t)((int16_t)cols * (int16_t)GLYPH_W);
    h = (int16_t)((int16_t)rows * (int16_t)GLYPH_H);
    jlFillRect(stage, x, y, (uint16_t)(w + 4), (uint16_t)(h + 4), border);
    jlFillRect(stage, (int16_t)(x + 2), (int16_t)(y + 2), (uint16_t)w, (uint16_t)h, bg);
}


static void fillRow(jlSurfaceT *stage, uint8_t row, uint8_t bg) {
    jlFillRect(stage, 0, (int16_t)((int16_t)row * (int16_t)GLYPH_H), (uint16_t)STAGE_W, (uint16_t)GLYPH_H, bg);
}


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

const uint16_t *agiTextAsciiMap(void) {
    // Not used in this impl (we render direct), but expose so callers
    // who want to use jlDrawText against agiTextFontSurface have the
    // standard 16x6 layout map. Built lazily on first call.
    static uint16_t  map[256];
    static bool      built;
    uint16_t         i;

    if (built) {
        return map;
    }
    for (i = 0u; i < 256u; i++) {
        if (i < FIRST_CHAR || i > LAST_CHAR) {
            map[i] = (uint16_t)0xFFFFu;
        } else {
            uint8_t  off;

            off = (uint8_t)(i - FIRST_CHAR);
            map[i] = (uint16_t)(((uint16_t)(off / 16u) << 8) | (uint16_t)(off % 16u));
        }
    }
    built = true;
    return map;
}


const jlSurfaceT *agiTextFontSurface(void) {
    // No surface built in this impl (drawChar walks raw bits direct
    // to the stage). Reserved for callers that prefer jlDrawText.
    return NULL;
}


void agiTextRender(const AgiVmT *vm, jlSurfaceT *stage) {
    uint8_t  r;

    if (vm->statusLineOn) {
        fillRow(stage, vm->statusLineRow, 15u);    // white background
        if (vm->textRows[vm->statusLineRow].text[0] != '\0') {
            drawString(stage, 0, (int16_t)((int16_t)vm->statusLineRow * (int16_t)GLYPH_H),
                       vm->textRows[vm->statusLineRow].text, 0u, 15u);
        }
    }
    // display() rows: walk every row that has content. Glyphs draw
    // transparently (no bg fill) so any PIC art behind shows through
    // where glyph pixels are 0. Filling the whole row's bg would
    // obscure picture elements that overlap the text band (e.g.
    // KQ3's "King's Quest III" title art).
    for (r = 0u; r < AGI_TEXT_ROWS; r++) {
        const char  *p;
        int16_t      x;
        int16_t      y;
        uint8_t      fg;

        if (r == vm->statusLineRow && vm->statusLineOn) {
            continue;       // status line handled above
        }
        if (vm->textRows[r].text[0] == '\0') {
            continue;
        }
        y  = (int16_t)((int16_t)r * (int16_t)GLYPH_H);
        x  = 0;
        fg = vm->textRows[r].fg;
        p  = vm->textRows[r].text;
        while (*p != '\0' && x < (int16_t)STAGE_W) {
            if (*p != ' ') {
                drawCharTransparent(stage, x, y, *p, fg);
            }
            x = (int16_t)(x + (int16_t)GLYPH_W);
            p++;
        }
    }

    if (vm->printModal.active) {
        const char  *p;
        int16_t      lineW;
        int16_t      maxW;
        int16_t      lineCount;
        int16_t      winX;
        int16_t      winY;
        int16_t      lineH;
        int16_t      textY;
        int16_t      curX;

        // Measure: width = longest line in chars; height = lines.
        lineW     = 0;
        maxW      = 1;
        lineCount = 1;
        p = vm->printModal.message;
        while (*p != '\0') {
            if (*p == '\n') {
                if (lineW > maxW) {
                    maxW = lineW;
                }
                lineW = 0;
                lineCount++;
            } else {
                lineW++;
            }
            p++;
        }
        if (lineW > maxW) {
            maxW = lineW;
        }
        if (maxW > (int16_t)AGI_TEXT_COLS) {
            maxW = (int16_t)AGI_TEXT_COLS;
        }
        lineH = (int16_t)GLYPH_H;
        winX  = (int16_t)((STAGE_W - (maxW * (int16_t)GLYPH_W + 4)) / 2);
        winY  = (int16_t)((STAGE_H - (lineCount * lineH + 4)) / 2);
        if (winX < 0) { winX = 0; }
        if (winY < 0) { winY = 0; }
        drawWindow(stage, winX, winY, (uint8_t)maxW, (uint8_t)lineCount, 15u, 4u);

        textY = (int16_t)(winY + 2);
        curX  = (int16_t)(winX + 2);
        p = vm->printModal.message;
        while (*p != '\0') {
            if (*p == '\n') {
                textY = (int16_t)(textY + lineH);
                curX  = (int16_t)(winX + 2);
            } else {
                drawChar(stage, curX, textY, *p, 0u, 15u);
                curX = (int16_t)(curX + (int16_t)GLYPH_W);
            }
            p++;
        }
    }
}
