// uiBuilder.c - declarative UI scaffolding implementation.
//
// Menu mini-format reference (Apple IIgs TBR Vol.2 ch.13.MenuMgr,
// section "Building Menus from a String"):
//
//   '>>' MenuName ' \\N' MenuID '\r'           menu header (text title)
//   '>>@' '\\XN' MenuID '\r'                   Apple menu header (icon)
//   '--' ItemName ('\\N' ItemID)? ('*Xx')? Flags '\r'
//                                              one item line
//                                                Flags letters:
//                                                  D = disabled
//                                                  V = checked-visible
//                                                  X = xor hilite
//                                                  I = item has icon
//                                                  S = item has style
//   '---' '\\N' ItemID 'D' '\r'                divider line
//   '.\r'                                      menu terminator
//
// We assemble the byte stream from a UiMenuT (more humane) spec.
// The Menu Manager parser is forgiving: extra spaces in the header
// are tolerated.  We mirror ORCA's style ('>> Name \N# \r') for
// round-trip consistency.

#include "iigs/uiBuilder.h"
#include "iigs/toolbox.h"

#include <stdint.h>


// --- Forward decls (alphabetical per project style) ----------------
static uint16_t emitChar(char *buf, uint16_t pos, uint16_t cap, char c);
static uint16_t emitDecimal(char *buf, uint16_t pos, uint16_t cap, uint16_t v);
static uint16_t emitItem(char *buf, uint16_t pos, uint16_t cap, const UiMenuItemT *item);
static uint16_t emitMenuHeader(char *buf, uint16_t pos, uint16_t cap, const UiMenuT *spec);
static uint16_t emitStr(char *buf, uint16_t pos, uint16_t cap, const char *s);
static uint16_t pascalStrLen(const char *s);
static void     toPascalStr(unsigned char *dst, const char *src);


// Scratch buffer for the byte stream + pascal-title staging.  Sized
// for the biggest menu in our demo set (reversi options menu ~ 200 B).
// Doubled to allow Apple+File+Edit+Level+Options to share one buffer
// if needed.
#define UIB_MENU_SCRATCH  512
static char           gMenuScratch[UIB_MENU_SCRATCH];

// Per-window/per-alert pascal-string staging.  We keep small fixed
// slots so consecutive uiBuilderOpenWindow() calls don't trash earlier
// titles.  16 windows / 64-char titles each is plenty for our demos.
#define UIB_PSTRING_SLOTS  16
#define UIB_PSTRING_LEN    64
static unsigned char  gPStringPool[UIB_PSTRING_SLOTS][UIB_PSTRING_LEN];
static uint16_t       gPStringNextSlot;


// Reusable NewWindow / Alert / Item template blocks.  Single-threaded
// runtime so one of each is enough.
typedef struct {
    int16_t v1, h1, v2, h2;
} RectS;

typedef struct {
    uint16_t  paramLength;
    uint16_t  wFrameBits;
    void     *wTitle;
    uint32_t  wRefCon;
    RectS     wZoom;
    void     *wColor;
    int16_t   wYOrigin, wXOrigin;
    int16_t   wDataH,   wDataV;
    int16_t   wMaxHeight, wMaxWidth;
    int16_t   wScrollVer, wScrollHor;
    int16_t   wPageVer,   wPageHor;
    uint32_t  wInfoRefCon;
    int16_t   wInfoHeight;
    void     *wFrameDefProc;
    void     *wInfoDefProc;
    void     *wContDefProc;
    RectS     wPosition;
    void     *wPlane;
    void     *wStorage;
} NewWindowParmS;

typedef struct {
    int16_t   itemID;
    int16_t   v1, h1, v2, h2;
    uint16_t  itemType;
    void     *itemDescr;
    int16_t   itemValue;
    int16_t   itemFlag;
    void     *itemColor;
} ItemTemplateS;

