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

 Mirror API client, ©1995 Dietmar Eilert. Dice:

 dcc main.c -// -proto -mi -l reqtoolssr.lib -r -2.0 -o ram:mirror

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

/// "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 <workbench/startup.h>
#include <rexx/errors.h>
#include <rexx/rxslib.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/rexxsyslib_protos.h>
#include <clib/alib_protos.h>
#include <clib/reqtools_protos.h>

#include "golded:api/include/golded.h"

struct Settings {

    UWORD  Range;                                    // range of lines to be considered
    BOOL   Online;                                   // scanner online ?
    UBYTE  Twins[255];                               // characters to look for
};

Prototype void   main(int, char **);
Prototype int    wbmain(struct WBStartup *);
Prototype void   HandleAPI(char *, struct Settings *);
Prototype ULONG *SendRexxCommand(char *, char *, struct MsgPort *, char *);
Prototype BOOL   MatchingBracket(struct APIMessage *, struct Settings *, UWORD, BOOL);
Prototype void   Dispatch(struct APIMessage *, struct Settings *);
Prototype LONG   CommandMatch(ULONG *, struct APIMessage *, struct Settings *);
Prototype BOOL   IsABracket(UBYTE, struct Settings *);
Prototype BOOL   FindMarkedBracket(struct EditConfig *, struct Settings *);
Prototype BOOL   NoUserBlock(struct EditConfig *, struct Settings *);

// globals

struct Library *ReqToolsBase;

///
/// "main"

int
wbmain(struct WBStartup *startup)
{
    if (ReqToolsBase = OpenLibrary("reqtools.library", 37)) {

        rtEZRequestTags("No executable - to be used as API client", "OK", NULL, NULL, TAG_DONE);

        CloseLibrary(ReqToolsBase);
    }
}

void
main(int argc, char **argv)
{
    static UBYTE version[] = "$VER: Mirror 1.3 (" __COMMODORE_DATE__ ")";

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

    struct RDArgs *rdArgs;

    if (rdArgs = ReadArgs("H=HOST/K/A,RANGE/N,ONLINE/S,OFFLINE/S,TWINS/K", argArray, NULL)) {

        struct Settings settings = { 30, TRUE, "(){}[]" };

        if (argArray[1])                             // RANGE/N
            settings.Range = *(ULONG *)argArray[1];

        if (argArray[2])
            settings.Online = TRUE;                  // ONLINE/S

        if (argArray[3])
            settings.Online = FALSE;                 // OFFLINE/S

        if (argArray[4])                             // TWINS/K
            strcpy(settings.Twins, (char *)argArray[4]);

        HandleAPI((UBYTE *)argArray[0], &settings);

        FreeArgs(rdArgs);
    }

    exit(0);
}

///
/// "api management"

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

 Register with GoldED & handle incoming API messages.

*/

void
HandleAPI(host, settings)

char            *host;
struct Settings *settings;
{
    struct MsgPort *apiPort, *rexxPort;

    if (apiPort = CreateMsgPort()) {

        if (rexxPort = CreateMsgPort()) {

            UBYTE command[255];
            ULONG *result;

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

            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,      "Mirror",
                                                        API_Client_Copyright, "Mirror ©1995 Dietmar Eilert",
                                                        API_Client_Purpose,   "Online bracket highlighting",
                                                        API_Client_Template,  "MIRROR CHECK/S,RANGE/N,ONLINE/S,OFFLINE/S",
                                                        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:

                                                    if ((settings->Online) && NoUserBlock(apiMsg->api_Config, settings)) {

                                                        if (IsABracket((UBYTE)nextMsg->api_Data, settings) && MatchingBracket(nextMsg, settings, (UBYTE)nextMsg->api_Data, TRUE))

                                                            break;

                                                        else if (FindMarkedBracket(nextMsg->api_Config, settings))

                                                            apiMsg->api_Refresh |=  API_REFRESH_NOMARKER;
                                                    }
                                                    break;

                                                default:

                                                    nextMsg->api_Error = API_ERROR_UNKNOWN;
                                            }
                                            break;

                                        case API_CLASS_REXX:

                                            switch (nextMsg->api_Action) {

                                                case API_ACTION_COMMAND:

                                                    Dispatch(nextMsg, settings);
                                                    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);
                }
            }

            DeleteMsgPort(rexxPort);
        }

        DeleteMsgPort(apiPort);
    }
}


