/*
 * Simple ARexx interface by Michael Sinz
 *
 * This is a very "Simple" interface to the world of ARexx...
 * For more complex interfaces into ARexx, it is best that you
 * understand the functions that are provided by ARexx.
 * In many cases they are more powerful than what is presented
 * here.
 *
 * This code is fully re-entrant and self-contained other than
 * the use of SysBase/AbsExecBase and the ARexx RVI support
 * library which is also self-contained...
 */

#include	<exec/types.h>
#include	<exec/nodes.h>
#include	<exec/lists.h>
#include	<exec/ports.h>
#include	<exec/memory.h>

#include	<proto/exec.h>

#include	<rexx/storage.h>
#include	<rexx/rxslib.h>

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

/*
 * The prototypes for the few ARexx functions we will call...
 */
struct RexxMsg *CreateRexxMsg(struct MsgPort *,char *,char *);
void *CreateArgstring(char *,long);
void DeleteRexxMsg(struct RexxMsg *);
void DeleteArgstring(char *);
BOOL IsRexxMsg(struct Message *);

/*
 * Pragmas for the above functions...  (To make this all self-contained...)
 * If you use RexxGlue.o, this is not needed...
 *
 * These are for Lattice C 5.x  (Note the use of RexxContext->RexxSysBase)
 */
#pragma libcall RexxContext->RexxSysBase CreateRexxMsg 90 09803
#pragma libcall RexxContext->RexxSysBase CreateArgstring 7E 0802
#pragma libcall RexxContext->RexxSysBase DeleteRexxMsg 96 801
#pragma libcall RexxContext->RexxSysBase DeleteArgstring 84 801
#pragma libcall RexxContext->RexxSysBase IsRexxMsg A8 801

/*
 * Now, we have made the pragmas needed, let's get to work...
 */

/*
 * A structure for the ARexx handler context
 * This is *VERY* *PRIVATE* and should not be touched...
 */
struct	ARexxContext
{
struct	MsgPort	*ARexxPort;	/* The port messages come in at... */
struct	Library	*RexxSysBase;	/* We will hide the library pointer here... */
	long	Outstanding;	/* The count of outstanding ARexx messages... */
	char	PortName[24];	/* The port name goes here... */
	char	ErrorName[28];	/* The name of the <base>.LASTERROR... */
	char	Extension[8];	/* Default file name extension... */
};

#define	AREXXCONTEXT	struct ARexxContext *

#include	"SimpleRexx.h"

/*
 * This function returns the port name of your ARexx port.
 * It will return NULL if there is no ARexx port...
 *
 * This string is *READ ONLY*  You *MUST NOT* modify it...
 */
char *ARexxName(AREXXCONTEXT RexxContext)
{
    register	char	*tmp=NULL;

    if (RexxContext) tmp=RexxContext->PortName;
    return(tmp);
}

/*
 * This function returns the signal mask that the Rexx port is
 * using.  It returns NULL if there is no signal...
 *
 * Use this signal bit in your Wait() loop...
 */
ULONG ARexxSignal(AREXXCONTEXT RexxContext)
{
    register	ULONG	tmp=NULL;

    if (RexxContext) tmp=1L << (RexxContext->ARexxPort->mp_SigBit);
    return(tmp);
}

/*
 * This function returns a structure that contains the commands sent from
 * ARexx or the results of commands you sent.  You will need to parse it 
 * and return the structure back so that the memory can be freed.
 *
 * This returns NULL if there was no message.
 */
struct RexxMsg *GetARexxMsg(AREXXCONTEXT RexxContext)
{
    register	struct	RexxMsg	*tmp=NULL;
    register		short	flag;

    if (RexxContext) tmp=(struct RexxMsg *)GetMsg(RexxContext->ARexxPort);
    return(tmp);
}

/* Use this to delete a message sent via SendARexxMsg and that has now been
   returned to you.
*/
void DeleteARexxMsg(AREXXCONTEXT RexxContext, struct RexxMsg *rmsg)
{
    /*
     * Free the arguments and the message...
     */
    if(rmsg->rm_Action & RXFF_RESULT) {
	if(rmsg->rm_Result1 == 0 && rmsg->rm_Result2) {
	    DeleteArgstring((STRPTR)rmsg->rm_Result2);
	}
    }
    DeleteArgstring(rmsg->rm_Args[0]);
    DeleteRexxMsg(rmsg);
    RexxContext->Outstanding-=1;
}