typedef struct {
    int16_t   atRectV1, atRectH1, atRectV2, atRectH2;
    int16_t   atBtnHorz;
    int16_t   atBeep0, atBeep1, atBeep2, atBeep3;
    void     *atSound;
    void     *atResv1;
    void     *atResv2;
    void     *atItemList[8];
} AlertTemplateS;

static NewWindowParmS gWp;
static ItemTemplateS  gAlertButton;
static ItemTemplateS  gAlertMessage;
static AlertTemplateS gAlertRec;


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

static uint16_t emitChar(char *buf, uint16_t pos, uint16_t cap, char c) {
    if (pos >= cap) {
        return cap + 1;  // sentinel: overflowed
    }
    buf[pos] = c;
    return (uint16_t)(pos + 1);
}


static uint16_t emitDecimal(char *buf, uint16_t pos, uint16_t cap, uint16_t v) {
    char tmp[6];
    uint16_t n = 0;
    if (v == 0) {
        return emitChar(buf, pos, cap, '0');
    }
    while (v > 0 && n < 6) {
        tmp[n++] = (char)('0' + (v % 10));
        v = (uint16_t)(v / 10);
    }
    while (n > 0) {
        pos = emitChar(buf, pos, cap, tmp[--n]);
        if (pos > cap) {
            return pos;
        }
    }
    return pos;
}


static uint16_t emitItem(char *buf, uint16_t pos, uint16_t cap, const UiMenuItemT *item) {
    // Divider: '---\NID D\r'.  Menu Manager treats ItemID-with-D-flag
    // and no name as a divider.
    if ((item->flags & MI_DIVIDER) || item->title == (const char *)0) {
        pos = emitStr(buf, pos, cap, "---\\N");
        pos = emitDecimal(buf, pos, cap, item->cmdId);
        pos = emitStr(buf, pos, cap, "D\r");
        return pos;
    }
    pos = emitStr(buf, pos, cap, "--");
    pos = emitStr(buf, pos, cap, item->title);
    pos = emitStr(buf, pos, cap, "\\N");
    pos = emitDecimal(buf, pos, cap, item->cmdId);
    if (item->flags & MI_CHECKED) {
        pos = emitChar(buf, pos, cap, 'V');
    }
    if (item->flags & MI_XOR) {
        pos = emitChar(buf, pos, cap, 'X');
    }
    if (item->flags & MI_DISABLED) {
        pos = emitChar(buf, pos, cap, 'D');
    }
    if (item->keyEquiv != 0) {
        char up = item->keyEquiv;
        char lo = item->keyEquiv;
        if (up >= 'a' && up <= 'z') {
            up = (char)(up - 32);
        }
        if (lo >= 'A' && lo <= 'Z') {
            lo = (char)(lo + 32);
        }
        pos = emitChar(buf, pos, cap, '*');
        pos = emitChar(buf, pos, cap, up);
        pos = emitChar(buf, pos, cap, lo);
    }
    pos = emitChar(buf, pos, cap, '\r');
    return pos;
}


static uint16_t emitMenuHeader(char *buf, uint16_t pos, uint16_t cap, const UiMenuT *spec) {
    if (spec->flags & MN_APPLE) {
        // Apple menu uses the system icon; title text ignored.
        pos = emitStr(buf, pos, cap, ">>@\\XN");
        pos = emitDecimal(buf, pos, cap, spec->menuId);
        if (spec->flags & MN_ALL_DISABLED) {
            pos = emitChar(buf, pos, cap, 'D');
        }
        pos = emitChar(buf, pos, cap, '\r');
        return pos;
    }
    pos = emitStr(buf, pos, cap, ">> ");
    if (spec->title != (const char *)0) {
        pos = emitStr(buf, pos, cap, spec->title);
    }
    pos = emitStr(buf, pos, cap, " \\N");
    pos = emitDecimal(buf, pos, cap, spec->menuId);
    if (spec->flags & MN_ALL_DISABLED) {
        pos = emitChar(buf, pos, cap, 'D');
    }
    pos = emitChar(buf, pos, cap, '\r');
    return pos;
}