///
/// "arexx class"

/* --------------------------------- Dispatch ----------------------------------

 Dispatch incoming command: examine command string (command part is uppercase
 already), look for handler function related to command, parse arguments (if 
 command supports arguments), call handler.

*/

void
Dispatch(apiMsg, settings)

struct Settings   *settings;
struct APIMessage *apiMsg;
{
    struct RDArgs *rdArgs, *args;

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

        static char buffer[1024];

        // table of supported commands, associated handlers & template strings

        static struct parser { char *command; LONG (*handler)(ULONG *, struct APIMessage *, struct Settings *); char *template; } parser[] = {

            "MIRROR", (APTR)CommandMatch, "CHECK/S,RANGE/N,ONLINE/S,OFFLINE/S,AUTOHIDE/S,TWINS/K",
             NULL
        };

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

        struct APIRexxNotify *notify = (struct APIRexxNotify *)apiMsg->api_Data;

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

        strcpy(buffer, notify->arn_Command);
        strcat(buffer, "\12");

        for (n = 0; parser[n].command; ++n) {

            if (!memcmp(buffer, parser[n].command, strlen(parser[n].command))) {

                char *arguments = buffer + strlen(parser[n].command);

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

                if (parser[n].template) {

                    if (args = ReadArgs(parser[n].template, argArray, rdArgs)) {

                        notify->arn_RC = (*parser[n].handler)(argArray, apiMsg, settings);

                        FreeArgs(args);
                    }
                    else {

                        static UBYTE errorText[81];

                        notify->arn_RC           = RC_WARN;
                        notify->arn_CommandError = errorText;

                        Fault(IoErr(), "IoErr()", errorText, 80);
                    }
                }
                else
                    notify->arn_RC = (*parser[n].handler)(argArray, apiMsg, settings);
            }
        }

        FreeDosObject(DOS_RDARGS, rdArgs);
    }
}

/* -------------------------------- CommandMatch -------------------------------

 Highlight matching bracket

 template: CHECK/S,RANGE/N,ONLINE/S,OFFLINE/S,AUTOHIDE/S,TWINS/K

*/

LONG
CommandMatch(argArray, apiMsg, settings)

ULONG  *argArray;

struct APIMessage *apiMsg;
struct Settings   *settings;
{
    UBYTE letter = apiMsg->api_Config->CurrentBuffer[apiMsg->api_Config->Column];

    apiMsg->api_State = API_STATE_CONSUMED;

    if (argArray[5])                                 // TWINS/K
        strcpy(settings->Twins, (char *)argArray[5]);

    if (argArray[0] && IsABracket(letter, settings) && NoUserBlock(apiMsg->api_Config, settings))

        MatchingBracket(apiMsg, settings, letter, FALSE);

    else if (argArray[4] && FindMarkedBracket(apiMsg->api_Config, settings))

        apiMsg->api_Refresh |= API_REFRESH_NOMARKER; // AUTOHIDE/S

    if (argArray[1])                                 // RANGE/N
        settings->Range = *(ULONG *)argArray[1];

    if (argArray[2])
        settings->Online = TRUE;                     // ONLINE/S

    if (argArray[3])                                 // OFFLINE/S
        settings->Online = FALSE;

    return(RC_OK);
}

///
/// "online check"

/* ------------------------------- MatchingBracket --------------------------------

 Find matching bracket. Only <distance> lines  are  considered.  Ignore  opening
 brackets  preceeded  by  '/' (ASCII 92, TeX style construction used to insert a
 bracket into text). Set <pending> to TRUE if current character hasn't yet  been
 inserted.

*/

BOOL
MatchingBracket(apiMsg, settings, examine, pending)

UWORD  examine;
BOOL   pending;
struct APIMessage *apiMsg;
struct Settings   *settings;
{
    UBYTE *current, *known;
    WORD   column, step, len;
    BOOL   success, inString;
    ULONG  line;

    struct EditConfig *config = apiMsg->api_Config;

