// desktop.c - startdesk()/enddesk() — ORCA-C-style minimal desktop init.
//
// Brings up the toolset chain a full desktop app needs:
//   Memory + DP allocation, MiscTools, QD, EM, Scheduler, Sound, ADB,
//   SANE, IntMath, Text, Window, Font, Control, LineEdit, Dialog,
//   Scrap, Menu Manager.  All 16-tool chain is started; the
//   DrawMenuBar / MenuSelect path works post-InitCursor (the prior
//   "hang" was iUndrawCursor walking NULL because no cursor save
//   buffer existed).  paintMenuBarTitles still ships as a fallback
//   for demos that explicitly want a hand-painted bar.
//
// Palette: all 16 palettes set to (black, white, black, white).  In
// 640 mode that maps to clean Finder-style B/W instead of NTSC chroma
// noise for the dithered patterns the WM uses.

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

static unsigned short gUserId = 0;
static void *gDpHandle = (void *)0;
static unsigned short gDpBase = 0;


static unsigned short blockAddrLo(void *handle) {
    return (unsigned short)(unsigned long)*(void **)handle;
}


unsigned short startdesk(unsigned short screenWidth) {
    gUserId = MMStartUp();
    // QD needs 512 bytes ($200) — its own DP at +$000 plus the cursor
    // manager's DP at +$100 (InitCursor does `tdc; clc; adc #$100;
    // tcd` to find its globals).  ROM Source Code/Bank FE/cursor.asm
    // confirms cursor uses QD's DP+$100.  Layout below allocates 0x800
    // total so QD/Cursor have $000..$1FF, then EM/FM/Menu/Ctl/LE/Dlg
    // each get $100.
    //   $000 QD | $100 Cursor | $200 EM | $300 FM | $400 Menu |
    //   $500 Ctl | $600 LE | $700 Dialog.
    gDpHandle = NewHandle(0x800UL, gUserId, 0xC015, (void *)0);
    unsigned short dp = blockAddrLo(gDpHandle);
    gDpBase = dp;

    MTStartUp();
    unsigned short masterSCB = (screenWidth == 640) ? 0x90 : 0x10;
    QDStartUp(dp, masterSCB, screenWidth, gUserId);
    EMStartUp((unsigned short)(dp + 0x200), 20, 0, 0,
              (short)(screenWidth - 1), 199, gUserId);
    SchStartUp();
    SoundStartUp(gUserId);
    ADBStartUp();
    SANEStartUp(gUserId);
    IMStartUp();
    TextStartUp();
    LoadOneTool(0x0E, 0x0301);
    WindStartUp(gUserId);
    LoadOneTool(0x1B, 0x0301);
    FMStartUp(gUserId, (unsigned short)(dp + 0x300));
    LoadOneTool(0x10, 0x0301);
    CtlStartUp(gUserId, (unsigned short)(dp + 0x500));
    LoadOneTool(0x14, 0x0301);
    LEStartUp(gUserId, (unsigned short)(dp + 0x600));
    LoadOneTool(0x15, 0x0301);
    DialogStartUp(gUserId);
    LoadOneTool(0x16, 0x0301);
    ScrapStartUp();

    // InitCursor allocates the cursor save buffer (1024-byte handle
    // stored at cursor mgr DP $B4).  Without it, the FIRST QD call
    // that internally triggers cursor shield/unshield hangs in ROM's
    // iUndrawCursor walking through a NULL pointer.  This was the
    // *real* cause of the "DrawMenuBar hang" investigation —
    // DrawMenuBar's _FillRect call shields the cursor, and iUndraw
    // loops forever copying garbage from $00:0000+Y into SHR.
    //   Confirmed via ROM Source Code/Bank FE/cursor.asm — see PC
    //   $FE:551E during hang matched iUndrawCursor:FinishLoop.
    InitCursor();

    LoadOneTool(0x0F, 0x0301);
    MenuStartUp(gUserId, (unsigned short)(dp + 0x400));

    // Force all 16 palettes to a Finder-style B/W spread that works
    // in 640 mode.  640 mode rotates through palette quadrants
    // (8..11, 12..15, 0..3, 4..7) across pixel positions, so SetForeColor(1)
    // only renders solid white if pal[1] == pal[5] == pal[9] == pal[13].
    // Even (0,2,4,6,8,10,12,14) → black, odd (1,3,5,7,9,11,13,15) → white.
    for (unsigned short p = 0; p < 16; p++) {
        volatile unsigned short *pal =
            (volatile unsigned short *)(0xE19E00UL + (unsigned long)p * 32UL);
        for (unsigned short k = 0; k < 16; k++) {
            pal[k] = (k & 1) ? 0x0FFF : 0x0000;
        }
    }

    return gUserId;
}


