/*  RexxFuncHost.c   */


/*		Copyright © 1989 by Donald T. Meyer
 *		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.
 *
 *		Donald T. Meyer
 *		Stormgate Software
 *		4 Rustic Creek Court
 *		St. Peters, MO  63376
 *
 *		BIX:	donmeyer
 *		GEnie:	D.MEYER
 *		PLINK:	Stormgate
 */



#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 );



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


/* These are in the user function module */

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;



/* 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 handle_message( struct RexxMsg *rexxmsg );

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;

/* 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, 20 );
	}
	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, 20 );
	}

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

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

	eventloop();

	cleanup();

	exit( 0 );
}



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


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

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

			/* Remove ourselves from the arexx library list */
			to_rexx( (ULONG)RXREMLIB, hostname, NULL, NULL, NULL );

			/* Make port un-findable to all others just in case */
			RemPort( hostport );
			hostport->mp_Node.ln_Name = NULL;
		}

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

				/* Default to a "not found" code. */
				rexxmsg->rm_Result1 = RC_WARN;
				rexxmsg->rm_Result2 = ERR10_001;

				/* See if we have been told to close, and are just
				 * flushing the message queue.
				 */
				if( !finished )
				{
					/* Handle the message contents */
					handle_message( rexxmsg );
				}
			}

			ReplyMsg( (struct Message *)rexxmsg );
		}
	}
}



static void handle_message( struct RexxMsg *rexxmsg )
{
	if(  (rexxmsg->rm_Action & RXCODEMASK) != RXFUNC  )
	{
		/* Something else (probably a command invocation if
		 * our host port was set as the ARexx address).
		 * We want to respond only to function invocations.
		 */
		return;
	}

	dispatch( rexxmsg );

	check_result( rexxmsg );
}



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

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



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

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

	/* 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", 20 );
	}
}


/* 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, 0 );
		}
			/* 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, 0 );
			}
			else
			{
				/* There was no previous invocation */
				bailout( noclone_string, 5 );
			}
		}
		else
		{
			/* The user is trying to invoke us. */
			if(  FindPort( hostname )  )
			{
				/* But we already exist */
				bailout( redundant_string, 5 );
			}
		}
	}
}



/* If thereis 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 );
}



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



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



static void cleanup( void )
{
	client_cleanup();

	/* Remove ourselves from the arexx library list */
	to_rexx( (ULONG)RXREMLIB, hostname, NULL, NULL, NULL );

	if( hostport )
		DeletePort( hostport );

	closelibrarys();

	if( message_console )
		Close( message_console );
}




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

	cleanup();

	exit( rc );
}



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 | (1L<<RXFB_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 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;
	}
}