static uint16_t emitStr(char *buf, uint16_t pos, uint16_t cap, const char *s) {
    while (*s != '\0') {
        pos = emitChar(buf, pos, cap, *s++);
        if (pos > cap) {
            return pos;
        }
    }
    return pos;
}


static uint16_t pascalStrLen(const char *s) {
    uint16_t n = 0;
    while (s[n] != '\0' && n < 255) {
        n++;
    }
    return n;
}


static void toPascalStr(unsigned char *dst, const char *src) {
    uint16_t n = pascalStrLen(src);
    if (n > UIB_PSTRING_LEN - 1) {
        n = UIB_PSTRING_LEN - 1;
    }
    dst[0] = (unsigned char)n;
    for (uint16_t i = 0; i < n; i++) {
        dst[i + 1] = (unsigned char)src[i];
    }
}


// --- public API (alphabetical) --------------------------------------

void uiBuilderDispatch(uint16_t cmdId, const UiCmdHandlerT *table, uint16_t tableLen) {
    if (table == (const UiCmdHandlerT *)0) {
        return;
    }
    for (uint16_t i = 0; i < tableLen; i++) {
        if (table[i].cmdId == cmdId) {
            if (table[i].handler != (void (*)(uint16_t))0) {
                table[i].handler(cmdId);
            }
            return;
        }
    }
}


void uiBuilderInstallMenuBar(const UiMenuT *menus, uint16_t numMenus) {
    uint16_t appleMenuId = 0;
    // Menu bar order: Menu Manager renders menus left-to-right in the
    // order they were inserted with `beforeMenuId == 0` (which appends
    // to the END).  So if the caller hands us {Apple, File, Edit, ...}
    // in left-to-right order, we walk forward.
    for (uint16_t i = 0; i < numMenus; i++) {
        if (menus[i].flags & MN_APPLE) {
            appleMenuId = menus[i].menuId;
        }
        (void)uiBuilderInstallMenu(&menus[i], 0);
    }
    if (appleMenuId != 0) {
        FixAppleMenu(appleMenuId);
    }
    (void)FixMenuBar();
    DrawMenuBar();
}


void *uiBuilderInstallMenu(const UiMenuT *spec, uint16_t beforeMenuId) {
    uint16_t n = uiBuilderMenuBytes(spec, gMenuScratch, UIB_MENU_SCRATCH);
    if (n == 0) {
        return (void *)0;
    }
    void *h = NewMenu(gMenuScratch);
    if (h != (void *)0) {
        InsertMenu(h, beforeMenuId);
    }
    return h;
}


uint16_t uiBuilderMenuBytes(const UiMenuT *spec, char *outBuf, uint16_t outBufSize) {
    if (spec == (const UiMenuT *)0 || outBuf == (char *)0 || outBufSize < 16) {
        return 0;
    }
    uint16_t cap = (uint16_t)(outBufSize - 1);  // leave room for NUL
    uint16_t pos = 0;
    pos = emitMenuHeader(outBuf, pos, cap, spec);
    for (uint16_t i = 0; i < spec->numItems; i++) {
        pos = emitItem(outBuf, pos, cap, &spec->items[i]);
        if (pos > cap) {
            return 0;
        }
    }
    pos = emitStr(outBuf, pos, cap, ".\r");
    if (pos > cap) {
        return 0;
    }
    outBuf[pos] = '\0';
    return pos;
}