    inString = FALSE;
    success  = FALSE;
    known    = settings->Twins;

    line     = config->Line;
    column   = config->Column;
    len      = config->CurrentLen;
    current  = config->CurrentBuffer;

    for (step = 1; *known; ++known, step = -step) {

        if ((examine == *known) && ((column == 0) || (current[column - 1] != 92))) {

            WORD level, count, distance;
            char twin;

            twin     = *(known + step);
            count    = 0;
            level    = (pending) ? 0 : -1;
            distance = settings->Range;

            for (distance = 0; distance <= settings->Range; ++distance) {

                while ((column >= 0) && (column < len)) {

                    char *next = current + column;

                    if ((*next == 34) || (*next == 39))

                        inString = !inString;

                    else if (!inString) {
                        
                        if ((*next == twin) && !(column && (*(next - 1) == 92))) {

                            if (level)
                                --level;

                            else {

                                success = TRUE;
                                break;
                            }
                        }
                        else if ((*next == *known) && !(column && (*(next - 1) == 92))) {

                            ++level;
                            ++count;
                        }
                    }

                    column += step;
                }

                if (success) {

                    BOOL adjacent = FALSE;

                    if (line == config->Line) {

                        if (pending)
                            adjacent = ((step == -1) && (config->Column + step == column)) || ((step == 1) && (column == config->Column));
                        else
                            adjacent = (column == (config->Column + step));
                    }

                    if (adjacent)

                        apiMsg->api_Refresh |= API_REFRESH_NOMARKER;

                    else {

                        if ((distance == 0) && ((config->Marker == BLOCKMODE_NONE) || ((line == config->BlockStartY) && (line == config->BlockEndY))))

                            apiMsg->api_Refresh = API_REFRESH_LINE;
                        else
                            apiMsg->api_Refresh = API_REFRESH_DISPLAY;

                        config->BlockStartX = config->BlockEndX = column;
                        config->BlockStartY = config->BlockEndY = line;

                        config->Marker = BLOCKMODE_CHAR;
                    }

                    return(TRUE);
                }
                else {

                    line    += step;
                    inString = FALSE;

                    if ((line >= 0) && (line < config->Lines)) {

                        current = (config->TextNodes + line)->Text;
                        len     = (config->TextNodes + line)->Len;

                        column = (step == -1) ? len - 1 : 0;
                    }
                    else                             // no matching bracket
                        break;
                }
            }

            apiMsg->api_Refresh |= API_REFRESH_NOMARKER;

            break;
        }
    }

    return(FALSE);
}


///
/// "misc"

/* ------------------------------- NoUserBlock ---------------------------------

 Return TRUE if there is no user-defined block, i.e. either no block at all or a
 marked bracket only (possibly marked by us).

*/

BOOL
NoUserBlock(config, settings)

struct EditConfig *config;
struct Settings   *settings;
{
    return((config->Marker == BLOCKMODE_NONE) || FindMarkedBracket(config, settings));
}


/* -------------------------------- IsABracket ---------------------------------

 Check whether character is a bracket

*/

BOOL
IsABracket(letter, settings)

UBYTE  letter;
struct Settings *settings;
{
    UBYTE *known;

    for (known = settings->Twins; *known; ++known)
        if (letter == *known)
            return(TRUE);

    return(FALSE);
}

/* ----------------------------- FindMarkedBracket -----------------------------

 Return TRUE if a single bracket has been marked

*/

BOOL
FindMarkedBracket(config, settings)

struct EditConfig *config;
struct Settings   *settings;
{
    if (config->Marker == BLOCKMODE_CHAR) {

        if (config->BlockStartY == config->BlockEndY) {

            if (config->BlockStartX == config->BlockEndX) {

                UWORD pos = config->BlockStartX;

                if (config->BlockStartY == config->Line) {

                    if (pos < config->CurrentLen)
                        return(IsABracket(config->CurrentBuffer[pos], settings));
                }
                else {

                    struct LineNode *lineNode = config->TextNodes + config->BlockStartY;

                    if (pos < lineNode->Len)
                        return(IsABracket(lineNode->Text[config->BlockStartX], settings));
                }

            }

        }
    }

    return(FALSE);
}



///
/// "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);
}

///
