/*  RexxFuncHost.c   */


/* Version 1.2 */

/*		Copyright © 1989 by Donald T. Meyer, Stormgate Software
 *		All Rights Reserved
 *
 *		This source code may be compiled and used in any software
 *		product.
 *		No portion of this source code is to be
 *		re-distributed or sold for profit without the written
 *		permission of the author, Donald T. Meyer.
 *
 *		Stormgate Software
 *		PO Box 383
 *		St. Peters, MO  63376
 *
 *		E-Mail can be sent to via the following:
 *		BIX:	donmeyer		(almost daily)
 *		GEnie:	D.MEYER			(weekly)
 *		PLINK:	Stormgate		(weekly)
 */



#include <exec/types.h>
#include <libraries/dos.h>
#include <workbench/startup.h>

#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/dos.h>

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

#include "rexxfunchost.h"



/*------------------------------------------------------------------*/
/*					External Function Declarations					*/
/*------------------------------------------------------------------*/

/* These are (better be!) in the user function module(s) */

char *client_init( void );

void client_cleanup( void );

void client_event_handler( ULONG flags );



/*------------------------------------------------------------------*/
/*					External Variable Declarations					*/
/*------------------------------------------------------------------*/


/* These are also in the user function module(s) */

extern char *hello_string;
extern char *success_string;
extern char *removal_string;
extern char *redundant_string;
extern char *noclone_string;

extern struct RexxFunction func_table[];

extern int hostpri;
extern char *hostname;

extern BPTR _Backstdout;
extern char *console_def_string;

extern ULONG client_event_flags;



/* Other misc. external variables. */

extern struct WBStartup *WBenchMsg;



/*------------------------------------------------------------------*/
/*					Local Function Declarations						*/
/*------------------------------------------------------------------*/

static void openlibrarys( void );
static void closelibrarys( void );

static void bailout( char *message, int rc );

static void cleanup( void );

static void init_port( void );

static void eventloop( void );

static int to_rexx( ULONG cmd, STRPTR arg0, STRPTR arg1, STRPTR arg2,
	STRPTR arg3 );

static void rexx_delete_port( struct MsgPort *port );

static void check_result( struct Rexxmsg *rexxmsg );

static void dispatch( struct RexxMsg *rexxmsg );

static void check_clone( int argc, char *argv[] );

static BOOL remove_clone( void );



/*------------------------------------------------------------------*/
/*					Variable Definitions							*/
/*------------------------------------------------------------------*/

struct RxsLib *RexxSysBase = NULL;


static struct MsgPort *hostport = NULL;

static BPTR message_console = NULL;

static BOOL finished = FALSE;

static BOOL on_list = FALSE;


/* For detaching */
long _BackGroundIO = 1;



/*------------------------------------------------------------------*/
/*					Functions										*/
/*------------------------------------------------------------------*/


void main( int argc, char *argv[] )
{
	char *errmsg;


	if( _Backstdout == NULL )
	{
		/* Probably child of workbench, open our own window */
		/* Open a window to print hello and copyright stuff to */
		message_console = Open( console_def_string, MODE_NEWFILE );

		/* After some thought, I decided to quit if we could not
		 * post our hello banner, etc.
		 * We could just as easily continue, but this _should_ never
		 * fail, so probably moot.
		 */
		if( message_console == NULL )
			bailout( NULL, RETURN_FAIL );
	}
	else
	{
		/* Send banner and error messages to the CLI that started us */
		message_console = _Backstdout;
	}

	openlibrarys();

	Write( message_console, hello_string, strlen(hello_string) );

	check_clone( argc, argv );

	init_port();

	/* Allow the client to do his initializations */
	if(  ( errmsg = client_init() ) != NULL  )
	{
		bailout( errmsg, RETURN_FAIL );
	}

	Write( message_console, success_string, strlen(success_string) );

	/* Done with window, close it */
	Delay( 200 );
	Close( message_console );
	message_console = NULL;

	eventloop();

	cleanup();

	exit( RETURN_OK );
}



