/* 
    abCalcNDA.c
        By: Jeremy Rand
 */


#pragma nda NDAOpen NDAClose NDAAction NDAInit -1 0xFFFF "  abCalc\\H**"


#include <orca.h>
#include <GSOS.h>
#include <Locator.h>
#include <QuickDraw.h>
#include <Window.h>
#include <Desk.h>
#include <Resources.h>
#include <Memory.h>
#include <Loader.h>
#include <Control.h>
#include <Event.h>
#include <List.h>
#include <Sane.h>
#include <LineEdit.h>
#include <Scrap.h>

#include <string.h>
#include <stdlib.h>

#include "nda/abCalcNDA.h"
#include "abCalc.h"
#include "abCStack.h"
#include "abCError.h"

#include "expr/abCExpr.h"

#include "ops/abCOp.h"


#define MIN_STACK_ITEMS 4

void UpdateStack(void);


typedef struct listElement {
    char *memPtr;
    Byte memFlag;
} listElement;


static BOOLEAN gListStarted = FALSE;
static Handle gSANEDirectPage = NULL;

static BOOLEAN gCalcActive = FALSE;
static GrafPortPtr gCalcWinPtr = NULL;
static unsigned int gUserId;
static unsigned int gResourceId;
listElement *gOpList = NULL;
listElement gStackList[AB_CALC_STACK_DEPTH];
abCalcExpr gNDAExpr;

static Str255 gStrBuf;
static int gSelectedStackItem = -1;

char *gBuffer = NULL;


void NDAClose(void)
{
    int i;

    if (gCalcActive) {
        CloseWindow(gCalcWinPtr);
        gCalcWinPtr = NULL;
        gCalcActive = FALSE;
    }

    if (gBuffer != NULL) {
        free(gBuffer);
        gBuffer = NULL;
    }

    if (gOpList != NULL) {
        free(gOpList);
        gOpList = NULL;
    }

    for (i = 0; i < AB_CALC_STACK_DEPTH; i++) {
        if (gStackList[i].memPtr != NULL) {
            free(gStackList[i].memPtr);
            gStackList[i].memPtr = NULL;
        }
    }

    CloseResourceFile(gResourceId);
    ResourceShutDown();
}


void NDAInit(int code)
{
    int i;

    if (code) {
        gCalcActive = FALSE;
        gUserId = MMStartUp();

        if (!ListStatus()) {
            LoadOneTool(0x1c, 0);
            ListStartUp();
            gListStarted = TRUE;
        }

        if (!SANEStatus()) {
            LoadOneTool(0x0a, 0);
            gSANEDirectPage = NewHandle(256, gUserId, 
                    attrBank | attrFixed | attrLocked | attrPage, NULL);
            SANEStartUp((Word) *gSANEDirectPage);
        }

        abCalcInit();

        for (i = 0; i < AB_CALC_STACK_DEPTH; i++) {
            gStackList[i].memPtr = NULL;
        }
    } else {
        if (gSANEDirectPage) {
            SANEShutDown();
            DisposeHandle(gSANEDirectPage);
            UnloadOneTool(0x0a);
            gSANEDirectPage = NULL;
        }

        if (gListStarted) {
            ListShutDown();
            UnloadOneTool(0x1c);
            gListStarted = FALSE;
        }
    }
}


#pragma databank 1
void DrawContents(void)
{
    PenNormal();
    DrawControls(GetPort());
}
#pragma databank 0


