/*
 * rexxhs.c -- A simple ARexx function library that implements
 *             support routines for implementing an ARexx command
 *             host using an ARexx program.
 *
 *             This file is common to both SAS/C and Manx Aztec C.
 *
 *             Written by Eric Giguere for the AmigaWorld Tech Journal.
 *             Function library skeleton is adapted from the one
 *             found in the Amiga Programmer's Guide to ARexx.
 */

#include <string.h>
#include <ctype.h>

#include "arexx.h"
#include "arexxsyslib.h"
#include "rexxhs.h"
#include <rexx/rexxio.h>

#if defined( LATTICE ) || defined( __SASC )
    #define AREXXDISPATCH LONG __saveds __stdargs
#else
    #define AREXXDISPATCH LONG
#endif

#define NULLCHAR '\0'

#define PROG_NOT_FOUND ERR10_001
#define BAD_NUM_ARGS   ERR10_017
#define FUNC_ERR       ERR10_012

/* Forward references */

static LONG ReplyToCall( struct Library *, struct RexxMsg *, char **, int );
static LONG NumArgs( struct Library *, struct RexxMsg *, char **, int );
static LONG IsFunctionCall( struct Library *, struct RexxMsg *, char **, int );
static LONG ValidPkt( struct Library *, struct RexxMsg *, char **, int );

static BOOL         StrIEq( char *a, char *b );
static struct List *FindPacket( struct RexxMsg *msg, struct RexxMsg *packet );

/*
 * ARLibDispatch -- This is the "query" function for the function library.
 *                  It receives a function request in the form of an
 *                  RXFUNC message.  It returns the return code in D0
 *                  and an argstring, if any, in A0.
 */

AREXXDISPATCH ARLibDispatch( struct RexxMsg *msg, char **result_string )
  {
    LONG            retval;
    int             nargs;
    struct Library *RexxSysBase;
    char           *fname;

    *result_string = NULL;

    if( !msg || ( msg->rm_Action & RXCODEMASK ) != RXFUNC )
        return( PROG_NOT_FOUND );

    RexxSysBase = (void *) OpenLibrary( (UBYTE *) "rexxsyslib.library", 0L );

    if( !RexxSysBase )
        return( PROG_NOT_FOUND );

    fname  = (char *) msg->rm_Args[0];
    nargs  = ( msg->rm_Action & RXARGMASK );
    retval = BAD_NUM_ARGS;

    /* Get the name of the function and the number of arguments */

    if( StrIEq( fname, "REPLYTOCALL" ) ){
        if( nargs >= 2 && nargs <= 3 )
            retval = ReplyToCall( RexxSysBase, msg, result_string, nargs );
    } else if( StrIEq( fname, "NUMARGS" ) ){
        if( nargs == 1 )
            retval = NumArgs( RexxSysBase, msg, result_string, nargs );
    } else if( StrIEq( fname, "ISFUNCTIONCALL" ) ){
        if( nargs == 1 )
            retval = IsFunctionCall( RexxSysBase, msg, result_string, nargs );
    } else if( StrIEq( fname, "VALIDPKT" ) ){
        if( nargs == 1 )
            retval = ValidPkt( RexxSysBase, msg, result_string, nargs );
    } else {
        retval = PROG_NOT_FOUND;
    }

    /* A function call should always return an argstring if it returns 0 */

    if( retval == RC_OK && *result_string == NULL )
        *result_string = (char *) CreateArgstring( "", 0 );

    CloseLibrary( RexxSysBase );
    return( retval );
  }

/*
 * ReplyToCall -- Reply to a message packet in the proper format.
 *
 *     REPLYTOCALL( packet, rc, [result] )
 *
 *             packet = message address as returned by GetPkt()
 *             rc     = return code (an integer)
 *             result = result string or secondary error
 *
 *     Very little error checking is done on the return code or the
 *     secondary error...
 */

static LONG ReplyToCall( struct Library *RexxSysBase, struct RexxMsg *msg,
                         char **result_string, int nargs )
  {
    struct RexxMsg  *packet;
    struct RexxMsg **arg1;
    char            *arg2, *arg3;
    long             rc;
    struct List     *list;

    arg1  = (struct RexxMsg **) msg->rm_Args[1];
    arg2  = (char *) msg->rm_Args[2];
    arg3  = (char *) msg->rm_Args[3];

    if( !arg1 || !arg2 )
        return( BAD_NUM_ARGS );

    /* We call FindPacket to make sure the packet is actually in
       the ARexx program's list (i.e., was retrieved with WaitPkt) */

    packet = *arg1;
    list   = FindPacket( msg, packet );

    if( !packet || !list ) goto return_0;
    if( packet->rm_Node.mn_Node.ln_Type == NT_REPLYMSG ) goto return_0;

    /*
     * Set the packet's result fields appropriately...
     */

    rc                 = atol( arg2 );
    packet->rm_Result1 = rc;
    packet->rm_Result2 = 0;

    if( rc == RC_OK ){
        if( nargs > 2 && arg3 != NULL &&
            ( packet->rm_Action & RXFF_RESULT ) != 0 ){
            packet->rm_Result2 = (LONG) CreateArgstring( arg3,
                                            LengthArgstring( arg3 ) );
        }
    } else {
        if( nargs > 2 && arg3 != NULL )
            packet->rm_Result2 = atol( arg3 );
        else
            packet->rm_Result2 = FUNC_ERR;
    }

    /*
     * Unlink from list and then send the packet off...
     */

    Remove( (struct Node *) packet );
    ReplyMsg( (struct Message *) packet );

    *result_string = CreateArgstring( "1", 1 );
    return( RC_OK );

  return_0:
    *result_string = CreateArgstring( "0", 1 );
    return( RC_OK );
  }

