/***************************************************************************

   Program:    
   File:       Rxil_Shell.c
   
   Version:    V1.0
   Date:       13.12.91
   Function:   Provide a friendly shell for the Rxil ARexx library
   
   Copyright:  SciTech Software 1991
   Author:     Andrew C. R. Martin
   Address:    SciTech Software
               23, Stag Leys,
               Ashtead,
               Surrey,
               KT21 2TD.
   Phone:      +44 (0372) 275775
   EMail:      UUCP: cbmuk!cbmuka!scitec!amartin
               JANET: andrew@uk.ac.ox.biop
               
****************************************************************************

   This program is not in the public domain, but it may be freely copied
   and distributed for no charge providing this header is included.
   The code may be modified as required, but any modifications must be
   documented so that the person responsible can be identified. If someone
   else breaks this code, I don't want to be blamed for code that does not
   work! The code may not be sold commercially without prior permission from
   the author, although it may be given away free with commercial products,
   providing it is made clear that this program is free and that the source
   code is provided with the program.

****************************************************************************

   Description:
   ============
   
*  The Rxil library makes calls to ARexx far easier. Error handling, etc.
   is done for the programmer. Rxil_Shell makes these calls even easier.
   Some flexibility is missing from these calls (although, when needed,
   this can be re-introduced by direct calls to Rxil or ARexx itself).

   
*  The following Rxil routines have been modified:
   
   RxilCheckPort()      Fix to memory allocation. ARG0() macro was getting
                        expanded wrongly causing MungWall hits.
   display_message()    Changed call to AutoRequest() to use MyAutoRequest()
                        which looks nicer under V1.3
   RxilHandleReturn()   Given a second parameter to control internal display
                        of returns from functions. Also returns a flag to
                        indicate whether a return value was obtained.


*  The Rexx command table, an array of RxilFunction's must be created and
   passed to SetupREXX(). A line of NULL's and 0's is used to mark the end
   of the table. For example:

   struct RxilFunction rexx_cmd_table[] = {
      { "help",   &rexx_parse,      0, 10,   FALSE,   RXIL_PUBLIC },
      { "quit",   &rexx_parse,      0, 10,   FALSE,   RXIL_PUBLIC },
      { "command",&cmd_launch_cmd,  0,  1,   FALSE,   RXIL_PUBLIC },
      { "func",   &cmd_launch_func, 0, 16,   FALSE,   RXIL_PUBLIC },
      { NULL,     NULL,             0,  0,   FALSE,   0           }
   };
   
   The structure members are:
   (char *)    Command name (in lower case)
   (void *)()  Function to be called for this command
   int         Min number of arguments required
   int         Max number of arguments required
   BOOL        Should command matching be case sensitive
   int         Privilege. RXIL_PUBLIC or RXIL_SECRET. Specifies which port
               the command is valid from. If REXX_PUBLIC, command may be
               executed through either port.


*  The functions specified in the command table are called with a single
   parameter, a pointer to a RexxMsg structure. The example command table
   shows 2 calls to rexx_parse(). This implies an additional layer of
   parsing is supplied by the client program. If required, the routine 
   BuildComLine() may be called in such a parser to rebuild the command
   line. The following example shows how this might be done:
   
   void rexx_parse(struct RexxMsg *rexxmsg)
   {  char  buffer[200];
   
      BuildComLine(buffer);                        Rebuild the command line
      Interpret(buffer);                           Call our own parser
   }
   

*  The only Rexx specific external which needs to be specified by the client
   program (and then only if the client is interested in returns from 
   macros) is:

   extern char *Rexx_return;

****************************************************************************

   Usage:
   ======
   
   SetupREXX(portname, extension, conspec, synch, comtab, startup)
   ---------------------------------------------------------------
   Input:   char                 *portname   Port name
            char                 *extension  Default extension
                                             NULL: "rexx"
                                             else: taken a string pointer
            char                 *conspec    Console spec for macros to use.
                                             NULL: none (use CLI)
                                             1:    default
                                             else: taken a string pointer
            int                  synch       Are macros to be synchronous?
                                             0: Synchronous
                                             1: Asynchronous
            struct RxilFunction  comtab[]    ARexx command table (see above)
            char                 *startup    Name of Startup macro
                                             NULL: No startup
   Returns: void
   
   Sets up public and private REXX ports and specifies various parameters
   for Rexx. Also runs a startup macro if required; thus any other 
   preparation routines should be called before SetupREXX().
   comtab[] should be prepared as shown above.


   ProcessReturns()
   ----------------
   Input:   void
   Returns: void
   
   This routine is for programs which launch macros. Handles any ARexx 
   invocation returns which may be back. Informs of any problems and
   allocates and sets the Rexx_return string if a function returned
   with a value.


   CloseREXX(wind, force)
   ----------------------
   Input:   struct Window        *wind       Pointer to window
            int                  force       0: Don't force;  1: Do force
   Returns: int                              0: Didn't close; 1: Did
   
   Clean up all REXX stuff. Unless force is set, will ask whether it 
   should proceed IF there are any macros waiting. This is done by posting
   a requester in wind (or Workbench if wind is NULL).


   LaunchCmd(buf)
   --------------
   Input:   char                 *buf        Command macro to be launched
   Returns: void
   
   Launch a REXX command.


   SetFuncParam(pos, string)
   -------------------------
   Input:   int                  pos         Parameter number (0--15)
            char                 *string     Parameter string
   Returns: int                              0: OK; 1: Failed
   
   Allocate space to put a function parameter and copy in the parameter
   string. The name of the function to be called should be placed in
   position 0. Returns 0 if succeeded; 1 if not


   LaunchFunc()
   ------------
   Input:   void
   Returns: void

   Launch a REXX function. Uses data created by calls to SetFuncParam()


   RexxReturnUsed()
   ----------------
   Input:   void
   Returns: void

   Free memory used for Rexx_return. Also sets Rexx_return back to NULL
   so its value can be tested to see of we've got a value back from a
   function call.


   BuildComLine(buffer)
   --------------------
   Output:  char                 *buffer     Pointer to character string
   Returns: void
   
   Rebuilds the command line into buffer (which must be pre-allocated).
   This is useful if you want to parse commands separately from the
   built-in parser.

****************************************************************************

   Revision History:
   =================

***************************************************************************/
/* Includes
*/