GrafPortPtr NDAOpen(void)
{
    Pointer pathToSelf;
    unsigned int oldResourceApp;
    LevelRecGS levelDCB;
    unsigned int oldLevel;
    SysPrefsRecGS prefsDCB;
    unsigned int oldPrefs;
    int numOps;
    int i;
    Handle opListCtl;

    if (gCalcActive)
        return NULL;

    oldResourceApp = GetCurResourceApp();
    ResourceStartUp(gUserId);
    pathToSelf = LGetPathname2(gUserId, 1);

    levelDCB.pCount = 2;
    GetLevelGS(&levelDCB);
    oldLevel = levelDCB.level;
    levelDCB.level = 0;
    SetLevelGS(&levelDCB);

    prefsDCB.pCount = 1;
    GetSysPrefsGS(&prefsDCB);
    oldPrefs = prefsDCB.preferences;
    prefsDCB.preferences = (prefsDCB.preferences & 0x1fff) | 0x8000;
    SetSysPrefsGS(&prefsDCB);

    gResourceId = OpenResourceFile(readEnable, NULL, pathToSelf);

    gCalcWinPtr = NewWindow2("\p abCalc ", 0, DrawContents, NULL, refIsResource,
        abCalcWinNum, rWindParam1);

    SetSysWindow(gCalcWinPtr);
    ShowWindow(gCalcWinPtr);
    SelectWindow(gCalcWinPtr);
    SetPort(gCalcWinPtr);

    if (gOpList == NULL) {
        numOps = abCalcOpNumOps();
        gOpList = malloc(sizeof(*gOpList) * numOps);
        for (i = 0; i < numOps; i++) {
            gOpList[i].memPtr = abCalcOpNth(i)->name;
            gOpList[i].memFlag = 0;
        }
    }

    opListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcOpList);
    NewList2(NULL, 1, (Ref)gOpList, 0, numOps, opListCtl);

    UpdateStack();

    gCalcActive = TRUE;

    prefsDCB.preferences = oldPrefs;
    SetSysPrefsGS(&prefsDCB);

    levelDCB.level = oldLevel;
    SetLevelGS(&levelDCB);

    SetCurResourceApp(oldResourceApp);

    return gCalcWinPtr;
}


void UpdateStack(void)
{
    Handle stackListCtl;
    int i;
    int numToDisplay = abCalcStackNumItems();

    if (numToDisplay < MIN_STACK_ITEMS) {
        numToDisplay = MIN_STACK_ITEMS;
    }

    stackListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcStackList);

    for (i = numToDisplay - 1; i >= 0; i--) {
        if (gStackList[i].memPtr == NULL) {
            gStackList[i].memPtr = malloc(AB_CALC_EXPR_STRING_MAX + 8);
        }
        abCalcStackExprStringAt(numToDisplay - i - 1, gStackList[i].memPtr,
                TRUE);
        gStackList[i].memFlag = 0;
    }

    NewList2(NULL, numToDisplay - 3, (Ref)gStackList, 0, numToDisplay, stackListCtl);
    gSelectedStackItem = -1;
}


BOOLEAN ErrorRaised(void)
{
    char *errorString = abCalcGetError();
    if (errorString == NULL) {
        return FALSE;
    }

    AlertWindow(awCString+awResource, (Pointer)&errorString, abCalcErrorAlert);

    abCalcClearError(); 
    return TRUE;
}


void PushCalcEntry(CtlRecHndl entryBox)
{
    abCalcOp *op;

    GetLETextByID(gCalcWinPtr, abCalcEntryBox, &gStrBuf);
    
    if (gStrBuf.textLength > 0) {
        gStrBuf.text[gStrBuf.textLength] = '\0';

        op = abCalcOpLookup(gStrBuf.text);

        if (op != NULL) {
            op->execute();
        } else if (abCalcParseExpr(&gNDAExpr, gStrBuf.text) != NULL) {
            abCalcStackExprPush(&gNDAExpr);
        } else {
            LERecHndl leHandle;

            HLock((Handle)entryBox);
            leHandle = (LERecHndl)(*entryBox)->ctlData;
            HUnlock((Handle)entryBox);

            LESetSelect(0, gStrBuf.textLength, leHandle);
            abCalcRaiseError(abCalcSyntaxError, NULL);
            return;
        }
    
        gStrBuf.textLength = 0;
        SetLETextByID(gCalcWinPtr, abCalcEntryBox, &gStrBuf);
    }
}


void ExecCalcCmd(char *cmd)
{
    abCalcOp *op = abCalcOpLookup(cmd);
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);

    PushCalcEntry(entryBox);

    if ((!ErrorRaised()) &&
        (op != NULL)) {
        op->execute();
    }

    ErrorRaised();
    
    UpdateStack();
}


void InsertChar(CtlRecHndl entryBox, char ch)
{
    LERecHndl leHandle;

    HLock((Handle)entryBox);
    leHandle = (LERecHndl)(*entryBox)->ctlData;
    HUnlock((Handle)entryBox);

    LEDelete(leHandle);
    LEInsert(&ch, 1, leHandle);
}