/*
 * NumArgs -- Returns the number of arguments in a packet.  If the packet
 *            is not a function call, then 0 is returned.
 *
 *        NUMARGS( packet )
 */

static LONG NumArgs( struct Library *RexxSysBase, struct RexxMsg *msg,
                     char **result_string, int nargs )
  {
    struct RexxMsg  *packet;
    struct RexxMsg **arg1;
    char             argbuf[3];
    struct List     *list;
    int              pargs;

    arg1 = (struct RexxMsg **) msg->rm_Args[1];

    if( !arg1 )
        return( BAD_NUM_ARGS );

    packet = *arg1;
    list   = FindPacket( msg, packet );

    if( !packet || !list ) goto return_0;
    if( packet->rm_Node.mn_Node.ln_Type == NT_REPLYMSG ) goto return_0;
    if( ( packet->rm_Action & RXCODEMASK ) != RXFUNC ) goto return_0;

    pargs = ( packet->rm_Action & RXARGMASK );

    if( pargs < 10 ){
        argbuf[0] = pargs + '0';
        argbuf[1] = NULLCHAR;
    } else {
        argbuf[0] = '1';
        argbuf[1] = ( pargs % 10 ) + '0';
        argbuf[2] = NULLCHAR;
    }

    *result_string = CreateArgstring( argbuf, strlen( argbuf ) );
    return( RC_OK );

  return_0:

    *result_string = CreateArgstring( "0", 1 );
    return( RC_OK );
  }

/*
 * IsFunctionCall -- Returns TRUE (1) if the packet is a function call.
 *
 *        ISFUNCTIONCALL( packet )
 */

static LONG IsFunctionCall( struct Library *RexxSysBase, struct RexxMsg *msg,
                            char **result_string, int nargs )
  {
    struct RexxMsg  *packet;
    struct RexxMsg **arg1;
    struct List     *list;

    arg1 = (struct RexxMsg **) msg->rm_Args[1];

    if( !arg1 )
        return( BAD_NUM_ARGS );

    packet = *arg1;
    list   = FindPacket( msg, packet );

    if( !packet || !list ) goto return_0;
    if( packet->rm_Node.mn_Node.ln_Type == NT_REPLYMSG ) goto return_0;
    if( ( packet->rm_Action & RXCODEMASK ) != RXFUNC ) goto return_0;

    *result_string = CreateArgstring( "1", 1 );
    return( RC_OK );

  return_0:

    *result_string = CreateArgstring( "0", 1 );
    return( RC_OK );
  }

/*
 * ValidPkt -- Returns TRUE or FALSE depending upond whether the given
 *             address is a valid message packet...
 *
 *        VALIDPKT( packet )
 */

static LONG ValidPkt( struct Library *RexxSysBase, struct RexxMsg *msg,
                      char **result_string, int nargs )
  {
    struct RexxMsg  *packet;
    struct RexxMsg **arg1;
    struct List     *list;

    arg1 = (struct RexxMsg **) msg->rm_Args[1];

    if( !arg1 )
        return( BAD_NUM_ARGS );

    packet = *arg1;
    list   = FindPacket( msg, packet );

    if( !packet || !list ) goto return_0;
    if( packet->rm_Node.mn_Node.ln_Type == NT_REPLYMSG ) goto return_0;

    *result_string = CreateArgstring( "1", 1 );
    return( RC_OK );

  return_0:

    *result_string = CreateArgstring( "0", 1 );
    return( RC_OK );
  }

/*
 * StrEq -- Returns TRUE if two strings match.
 */

static BOOL StrEq( char *a, char *b )
  {
    return( strcmp( a, b ) == 0 );
  }

/*
 * StrIEq -- Like StrEq, but case-insensitive.
 */

static BOOL StrIEq( char *a, char *b )
  {
    while( *a != NULLCHAR && *b != NULLCHAR ){
        if( tolower( *a ) != tolower( *b ) )
            return( FALSE );

        ++a;
        ++b;
    }

    return( *a == *b );
  }

/*
 * FindPacket -- Given a packet address, check the message port lists
 *               for the ARexx program and find the list to which
 *               the packet belongs. (NULL if packet can't be found.)
 *
 *               This routine depends upon knowledge of Rexx's internal
 *               structures, as described in the storage.h and rexxio.h
 *               header files.  The task pointer is taken from the
 *               RexxMsg that is passed to the function library.
 */

static struct List *FindPacket( struct RexxMsg *msg, struct RexxMsg *packet )
  {
    struct RexxTask    *rt;
    struct RexxMsg     *rmsg;
    struct RexxMsgPort *port;

    if( !msg || !packet )
        return( NULL );

    /* Get the ARexx program's global data structure */

    rt = (struct RexxTask *) msg->rm_TaskBlock;

    if( rt ){

        /* OK, search through the list of active message ports
           for the ARexx program... */

        for( port = (void *) rt->rt_Header5.lh_Head;
             port->rmp_Node.rr_Node.ln_Succ;
             port = (void *) port->rmp_Node.rr_Node.ln_Succ ){

             /* For each port, search through the list of messages
                awaiting reply... */

             for( rmsg = (void * ) port->rmp_ReplyList.lh_Head;
                  rmsg->rm_Node.mn_Node.ln_Succ;
                  rmsg = (void *) rmsg->rm_Node.mn_Node.ln_Succ ){

                  if( rmsg == packet ){
                      return( &port->rmp_ReplyList );
                  }
             }
        }
    }

    return( NULL );
  }