#include "rxil.h"

#include <graphics/gfxbase.h>
#include <libraries/dos.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#ifdef LATTICE
#include <proto/intuition.h>
#endif

/************************************************************************/
/* Intuition Externs
*/

extern   struct   IntuitionBase  *IntuitionBase;
extern   struct   GfxBase        *GfxBase;

/************************************************************************/
/* REXX-specific extern defines. These are required by other rxil routines
   and/or by client code.
*/

/* This is the Rxil definition structure.
*/
struct RxilDef *global_rdef = NULL;

/* This is used to place return values from functions
*/
char *Rexx_return = NULL;

/************************************************************************/
/* REXX-specific static defines. These are only required by routines
   defined in this file.
*/

/* We provide one invocation structure for commands and one for functions.
   These could be allocated and deallocated on the fly.
*/
static struct RxilInvocation *rxi_func;
static struct RxilInvocation *rxi_cmd;

/* This one is for the startup macro.  We want to retain it's pointer
   even after it is launched so that we can recognise it when it 
   terminates and handle it differently.
*/
static struct RxilInvocation *rxi_startup;

/* This is used to build REXX function arguments
*/
static char *RexxFuncBuf[16] = {NULL, NULL, NULL, NULL,
                                NULL, NULL, NULL, NULL,
                                NULL, NULL, NULL, NULL,
                                NULL, NULL, NULL, NULL};