void ExecMinusButton(void)
{
    int i;
    char aChar;
    BOOLEAN doCmd = FALSE;
    BOOLEAN dotSeen = FALSE;

    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);
    GetLETextByID(gCalcWinPtr, abCalcEntryBox, &gStrBuf);

    if (gStrBuf.textLength > 0) {
        doCmd = TRUE;

        gStrBuf.text[gStrBuf.textLength] = '\0';

        aChar = gStrBuf.text[gStrBuf.textLength - 1];
        if (((aChar == 'e') ||
             (aChar == 'E')) &&
            (gStrBuf.textLength > 1)) {
            doCmd = FALSE;
            for (i = 0; i < gStrBuf.textLength - 1; i++) {
                switch (gStrBuf.text[i]) {
                    case '-':
                        if (i != 0) {
                            doCmd = TRUE;
                        }
                        break;

                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        break;

                    case '.':
                        if (dotSeen) {
                            doCmd = TRUE;
                        } else {
                            dotSeen = TRUE;
                        }
                        break;

                    default:
                        doCmd = TRUE;
                        break;
                }
                if (doCmd)
                    break;
            }
        }
    }

    if (doCmd) {
        ExecCalcCmd("-");
    } else {
        InsertChar(entryBox, '-');
    }

    return;
}


void HandleEntryBox(void)
{
    Handle stackListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcStackList);
    if (gSelectedStackItem != -1) {
        ResetMember2(stackListCtl);
        DrawMember2(gSelectedStackItem, stackListCtl);
        gSelectedStackItem = -1;
    }
}


void HandleStackClick(void)
{
    Handle stackListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcStackList);
    int nthOp;
    int numStackItems = abCalcStackNumItems();
    int numDisplayed = numStackItems;
    int selectedStackItem = -1;

    nthOp = NextMember2(0, stackListCtl);
    if (nthOp == gSelectedStackItem) {
        ResetMember2(stackListCtl);
        DrawMember2(gSelectedStackItem, stackListCtl);
        gSelectedStackItem = -1;
        nthOp = NextMember2(0, stackListCtl);
    }
    if (nthOp == 0)
        return;

    if (numDisplayed < MIN_STACK_ITEMS)
        numDisplayed = MIN_STACK_ITEMS;

    selectedStackItem = numDisplayed + 1 - nthOp;

    if (selectedStackItem > numStackItems) {
        ResetMember2(stackListCtl);
        DrawMember2(nthOp, stackListCtl);
        gSelectedStackItem = -1;
    } else {
        gSelectedStackItem = nthOp;
    }
}


void HandleOpClick(void)
{
    Handle opListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcOpList);
    int nthOp = ResetMember2(opListCtl);
    abCalcOp *op;

    if (nthOp > 0) {
        nthOp--;
        
        op = abCalcOpNth(nthOp);
        if (op != NULL) {
            CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);
            PushCalcEntry(entryBox);

            if (!ErrorRaised()) {
                op->execute();
            }

            ErrorRaised();

            UpdateStack();
        }

        DrawMember2(nthOp + 1, opListCtl);
    }
}


void HandleControl(EventRecord *event)
{
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);
    SetPort(gCalcWinPtr);

    switch (event->wmTaskData4) {
        case abCalcBtn0:
            InsertChar(entryBox, '0');
            break;

        case abCalcBtn1:
            InsertChar(entryBox, '1');
            break;

        case abCalcBtn2:
            InsertChar(entryBox, '2');
            break;

        case abCalcBtn3:
            InsertChar(entryBox, '3');
            break;

        case abCalcBtn4:
            InsertChar(entryBox, '4');
            break;

        case abCalcBtn5:
            InsertChar(entryBox, '5');
            break;

        case abCalcBtn6:
            InsertChar(entryBox, '6');
            break;

        case abCalcBtn7:
            InsertChar(entryBox, '7');
            break;

        case abCalcBtn8:
            InsertChar(entryBox, '8');
            break;

        case abCalcBtn9:
            InsertChar(entryBox, '9');
            break;

        case abCalcBtnEnter:
            PushCalcEntry(entryBox);
            ErrorRaised();
            UpdateStack();
            break;

        case abCalcBtnDot:
            InsertChar(entryBox, '.');
            break;

        case abCalcBtnNum:
            InsertChar(entryBox, '#');
            break;

        case abCalcBtnAdd:
            ExecCalcCmd("+");
            break;

        case abCalcBtnSub:
            ExecMinusButton();
            break;

        case abCalcBtnMult:
            ExecCalcCmd("*");
            break;

        case abCalcBtnDiv:
            ExecCalcCmd("/");
            break;

        case abCalcBtnPow:
            ExecCalcCmd("^");
            break;

        case abCalcBtnA:
            InsertChar(entryBox, 'A');
            break;

        case abCalcBtnB:
            InsertChar(entryBox, 'B');
            break;

        case abCalcBtnC:
            InsertChar(entryBox, 'C');
            break;

        case abCalcBtnD:
            InsertChar(entryBox, 'D');
            break;

        case abCalcBtnE:
            InsertChar(entryBox, 'E');
            break;

        case abCalcBtnF:
            InsertChar(entryBox, 'F');
            break;

        case abCalcEntryBox:
            HandleEntryBox();
            break;

        case abCalcStackList:
            HandleStackClick();
            break;

        case abCalcOpList:
            HandleOpClick();
            break;
    }
}


