// minicad.c - faithful port of ORCA-C's MiniCAD.cc sample.
//
// Mike Westerfield, Byte Works 1989.  Original at
// tools/orca-c/C.Samples/Desktop.Samples/MiniCAD.cc.
//
// A simple multi-window CAD: File>New opens a drawing window (up to
// 4), click+drag inside a window's content rubber-bands a line,
// release commits it.  File>Close closes the front window.  Each
// window's lines are remembered so the WM can repaint on update.
//
// Phase 4.1 migration: menu mini-format strings, AlertTemplate, and
// NewWindowParm folded into iigs/uiBuilder.h.

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


#define apple_About 257
#define file_Quit   256
#define file_New    258
#define file_Close  255

#define wInGoAway   17
#define wInContent  19

#define mUpMask     0x0002

#define modeCopy    0
#define modeXOR     2

#define maxWindows  4
#define maxLines    50


typedef struct { short v, h; } Point;
typedef struct { Point p1, p2; } LineRec;


typedef struct {
    unsigned short wmWhat;
    unsigned long  wmMessage;
    unsigned long  wmWhen;
    short          wmWhereV, wmWhereH;
    unsigned short wmModifiers;
} EventRec;


typedef struct {
    void          *wPtr;
    unsigned char *name;
    unsigned short numLines;
    LineRec        lines[maxLines];
} WindowRecord;


// --- alphabetised forward decls -----------------------------------
static void doClose(void);
static void doNew(void);
static void drawWindow(void);
static void onAbout(uint16_t cmdId);
static void onCloseMenu(uint16_t cmdId);
static void onMenu(uint16_t menuId, uint16_t itemId);
static void onNew(uint16_t cmdId);
static void onQuit(uint16_t cmdId);
static void sketch(const IigsEventT *ev);


static void onNew(uint16_t cmdId) {
    (void)cmdId;
    doNew();
}


static void onCloseMenu(uint16_t cmdId) {
    (void)cmdId;
    doClose();
}


static const UiCmdHandlerT gCmdTable[] = {
    { apple_About, onAbout },
    { file_Quit,   onQuit  },
    { file_New,    onNew  },
    { file_Close,  onCloseMenu },
};


static const UiMenuItemT gEditItems[] = {
    { 250, "Undo",  'Z', MI_CHECKED },
    { 251, "Cut",   'X', 0 },
    { 252, "Copy",  'C', 0 },
    { 253, "Paste", 'V', 0 },
    { 254, "Clear", 0,   0 },
};

static const UiMenuItemT gFileItems[] = {
    { 258, "New",   'N', 0 },
    { 255, "Close", 0,   MI_CHECKED },
    { 256, "Quit",  'Q', 0 },
};

static const UiMenuItemT gAppleItems[] = {
    { 257, "About...", 0, MI_CHECKED },
};

static const UiMenuT gMenus[] = {
    { 1, "Apple", MN_APPLE, 1, gAppleItems },
    { 2, " File ", 0,        3, gFileItems  },
    { 3, " Edit ", 0,        5, gEditItems  },
};


static unsigned char gTitle0[] = "\x07Paint 1";
static unsigned char gTitle1[] = "\x07Paint 2";
static unsigned char gTitle2[] = "\x07Paint 3";
static unsigned char gTitle3[] = "\x07Paint 4";

static WindowRecord gWindows[maxWindows] = {
    { (void *)0, gTitle0, 0, { { {0,0}, {0,0} } } },
    { (void *)0, gTitle1, 0, { { {0,0}, {0,0} } } },
    { (void *)0, gTitle2, 0, { { {0,0}, {0,0} } } },
    { (void *)0, gTitle3, 0, { { {0,0}, {0,0} } } }
};


// Window-content def-proc.  Called by the WM with our bank set up
// (Loader sets DBR via JSL).  Uses GetWRefCon to identify which
// gWindows[] entry to redraw.
static void drawWindow(void) {
    unsigned long refcon = (unsigned long)GetWRefCon(GetPort());
    unsigned short i = (unsigned short)refcon;
    if (i >= maxWindows) {
        return;
    }
    WindowRecord *wp = &gWindows[i];
    if (wp->numLines == 0) {
        return;
    }
    SetPenMode(modeCopy);
    SetSolidPenPat(0);
    SetPenSize(2, 1);
    for (unsigned short j = 0; j < wp->numLines; j++) {
        LineRec *lp = &wp->lines[j];
        MoveTo(lp->p1.h, lp->p1.v);
        LineTo(lp->p2.h, lp->p2.v);
    }
}


