/* -----------------------------------------------------------------------------

 WarpSPELL ©1996 Dietmar Eilert

 Spell checking syntax parser. Dice:

 DMAKE

 -------------------------------------------------------------------------------

*/

#include "defs.h"

/// "Header stuff"

// Buffer handles are allocated for each text buffer to keep track of ressources:

struct BufferHandle {

    struct EditConfig     *bh_EditConfig;            // pointer to text data
    struct GlobalConfig   *bh_GlobalConfig;          // editor configuration
    struct SyntaxChunk    *bh_SyntaxStack;           // parser output
    struct RefreshRequest  bh_RefreshRequest;        // display refresh request
    UBYTE                  bh_Command[4096];         // ISpell command buffer
    UBYTE                  bh_Results[4096];         // ISpell result buffer
    struct MsgPort        *bh_Port;                  // ISpell reply port
};

// AutoConfig semaphore coordinates ISpell usage

struct AutoConfig {

    struct SignalSemaphore Semaphore;
    UWORD                  Users;                    // ISpell users
    ULONG                  Words;                    // new words counter
};

#define EMPTY_STACK ((struct SyntaxChunk *)~0)       // empty stack flag

///
/// "Prototype"

// library functions

Prototype LibCall struct ParserData     *MountScanner(void);
Prototype LibCall ULONG                  StartScanner(__A0 struct GlobalConfig *, __A1 struct EditConfig *, __D0 struct SyntaxChunk *);
Prototype LibCall ULONG                  CloseScanner(__D0 ULONG);
Prototype LibCall void                   FlushScanner(__D0 ULONG);
Prototype LibCall void                   SetupScanner(__A0 struct GlobalConfig  *);
Prototype LibCall struct RefreshRequest *BriefScanner(__D0 ULONG, __A0 struct ScannerNotify *);
Prototype LibCall struct SyntaxChunk    *ParseLine   (__D0 ULONG, __A0 struct LineNode *, __D1 ULONG);
Prototype LibCall void                   UnparseLines(__A0 struct LineNode *, __D0 ULONG);
Prototype LibCall void                   ParseSection(__D0 ULONG, __A0 struct LineNode *, __D1 ULONG);

// private functions

Prototype struct SyntaxChunk            *ParseString(UBYTE *, UWORD, ULONG);
Prototype struct SyntaxChunk            *DupStack(struct SyntaxChunk *);
Prototype ULONG                         *SendRexxCommand(UBYTE *, UBYTE *, struct MsgPort *replyPort, UBYTE *);
Prototype BOOL                           CheckThisWord(ULONG, UBYTE *, UWORD);
Prototype void                           AlertInfo(UBYTE *);
Prototype struct Window                 *OpenMessageWin(UBYTE *, BOOL);
Prototype struct Window                 *Message(UBYTE *, BOOL, struct Window *);
Prototype struct MsgPort                *RunISpell(void);
Prototype BOOL                           ExistPath(UBYTE *);
Prototype BOOL                           DoExecute(UBYTE *, BOOL, UBYTE *, UBYTE *, ULONG, WORD);
Prototype struct MsgPort *               RunISpell(void);
Prototype struct MsgPort                *WaitForISpell(void);
Prototype void                           StopISpell(void);
Prototype struct FileInfoBlock          *FileInfo(UBYTE *, struct FileInfoBlock *);

///
/// "Globals"

struct SignalSemaphore ISpellSemaphore;
BOOL                   IsLetter  [256];
BOOL                   IsBoundary[256];
BOOL                   QuickCheck;

///
/// "Library functions"

/* ------------------------------- MountScanner --------------------------------

 Called by the editor before first usage of a scanner. Return a description of
 our abilities.

*/