// Paint menu bar text via QD's DrawString.  Each title is a
// pascal-counted string (length-prefixed); titles are placed
// left-to-right at y=10, starting at x=4 with kSpacing between
// titles.  Kept as a fallback for demos that want hand-painted
// menu titles (e.g. when running with a stripped-down toolset
// chain).  DrawMenuBar() now works in the standard startdesk()
// environment - prefer that.
void paintMenuBarTitles(const unsigned char *const *pascalTitles, unsigned short count) {
    SetForeColor(0);
    SetBackColor(15);
    unsigned short x = 4;
    for (unsigned short i = 0; i < count; i++) {
        MoveTo((short)x, 10);
        const unsigned char *s = pascalTitles[i];
        DrawString((void *)s);
        x = (unsigned short)(x + (unsigned short)s[0] * 8U + 16U);
    }
}


// Paint a Finder-style backdrop directly into SHR: white menu bar
// (rows 0..12), 1-pixel black separator (row 13), white desktop
// (rows 14..199).  Bypasses the WM's dithered desktop fill that
// MAME's NTSC chroma simulator renders as colored noise.  Useful
// for ORCA-style demos that want the WM's chrome without the
// chroma fringing.
void paintDesktopBackdrop(void) {
    __asm__ volatile (
        "rep #0x30\n"
        "ldx #0x0000\n"
        "1:\n"
        ".byte 0xa9, 0xff, 0xff\n"
        ".byte 0x9f, 0x00, 0x20, 0xe1\n"
        "inx\n inx\n"
        ".byte 0xe0, 0x20, 0x08\n"
        "bcc 1b\n"
        "2:\n"
        ".byte 0xa9, 0x00, 0x00\n"
        ".byte 0x9f, 0x00, 0x20, 0xe1\n"
        "inx\n inx\n"
        ".byte 0xe0, 0xc0, 0x08\n"
        "bcc 2b\n"
        "3:\n"
        ".byte 0xa9, 0xff, 0xff\n"
        ".byte 0x9f, 0x00, 0x20, 0xe1\n"
        "inx\n inx\n"
        ".byte 0xe0, 0x00, 0x7d\n"
        "bcc 3b\n"
        ::: "a", "x", "memory");
}


void enddesk(unsigned short userId) {
    WindShutDown();
    FMShutDown();
    TextShutDown();
    ADBShutDown();
    SchShutDown();
    EMShutDown();
    QDShutDown();
    DisposeHandle(gDpHandle);
    // Do NOT call MMShutDown(userId) here.  MMShutDown disposes EVERY handle
    // owned by the app's user ID -- including the locked handle that backs
    // the C malloc heap (crt0Gsos/__heapInitMM allocate it under this same
    // ID).  Tearing that down while the program keeps running, or holds
    // malloc'd data past enddesk(), dangles every malloc pointer.  An app
    // need not call MMShutDown at all: GS/OS disposes all of the app's
    // memory when it QUITs.
    (void)userId;
}


unsigned short desktopUserId(void) {
    return gUserId;
}


unsigned short desktopDpBase(void) {
    return gDpBase;
}
