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

 GoldED API client example code, ©1995 Dietmar Eilert. Dice:

 dcc main.c -// -proto -mi -r -2.0 -l reqtoolss.lib -o ram:spell

 Note: Compiling this code requires reqtools includes & reqtools linker
       libraries. The ReqTools library is © Nico François.

 The following code adds spell checking capabilities to GoldED. It is  based  on
 the  ISpell package and the ISpell dictionary (available on Fish disks). ISpell
 has to be installed properly (within a valid command path) before you  can  use
 this  API  client.  This example uses synchronous ARexx communication: Requests
 are PutMsg()'ed to GoldED's port, followed by a WaitPort() to get the  editor's
 response.  This  works  fine since we need no ARexx communication after the API
 link has been established. If there were ARexx communication with GoldED  AFTER
 the link has been established (i.e. after sending the 'API PORT=...' command to
 register with GoldED), we would have  to  use  an  asynchronous  design  beeing
 capable  of  answering  incoming  API  messages while waiting for completion of
 ARexx requests sent to GoldED.

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

/// "includes"

#define Prototype extern

#include <exec/exec.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <intuition/intuition.h>
#include <utility/tagitem.h>
#include <libraries/gadtools.h>
#include <rexx/errors.h>
#include <rexx/rxslib.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/graphics_protos.h>
#include <clib/intuition_protos.h>
#include <clib/diskfont_protos.h>
#include <clib/rexxsyslib_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/reqtools_protos.h>
#include <clib/alib_protos.h>
#include <devices/audio.h>
#include "golded:api/include/golded.h"

Prototype void   main(int, char **);
Prototype BOOL   InitISpell(void);
Prototype void   HandleAPI(BOOL, BOOL, char *);
Prototype ULONG *SendRexxCommand(char *, char *, struct MsgPort *, char *);
Prototype void   CheckWord(struct APIMessage *, struct MsgPort *, BOOL, BOOL);
Prototype void   Beep(void);
Prototype UWORD  ComputeX(struct TextFont *, UWORD);
Prototype UWORD  ComputeY(struct TextFont *, UWORD);
Prototype char  *ShowSpell(char *, struct List *);
Prototype struct Node *SearchNode(struct List *, UWORD);

struct Library *ReqToolsBase;

///
/// "main"

void
main(int argc, char **argv)
{
    const char *version = "$VER: spell 1.5 (" __COMMODORE_DATE__ ")";

    struct RDArgs *rdArgs;

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

    if (rdArgs = ReadArgs("ASK/S,NOFLASH/S,HOST/K/A", args, NULL)) {

        if (ReqToolsBase = (struct ReqToolsBase *)OpenLibrary("reqtools.library", 0)) {

            if (InitISpell())
                HandleAPI(args[0], args[1], (char *)args[2]);

            CloseLibrary(ReqToolsBase);
        }

        FreeArgs(rdArgs);
    }
    else
        puts("syntax error: SPELL ASK/S,HOST/K/A");

    exit(0);
}

///
/// "init"

/* -------------------------------- IntiISpell ---------------------------------

 Prepare ISpell. Return TRUE if successful.

*/

BOOL
InitISpell()
{
    if (FindPort("IRexxSpell"))

        return(TRUE);

    else {

        struct TagItem tags[] = {SYS_Output, NULL, SYS_Input, NULL, SYS_Asynch, TRUE, TAG_DONE};

        if (SystemTagList("ispell -r", tags) != -1) {

            UWORD n;

            for (n = 0; n < 100; n++, Delay(10))
                if (FindPort("IRexxSpell"))
                    return(TRUE);
        }
    }

    puts("ISpell not available");

    return(FALSE);
}

///
/// "api management"

/* --------------------------------- HandleAPI ---------------------------------

 Register with GoldED & handle incoming API messages.

*/

void
HandleAPI(ask, noFlash, host)