__geta4 LibCall struct ParserData *
MountScanner()
{
    static UBYTE version[] = "$VER: WarpSPELL 2.5 (" __COMMODORE_DATE__ ")";

    static struct ParserData parserData;

    // syntax elements understood by parser

    static UBYTE *levelNames[] = { "Standard text", "Spelling error", NULL };

    static UBYTE *example[] = {

        "Aaron      ",
        "aback      ",
        "abandon    ",
        "abase      ",
        "abash      ",
        "abate      ",
        "abatement  ",
        "abbe       ",
        "abbess     ",
        "abbey      ",
        "abbot      ",
        "abbreviate ",
        "abdicate   ",
        "abdominal  ",
        "abduct     ",
        "adenoids   ",
        "already    ",
        "alsatian   ",
        "also       ",
        "altar      ",
        "alter      ",

        NULL
    };

    // color suggestions

    static ULONG levelColors[] = {

        MAKE_RGB4(0,  0,  0),
        MAKE_RGB4(15, 15, 0),
    };

    parserData.pd_Release  = SCANLIBVERSION;
    parserData.pd_Version  = 1;
    parserData.pd_Serial   = 0;
    parserData.pd_Info     = "WarpSPELL 2.5";
    parserData.pd_Levels   = 2;
    parserData.pd_Names    = levelNames;
    parserData.pd_Colors   = levelColors;
    parserData.pd_Flags    = SCPRF_SYNTAXCACHE;
    parserData.pd_Example  = example;

    return(&parserData);
}


/* ------------------------------- StartScanner --------------------------------

 Called by the editor after a new text buffer has been created. We allocate a
 buffer to hold text-specific data. The buffer address is returned as handle.

*/

LibCall ULONG
StartScanner(__A0 struct GlobalConfig *globalConfigPtr, __A1 struct EditConfig *editConfigPtr, __D0 struct SyntaxChunk *syntaxStack)
{
    struct BufferHandle *handle;

    if (handle = AllocVec(sizeof(struct BufferHandle), MEMF_PUBLIC | MEMF_CLEAR)) {

        if (handle->bh_Port = CreateMsgPort()) {

            handle->bh_GlobalConfig = globalConfigPtr;
            handle->bh_EditConfig   = editConfigPtr;
            handle->bh_SyntaxStack  = syntaxStack;

            if (RunISpell())

                return((ULONG)handle);

            else {

                DeleteMsgPort(handle->bh_Port);

                FreeVec(handle);
            }
        }
        else
            FreeVec(handle);
    }

    return(NULL);
}


/* ------------------------------- CloseScanner --------------------------------

 Called by the editor if a text buffer is about to be closed. Deallocate buffer
 specific 'global' data.

*/

LibCall ULONG
CloseScanner(__D0 ULONG scanID)
{
    if (scanID) {

        struct BufferHandle *handle = (struct BufferHandle *)scanID;

        if (handle->bh_Port)

            DeleteMsgPort(handle->bh_Port);

        StopISpell();

        FreeVec(handle);
    }

    return(0);
}


/* ------------------------------- FlushScanner --------------------------------

 Called by the editor in low memory situations: we are supposed to free as much
 memory as possible.

*/

LibCall void
FlushScanner(__D0 ULONG scanID)
{
    struct BufferHandle *handle;
    struct EditConfig   *config;
    struct LineNode     *lineNode;

    handle = (struct BufferHandle *)scanID;
    config = handle->bh_EditConfig;

    if (lineNode = config->TextNodes)

        UnparseLines(lineNode, config->Lines);
}


/* ------------------------------- SetupScanner --------------------------------

 Called by the editor if the user wants to change the scanner's configuration.
 We do not support user configuration (parserData.pd_Flags: SCPRF_CONFIGWIN flag
 unset).

*/

LibCall void
SetupScanner(__A0 globalConfigPtr)
{
    ;
}


/* ------------------------------- BriefScanner --------------------------------

 Called to notify a context scanner if lines have been added, deleted or
 modified. We aren't a context scanner (parserData.pd_Flags: SCPRF_CONTEXT
 flag unset), so we won't ever have to request additional display requests:
 the editor's built-in refresh of damage regions is sufficient.

*/

LibCall struct RefreshRequest *
BriefScanner(__D0 ULONG scanID, __A0 struct ScannerNotify *notify)
{
    return(NULL);
}


/* --------------------------------- ParseLine ---------------------------------

 Parse a line, build a syntax description

*/

LibCall struct SyntaxChunk *
ParseLine(__D0 ULONG scanID, __A0 struct LineNode *lineNode, __D1 ULONG line)
{
    if (IS_FOLD(lineNode))

        return(NULL);

    else if (lineNode->Len) {

        // line not yet parsed ?

        if (lineNode->UserData == NULL) {

            struct SyntaxChunk *syntaxStack = ParseString(lineNode->Text, lineNode->Len, scanID);

            if (syntaxStack == EMPTY_STACK)
                lineNode->UserData = EMPTY_STACK;
            else
                lineNode->UserData = DupStack(syntaxStack);
        }

        if (lineNode->UserData == EMPTY_STACK)
            return((struct SyntaxChunk *)NULL);
        else
            return((struct SyntaxChunk *)lineNode->UserData);
    }
    else
        return(NULL);
}


