/*
 * 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

/*
 * Prototypes for the RVI ARexx calls...  (link with RexxVars.o)
 */
__stdargs long CheckRexxMsg(struct RexxMsg *);
__stdargs long GetRexxVar(struct RexxMsg *,char *,char **);
__stdargs long SetRexxVar(struct RexxMsg *,char *,char *,long);

/*
 * 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...  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)
	    if (tmp=(struct RexxMsg *)GetMsg(RexxContext->ARexxPort))
	{
		if (tmp->rm_Node.mn_Node.ln_Type==NT_REPLYMSG)
		{
			/*
			 * If we had sent a command, it would come this way...
			 *
			 * Since we don't in this simple example, we just throw
			 * away anything that looks "strange"
			 */
			flag=FALSE;
			if (tmp->rm_Result1) flag=TRUE;

			/*
			 * Free the arguments and the message...
			 */
			DeleteArgstring(tmp->rm_Args[0]);
			DeleteRexxMsg(tmp);
			RexxContext->Outstanding-=1;

			/*
			 * Return the error if there was one...
			 */
			tmp=flag ? REXX_RETURN_ERROR : NULL;
		}
	}
	return(tmp);
}

/*
 * 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 TRUE if it send the message, FALSE if it did not...
 */
short SendARexxMsg(AREXXCONTEXT RexxContext,char *RString,
			short StringFile)
{
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);
			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);
}

/*
 * 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!");
					ReplyARexxMsg(RexxContext,rmsg,
							NULL,100);
				}
			}
		}

		/*
		 * 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);
			}
			DeletePort(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);
			Permit();
		}

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