/************************************************************************/
/* Setup the REXX port
*/
void SetupREXX(char *portname,   /* Port name */
               char *extension,  /* Default extension 0: "rexx"         */
               char *conspec,    /* CON: 0: none, 1: default, or string */
               int  synch,       /* 0: Synchronous,   1: Asynchronous   */
               struct RxilFunction comtab[],    /* REXX command table   */
               char *startup)    /* Startup macro                       */
{
   /* Open rexx library and desired ports.
      Only the PUBLIC port is needed if we're only receiving commands
   */
   global_rdef = RxilInit(RXIL_PUBLIC | RXIL_SECRET, portname);
   if(global_rdef == NULL)
   {
      Die("Unable to init rexx");
   }

   /* Initialise RxilDef to customise things.
      Here, we could set
         global_rdef->Flags
      to turn features off.
   */
   global_rdef->Flags = RXIL_NO_ABORT;
   
   /* Set up console for commands or functions launced by this program.
      If conspec was 0, we do this from a CLI;
      if 1, we use a standard spec;
      else, this is a pointer to a string for the required CON:
   */
   if(conspec == (char *)0L)
      global_rdef->Console = NULL;
   else if(conspec == (char *)1L)
      global_rdef->Console = "CON:10/10/400/150/RexxCon";
   else
      global_rdef->Console = conspec;


   /* Set the default extension for macros */
   if(extension == (char *)0L)
      global_rdef->Extension = "rexx";
   else
      global_rdef->Extension = extension;

   /* The port to use for launched commands.
      REXX:    synchronous
      AREXX:   asynchronous
   */
   if(synch == 0)
      global_rdef->HostPort = "REXX";
   else
      global_rdef->HostPort = "AREXX";

   /* The command table we've created
   */
   global_rdef->CommandTable = &comtab[0];


   /***
      Pre-allocate the command and function some invocation structures.  
      These could be allocated and de-allocated on-the-fly if desired.
   ***/
   rxi_cmd = RxilCreateRxi(NULL, RXCOMM);
   if(rxi_cmd == NULL)
   {
      Die("Unable to allocate command_rxi");
   }

   rxi_func = RxilCreateRxi(NULL, RXFUNC);
   if( rxi_func == NULL )
   {
      Die("Unable to allocate function_rxi");
   }

   /***
      Run a startup macro when the program is invoked
   ***/
   rxi_startup = NULL;
   if(startup)
   {
      rxi_startup = RxilCreateRxi(startup, RXCOMM);
      if(rxi_startup == NULL)
      {
         /* We don't die, just give a message */
         ReqMessage(NULL,"** Unable to allocate startup rxi **",0);
      }
      else
      {
         /* We don't need a console */
         rxi_startup->Console = NULL;

         /* Now launch the macro */
         if(RxilLaunch( rxi_startup ) != 0)
         {
            /* We don't die, just give a message */
            ReqMessage(NULL,"** Unable to launch the startup macro **",0);
         }
      }
   }
}

/************************************************************************/
/* This code is for programs which launch macros.
   Handle any ARexx invocation returns which may be back.
*/
void ProcessReturns(void)
{
   struct   RxilInvocation *rxi;

   while((rxi = RxilGetReturn()) != NULL)
   {
#ifdef DEBUG2
      if(rxi->Type == RXCOMM)
      {
         printf("Command Invocation completed\n");
      }
      else
      {
         printf( "Function Invocation completed\n" );
      }
#endif

      if(rxi == rxi_startup)
      {
         /* This is our startup macro terminating.  We handle this 
            differently since we don't want to display an error 
            if not found (which the standard routine 
            RxilHandleReturn() would do). We also want to de-allocate 
            the RxilInvocation structure.
         */

         /* Deallocate things, close console windows, etc.
            Then free the structure.
         */
         if((rxi->RexxMsg->rm_Result1 != RC_WARN) ||
            (rxi->RexxMsg->rm_Result2 != ERR10_001))
         {
            /* The error is not just "program not found".
               It is a real error, so tell the user.
               
               ACRM: surely, this should be &&, not || !)
            */
            RxilHandleReturn(rxi, 1);
         }
         RxilCleanupReturn(rxi);
         RxilDeleteRxi(rxi);
         rxi_startup = NULL;     /* To be tidy... */
      }
      else
      {
         /* This is a macro other than the startup, so handle the 
            return normally, telling the user about any problems.
            Nothing will break if this code is removed...
         */
         if(Rexx_return) FreeMem(Rexx_return, (strlen(Rexx_return) + 2));
         Rexx_return = NULL;
         if(RxilHandleReturn(rxi, 0))
         {
            Rexx_return = (char *)
               AllocMem((strlen((char *)(rxi->RexxMsg->rm_Result2)) + 2), 0L);
            if(Rexx_return)
               strcpy(Rexx_return, (char *)(rxi->RexxMsg->rm_Result2));
         }

         /* Clean up: deallocate memory and close CON:   */
         RxilCleanupReturn(rxi);
      }
   }
}