/* -------------------------------- UnparseLines -------------------------------

 Called by the editor if lines are to be deleted. We are supposed to free
 private data attached to the lines.

*/

LibCall void
UnparseLines(__A0  struct LineNode *lineNode, __D0 ULONG lines)
{
    while (lines--) {

        // free syntax cache

        if (lineNode->UserData) {

            if (lineNode->UserData != (APTR)EMPTY_STACK)
                FreeVec((APTR)lineNode->UserData);

            lineNode->UserData = NULL;
        }

        // free folded subblock

        if (IS_FOLD(lineNode)) {

            struct Fold *fold = (struct Fold *)lineNode->SpecialInfo;

            UnparseLines(fold->TextNodes, fold->Lines);
        }

        ++lineNode;
    }
}


/* -------------------------------- ParseSection -------------------------------

 Called by the editor if lines are to be displayed. The scanner is encouraged to
 preparse the lines.

*/

LibCall void
ParseSection(__D0 ULONG scanID, __A0  struct LineNode *lineNode, __D1 ULONG lines)
{
    struct Window *win;
    ULONG          done;
    UBYTE          title[80];

    if (lines > 100)
        win = Message("Checking text...", FALSE, NULL);
    else
        win = NULL;

    for (done = 0; done < lines; ++done) {

        if (win) {

            if ((done & 63) == 63) {

                sprintf(title, "Words (%d/%d)", done, lines);

                SetWindowTitles(win, title, (UBYTE *)~0);
            }
        }

        // fold headers have to be ignored

        if (IS_FOLD(lineNode) == FALSE) {

            // line not yet parsed ?

            if (lineNode->Len)

                if (lineNode->UserData == NULL)

                    lineNode->UserData = DupStack(ParseString(lineNode->Text, lineNode->Len, scanID));
        }

        ++lineNode;
    }

    if (win)
        CloseWindow(win);
}

///
/// "Private"

/* -------------------------------- ParseString --------------------------------

 Parse a string, build a syntax description. This is a simple example only:
 C++-Comments (// ....) are highlighted. Return EMPTY_STACK in case there is
 nothing to highlight.

*/

struct SyntaxChunk *
ParseString(UBYTE *text, UWORD len, ULONG scanID)
{
    if (len) {

        struct SyntaxChunk *syntaxStack = ((struct BufferHandle *)scanID)->bh_SyntaxStack;

        UWORD indent, elements, wordLen;

        elements = 0;

        // leading spaces have to be ignored

        for (indent = 0; len && (*text == 32); ++indent, --len)

            ++text;

        // trailing spaces have to be ignored

        while (len && (text[len - 1] == 32))

            --len;

        // check every word of this line

        while (len) {

            // ignore spaces

            while (len && (IsLetter[*text] == FALSE)) {

                ++text;
                ++indent;
                --len;
            }

            if (len) {

                // get next word

                for (wordLen = 1; wordLen < len; ++wordLen) {

                    UBYTE *check = text + wordLen;

                    // end of word detected ?

                    if (IsLetter[*check] == FALSE) {

                        // verify end of word (boundary characters can be space OR letter)

                        if (IsBoundary[*check]) {

                            if ((wordLen + 1) < len) {

                                if (IsLetter[*++check] == FALSE)

                                    break;
                            }
                            else
                                break;
                        }
                        else
                            break;
                    }
                }

                // check word

                if (CheckThisWord(scanID, text, wordLen)) {

                    // highlight word

                    syntaxStack[elements].sc_Start = indent;
                    syntaxStack[elements].sc_End   = indent + (wordLen - 1);
                    syntaxStack[elements].sc_Level = 1;

                    ++elements;
                }

                // move to next word

                text   += wordLen;
                indent += wordLen;
                len    -= wordLen;
            }
        }

        if (elements) {

            // terminate syntax stack

            syntaxStack[elements].sc_Start = FALSE;
            syntaxStack[elements].sc_End   = FALSE;
            syntaxStack[elements].sc_Level = FALSE;

            return(syntaxStack);

        }
    }

    return(EMPTY_STACK);
}

/* --------------------------------- DupStack ----------------------------------

 Duplicate syntax stack (to be FreeVec'ed). Return NULL in case of failure.

*/