static void doNew(void) {
    unsigned short i = 0;
    while (i < maxWindows && gWindows[i].wPtr != (void *)0) {
        i++;
    }
    if (i >= maxWindows) {
        return;
    }
    gWindows[i].numLines = 0;

    // We pass a Pascal title directly via uiBuilderOpenWindow's
    // contract... but uiBuilder takes a C string.  Convert by skipping
    // the pascal length byte and stuffing into a temporary.
    char title[16];
    unsigned short tn = gWindows[i].name[0];
    if (tn > 14) {
        tn = 14;
    }
    for (unsigned short k = 0; k < tn; k++) {
        title[k] = (char)gWindows[i].name[k + 1];
    }
    title[tn] = '\0';

    UiWindowT spec = {
        title,
        UW_STD_DOC_GZ,
        { (int16_t)(25 + i * 10),  (int16_t)(10 + i * 10),
          (int16_t)(180 + i * 10), (int16_t)(600 + i * 10) },
        188, 615,
        (uint32_t)i,
        (void *)&drawWindow
    };
    gWindows[i].wPtr = uiBuilderOpenWindow(&spec);
    if (i == maxWindows - 1) {
        DisableMItem(file_New);
    }
}


static void doClose(void) {
    void *fw = FrontWindow();
    if (!fw) {
        return;
    }
    unsigned short i = (unsigned short)(unsigned long)GetWRefCon(fw);
    if (i >= maxWindows) {
        return;
    }
    CloseWindow(gWindows[i].wPtr);
    gWindows[i].wPtr = (void *)0;
    EnableMItem(file_New);
}


static void onAbout(uint16_t cmdId) {
    (void)cmdId;
    uiBuilderAlert(UA_NOTE,
                   "Mini-CAD 1.0\r"
                   "Copyright 1989\r"
                   "Byte Works, Inc.\r\r"
                   "By Mike Westerfield");
}


static volatile uint16_t gDone;


static void onQuit(uint16_t cmdId) {
    (void)cmdId;
    gDone = 1;
    iigsEventLoopQuit();
}


static void onMenu(uint16_t menuId, uint16_t itemId) {
    (void)menuId;
    uiBuilderDispatch(itemId, gCmdTable, (uint16_t)(sizeof gCmdTable / sizeof gCmdTable[0]));
}


static void sketch(const IigsEventT *ev) {
    void *fw = FrontWindow();
    if (!fw) {
        return;
    }
    unsigned short i = (unsigned short)(unsigned long)GetWRefCon(fw);
    if (i >= maxWindows) {
        return;
    }
    if (gWindows[i].numLines >= maxLines) {
        uiBuilderAlert(UA_STOP,
                       "The window is full -\r"
                       "more lines cannot be\r"
                       "added.");
        return;
    }

    StartDrawing(fw);
    SetSolidPenPat(15);
    SetPenSize(2, 1);
    SetPenMode(modeXOR);

    Point firstPt;
    firstPt.h = ev->whereX;
    firstPt.v = ev->whereY;
    GlobalToLocal(&firstPt);
    MoveTo(firstPt.h, firstPt.v);
    LineTo(firstPt.h, firstPt.v);
    Point endPt = firstPt;

    EventRec evDrag;
    while (!GetNextEvent(mUpMask, &evDrag)) {
        Point cur;
        cur.h = evDrag.wmWhereH;
        cur.v = evDrag.wmWhereV;
        GlobalToLocal(&cur);
        if (cur.h != endPt.h || cur.v != endPt.v) {
            MoveTo(firstPt.h, firstPt.v);
            LineTo(endPt.h, endPt.v);
            MoveTo(firstPt.h, firstPt.v);
            LineTo(cur.h, cur.v);
            endPt = cur;
        }
    }

    // Erase final XOR line.
    MoveTo(firstPt.h, firstPt.v);
    LineTo(endPt.h, endPt.v);

    if (firstPt.h != endPt.h || firstPt.v != endPt.v) {
        unsigned short n = gWindows[i].numLines++;
        gWindows[i].lines[n].p1 = firstPt;
        gWindows[i].lines[n].p2 = endPt;
        SetPenMode(modeCopy);
        SetSolidPenPat(0);
        MoveTo(firstPt.h, firstPt.v);
        LineTo(endPt.h, endPt.v);
    }
}


int main(void) {
    unsigned short userId = startdesk(640);
    (void)userId;

    paintDesktopBackdrop();
    uiBuilderInstallMenuBar(gMenus, (uint16_t)(sizeof gMenus / sizeof gMenus[0]));
    ShowCursor();

    // Open one window so the demo has visible content immediately.
    doNew();

    // Use a direct TaskMaster loop so the watchdog increments on
    // every iteration regardless of TaskMaster's return code.
    // iigsEventLoop's onIdle only ticks on EV_NULL which TaskMaster
    // rarely emits with our task mask.
    IigsEventT ev;
    {
        unsigned char *p = (unsigned char *)&ev;
        for (uint16_t i = 0; i < sizeof ev; i++) {
            p[i] = 0;
        }
    }
    ev.taskMask = 0x1FFF;
    uint16_t watchdog = 0;
    do {
        uint16_t code = TaskMaster(0x076E, &ev);
        switch (code) {
            case 3:   // wInMenuBar
            case 25:  // wInSpecial
                onMenu(0, (uint16_t)ev.taskData);
                break;
            case wInGoAway:
                doClose();
                break;
            case wInContent:
                sketch(&ev);
                break;
            default:
                break;
        }
        watchdog++;
    } while (!gDone && watchdog < 4000);

    *(volatile unsigned char *)0x70 = 0x99;
    return 0;
}