BOOL ask, noFlash;
char *host;
{
    struct MsgPort *rexxPort, *apiPort;

    if (apiPort = CreateMsgPort()) {

        if (rexxPort = CreateMsgPort()) {

            char  command[255];
            ULONG *result;

            sprintf(command, "API PORT=%ld CLASS=%ld", apiPort, API_CLASS_ROOT | API_CLASS_KEY);

            if (result = SendRexxCommand(host, command, rexxPort, NULL)) {

                if (*result == RC_OK) {

                    BOOL active = TRUE;

                    do {

                        struct APIMessage *apiMsg, *nextMsg;

                        while (!(apiMsg = (struct APIMessage *)GetMsg(apiPort)))
                            WaitPort(apiPort);

                        do {

                            for (nextMsg = apiMsg; nextMsg; nextMsg = nextMsg->api_Next) {

                                if (nextMsg->api_State == API_STATE_NOTIFY) {

                                    switch (nextMsg->api_Class) {

                                        case API_CLASS_ROOT:

                                            switch (nextMsg->api_Action) {

                                                case API_ACTION_DIE:

                                                    active = FALSE;
                                                    break;

                                                case API_ACTION_INTRODUCE:

                                                    static struct TagItem tags[] = {

                                                        API_Client_Name,      "spell",
                                                        API_Client_Copyright, "©1995 Dietmar Eilert",
                                                        API_Client_Purpose,   "Adds online spell checking",
                                                        TAG_DONE
                                                    };

                                                    nextMsg->api_Data = tags;
                                                    break;

                                                default:

                                                    nextMsg->api_Error = API_ERROR_UNKNOWN;
                                            }

                                            break;

                                        case API_CLASS_KEY:

                                            switch (nextMsg->api_Action) {

                                                case API_ACTION_VANILLAKEY:

                                                    // checks are performed after white space characters:

                                                    if ((UBYTE)nextMsg->api_Data < 'A')
                                                        CheckWord(nextMsg, rexxPort, ask, noFlash);

                                                    break;

                                                default:

                                                    nextMsg->api_Error = API_ERROR_UNKNOWN;
                                            }
                                            break;

                                        default:

                                            nextMsg->api_Error = API_ERROR_UNKNOWN;
                                    }
                                }
                            }

                            ReplyMsg((struct Message *)apiMsg);

                        } while (apiMsg = (struct APIMessage *)GetMsg(apiPort));

                    } while (active);
                }
            }

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

            DeleteMsgPort(rexxPort);
        }

        DeleteMsgPort(apiPort);
    }
}


///
/// "check word"

/* --------------------------------- CheckWord ---------------------------------

 Check word. This function is called BEFORE  GoldED  actually  inserts  a  white
 space  character  (space, colon, ...) at the current cursor position. We assume
 the current position to  be  a  white  space  character  to  guarantee  correct
 behavior if the user inserts a character into a word. If ISpell complains about
 a word we will either issue a beep (ask = FALSE) or additionally display a list
 of suggestions (<ask> = TRUE).

 Excerpt taken from man/ispell.1: If the word is  not  in  the  dictionary,  but
 there  are  near  misses,  then the result line contains an '&', a space, and a
 list of the near misses separated by spaces.

*/

void
CheckWord(apiMsg, rexxPort, ask, noFlash)