struct SyntaxChunk *
DupStack(syntaxStack)

struct SyntaxChunk *syntaxStack;
{
    if (syntaxStack && (syntaxStack != EMPTY_STACK)) {

        struct SyntaxChunk *chunk;
        UWORD               elements;

        // determine stack size

        for (elements = 0, chunk = syntaxStack; chunk->sc_Level; ++chunk)

            ++elements;

        // create copy of syntax stack (to be attached to a text line by the caller)

        if (elements) {

            ULONG size = (++elements) * sizeof(struct SyntaxChunk);

            chunk = syntaxStack;

            if (syntaxStack = AllocVec(size, MEMF_PUBLIC))
                movmem(chunk, syntaxStack, size);
        }
        else
            syntaxStack = EMPTY_STACK;
    }

    return(syntaxStack);
}

/* ------------------------------- CheckThisWord -------------------------------

 Check word <check> of length <len>. Return TRUE if word is not known. Ignore
 single letters.

*/

__geta4 BOOL
CheckThisWord(scanID, check, len)

UBYTE  *check;
UWORD   len;
ULONG   scanID;
{
    BOOL error = FALSE;

    if (len > 1) {

        struct BufferHandle *handle = (struct BufferHandle *)scanID;

        UBYTE *command = handle->bh_Command;

        movmem(check, command, len);

        command[len] = 0;

        strins(command, "QUICKCHECK ");

        // make ISpell check command quickly

        if (SendRexxCommand("IRexxSpell", command, handle->bh_Port, handle->bh_Results)) {

            // unknown word (not 'ok') ?

            if (stricmp(handle->bh_Results, "ok")) {

                if (QuickCheck)

                    error = TRUE;

                else if (SendRexxCommand("IRexxSpell", command + 5, handle->bh_Port, handle->bh_Results)) {

                    switch (*handle->bh_Results) {

                        case '&':                        // spelling error, near misses available
                        case '?':                        // spelling error, no near misses

                            error = TRUE;
                            break;

                        case '*':                        // valid word
                        case '+':                        // valid after affix removal
                        case '-':                        // valid compound

                            error = FALSE;
                            break;

                        default:

                            error = FALSE;               // unknown return code (assume that word is valid)
                    }
                }
            }
        }
    }

    return(error);
}


/* ---------------------------------- SendRexxCommand -------------------------

 Send ARexx message & wait for answer. Return pointer to result or NULL.

*/

__geta4 ULONG *
SendRexxCommand(port, cmd, replyPort, buffer)

struct MsgPort *replyPort;
UBYTE          *cmd, *port, *buffer;
{
    struct MsgPort *rexxport;

    Forbid();

    if (rexxport = FindPort(port)) {

        struct RexxMsg *rexxMsg, *answer;

        if (rexxMsg = CreateRexxMsg(replyPort, NULL, NULL)) {

            if (rexxMsg->rm_Args[0] = CreateArgstring(cmd, strlen(cmd))) {

                static ULONG result;

                rexxMsg->rm_Action = RXCOMM | RXFF_RESULT;

                PutMsg(rexxport, &rexxMsg->rm_Node);

                do {

                    WaitPort(replyPort);

                    if (answer = (struct RexxMsg *)GetMsg(replyPort))

                        result = answer->rm_Result1;

                } while (!answer);

                Permit();

                if (answer->rm_Result1 == RC_OK) {

                    if (answer->rm_Result2) {

                        if (buffer)
                            strcpy(buffer, (char *)answer->rm_Result2);

                        DeleteArgstring((char *)answer->rm_Result2);
                    }
                }

                DeleteArgstring((char *)ARG0(answer));

                DeleteRexxMsg(answer);

                return(&result);
            }
        }
    }

    Permit();

    return(NULL);
}

/* --------------------------------- AlertInfo -----------------------------------

 Show alert. Returns TRUE if user pressed left button.

*/

__geta4 void
AlertInfo(text)

UBYTE *text;
{
    UBYTE *buffer;

    if (buffer = AllocVec(80, MEMF_PUBLIC | MEMF_CLEAR)) {

        buffer[1] = '\30';
        buffer[2] = '\25';

        strcpy(buffer + 3, text);

        DisplayAlert(RECOVERY_ALERT, buffer, 40);

        FreeVec(buffer);
    }
}