void DoCut(void)
{
    LERecHndl leHandle;
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);

    HLock((Handle)entryBox);
    leHandle = (LERecHndl)(*entryBox)->ctlData;
    HUnlock((Handle)entryBox);

    LECut(leHandle);
    LEToScrap();
}


void DoCopy(void)
{
    Handle stackListCtl = (Handle)GetCtlHandleFromID(gCalcWinPtr, abCalcStackList);
    LERecHndl leHandle;
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);

    int numStackItems = abCalcStackNumItems();
    int numDisplayed = numStackItems;
    int selectedStackItem;

    if (gSelectedStackItem == -1) {
        HLock((Handle)entryBox);
        leHandle = (LERecHndl)(*entryBox)->ctlData;
        HUnlock((Handle)entryBox);

        LECopy(leHandle);
        LEToScrap();
        return;
    }

    if (numDisplayed < MIN_STACK_ITEMS)
        numDisplayed = MIN_STACK_ITEMS;

    selectedStackItem = numDisplayed - gSelectedStackItem;

    if (gBuffer == NULL) {
        gBuffer = malloc(AB_CALC_EXPR_STRING_MAX);
    }

    if ((selectedStackItem >= 0) &&
        (selectedStackItem < numStackItems) &&
        (abCalcStackExprStringAt(selectedStackItem, gBuffer, FALSE) != NULL)) {
        ZeroScrap();
        PutScrap(strlen(gBuffer), textScrap, gBuffer);
    }

    SetPort(gCalcWinPtr);
    ResetMember2(stackListCtl);
    DrawMember2(gSelectedStackItem, stackListCtl);
    gSelectedStackItem = -1;
}


void DoPaste(void)
{
    LERecHndl leHandle;
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);

    HLock((Handle)entryBox);
    leHandle = (LERecHndl)(*entryBox)->ctlData;
    HUnlock((Handle)entryBox);

    LEFromScrap();
    LEPaste(leHandle);
}


void DoClear(void)
{
    LERecHndl leHandle;
    CtlRecHndl entryBox = GetCtlHandleFromID(gCalcWinPtr, abCalcEntryBox);

    HLock((Handle)entryBox);
    leHandle = (LERecHndl)(*entryBox)->ctlData;
    HUnlock((Handle)entryBox);

    LEDelete(leHandle);
}


void HandleMenu(int menuItem)
{
    SetPort(gCalcWinPtr);

    switch (menuItem) {
        case cutAction:
            DoCut();
            break;

        case copyAction:
            DoCopy();
            break;

        case pasteAction:
            DoPaste();
            break;

        case clearAction:
            DoClear();
            break;

        default:
            break;
    }
}


void HandleKey(EventRecord *event)
{
    BOOLEAN appleKeyPressed = ((event->modifiers & appleKey) != 0);
    char key = (event->message & 0xff);

    if (!appleKeyPressed)
        return;

    switch (key) {
        case 'C':
        case 'c':
            DoCopy();
            break;

        case 'X':
        case 'x':
            DoCut();
            break;

        case 'V':
        case 'v':
            DoPaste();
            break;
    }
}


BOOLEAN NDAAction(EventRecord *sysEvent, int code)
{
    int event;
    static EventRecord localEvent;
    unsigned int eventCode;
    BOOLEAN result = FALSE;

    switch (code) {
        case runAction:
            return result;

        case eventAction:
            BlockMove((Pointer)sysEvent, (Pointer)&localEvent, 16);
            localEvent.wmTaskMask = 0x001FFFFF;
            eventCode = TaskMasterDA(0, &localEvent);
            switch(eventCode) {
                case updateEvt:
                    BeginUpdate(gCalcWinPtr);
                    DrawContents();
                    EndUpdate(gCalcWinPtr);
                    break;

                case wInControl:
                    HandleControl(&localEvent);
                    break;

                case keyDownEvt:
                case autoKeyEvt:
                    HandleKey(&localEvent);
                    break;
            }
            break;

        case cutAction:
        case copyAction:
        case pasteAction:
        case clearAction:
            result = TRUE;
            HandleMenu(code);
            break;
    }

    return result;
}