struct APIMessage *apiMsg;
struct MsgPort    *rexxPort;
BOOL   ask, noFlash;
{
    static UBYTE buffer[4096];

    struct EditConfig *config;
    UBYTE             *next;
    UWORD              column;

    config = apiMsg->api_Config;
    column = config->Column;

    // create copy of current line

    movmem(config->CurrentBuffer, buffer, config->CurrentLen);

    // cursor placed directly after word ?

    if (column && (buffer[--column] > '@')) {

        static char word[1000], result[1000];

        UWORD wordLen;

        // find beginning of word

        for (next = buffer + column, wordLen = 1; column && (*(next - 1) > '@'); --column, --next)
            ++wordLen;

        // create ISpell command line

        movmem(next, word, wordLen);

        word[wordLen] = 0;

        strins(word, "QUICKCHECK ");

        // make ISpell check word

        if (SendRexxCommand("IRexxSpell", word, rexxPort, result)) {

            if (stricmp(result, "ok")) {

                if (noFlash)
                    Beep();
                else
                    DisplayBeep(NULL);

                // send additional CHECK command to get further information

                if (ask && SendRexxCommand("IRexxSpell", word + 5, rexxPort, result)) {

                    if (*result == '&') {

                        // translate ISpell result string (suggestions) into exec list

                        struct List list;
                        struct Node *node,  *nextNode;
                        char        *start, *end, *selection;

                        for (NewList(&list), end = result + 2; *end;) {

                            for (start = end; *end && (*end != 32); ++end);

                            if (node = (struct Node *)AllocVec(sizeof(struct Node), MEMF_ANY | MEMF_CLEAR)) {

                                node->ln_Name = start;
                                AddTail(&list, node);
                            }

                            if (*end)
                                *end++ = 0;
                        }

                        // ask for user selection from list, insert user selection into text

                        if (selection = ShowSpell(apiMsg->api_Global->F_ScrnName, &list)) {

                            static struct APIModifyRequest modifyRequest;

                            UWORD newLen = strlen(selection);

                            if (newLen != wordLen)
                                movmem(next + wordLen, next + newLen, config->CurrentLen - (next - buffer));

                            movmem(selection, next, newLen);

                            // make GoldED change the current line

                            modifyRequest.mr_Next   = NULL;
                            modifyRequest.mr_Line   = config->Line;
                            modifyRequest.mr_Column = config->Column     + (newLen - wordLen);
                            modifyRequest.mr_Size   = config->CurrentLen + (newLen - wordLen);
                            modifyRequest.mr_Data   = buffer;

                            apiMsg->api_Modify = &modifyRequest;
                        }

                        // free list of suggestions

                        for (node = list.lh_Head; nextNode = node->ln_Succ; node = nextNode)
                            FreeVec(node);
                    }
                }
            }
        }
    }
}

///
/// "gui"

/* --------------------------------- ShowSpell ---------------------------------

 Show ISpell suggestions. Return user selection or NULL.

*/

char *
ShowSpell(screen, list)

char        *screen;
struct List *list;
{
    char *result = NULL;

    struct Screen *scr;

    if (scr = LockPubScreen(screen)) {

        struct TextFont *font;

        if (font = OpenDiskFont(scr->Font)) {

            APTR visualInfo;

            if (visualInfo = GetVisualInfoA(scr, NULL )) {

                struct Gadget *context, *glist, *gad;

                if (context = CreateContext(&glist)) {

                    UWORD  ww, wh, offX, offY;
                    LONG   displayWidth, displayHeight;

                    struct Window *win;

                    offX  = scr->WBorLeft;
                    offY  = scr->RastPort.TxHeight + scr->WBorTop + 1;

                    struct NewGadget newGad = {
                    
                        offX,
                        offY,
                        ComputeX(font, 221),
                        ComputeY(font, 156),
                        NULL, scr->Font, 0, 0, visualInfo, 0
                    };

                    gad = CreateGadget(LISTVIEW_KIND, context, &newGad, GTLV_ShowSelected, NULL, GTLV_Labels, list, GTLV_Selected, 0, TAG_DONE);

                    rtGetVScreenSize(scr, &displayWidth, &displayHeight);

                    ww = ComputeX(font, 221);
                    wh = ComputeY(font, 156);

                    if (win = OpenWindowTags( NULL,

                        WA_Left,      ((displayWidth  - ww)>>1) - scr->ViewPort.DxOffset,
                        WA_Top,       ((displayHeight - wh)>>1) - scr->ViewPort.DyOffset,
                        WA_Width,     ww + offX + scr->WBorRight,
                        WA_Height,    wh + offY + scr->WBorBottom,
                        WA_IDCMP,     LISTVIEWIDCMP | IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY  | IDCMP_RAWKEY,
                        WA_Flags,     WFLG_DRAGBAR  | WFLG_DEPTHGADGET  | WFLG_CLOSEGADGET  | WFLG_SMART_REFRESH | WFLG_ACTIVATE,
                        WA_Gadgets,   glist,
                        WA_Title,     "ISpell",
                        WA_PubScreen, scr,
                        TAG_DONE 

                     )) {

                        ULONG class;
                        UWORD active;

                        active = 0;
                        result = list->lh_Head->ln_Name;

                        struct IntuiMessage *msg;
                        struct Node         *node;

                        GT_RefreshWindow(win, NULL);


                        do {

                            while (!(msg = GT_GetIMsg(win->UserPort)))
                                WaitPort(win->UserPort);

                            switch (class = msg->Class) {

                                case RAWKEY:

                                    WORD step = (msg->Code == CURSORUP) ? -1 : 1;

                                    if (node = SearchNode(list, active + step)) {

                                        active += step;
                                        result  = node->ln_Name;

                                        GT_SetGadgetAttrs(gad, win, NULL, GTLV_Selected, active, GTLV_MakeVisible, active, TAG_DONE);
                                    }

                                    break;

                                case IDCMP_VANILLAKEY:

                                    if (msg->Code == 27)
                                        class  = CLOSEWINDOW;

                                    if (msg->Code == 13)
                                        class = IDCMP_GADGETUP;

                                    break;

                                case IDCMP_GADGETUP:

                                    result = SearchNode(list, active = msg->Code)->ln_Name;
                                    break;
                            }

                            GT_ReplyIMsg(msg);

                        } while ((class & (GADGETUP | CLOSEWINDOW)) == NULL);

                        if (class == CLOSEWINDOW)
                            result = NULL;

                        CloseWindow(win);
                    }
                }
                FreeVisualInfo(visualInfo);
            }
        }
        UnlockPubScreen(screen, scr);
    }