/* -------------------------------- OpenMessageWin -----------------------------

 Open info window

*/

struct Window *
OpenMessageWin(text, sync)

UBYTE *text;
BOOL   sync;
{
    struct TextAttr textAttr = { "topaz.font", 8, 0, FPF_DESIGNED };

    struct Window   *win;
    struct Screen   *screen;
    struct TextFont *font;
    WORD             len, width, height, fontW, fontH, x, y, displayW, displayH, xOffset, yOffset;

    displayW = FALSE;
    displayH = FALSE;

    // critical stuff (barely legal - don't do this at home, kids ;-)

    Forbid();

    if (screen = ((struct IntuitionBase *)IntuitionBase)->ActiveScreen) {

        struct Rectangle clip;

        if (QueryOverscan(GetVPModeID(&screen->ViewPort), &clip, OSCAN_TEXT)) {

            displayW = clip.MaxX - clip.MinX + 1;
            displayH = clip.MaxY - clip.MinY + 1;
        }

        xOffset = -screen->ViewPort.DxOffset;
        yOffset = -screen->ViewPort.DyOffset;

        textAttr = *screen->Font;
    }

    Permit();

    if (font = OpenDiskFont(&textAttr)) {

        fontW = font->tf_XSize;
        fontH = font->tf_YSize;

        CloseFont(font);
    }
    else
        fontW = fontH = textAttr.ta_YSize;

    len = strlen(text);

    if (len < 30)
        len = 30;

    width  = 20 + fontW * len;
    height = 20 + fontH;

    if (displayW) {

        x = ((displayW - width )>>1) + xOffset;
        y = ((displayH - height)>>1) + yOffset;
    }
    else
        x = y = 0;

    win = OpenWindowTags(NULL,

        WA_PubScreen,     screen,
        WA_Left,          x,
        WA_Top,           y,
        WA_InnerWidth,    width,
        WA_InnerHeight,   height,
        WA_Title,        "Words",
        WA_ScreenTitle,  "Words",
        WA_Flags,         WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_SMART_REFRESH | WFLG_ACTIVATE | WFLG_RMBTRAP,
        WA_GimmeZeroZero, TRUE,
        TAG_DONE
    );

    if (win) {

        if (font)
            SetFont(win->RPort, font);

        win = Message(text, sync, win);
    }

    return(win);
}


/* ---------------------------------- Message ----------------------------------

 Show message (assume that window is sufficiently sized)

*/

struct Window *
Message(text, sync, win)

struct Window *win;
UBYTE         *text;
BOOL           sync;
{
    if (win == NULL)

        win = OpenMessageWin(text, sync);

    else {

        struct RastPort *rast = win->RPort;

        SetAPen(rast, 0);
        SetRast(rast, 0);
        SetAPen(rast, 1);

        Move(rast, 10, 10 + rast->TxBaseline);

        Text(rast, text, strlen(text));

        if (sync) {

            Delay(150);

            CloseWindow(win);

            win = NULL;
        }
    }

    return(win);
}


/* --------------------------------- ExistPath -----------------------------------

 Check wether file/directory exists.

*/

BOOL
ExistPath(path)

UBYTE *path;
{
    BPTR lock;

    if (lock = Lock(path, ACCESS_READ))
        UnLock(lock);

    if (lock)
        return(TRUE);
    else
        return(FALSE);
}


/* --------------------------------- DoExecute ---------------------------------

 Run DOS command

*/

BOOL
DoExecute(cmd, async, output, directory, stack, prio)