/* We come here after all of the initialization is done, and don't
 * leave until we receive a termination signal.
 */

static void eventloop( void )
{
	struct RexxMsg *rexxmsg;
	ULONG flags;


	while( ! finished )
	{
		flags = Wait(  ( 1L<<hostport->mp_SigBit ) | SIGBREAKF_CTRL_C
			| client_event_flags  );

		/* Call a routine in the client to allow any application-
		 * specific things such as window events to be dealt with.
		 */
		client_event_handler( flags );

		while( rexxmsg = (struct RexxMsg *)GetMsg( hostport ) )
		{
			if( IsRexxMsg(rexxmsg) )
			{
				/* The message is from an ARexx program */

				/* Handle the message contents */

				if(  (rexxmsg->rm_Action & RXCODEMASK) == RXFUNC  )
				{
					/* We only respond to function invocations, as
					 * opposed to anything else (i.e. a command
					 * invocation if our host port was set as the
					 * ARexx address).
					 */
					dispatch( rexxmsg );

					check_result( rexxmsg );
				}
			}


			/* We always reply, even if the message is not really
			 * something we want to handle
			 */
			ReplyMsg( (struct Message *)rexxmsg );
		}


		if( flags & SIGBREAKF_CTRL_C )
		{
			/* Somebody wants us to quit.  Probably another us! */
			finished = TRUE;
		}
	}
}



/* This will take the function name from the RexxMsg and try to
 * find a function in our command table which matches.  This is
 * also where the argument count verification is done.
 * If a function which matches is found, and it has the correct
 * number of arguments, it is vectored to.
 */

static void dispatch( struct RexxMsg *rexxmsg )
{
	int i, match = -1;
	char *cmd;


	cmd = ARG0(rexxmsg);

	for( i=0; func_table[i].fname != NULL; i++ )
	{
		if( func_table[i].caseflag == TRUE )
		{
			/* Case sensitive */
			if(  strcmp( func_table[i].fname, cmd ) == 0  )
			{
				/* A match */
				match = i;
				break;
			}
		}
		else
		{
			/* Not case sensitive */
			if(  stricmp( func_table[i].fname, cmd ) == 0  )
			{
				/* A match */
				match = i;
				break;
			}
		}
	}

	if( match == -1 )
	{
		/* No match found */
		rexxmsg->rm_Result1 = RC_WARN;
		rexxmsg->rm_Result2 = ERR10_001;
		return;
	}

	/* Found a match.  Let's do the argument checking */
	if(   ( func_table[match].argcount != -1 ) &&
		(  ( rexxmsg->rm_Action & RXARGMASK ) !=
		func_table[match].argcount  )   )
	{
		/* Wrong number of args */
		rexxmsg->rm_Result1 = RC_ERROR;
		rexxmsg->rm_Result2 = ERR10_017;
		return;
	}


	/* Since we found a function, default to a "success" code. */
	rexxmsg->rm_Result1 = RC_OK;
	rexxmsg->rm_Result2 = 0;

	/* Call the function. */
	(*(func_table[match].func))(rexxmsg);
}



/* To ease the burden on a function, the checking for wether or not
 * an argstring was requested is done here.  This will ensure that
 * one is never returned if ARexx did not ask for one.
 */

static void check_result( struct RexxMsg *rexxmsg )
{
	/* Make sure that we don't return an argstring
	 * if one was not requested
	 */
	if( rexxmsg->rm_Action & RXFF_RESULT )
	{
		/* Result string wanted */
		return;
	}


	/* A result string was not requested.  Let's see if there is one */

	if( (rexxmsg->rm_Result1==0) && (rexxmsg->rm_Result2!=0) )
	{
		/* There is one, so we need to delete it. */
		DeleteArgstring( (struct RexxArg *)(rexxmsg->rm_Result2) );
		rexxmsg->rm_Result2 = 0;
	}
}



