// 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.

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


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

#define wInMenuBar  3
#define wInSpecial  25
#define wInGoAway   17
#define wInContent  19

#define mUpMask     0x0002

#define modeCopy    0
#define modeXOR     2

#define topMost     ((void *)-1L)
#define bottomMost  ((void *)0)

#define maxWindows  4
#define maxLines    50

#define norml       0
#define stop        1
#define note        2
#define caution     3
#define buttonItem  10
#define statText    136
#define itemDisable 0x8000


typedef struct { short v1, h1, v2, h2; } Rect;
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;
    unsigned long   wmTaskData;
    unsigned long   wmTaskMask;
    unsigned long   wmLastClickTick;
    unsigned long   wmClickCount;
    unsigned long   wmTaskData2;
    unsigned long   wmTaskData3;
    unsigned long   wmTaskData4;
} WmTaskRec;


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


typedef struct {
    unsigned short paramLength;
    unsigned short wFrameBits;
    void          *wTitle;
    unsigned long  wRefCon;
    Rect           wZoom;
    void          *wColor;
    short          wYOrigin, wXOrigin;
    short          wDataH,   wDataV;
    short          wMaxHeight, wMaxWidth;
    short          wScrollVer, wScrollHor;
    short          wPageVer,   wPageHor;
    unsigned long  wInfoRefCon;
    short          wInfoHeight;
    void          *wFrameDefProc;
    void          *wInfoDefProc;
    void          *wContDefProc;
    Rect           wPosition;
    void          *wPlane;
    void          *wStorage;
} NewWindowParm;


typedef struct {
    short          itemID;
    short          itemRectV1, itemRectH1, itemRectV2, itemRectH2;
    unsigned short itemType;
    void          *itemDescr;
    short          itemValue;
    short          itemFlag;
    void          *itemColor;
} ItemTemplate;

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


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


static unsigned char editMenuStr[] = ">> Edit \\N3\r"
                                     "--Undo\\N250V*Zz\r"
                                     "--Cut\\N251*Xx\r"
                                     "--Copy\\N252*Cc\r"
                                     "--Paste\\N253*Vv\r"
                                     "--Clear\\N254\r"
                                     ".\r";

static unsigned char fileMenuStr[] = ">>  File \\N2\r"
                                     "--New\\N258*Nn\r"
                                     "--Close\\N255V\r"
                                     "--Quit\\N256*Qq\r"
                                     ".\r";

static unsigned char appleMenuStr[] = ">>@\\XN1\r"
                                      "--About...\\N257V\r"
                                      ".\r";

static unsigned char gAboutMsg[] =
    "\x3d" "Mini-CAD 1.0\r"
           "Copyright 1989\r"
           "Byte Works, Inc.\r\r"
           "By Mike Westerfield";

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} } } }
};

static WmTaskRec gEvent;
static volatile unsigned short gDone;


static void doAlert(unsigned short kind, void *msg) {
    static unsigned char okStr[] = "\x02OK";
    static ItemTemplate button = {
        1, 36, 15, 0, 0, buttonItem, okStr, 0, 0, (void *)0
    };
    static ItemTemplate message = {
        100, 5, 100, 90, 280, itemDisable | statText, (void *)0, 0, 0, (void *)0
    };
    static AlertTemplate alertRec = {
        50, 180, 107, 460, 2, 0x80, 0x80, 0x80, 0x80,
        (void *)0, (void *)0, (void *)0,
        { (void *)0, (void *)0, (void *)0, (void *)0,
          (void *)0, (void *)0, (void *)0, (void *)0 }
    };
    SetForeColor(0);
    SetBackColor(15);
    message.itemDescr      = msg;
    alertRec.atItemList[0] = (void *)&button;
    alertRec.atItemList[1] = (void *)&message;
    alertRec.atItemList[2] = (void *)0;
    switch (kind) {
        case norml:   (void)Alert(&alertRec, (void *)0);        break;
        case stop:    (void)StopAlert(&alertRec, (void *)0);    break;
        case note:    (void)NoteAlert(&alertRec, (void *)0);    break;
        case caution: (void)CautionAlert(&alertRec, (void *)0); break;
        default: break;
    }
}


// Window-content def-proc.  The WM calls this with DBR set to our
// bank (Loader sets up the JSL chain).  We use GetWRefCon on the
// current port to know 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) {
    static NewWindowParm wp;
    unsigned short i = 0;
    while (i < maxWindows && gWindows[i].wPtr != (void *)0) i++;
    if (i >= maxWindows) return;
    gWindows[i].numLines = 0;

    unsigned char *p = (unsigned char *)&wp;
    for (unsigned short k = 0; k < sizeof wp; k++) p[k] = 0;
    wp.paramLength  = (unsigned short)sizeof wp;
    wp.wFrameBits   = 0x4007 | 0x0020 | 0x0080 | 0x0400 | 0x4000;  // fTitle+fClose+fVis+fMove+fGrow
    wp.wTitle       = gWindows[i].name;
    wp.wRefCon      = (unsigned long)i;
    wp.wMaxHeight   = 188;
    wp.wMaxWidth    = 615;
    wp.wPosition.v1 = (short)(25 + i * 10);
    wp.wPosition.h1 = (short)(10 + i * 10);
    wp.wPosition.v2 = (short)(180 + i * 10);
    wp.wPosition.h2 = (short)(600 + i * 10);
    wp.wContDefProc = (void *)&drawWindow;
    wp.wPlane       = topMost;
    gWindows[i].wPtr = NewWindow(&wp);
    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 menuAbout(void) {
    doAlert(note, gAboutMsg);
}


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

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

    Point firstPt;
    firstPt.h = gEvent.wmWhereH;
    firstPt.v = gEvent.wmWhereV;
    GlobalToLocal(&firstPt);
    MoveTo(firstPt.h, firstPt.v);
    LineTo(firstPt.h, firstPt.v);
    Point endPt = firstPt;

    EventRec ev;
    while (!GetNextEvent(mUpMask, &ev)) {
        Point cur;
        cur.h = ev.wmWhereH;
        cur.v = ev.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);
    }
}


static void handleMenu(unsigned short menuNum) {
    switch (menuNum) {
        case apple_About: menuAbout(); break;
        case file_Quit:   gDone = 1;   break;
        case file_New:    doNew();     break;
        case file_Close:  doClose();   break;
        default: break;
    }
    HiliteMenu(0, (unsigned short)(gEvent.wmTaskData >> 16));
}


static void initMenus(void) {
    InsertMenu(NewMenu(editMenuStr),  0);
    InsertMenu(NewMenu(fileMenuStr),  0);
    InsertMenu(NewMenu(appleMenuStr), 0);
    FixAppleMenu(1);
    FixMenuBar();
    DrawMenuBar();
}


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

    paintDesktopBackdrop();
    initMenus();
    gEvent.wmTaskMask = 0x1FFFL;
    ShowCursor();

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

    gDone = 0;
    unsigned short watchdog = 0;
    do {
        unsigned short event = TaskMaster(0x076E, &gEvent);
        switch (event) {
            case wInSpecial:
            case wInMenuBar:
                handleMenu((unsigned short)gEvent.wmTaskData);
                break;
            case wInGoAway:
                doClose();
                break;
            case wInContent:
                sketch();
                break;
            default:
                break;
        }
        watchdog++;
    } while (!gDone && watchdog < 4000);

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