UBYTE *cmd, *output, *directory;
ULONG  stack;
WORD   prio;
BOOL   async;
{
    BPTR newCurrentDir;
    BOOL success;

    success = FALSE;

    if (newCurrentDir = Lock(directory, SHARED_LOCK)) {

        BPTR inHandle, outHandle, oldCurrentDir;

        oldCurrentDir = CurrentDir(newCurrentDir);

        if (outHandle = Open(output, MODE_NEWFILE)) {

            struct MsgPort *oldct, *newct;

            if (IsInteractive(outHandle)) {

                newct = ((struct FileHandle *)BADDR(outHandle))->fh_Type;

                oldct = SetConsoleTask(newct);

                inHandle = Open("CONSOLE:", MODE_OLDFILE);

                SetConsoleTask(oldct);
            }
            else {

                newct = NULL;

                inHandle = Open("NIL:", MODE_OLDFILE);
            }

            if (inHandle) {

                struct TagItem tagItems[] = {

                    SYS_Output,     (ULONG)NULL,
                    SYS_Input,      (ULONG)NULL,
                    NP_ConsoleTask, (ULONG)NULL,
                    SYS_Asynch,     (ULONG)FALSE,
                    NP_StackSize,   (ULONG)8196,
                    NP_Priority,    (ULONG)0,
                    SYS_UserShell,  (ULONG)TRUE,
                    TAG_DONE
                };

                tagItems[0].ti_Data = (ULONG)outHandle;
                tagItems[1].ti_Data = (ULONG)inHandle;
                tagItems[2].ti_Data = (ULONG)newct;
                tagItems[3].ti_Data = (ULONG)async;
                tagItems[4].ti_Data = (ULONG)stack;
                tagItems[5].ti_Data = (ULONG)prio;

                if (SystemTagList(cmd, tagItems) != -1L)

                    success = TRUE;

                if (!(async && success))

                    Close(inHandle);
            }

            if (!(async && success))

                Close(outHandle);
        }

        CurrentDir(oldCurrentDir);

        UnLock(newCurrentDir);
    }

    return(success);
}


/* --------------------------------- FileInfo ----------------------------------

 Return file information or NULL (writes to <fib>)

*/

struct FileInfoBlock *
FileInfo(name, fib)

struct FileInfoBlock *fib;
UBYTE                *name;
{
    BPTR handle;

    if (handle = Lock(name, ACCESS_READ)) {

        if (Examine(handle, fib)) {

            UnLock(handle);

            return(fib);
        }
        else
            UnLock(handle);
    }

    return(NULL);
}

///
/// "ISpell"

/* --------------------------------- RunISpell ---------------------------------

 Run ISpell (if not yet running); increase user count

*/

struct MsgPort *
RunISpell()
{
    struct MsgPort    *port;
    struct AutoConfig *autoConfig;

    // serialize access within this library

    ObtainSemaphore(&ISpellSemaphore);

    Forbid();

    // ISpell running/starting already ?

    if (autoConfig = (struct AutoConfig *)FindSemaphore("WORDS")) {

        ObtainSemaphore(&autoConfig->Semaphore);

        ++autoConfig->Users;

        ReleaseSemaphore(&autoConfig->Semaphore);

        port = WaitForISpell();

        if (port == NULL)

            Message("STARTUP FAILURE", TRUE, NULL);
    }
    else {

        port = FindPort("IRexxSpell");

        // run ISpell on our own ?

        if (port == NULL) {

            struct AutoConfig *autoConfig;

            if (autoConfig = (struct AutoConfig *)AllocVec(sizeof(struct AutoConfig), MEMF_PUBLIC | MEMF_CLEAR)) {

                static UBYTE hashfile[255], language[255], personal[255], options[255], command[255], home[255];

                __aligned struct FileInfoBlock fib = { 0 };

                autoConfig->Semaphore.ss_Link.ln_Name = "WORDS";
                autoConfig->Semaphore.ss_Link.ln_Pri  = 0;
                autoConfig->Users                     = 1;

                AddSemaphore   (&autoConfig->Semaphore);
                ObtainSemaphore(&autoConfig->Semaphore);

                // set default language

                strcpy(language, "english");

                // set default options

                strcpy(options,  "-W 1");

                // did user provide special startup arguments for ISpell ?

                if (GetVar("WORDS.prefs", command, sizeof(command), GVF_GLOBAL_ONLY) != -1) {

                    if (*command) {

                        struct RDArgs *rdArgs, *args;

                        if (rdArgs = AllocDosObject(DOS_RDARGS, NULL)) {

                            ULONG argArray[] = { 0, 0, 0, 0, 0 };

                            // make LF-terminated copy of command string (required by dos/readArgs):

                            strcat(command, "\12");

                            rdArgs->RDA_Source.CS_Buffer = command;
                            rdArgs->RDA_Source.CS_Length = strlen(command);
                            rdArgs->RDA_Source.CS_CurChr = 0;
                            rdArgs->RDA_DAList           = 0;
                            rdArgs->RDA_Buffer           = NULL;

                            if (args = ReadArgs("LANGUAGE/K,QUICKCHECK/K,BOUNDARYCHARS/K,BEEP/N,ARGS/F", argArray, rdArgs)) {

                                if (argArray[0])             // LANGUAGE/K
                                    strcpy(language, (UBYTE *)argArray[0]);

                                if (argArray[4])             // ARGS/F
                                    strcpy(options,  (UBYTE *)argArray[4]);

                                FreeArgs(args);
                            }
                            else
                                Message("Syntax error", TRUE, NULL);

                            FreeDosObject(DOS_RDARGS, rdArgs);
                        }
                    }
                }

                // personal dictionary file (for new words)

                sprintf(personal, "/ispell/lib/.ispell_%s", language);

                // standard dictionary file

                sprintf(hashfile, "ispell:lib/%s.hash", language);

                // dictionary available ?

                if (FileInfo(hashfile, &fib)) {

                    ULONG avail = AvailMem(MEMF_LARGEST);

                    if (fib.fib_Size < avail) {

                        struct Window *win = Message("Starting ISpell...", FALSE, NULL);

                        // $HOME required by ISpell 3.1.18

                        if (GetVar("HOME", home, sizeof(home), GVF_GLOBAL_ONLY) == -1)

                            SetVar("HOME", "sys:", -1, GVF_GLOBAL_ONLY);

                        // run ISpell in host mode

                        if (*options)
                            sprintf(command, "ispell:bin/ispell -d%s -p%s -r %s", hashfile, personal, options);
                        else
                            sprintf(command, "ispell:bin/ispell -d%s -p%s -r", hashfile, personal);

                        // wait for completion of ISpell startup

                        if (DoExecute(command, TRUE, "NIL:", "ispell:", 32768, 1))

                            port = WaitForISpell();

                        if (port == NULL)

                            Message("STARTUP FAILURE", TRUE, win);

                        else if (win)

                            CloseWindow(win);
                    }
                    else {

                        if (fib.fib_Size < AvailMem(MEMF_ANY))
                            Message("Out of RAM (fragmentation error)", TRUE, NULL);
                        else
                            Message("Out of RAM", TRUE, NULL);
                    }
                }
                else
                    Message("Dictionary not found", TRUE, NULL);

                ReleaseSemaphore(&autoConfig->Semaphore);

                if (port == NULL)

                    StopISpell();
            }
        }
    }

    Permit();

    ReleaseSemaphore(&ISpellSemaphore);

    return(port);
}