/* Open a public port and add it to the ARexx Library List.  This is
 * where ARexx will send messages for functions it wants resolved.
 *
 * Note that we create our port with a priority higher than zero.
 * This should give a _very_slight_ increase in speed when ARexx
 * FindPort()'s us.
 */

static void init_port( void )
{
	/* Setup the public host port */
	if(  ( hostport = CreatePort( hostname, 5 ) ) == NULL  )
	{
		bailout( "Unable to create message port", RETURN_FAIL );
	}

	/* Add ourselves to the arexx library list */
	if( to_rexx( (ULONG)RXADDFH, hostname, (STRPTR)hostpri, NULL, NULL )  )
	{
		/* Could not add our port to the library list */
		bailout( "Unable to add self to ARexx library list",
			RETURN_FAIL );
	}

	on_list = TRUE;
}



/* This function does some fairly convoluted things involving
 * terminating another previously running invocation of ourselves.
 * Depending on wether we are launched from CLI or WorkBench and
 * command line options (only if from the CLI of course).
 * The logic is:
 *
 *		IF from WorkBench
 *			IF already invoked
 *				remove existing invocation
 *				exit
 *
 *		IF from CLI
 *			IF -q option
 *				IF already invoked
 *					remove existing invocation
 *					exit
 *				ELSE
 *					tell user there is no existing invocation
 *					exit
 *			ELSE
 *				IF already invoked
 *					tell user that we cannot install again
 *					exit
 *
 */

static void check_clone( int argc, char *argv[] )
{
	if( WBenchMsg )
	{
		/* We are running from workbench, see if we want to
		 * kill off the existing invocation.
		 */
		if( remove_clone() )
		{
			/* There was and we did */
			bailout( removal_string, RETURN_OK );
		}
			/* Else, there was no clone, continue execution. */ 
	}
	else
	{
		/* From the CLI.  If the argument "-q" is given, we try to kill
		 * the existing invocation.  Otherwise we just refuse
		 * to start another invocation.
		 */
		if(  ( argc > 1 ) && ( stricmp(argv[1],"-q") == 0 )  )
		{
			/* User wants us to remove ourselves */
			if( remove_clone() )
			{
				/* There was and we did */
				bailout( removal_string, RETURN_OK );
			}
			else
			{
				/* There was no previous invocation */
				bailout( noclone_string, RETURN_WARN );
			}
		}
		else
		{
			/* The user is trying to invoke us. */
			if(  FindPort( hostname )  )
			{
				/* But we already exist */
				bailout( redundant_string, RETURN_WARN );
			}
		}
	}
}



/* If there is already another instance of ourselves running, kill it
 * and return TRUE, otherwise return FALSE.
 */

static BOOL remove_clone( void )
{
	struct MsgPort *otherport;


	if(   (  otherport = FindPort( hostname )  ) != NULL  )
	{
		/* Yes, we are */
		Signal( otherport->mp_SigTask, SIGBREAKF_CTRL_C );
		return( TRUE );
	}

	return( FALSE );
}



/* Open any librarys needed.  In this case, just one.  The client
 * modules may also open additional librarys as needed of course.
 */

static void openlibrarys( void )
{
	/*   ARexx library   */
	if(  ! ( RexxSysBase = (struct RxsLib *)OpenLibrary( RXSNAME, 0L) )  )
	{
		bailout( "Unable to open the ARexx Library", RETURN_FAIL );
	}
}



/* Complement to openlibrarys()
 */

static void closelibrarys( void )
{
	if( RexxSysBase )
		CloseLibrary( (struct Library *)RexxSysBase );
}



/* This routine will do everything neccessary to see that we make an
 * orderly exit, regardless of what point in the program we were at.
 */

static void cleanup( void )
{
	client_cleanup();

	if( on_list == TRUE )
	{
		/* Remove ourselves from the arexx library list */
		to_rexx( (ULONG)RXREMLIB, hostname, NULL, NULL, NULL );
	}

	if( hostport )
	{
		rexx_delete_port( hostport );
	}

	closelibrarys();

	if( message_console )
	{
		Close( message_console );
	}
}