/*
 * Use this to return a ARexx message...
 *
 * If you wish to return something, it must be in the RString.
 * If you wish to return an Error, it must be in the Error.
 * If there is an error, the RString is ignored.
 */
void ReplyARexxMsg(AREXXCONTEXT RexxContext,struct RexxMsg *rmsg,
		   char *RString,LONG Error)
{
    if (RexxContext) if (rmsg) if (rmsg!=REXX_RETURN_ERROR)
    {
	rmsg->rm_Result2=0;
	if (!(rmsg->rm_Result1=Error))
	{
	    /*
	     * if you did not have an error we return the string
	     */
	    if (rmsg->rm_Action & (1L << RXFB_RESULT)) if (RString)
	    {
		rmsg->rm_Result2=(LONG)CreateArgstring(RString,
						       (LONG)strlen(RString));
	    }
	}

	/*
	 * Reply the message to ARexx...
	 */
	ReplyMsg((struct Message *)rmsg);
    }
}

/*
 * This function will set an error string for the ARexx
 * application in the variable defined as <appname>.LASTERROR
 *
 * Note that this can only happen if there is an ARexx message...
 *
 * This returns TRUE if it worked, FALSE if it did not...
 */
short SetARexxLastError(AREXXCONTEXT RexxContext,struct RexxMsg *rmsg,
			char *ErrorString)
{
    register	short	OkFlag=FALSE;

    if (RexxContext) if (rmsg) if (CheckRexxMsg(rmsg))
    {
	/*
	 * Note that SetRexxVar() has more than just a TRUE/FALSE
	 * return code, but for this "basic" case, we just care if
	 * it works or not.
	 */
	if (!SetRexxVar(rmsg,RexxContext->ErrorName,ErrorString,
			(long)strlen(ErrorString)))
	{
	    OkFlag=TRUE;
	}
    }
    return(OkFlag);
}

/*
 * This function will send a string to ARexx...
 *
 * The default host port will be that of your task...
 *
 * If you set StringFile to TRUE, it will set that bit for the message...
 *
 * Returns the message sent, or NULL in case of error.
 */
struct RexxMsg *SendARexxMsg(AREXXCONTEXT RexxContext,char *RString,
			      short StringFile, short results)
{
    register	struct	MsgPort	*RexxPort;
    register	struct	RexxMsg	*rmsg;
    register		short	flag=FALSE;

    if (RexxContext) if (RString)
    {
	if (rmsg=CreateRexxMsg(RexxContext->ARexxPort,
			       RexxContext->Extension,
			       RexxContext->PortName))
	{
	    rmsg->rm_Action=RXCOMM | (StringFile ?
				      (1L << RXFB_STRING):0);
	    rmsg->rm_Action |= (results ? RXFF_RESULT : 0);
	    if (rmsg->rm_Args[0]=CreateArgstring(RString,
						 (LONG)strlen(RString)))
	    {
		/*
		 * We need to find the RexxPort and this needs
		 * to be done in a Forbid()
		 */
		Forbid();
		if (RexxPort=FindPort(RXSDIR))
		{
		    /*
		     * We found the port, so put the
		     * message to ARexx...
		     */
		    PutMsg(RexxPort,(struct Message *)rmsg);
		    RexxContext->Outstanding+=1;
		    flag=TRUE;
		}
		else
		{
		    /*
		     * No port, so clean up...
		     */
		    DeleteArgstring(rmsg->rm_Args[0]);
		    DeleteRexxMsg(rmsg);
		}
		Permit();
	    }
	    else DeleteRexxMsg(rmsg);
	}
    }
    return flag ? rmsg : NULL;
}

int PendingCommands(AREXXCONTEXT RexxContext)
{
    if (RexxContext) return RexxContext->Outstanding;
    else return 0;
}

/*
 * This function closes down the ARexx context that was opened
 * with InitARexx...
 */