/* ------------------------------- WaitForISpell -------------------------------

 Wait for ISpell

*/

struct MsgPort *
WaitForISpell()
{
    struct MsgPort *port;
    UWORD           try;

    for (try = 50; try-- && (port == NULL); Delay(10)) {

        Forbid();

        port = FindPort("IRexxSpell");

        Permit();
    }

    return(port);
}


/* -------------------------------- StopISpell ---------------------------------

 Decrement ISpell user count. Unload ISpell if unused.

*/

void
StopISpell()
{
    struct AutoConfig *autoConfig;

    Forbid();

    if (autoConfig = (struct AutoConfig *)FindSemaphore("WORDS")) {

        ObtainSemaphore(&autoConfig->Semaphore);

        if (autoConfig->Users)

            --autoConfig->Users;

        ReleaseSemaphore(&autoConfig->Semaphore);

        if (autoConfig->Users == 0) {

            struct MsgPort *port;

            RemSemaphore    (&autoConfig->Semaphore);
            ObtainSemaphore (&autoConfig->Semaphore);
            ReleaseSemaphore(&autoConfig->Semaphore);

            FreeVec(autoConfig);

            if (port = CreateMsgPort()) {

                if (autoConfig->Words) {

                    struct Screen *screen;
                    ULONG          lock;

                    // critical stuff (barely legal - don't do this at home, kids ;-)

                    lock = LockIBase(0);

                    // find frontmost screen

                    screen = ((struct IntuitionBase *)IntuitionBase)->ActiveScreen;

                    // keep system frozen

                    Forbid();

                    // enable rendering functions

                    UnlockIBase(lock);

                    if (rtEZRequestTags("Save new words to user dictionary ?", "_SAVE|_cancel", NULL, NULL, RT_Underscore, '_', RTEZ_ReqTitle, "Words", RT_Screen, screen, TAG_DONE) == 1)

                        SendRexxCommand("IRexxSpell", "ADD A", port, NULL);

                    Permit();
                }

                SendRexxCommand("IRexxSpell", "EXIT", port, NULL);

                DeleteMsgPort(port);
            }
        }
    }

    Permit();
}

///