    return(result);
}

/* --------------------------------- ComputeX ----------------------------------

 Resize element of width <value> according to current font

*/

UWORD 
ComputeX(font, value)

UWORD  value;
struct TextFont *font;
{
    return((font->tf_XSize * value) / 8);
}          

/* --------------------------------- ComputeY ----------------------------------

 Resize element of height <value> according to current font

*/

UWORD 
ComputeY(font, value)

UWORD  value;
struct TextFont *font;
{
    return((font->tf_YSize * value) / 8);
}

///
/// "misc"

/* ---------------------------------- SearchNode --------------------------------

 Return pointer to node if ordinal number (0, ...) is known.

*/

struct Node *
SearchNode(list, ordinal)

struct List *list;
UWORD  ordinal;
{
    struct Node *node;

    for (node = list->lh_Head; node->ln_Succ; --ordinal, node = node->ln_Succ)
        if (!ordinal)
            return(node);

    return(NULL);
}

/* ----------------------------------- Beep ------------------------------------

 Short audible beep

*/

void
Beep()
{
    struct IOAudio *audioIO;

    if (audioIO = (struct IOAudio *)AllocVec(sizeof(struct IOAudio), MEMF_PUBLIC | MEMF_CLEAR)) {

        struct MsgPort *audioMP;

        if (audioMP = CreateMsgPort()) {

            UBYTE whichannel[] = { 1, 2, 4, 8 };

            audioIO->ioa_Request.io_Message.mn_ReplyPort   = audioMP;
            audioIO->ioa_Request.io_Message.mn_Node.ln_Pri = 0;
            audioIO->ioa_Request.io_Command                = ADCMD_ALLOCATE;
            audioIO->ioa_Request.io_Flags                  = ADIOF_NOWAIT;
            audioIO->ioa_AllocKey                          = 0;
            audioIO->ioa_Data                              = whichannel;
            audioIO->ioa_Length                            = sizeof(whichannel);

            if (!OpenDevice("audio.device", 0, (struct IORequest *)audioIO, 0)) {

                __chip const static UBYTE waveptr[2] = {127, -127};

                audioIO->ioa_Request.io_Message.mn_ReplyPort = audioMP;
                audioIO->ioa_Request.io_Command              = CMD_WRITE;
                audioIO->ioa_Request.io_Flags                = ADIOF_PERVOL;

                audioIO->ioa_Data   = waveptr;
                audioIO->ioa_Length = 2;
                audioIO->ioa_Period = 1015;
                audioIO->ioa_Volume = 32;
                audioIO->ioa_Cycles = 60; // 44;

                BeginIO((struct IORequest *)audioIO );

                WaitPort(audioMP);
                GetMsg  (audioMP);

                CloseDevice((struct IORequest *)audioIO);
            }

            DeleteMsgPort(audioMP);
        }

        FreeVec(audioIO);
    }
}

///
/// "arexx"

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

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

*/

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

char   *cmd, *port, *buffer;
struct MsgPort *replyPort;
{
    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);
}

///