/* Typicaly the place we come when something has gone awry, although
 * some fairly innocous events (like already being installed) will
 * call this also as their way out.
 * If there is a message, and a way to display it, it will be displayed.
 * Then free any resources, and exit!
 */

static void bailout( char *message, int rc )
{
	if( message_console && message )
	{
		Write( message_console, message, strlen(message) );
		Delay( 200 );
	}

	cleanup();

	exit( rc );
}



/* General purpose utility function to send messages to the ARexx
 * resident process.  For simplicity's sake, we ask ARexx to not
 * respond.  This does mean that a request could fail and we would
 * never know.  Since there is _very_ little reason for anything
 * we are doing here to fail, it seems a reasonable risk.  Else
 * why would Bill have given us the RXFB_NONRET flag? :-)
 */

static int to_rexx( ULONG cmd, STRPTR arg0, STRPTR arg1, STRPTR arg2,
	STRPTR arg3 )
{
	struct MsgPort *rmast;
	struct RexxMsg *rexxmsg;


	/* Allocate a packet to send to rexxmaster */
	rexxmsg = CreateRexxMsg( NULL, NULL, NULL );
	if( rexxmsg == NULL )
	{
		return( 1 );
	}

	rexxmsg->rm_Action = cmd | RXFF_NONRET;

	rexxmsg->rm_Args[0] = arg0;
	rexxmsg->rm_Args[1] = arg1;
	rexxmsg->rm_Args[2] = arg2;
	rexxmsg->rm_Args[3] = arg3;

	Forbid();
	if(  rmast = FindPort( "REXX" )  )
	{
		PutMsg( rmast, (struct Message *)rexxmsg );
	}
	Permit();

	if( rmast == NULL )
	{
		/* we could not find the REXX port, this failed! */
		DeleteRexxMsg( rexxmsg );
		return( 2 );
	}

	return( 0 );
}



/* This will safely delete a message port used to recieve commands
 * from ARexx.
 * This is acomplished by setting a failure return code and replying the
 * message if it is from ARexx.  If the message is not from ARexx, it
 * is merely replied.
 *
 * The Forbid() Permit() bracketing is done to insure that the port
 * is empty when we delete it.  It would be *very* rude to cause
 * the ARexx resident process to hang waiting for us to reply a
 * message that we never will...
 */
 
static void rexx_delete_port( struct MsgPort *port )
{
	struct RexxMsg *rexxmsg;
 
 
	Forbid();
 
	while( rexxmsg = (struct RexxMsg *)GetMsg( port ) )
	{
		if(  IsRexxMsg( rexxmsg )  )
		{
			/* Set failure code */
			rexxmsg->rm_Result1 = RC_ERROR;
			rexxmsg->rm_Result2 = ERR10_015;
		}
 
		ReplyMsg( (struct Message *)rexxmsg );
	}
 
	DeletePort( port );
 
	Permit();
}



/* This can be called to handle the details of placing a result
 * string into the rexx message packet as the result Argstring.
 * This will deal with inability to allocate the Argstring.
 * If the string pointer is NULL, a general failure code will
 * be set.
 */

void SetResultString( struct RexxMsg *rexxmsg, char *string )
{
	if( string )
	{
		rexxmsg->rm_Result1 = RC_OK;
		rexxmsg->rm_Result2 =
			(ULONG)CreateArgstring( string, strlen(string) );

		if( rexxmsg->rm_Result2 == NULL )
		{
			/* Unable to create the argstring */
			rexxmsg->rm_Result1 = RC_ERROR;
			rexxmsg->rm_Result2 = ERR10_012;
		}
	}
	else
	{
		/* Result string is null, set general failure code */
		rexxmsg->rm_Result1 = RC_ERROR;
		rexxmsg->rm_Result2 = ERR10_012;
	}
}