void FreeARexx(AREXXCONTEXT RexxContext)
{
    register	struct	RexxMsg	*rmsg;

    if (RexxContext)
    {
	/*
	 * Clear port name so it can't be found...
	 */
	RexxContext->PortName[0]='\0';

	/*
	 * Clean out any outstanding messages we had sent out...
	 */
	while (RexxContext->Outstanding)
	{
	    WaitPort(RexxContext->ARexxPort);
	    while (rmsg=GetARexxMsg(RexxContext))
	    {
		if (rmsg!=REXX_RETURN_ERROR)
		{
		    /*
		     * Any messages that come now are blown
		     * away...
		     */
		    SetARexxLastError(RexxContext,rmsg,
				      "99: Port Closed!");
		    /* removed ReplyARexxMsg() this was a bug that would */
		    /* obviously cause a loop (we would continue to reply and */
		    /* then get the message.  It now deletes the message as */
		    /* should be done.  -ch5/10/93. */
		    DeleteARexxMsg(RexxContext,rmsg);
		}
	    }
	}

	/*
	 * Clean up the port and delete it...
	 */
	if (RexxContext->ARexxPort)
	{
	    while (rmsg=GetARexxMsg(RexxContext))
	    {
		/*
		 * Any messages that still are coming in are
		 * "dead"  We just set the LASTERROR and
		 * reply an error of 100...
		 */
		SetARexxLastError(RexxContext,rmsg,
				  "99: Port Closed!");
		ReplyARexxMsg(RexxContext,rmsg,NULL,100);
	    }
	    RemPort(RexxContext->ARexxPort);
	    DeleteMsgPort(RexxContext->ARexxPort);
	}

	/*
	 * Make sure we close the library...
	 */
	if (RexxContext->RexxSysBase)
	{
	    CloseLibrary(RexxContext->RexxSysBase);
	}

	/*
	 * Free the memory of the RexxContext
	 */
	FreeMem(RexxContext,sizeof(struct ARexxContext));
    }
}

/*
 * This routine initializes an ARexx port for your process
 * This should only be done once per process.  You must call it
 * with a valid application name and you must use the handle it
 * returns in all other calls...
 *
 * NOTE:  The AppName should not have spaces in it...
 *        Example AppNames:  "MyWord" or "FastCalc" etc...
 *        The name *MUST* be less that 16 characters...
 *        If it is not, it will be trimmed...
 *        The name will also be UPPER-CASED...
 *
 * NOTE:  The Default file name extension, if NULL will be
 *        "rexx"  (the "." is automatic)
 */
AREXXCONTEXT InitARexx(char *AppName,char *Extension)
{
    register	AREXXCONTEXT	RexxContext=NULL;
    register	short		loop;
    register	short		count;
    register	char		*tmp;

    if (RexxContext=AllocMem(sizeof(struct ARexxContext),
			     MEMF_PUBLIC|MEMF_CLEAR))
    {
	if (RexxContext->RexxSysBase=OpenLibrary("rexxsyslib.library", NULL))
	{
	    /*
	     * Set up the extension...
	     */
	    if (!Extension) Extension="rexx";
	    tmp=RexxContext->Extension;
	    for (loop=0;(loop<7)&&(Extension[loop]);loop++)
	    {
		*tmp++=Extension[loop];
	    }
	    *tmp='\0';

	    /*
	     * Set up a port name...
	     */
	    tmp=RexxContext->PortName;
	    for (loop=0;(loop<16)&&(AppName[loop]);loop++)
	    {
		*tmp++=toupper(AppName[loop]);
	    }
	    *tmp='\0';

	    /*
	     * Set up the last error RVI name...
	     *
	     * This is <appname>.LASTERROR
	     */
	    strcpy(RexxContext->ErrorName,RexxContext->PortName);
	    strcat(RexxContext->ErrorName,".LASTERROR");

	    /* We need to make a unique port name... */
	    Forbid();
	    for (count=1,RexxContext->ARexxPort=(VOID *)1;
		 RexxContext->ARexxPort;count++)
	    {
		stci_d(tmp,count);
		RexxContext->ARexxPort=
		    FindPort(RexxContext->PortName);
	    }

	    /*RexxContext->ARexxPort=CreatePort(RexxContext->PortName,NULL);*/
	    RexxContext->ARexxPort=CreateMsgPort();
	    RexxContext->ARexxPort->mp_Node.ln_Name = RexxContext->PortName;
	    RexxContext->ARexxPort->mp_Node.ln_Pri = 0;
	    AddPort(RexxContext->ARexxPort);
	    Permit();
	}

	if (!RexxContext->RexxSysBase || !RexxContext->ARexxPort)
	{
	    FreeARexx(RexxContext);
	    RexxContext=NULL;
	}
    }
    return(RexxContext);
}