void *uiBuilderOpenWindow(const UiWindowT *spec) {
    if (spec == (const UiWindowT *)0) {
        return (void *)0;
    }
    // Zero the parm block.
    {
        unsigned char *p = (unsigned char *)&gWp;
        for (uint16_t i = 0; i < sizeof gWp; i++) {
            p[i] = 0;
        }
    }
    gWp.paramLength  = (uint16_t)sizeof gWp;
    gWp.wFrameBits   = spec->frameBits;
    if (spec->title != (const char *)0) {
        unsigned char *slot = gPStringPool[gPStringNextSlot];
        gPStringNextSlot = (uint16_t)((gPStringNextSlot + 1) % UIB_PSTRING_SLOTS);
        toPascalStr(slot, spec->title);
        gWp.wTitle = slot;
    } else {
        gWp.wTitle = (void *)0;
    }
    gWp.wRefCon       = spec->refCon;
    gWp.wMaxHeight    = spec->maxHeight;
    gWp.wMaxWidth     = spec->maxWidth;
    gWp.wPosition.v1  = spec->position.v1;
    gWp.wPosition.h1  = spec->position.h1;
    gWp.wPosition.v2  = spec->position.v2;
    gWp.wPosition.h2  = spec->position.h2;
    gWp.wContDefProc  = spec->contentDefProc;
    gWp.wPlane        = (void *)-1L;
    return NewWindow(&gWp);
}


uint16_t uiBuilderAlert(uint16_t kind, const char *msg) {
    static unsigned char okStr[] = "\x02OK";
    unsigned char *slot = gPStringPool[gPStringNextSlot];
    gPStringNextSlot = (uint16_t)((gPStringNextSlot + 1) % UIB_PSTRING_SLOTS);
    toPascalStr(slot, msg);

    gAlertButton.itemID    = 1;
    gAlertButton.v1        = 36;
    gAlertButton.h1        = 15;
    gAlertButton.v2        = 0;
    gAlertButton.h2        = 0;
    gAlertButton.itemType  = 10;       // buttonItem
    gAlertButton.itemDescr = okStr;
    gAlertButton.itemValue = 0;
    gAlertButton.itemFlag  = 0;
    gAlertButton.itemColor = (void *)0;

    gAlertMessage.itemID    = 100;
    gAlertMessage.v1        = 5;
    gAlertMessage.h1        = 100;
    gAlertMessage.v2        = 90;
    gAlertMessage.h2        = 280;
    gAlertMessage.itemType  = 0x8000 | 136;   // itemDisable | statText
    gAlertMessage.itemDescr = slot;
    gAlertMessage.itemValue = 0;
    gAlertMessage.itemFlag  = 0;
    gAlertMessage.itemColor = (void *)0;

    gAlertRec.atRectV1     = 50;
    gAlertRec.atRectH1     = 180;
    gAlertRec.atRectV2     = 107;
    gAlertRec.atRectH2     = 460;
    gAlertRec.atBtnHorz    = 2;
    gAlertRec.atBeep0      = 0x80;
    gAlertRec.atBeep1      = 0x80;
    gAlertRec.atBeep2      = 0x80;
    gAlertRec.atBeep3      = 0x80;
    gAlertRec.atSound      = (void *)0;
    gAlertRec.atResv1      = (void *)0;
    gAlertRec.atResv2      = (void *)0;
    gAlertRec.atItemList[0] = &gAlertButton;
    gAlertRec.atItemList[1] = &gAlertMessage;
    gAlertRec.atItemList[2] = (void *)0;
    gAlertRec.atItemList[3] = (void *)0;
    gAlertRec.atItemList[4] = (void *)0;
    gAlertRec.atItemList[5] = (void *)0;
    gAlertRec.atItemList[6] = (void *)0;
    gAlertRec.atItemList[7] = (void *)0;

    SetForeColor(0);
    SetBackColor(15);

    uint16_t r = 1;
    switch (kind) {
        case UA_STOP:
            r = (uint16_t)StopAlert(&gAlertRec, (void *)0);
            break;
        case UA_NOTE:
            r = (uint16_t)NoteAlert(&gAlertRec, (void *)0);
            break;
        case UA_CAUTION:
            r = (uint16_t)CautionAlert(&gAlertRec, (void *)0);
            break;
        case UA_NORMAL:
        default:
            r = (uint16_t)Alert(&gAlertRec, (void *)0);
            break;
    }
    return r;
}