/************************************************************************/
/* Clean up all REXX stuff. Unless force is set, will ask whether it 
   should proceed if there are any macros waiting.
   Returns 1 if it cleaned up OK; otherwise returns 0.
*/
int CloseREXX(struct Window *wind,
              int           force)
{
   int   cleanup = 1,
         i;

   if(!force)
   {
      if(RxilPending())
         cleanup = ReqMessage3(wind,"There are REXX commands pending"," ",
                                    "Really quit?",1);
   }

   if(cleanup)
   {
      /* Clean up the rxil stuff */
      RxilCleanup(global_rdef);
      global_rdef = NULL;
      
      /* Clean up additional rxil_shell stuff */
      if(Rexx_return) FreeMem(Rexx_return, (strlen(Rexx_return) + 2));
      Rexx_return = NULL;
      
      for(i=0; i<16; i++)
      {
         if(RexxFuncBuf[i] != NULL) 
            FreeMem(RexxFuncBuf[i], (strlen(RexxFuncBuf[i]) + 2));
         RexxFuncBuf[i] = NULL;
      }
      
   }
   
   return(cleanup);
}

   

/************************************************************************/
/* Launch a REXX command
*/
void LaunchCmd(char *buf)
{
   LONG rc;
   
   rxi_cmd->Name = buf;
   rc = RxilLaunch( rxi_cmd );
   if(rc)
   {
      char buffer[80];
      sprintf(buffer,"Command launch returned %d",rc);
      ReqMessage2(NULL,buf,buffer,0);
   }
}   

/************************************************************************/
/* Allocate a space to put a function parameter
*/
int SetFuncParam(int pos,
                 char *string)
{
   if(pos < 0 || pos > 15) return(1);
   
   if(string != NULL)
   {
      /* If currently allocated, free it */
      if(RexxFuncBuf[pos] != NULL) 
         FreeMem(RexxFuncBuf[pos], (strlen(RexxFuncBuf[pos]) + 2));

      /* Reallocate for our string */
      RexxFuncBuf[pos] = (char *)AllocMem((strlen(string) + 2), 0L);
      if(RexxFuncBuf[pos] == NULL) Die("No memory for Rexx buffer");

      /* Copy in our string */
      strcpy(RexxFuncBuf[pos], string);
      
      /* Free up the next position too */
      if((pos + 1) < 16)
      {
         if(RexxFuncBuf[pos+1] != NULL) 
            FreeMem(RexxFuncBuf[pos+1], (strlen(RexxFuncBuf[pos+1]) + 2));
         RexxFuncBuf[pos+1] = NULL;
      }
   }
   
   return(0);
}

/************************************************************************/
/* Launch a REXX function
*/
void LaunchFunc(void)
{
   int   i;
   LONG  rc;
   
   /* Set the Name to point to our allocated function name string       */
   rxi_func->Name = RexxFuncBuf[0];
   
   /* Set the FuncArg[] array to point to our allocated strings         */
   for(i=1; i<16; i++)
   {
      /* First set to NULL */
      rxi_func->FuncArg[i] = NULL;
      
      /* Break if this is the last one */
      if(RexxFuncBuf[i] == NULL) break;
      
      /* Otherwise copy in the pointer */
      rxi_func->FuncArg[i] = RexxFuncBuf[i];
   }
   
   /* Launch the function */
   rc = RxilLaunch( rxi_func );
   if(rc) 
   {
      char  RexxFuncBuffer[80];
      sprintf(RexxFuncBuffer,"Function launch returned %d",rc);
      ReqMessage2(NULL,RexxFuncBuf,RexxFuncBuffer,0);
   }
}

/************************************************************************/
/* Free memory used for Rexx_return.
*/
void RexxReturnUsed(void)
{
   Rexx_return = NULL;
}

/************************************************************************/
/* Rebuild the command line if a separate command parser is being used
*/
void BuildComLine(char *buffer)
{
   int   i;

   buffer[0] = '\0';
   for(i=0; i<RXIL_ARGC; i++)
   {
      strcat(buffer, RXIL_ARGV(i));
      strcat(buffer," ");
   }
}