/*
 *
 * Toolmaker Simple ARexx interface... (SAS version)
 *
 * 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...
 *
 * Appends .1, .2, etc. to requested portname as specified in Style Guide
 *
*/

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

#include	<clib/exec_protos.h>
#include	<clib/rexxsyslib_protos.h>
#include	<clib/alib_protos.h>

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

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

/*
 * Pragmas for the above functions...  (To make this all self-contained...)
 * If you use at least 2.04 amiga.lib, this is not needed...
*/

#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

/*
 * 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) && (rmsg) && (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)) && (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 && rmsg && CheckRexxMsg((struct Message *)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((struct Message *)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) && (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; 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;
          }
	Permit();

        if(!RexxPort)
          {
          /* No port, so clean up... */
          DeleteArgstring(rmsg->rm_Args[0]);
          DeleteRexxMsg(rmsg);
          }
        }
      else
        {
        DeleteRexxMsg(rmsg);
        }
      }
    }

  return(flag);
  }

/* This function closes down the ARexx context 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++)
        {
        sprintf(tmp,".%ld",count);
        RexxContext->ARexxPort=FindPort(RexxContext->PortName);
        }

      RexxContext->ARexxPort=CreatePort(RexxContext->PortName,NULL);

      Permit();
      }

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

  return(RexxContext);
  }
