/* Agent Tcl
   Bob Gray
   25 January 1995
  
   tclBasicCmd.cc

   This file implements the Tcl commands that handle migration, message
   passing and meetings.

   Copyright (c) 1995, Robert S. Gray, Sumit Chawla, Saurab Nog, 
   Dartmouth College

   See the file "agent.terms" for information on usage and redistribution
   of this file and for a DISCLAIMER OF ALL WARRANTIES.
*/

#ifndef NO_PRAGMAS
#pragma implementation "agentTclInt.h"
#endif

#include "platPorting.h"
#include "agentAgentId.h"	// AgentId class
#include "mesgMachId.h"	// MachineId class
#include "agentVersion.h"
#include "genConversion.h"
#include "suppDString.h"
#include "genMeeting.h"
#include "agentScript.h"	// compiled-in copy of agent.tcl
#include "tclMakeLists.h"

/* ------------------------------------------------ */

#include "agentId.h"
#include "agentTcl.h"
#include "agentTclInt.h"
#include "genManager.h"
#include "genVersion.h"
#include "tclAgent.h"
#include "tcl.h"
#include "tclParse.h"
#include "tcpipTcl.h"
#include "tclUtility.h"
#include "truefalse.h"

#ifdef TCL8

struct COMMAND_INFO
{
    char *name;
    Tcl_CmdProc *proc;
    Tcl_ObjCmdProc *objProc;
};

static COMMAND_INFO agent_commands[] = 
{
	/* core commands */

    {"dummystring"	, Agent_DummystringCmd  	, (Tcl_ObjCmdProc *) NULL},
    {"agent_begin"	, Agent_BeginCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_end"	, Agent_EndCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_name"	, Agent_NameCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_root"       , Agent_RootCmd         	, (Tcl_ObjCmdProc *) NULL},
    {"agent_receive"	, (Tcl_CmdProc *) NULL		, Agent_ReceiveObjCmd    },
    {"agent_send"	, (Tcl_CmdProc *) NULL		, Agent_SendObjCmd	 },
    {"agent_getevent"   , Agent_GetEventCmd     	, (Tcl_ObjCmdProc *) NULL},
    {"agent_event"	, Agent_EventCmd		, (Tcl_ObjCmdProc *) NULL},
    {"request_meeting"  , Agent_RequestCmd      	, (Tcl_ObjCmdProc *) NULL},
    {"accept_meeting"	, Agent_AcceptCmd		, (Tcl_ObjCmdProc *) NULL},
    {"reject_meeting"	, Agent_RejectCmd		, (Tcl_ObjCmdProc *) NULL},
    {"get_meeting"	, Agent_GetReqCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_meet"	, Agent_MeetCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_accept"	, Agent_AcceptAndWaitCmd	, (Tcl_ObjCmdProc *) NULL},
    {"meeting_close"	, Meeting_CloseCmd		, (Tcl_ObjCmdProc *) NULL},
    {"meeting_send"	, Meeting_SendCmd		, (Tcl_ObjCmdProc *) NULL},
    {"meeting_receive"  , Meeting_ReceiveCmd		, (Tcl_ObjCmdProc *) NULL},
    {"meeting_sendfile" , Meeting_SendfileCmd   	, (Tcl_ObjCmdProc *) NULL},
    {"meeting_getfile"  , Meeting_GetfileCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_force"	, Agent_ForceCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_submit"	, Agent_SubmitCmd 		, (Tcl_ObjCmdProc *) NULL},
    {"agent_fork"	, Agent_ForkCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_jump"	, Agent_JumpCmd			, (Tcl_ObjCmdProc *) NULL},

	/* other commands */ 

    {"agent_sleep"	, Agent_SleepCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_time"	, Agent_TimeCmd			, (Tcl_ObjCmdProc *) NULL},
    {"crypt"		, Agent_CryptCmd		, (Tcl_ObjCmdProc *) NULL},
    {"mask"		, Agent_MaskCmd			, (Tcl_ObjCmdProc *) NULL},
    {"seed"             , Agent_SeedCmd         	, (Tcl_ObjCmdProc *) NULL},
    {"trueseed"		, Agent_TrueseedCmd		, (Tcl_ObjCmdProc *) NULL},
    {"trueevent"	, Agent_TrueeventCmd		, (Tcl_ObjCmdProc *) NULL},
    {"random"           , Agent_RandomCmd       	, (Tcl_ObjCmdProc *) NULL},
    {"security"		, Agent_SecurityCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_info"	, Agent_InfoCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_script"	, Agent_ScriptCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_status"	, Agent_StatusCmd		, (Tcl_ObjCmdProc *) NULL},
    {"agent_select"     , Agent_SelectCmd       	, (Tcl_ObjCmdProc *) NULL},
    {"agent_disk"	, Agent_DiskCmd			, (Tcl_ObjCmdProc *) NULL},
    {"agent_transfer"	, Agent_TransferCmd		, (Tcl_ObjCmdProc *) NULL},

	/* end of list marker */ 

    { NULL 		, (Tcl_CmdProc *) NULL		, (Tcl_ObjCmdProc *) NULL}
};

#else

struct COMMAND_INFO
{
  char *name;
  Tcl_CmdProc *proc;
  int interruptable;    /* 1 if the command is interruptable   */
};

static COMMAND_INFO agent_commands[] = 
{
	/* core commands */

    {"dummystring"	, Agent_DummystringCmd  	, 0},
    {"agent_begin"	, Agent_BeginCmd		, 0},
    {"agent_end"	, Agent_EndCmd			, 0},
    {"agent_name"	, Agent_NameCmd			, 0},
    {"agent_root"       , Agent_RootCmd         	, 0},
    {"agent_receive"	, Agent_ReceiveCmd		, 0},
    {"agent_send"	, Agent_SendCmd			, 0},
    {"agent_getevent"   , Agent_GetEventCmd     	, 0},
    {"agent_event"	, Agent_EventCmd		, 0},
    {"request_meeting"  , Agent_RequestCmd      	, 0},
    {"accept_meeting"	, Agent_AcceptCmd		, 0},
    {"reject_meeting"	, Agent_RejectCmd		, 0},
    {"get_meeting"	, Agent_GetReqCmd		, 0},
    {"agent_meet"	, Agent_MeetCmd			, 0},
    {"agent_accept"	, Agent_AcceptAndWaitCmd	, 0},
    {"meeting_close"	, Meeting_CloseCmd		, 0},
    {"meeting_send"	, Meeting_SendCmd		, 0},
    {"meeting_receive"  , Meeting_ReceiveCmd		, 0},
    {"meeting_sendfile" , Meeting_SendfileCmd   	, 0},
    {"meeting_getfile"  , Meeting_GetfileCmd		, 0},
    {"agent_force"	, Agent_ForceCmd		, 0},
    {"agent_submit"	, Agent_SubmitCmd 		, 0},
    {"agent_fork"	, Agent_ForkCmd			, 1},
    {"agent_jump"	, Agent_JumpCmd			, 1},

	/* other commands */ 

    {"agent_sleep"	, Agent_SleepCmd		, 0},
    {"agent_time"	, Agent_TimeCmd			, 0},
    {"crypt"		, Agent_CryptCmd		, 0},
    {"mask"		, Agent_MaskCmd			, 0},
    {"seed"             , Agent_SeedCmd         	, 0},
    {"trueseed"		, Agent_TrueseedCmd		, 0},
    {"trueevent"	, Agent_TrueeventCmd		, 0},
    {"random"           , Agent_RandomCmd       	, 0},
    {"security"		, Agent_SecurityCmd		, 0},
    {"agent_info"	, Agent_InfoCmd			, 0},
    {"agent_script"	, Agent_ScriptCmd		, 0},
    {"agent_status"	, Agent_StatusCmd		, 0},
    {"agent_select"     , Agent_SelectCmd       	, 0},
    {"agent_disk"	, Agent_DiskCmd			, 1}, 
    {"agent_transfer"	, Agent_TransferCmd		, 0},

	/* end of list marker */ 

    { NULL 		, (Tcl_CmdProc *) NULL		, 0}
};

#endif

/* Agent_DummyStringCmd

   Tcl syntax: dummystring <length>

*/

int Agent_DummystringCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int length;

    if (argc != 2) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " <length>\"", (char *) NULL);
	return (TCL_ERROR);
    }

    if (Tcl_GetInt(interp, argv[1], &length) != TCL_OK) {
	return TCL_ERROR;
    }

    char *buffer = new char [length + 1];
    memset (buffer, 'A', length);
    buffer[length] = '\0';
    Tcl_SetResult (interp, buffer, TCL_DYNAMIC);
    return (TCL_OK);
}

/* Agent_TransferCmd

   Tcl syntax: agent_tranfser <server> <channel> [ -time <seconds> ]

      Purpose: Transfer a server message from a disk file to a server

        Input: dummy  = client data (unused)
			(ClientData)

               interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = the number of command arguments
			(int)

               argv   = the command arguments
			(char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter result
               to an appropriate error message on error.  Otherwise the
               procedure returns TCL_OK and sets the interpreter result to the
	       id of the newly transferred agent.
*/

int Agent_TransferCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int mode;
    double seconds;
    SmartIdPtr agentId;
    Tcl_Channel channel;

	/* assertions */

    assert (interp != NULL);
    assert (argv != NULL);

	/* check the number of arguments */

    if (argc < 3) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " <machine> <channel> [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the machine name */

    argc -= 1;
    argv += 1;
    MachineId machine (argv[0], UNKNOWN_IP);

	/* get the file channel */

    argc -= 1;
    argv += 1;

    if ((channel = Tcl_GetChannel (interp, argv[0], &mode)) == NULL) {
	return TCL_ERROR;
    } else if (!(mode & TCL_READABLE)) {
        Tcl_AppendResult(interp, "channel \"", argv[0], "\" wasn't opened for reading", (char *) NULL);
        return TCL_ERROR;
    }

	/* get the timeout interval */

    argc -= 1;
    argv += 1;

    if (argc == 0) {
	seconds = DEFAULT_CONNECT_SECONDS;
    } else if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	return TCL_ERROR;
    } 

	/* transfer the agent */

    if ((agentId = Agent_Transfer (interp, seconds, &machine, channel)) == (AgentId *) NULL) {
	return TCL_ERROR;
    }

	/* return the identification of the new agent */

    Tcl_DString dstring;
    Tcl_DStringInit (&dstring);
    Agent_IdToTclList (agentId, &dstring);
    Tcl_DStringResult (interp, &dstring);
    Tcl_DStringFree (&dstring);
    return TCL_OK;
}

/* Agent_RootCmd

   Tcl syntax: agent_root

      Purpose: Turn the current agent into a root agent

        Input: dummy  = client data (unused)
               interp = the current interpreter
               argc   = number of command arguments
               argv   = the command arguments

       Output: The procedure returns TCL_ERROR and sets the interpreter
               result to an appropriate error message on error.  Otherwise
               the procedure returns TCL_OK.
*/

int Agent_RootCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
	/* check the number of command arguments */

    if (argc != 1) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], "\"", (char *) NULL);
    	return TCL_ERROR;
    }

	/* turn the current agent into a root agent */

    return (Agent_Root (interp));
}

/* Agent_SelectCmd

      Authors: Sumit Chawla, Saurab Nog, 5 November 1995
      Revised: Bob Gray, 12 November 1995
	
   Tcl syntax: agent_select {message | meeting | event | {handle ... }}
	       [-nonblocking | -time <seconds> | -blocking]
 
      Purpose: Wait for one or more meetings to become ready for reading or
	       to change state AND/OR until there is an incoming message,
	       meeting, or event that matches the masks.

        Input: dummy  = client data (unused)
			(ClientData)

               interp = the current interpreter
		        (struct Tcl_Interp *)

               argc   = the number of command arguments
			(int)

               argv   = the command arguments
			(char **argv)

       Output: The procedure returns TCL_ERROR and sets the interpreter
               result to an appropriate error message on error.  Otherwise
               the procedure returns TCL_OK and sets the interpreter result
               to a ready list.
*/

int Agent_SelectCmd (ClientData,Tcl_Interp *interp,int argc,char ** argv )
{
    int i;
    UINT_32 j;
    int rc;
    int handle;
    char temp[16];
    char *statusString;
    double seconds;                 /* timeout interval                   */
    int listArgc = 0;               /* number of descriptors in the list  */
    char **listArgv = NULL;         /* the list of descriptors            */
    AgentSelectParameters selectParams;

	/* check the number of arguments */

    if ((argc < 2) || (argc > 4)) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " { message | meeting | event | {handle ... }} [-nonblocking | -time seconds | -blocking]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the timeout */

    if (argc == 2) {
	seconds = INFINITY;
    } else if ((i = parseTimeout (interp, seconds, argc, argv, 2)) < 0) {
	return TCL_ERROR;
    } else if (i < argc) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* split apart the list of file descriptors */

    if ((Tcl_SplitList (interp, argv[1], &listArgc, &listArgv) != TCL_OK)) {
	Tcl_AppendResult (interp, "second argument must be a valid Tcl list", (char *) NULL);
	return TCL_ERROR;
    }

    if (listArgc == 0) {
	Tcl_AppendResult (interp, "must select on at least one thing", (char *) NULL);
	return TCL_ERROR;
    }

	/* make sure that listArgv gets deleted */

    SmartCharPtr listManager ((char *) listArgv);

	/* worst-case number of handles and fds */

    selectParams.setMaximumHandles (listArgc);
    selectParams.setMaximumFds     (listArgc);

	/* pull apart the descriptor list */

    for(i = 0; i < listArgc; i++) {

	if (!strcmp(listArgv[i], "message")) {

	    selectParams.watchItem (MESSAGE_ITEM);

	} else if (!strcmp(listArgv[i], "meeting")) {

	    selectParams.watchItem (MEETING_ITEM);

	} else if (!strcmp(listArgv[i], "event")) {

	    selectParams.watchItem (EVENT_ITEM);

	} else if ((Tcl_GetInt(interp, listArgv[i], &handle) != TCL_OK) || (handle < 0)) {

	    Tcl_ResetResult (interp);
	    Tcl_AppendResult (interp, "invalid handle \"", listArgv[i], "\": should be \"meeting\", \"meesage\", \"event\" or a meeting handle", (char *) NULL);
	    return TCL_ERROR;

	} else {

	    selectParams.watchHandle ((UINT_32) handle);
	}
    }

	/* do the selection */

    rc = Agent_Select (interp, seconds, &selectParams);

	/* assemble the list of ready descriptors */

    if (rc == TCL_OK) {

	UINT_8 status;
	UINT_32 handle;
	Tcl_DString dstring;
	Tcl_DStringInit (&dstring);

	if (selectParams.isItemReady(MESSAGE_ITEM)) {
	    Tcl_DStringAppendElement (&dstring, "message");
	}

	if (selectParams.isItemReady(EVENT_ITEM)) {
	    Tcl_DStringAppendElement (&dstring, "event");
	}


	if (selectParams.isItemReady(MEETING_ITEM)) {
	    Tcl_DStringAppendElement (&dstring, "meeting");
	}

	for (j = 0; j < selectParams.getNumHandles(); j++) {

	    status = selectParams.isHandleReady (j, handle);

	    if (status != MEET_NO_CHANGE) {
		Tcl_DStringStartSublist (&dstring);
		fastIntegerToAscii (handle, temp);
		Tcl_DStringAppendElement (&dstring, temp);
		statusString = Agent_MeetingStatusToString (status);
		Tcl_DStringAppendElement (&dstring, statusString);
		delete (statusString);
		Tcl_DStringEndSublist (&dstring); 
	    }
	}

	Tcl_SetResult (interp, Tcl_DStringValue (&dstring), TCL_VOLATILE);
	Tcl_DStringFree (&dstring); 
    }

    return (rc);
}

/* Agent_BeginCmd

   Tcl syntax: agent_begin ?machine? ?-time seconds?

      Purpose: Notify the machine that we wish to become an agent
               under its control

        Input: clientData = client data (unused)
               interp     = the current interpreter
               argc       = number of command arguments
               argv       = the command arguments

       Output: The procedure returns TCL_ERROR on error.  Otherwise the
               procedure returns TCL_OK and sets the intepreter result to the
	       new agent identification.
*/

int Agent_BeginCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{ 
    int code;
    AgentId *id = NULL;
    MachineId *machine = NULL;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* get the timeout if specified */

    if ((argc == 3) || (argc == 4)) {
	if (parseShortTimeout (interp, seconds, argc, argv, argc - 2) < 0) {
	    return TCL_ERROR;
	} 
    }

	/* determine the machine identification */  

    if ((argc == 1) || (argc == 3)) {
	machine = new MachineId (NULL, UNKNOWN_IP);
    } else if ((argc == 2) || (argc == 4)) {
	machine = new MachineId (argv[1], UNKNOWN_IP); 
    } else {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " ?machine? ?-time seconds?\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* begin the agent */

    if ((id = Agent_Begin (interp, seconds, machine)) == NULL) {

	code = TCL_ERROR;

    } else {

	Tcl_DString dstring;
	Tcl_DStringInit (&dstring);
	Agent_IdToTclList (id, &dstring);
	Tcl_DStringResult (interp, &dstring);
	Tcl_DStringFree (&dstring);
	code = TCL_OK;
    }
 
	/* cleanup */

    delete (machine);
    delete (id);
    return (code);
} 

/* Agent_NameCmd 

   Tcl syntax: agent_name string [-time seconds]

      Purpose: Assign a descriptive name to the agent

        Input: clientData = client data (unused)
               interp     = the current interpreter
               argc       = number of command arguments
               argv       = the command arguments
        
       Output: The procedure returns TCL_ERROR and sets the interpreter result
               to an appropriate error message on error.  Otherwise the
               procedure returns TCL_OK and sets the intepreter result to the
               new 4-element identification of the agent. 
*/

int Agent_NameCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
  int returnCode;
  AgentId *id = NULL;
  double seconds = DEFAULT_CONNECT_SECONDS;

    /* check the number of arguments */
 
  if (argc < 2) {
    Tcl_AppendResult (interp, "wrong # of args: should be \"agent_name string ?-time seconds?\"", (char *) NULL);
    return TCL_ERROR;
  }

    /* get the timeout if it is specified */

  if (argc > 2) {
    if (parseShortTimeout (interp, seconds, argc, argv, 2) < 0) {
      return TCL_ERROR;
    }
  }

    /* assign the name */

  if ((id = Agent_Name (interp, seconds, argv[1])) == NULL) {

    returnCode = TCL_ERROR;

  } else {

    Tcl_DString dstring;
    Tcl_DStringInit (&dstring);
    Agent_IdToTclList (id, &dstring);
    Tcl_DStringResult (interp, &dstring);
    Tcl_DStringFree (&dstring);
    returnCode = TCL_OK;
  }

    /* cleanup */

  delete (id);
  return (returnCode); 
}

/* Agent_EndCmd

   Tcl syntax: agent_end ?-time seconds? 

      Purpose: Notify the controlling machine that we are no longer an agent

        Input: clientData = client data (unused)
               interp     = the current interpreter
               argc       = number of command arguments
               argv       = the command arguments

       Output: 
*/

int Agent_EndCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if ((argc != 1) && (argc != 3)) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"agent_end ?-time seconds?\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the timeout if specified */

    if (argc == 3) {
	if (parseShortTimeout (interp, seconds, argc, argv, 1) < 0) {
	    return TCL_ERROR;
	}
    } 
 
	/* end the agent */
  
    return (Agent_End (interp, seconds));
}

/* Agent_EventCmd
 
   Tcl syntax: agent_event {destination} tag string [-time seconds]

      Purpose: Send an event to an agent

        Input: clientData = client data (unused)
               interp     = the current interpreter
               argc       = number of command arguments
               argv       = the command arguments

       Output: The procedure returns TCL_ERROR and sets the interpreter result
               to an appropriate error message on error.  Otherwise the  
               procedure returns TCL_OK and sets the interpreter result to the
               empty string.
*/

int Agent_EventCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    AgentId *id = NULL;
    int errorCode = TCL_ERROR;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 4) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " {destination} tag string ?-time seconds?", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the agent identification */

    if ((id = Agent_SplitAgentId (interp, argv[1])) == NULL) {
	return TCL_ERROR;
    }

	/* get the timout if one is specified */

    if (argc > 4) {
	if (parseShortTimeout (interp, seconds, argc, argv, 4) < 0) {
	    goto cleanup;
	} else if (argc > 6) {
	    Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	    goto cleanup;
	} 
    }

	/* send the event */

    errorCode = Agent_Event (interp, seconds, id, argv[2], argv[3]);

cleanup:

    delete (id);
    return (errorCode);
}

/* Meeting_SendfileCmd

   Tcl syntax: meeting_sendfile <handle> <file handle> <bytes>

      Purpose: Send a file across a meeting connection

	Input: clientData = client data (unused)
		    	    (ClientData)

	       interp     = the current interpreter
			    (struct Tcl_Interp *)

	       argc       = the number of command arguments
			    (int)
	
	       argv       = the command arguments
			    (char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result to an appropriate error message on error.  Otherwise
	       the procedure returns TCL_OK. 
*/

int Meeting_SendfileCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int mode;
    int bytes;
    int handle;
    int errorCode;
    Tcl_Channel channel;

	/* assertions on the parameters */

    assert (interp != NULL);
    assert (argv != NULL);

	/* check the number of arguments */

    if (argc != 4) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " <handle> <file handle> <bytes>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the meeting handle */ 

    argc -= 1;
    argv += 1; 

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the file channel */

    argc -= 1;
    argv += 1;

    if ((channel = Tcl_GetChannel (interp, argv[0], &mode)) == NULL) {
	return TCL_ERROR;
    } else if (!(mode & TCL_READABLE)) {
        Tcl_AppendResult(interp, "channel \"", argv[0], "\" wasn't opened for reading", (char *) NULL);
        return TCL_ERROR;
    }

	/* split out the number of bytes */

    argc -= 1;
    argv += 1;

    if ((Tcl_GetInt (interp, argv[0], &bytes) != TCL_OK) || (bytes < 0)) {
	Tcl_AppendResult (interp, ": invalid byte count \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* check for extraneous arguments */

    argc -= 1;
    argv += 1;

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the file along the meeting */

    errorCode = Meeting_Sendfile (interp, (UINT_32) handle, channel, (UINT_32) bytes);
    return (errorCode);
}

/* Meeting_SendCmd

   Tcl syntax: meeting_send {handle} [integer] string

      Purpose: Send a message across a meeting connection

	Input: clientData = client data (unused)
		    	    (ClientData)

	       interp     = the current interpreter
			    (struct Tcl_Interp *)

	       argc       = the number of command arguments
			    (int)
	
	       argv       = the command arguments
			    (char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result to an appropriate error message on error.  Otherwise
	       the procedure returns TCL_OK.
*/

int Meeting_SendCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int handle;
    char *string;
    int code = 0;
    int errorCode;

	/* check the number of arguments */

    if (argc < 3) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " {handle} [integer] string\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the handle */ 

    argc -= 1;
    argv += 1; 

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the message code if it is there */

    argc -= 1;
    argv += 1; 

    if (argc >= 2) {
 
	if (Tcl_GetInt (interp, argv[0], &code) != TCL_OK) {
	    Tcl_AppendResult (interp, ": message code must be an integer", (char *) NULL);
	    return TCL_ERROR;
        } else if (code < 0) {
	    Tcl_AppendResult (interp, "message code must be 0 or greater", (char *) NULL);
	    return TCL_ERROR;
	} else {
	    argc -= 1;
	    argv += 1;
	}
    }

	/* make sure that we have a message string */

    if (argc == 0) {
	Tcl_AppendResult (interp, "must specify a message string", (char *) NULL);
	return TCL_ERROR;
    } else {
	string = argv[0];
	argc -= 1;
	argv += 1;
    }

	/* check for extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    } 
 
	/* send the message */

    errorCode = Meeting_Send (interp, (UINT_32) handle, (UINT_32) code, string);
    return (errorCode);
}

/* Agent_SendCmd

   Tcl syntax: agent_send {destination} [integer] string [-time seconds]  

      Purpose: Send a result or a message to an agent

        Input: clientData = client data (unused)
               interp     = the current interpreter
               argc       = number of command arguments
               argv       = the command arguments

       Output: The procedure returns TCL_ERROR on error.  Otherwise the
               procedure sends the string to the specified agent and 
               returns TCL_OK.
*/

int Agent_SendCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int code = 0;                
    double seconds;
    char *string = NULL;
    AgentId *id = NULL;
    int errorCode = TCL_ERROR;

	/* check the number of arguments */

    if (argc < 3) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " {destination} [integer] string [-time seconds]\"", (char *) NULL);
	goto cleanup;
    }

	/* split out the agent identification  */

    argc -= 1;
    argv += 1; 

    if ((id = Agent_SplitAgentId (interp, argv[0])) == NULL) {
	goto cleanup;
    }

	/* get the message code if it is there */

    argc -= 1;
    argv += 1; 

    if ((argc == 2) || (argc == 4)) {
	if (Tcl_GetInt (interp, argv[0], &code) != TCL_OK) {
	    Tcl_AppendResult (interp, ": message code must be an integer", (char *) NULL);
	    goto cleanup;
        } else if (code < 0) {
	    Tcl_AppendResult (interp, "message code must be 0 or greater", (char *) NULL);
	    goto cleanup; 
	} else {
	    argc -= 1;
	    argv += 1;
	}
    }

	/* make sure that we have a message string */

    if (argc == 0) {
	Tcl_AppendResult (interp, "must specify a message string", (char *) NULL);
	goto cleanup;
    } else {
	string = argv[0];
	argc -= 1;
	argv += 1;
    }

	/* get the timeout interval */

    if (argc == 0) {
	seconds = DEFAULT_CONNECT_SECONDS;
    } else if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	goto cleanup;
    } else {
	argc -= 2;
	argv += 2;
    }

	/* check for extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	goto cleanup;
    } 
 
	/* send the message */

    errorCode = Agent_Send (interp, seconds, id, code, string);

cleanup:

    delete (id);
    return (errorCode);
}

#ifdef TCL8

int Agent_SendObjCmd (ClientData, Tcl_Interp *interp, int objc, Tcl_Obj * const*objv)
{
    int code;				// message code
    int length;
    char *string;			// message string
    double seconds = 0.0;		// timeout seconds
    AgentId *agentId = (AgentId *) NULL;

	// check the number of arguments

    if (objc < 3) {
	Tcl_WrongNumArgs (interp, 1, objv, "{destination} [integer] string [-time seconds]\"");
	return TCL_ERROR;
    }

	// skip over the command name

    objc -= 1;
    objv += 1;

	// get the recipient agent

    if ((agentId = Agent_SplitAgentIdObj (interp, objv[0])) == (AgentId *) NULL) {
	return TCL_ERROR;
    }

    SmartIdPtr agentIdManager (agentId);
    objc -= 1;
    objv += 1;

	// get the message code *** if it is there ***

    if ((objc == 2) || (objc == 4)) {

	if (Tcl_GetIntFromObj (interp, objv[0], &code) != TCL_OK) {

	    Tcl_AppendToObj (Tcl_GetObjResult(interp),
		": message code must be an integer", -1);
	    return TCL_ERROR;

        } else if (code < 0) {

	    Tcl_AppendToObj (Tcl_GetObjResult(interp),
		"message code must be 0 or greater", -1);
	    return TCL_ERROR;
	}

	objc -= 1;
	objv += 1;
    }

	// get the message string

    if (objc == 0) {
	Tcl_AppendToObj (Tcl_GetObjResult(interp), 
	    "must specify a message string", -1);
	return TCL_ERROR;
    } 

    string = Tcl_GetStringFromObj (objv[0], &length);
    objc -= 1;
    objv += 1;

	// get the timeout interval

    if (objc == 0) {

	seconds = DEFAULT_CONNECT_SECONDS;

    } else if (parseShortTimeoutObj (interp, seconds, objc, objv, 0) < 0) {

	return TCL_ERROR;

    } else {

	objc -= 2;
	objv += 2;
    }

	// check for extraneous arguments

    if (objc != 0) {
	Tcl_AppendToObj (Tcl_GetObjResult(interp), "extraneous arguments", -1);
	return TCL_ERROR;
    } 

	// send the message

    return (Agent_Send (interp, seconds, agentId, code, string));
}

#endif

/* Agent_DiskCmd

   Tcl syntax: agent_disk <fd>

      Purpose: Save the current state of the agent (as if the agent were
               about to jump) to a file.  The file can be transferred to a 
	       server later with the "agent_transfer" command.

        Input: clientData = client data (unused)
			    (ClientData)

               interp     = the current interpreter
			    (Tcl_Interp *)

               argc       = the number of command arguments
			    (int)

               argv       = the command arguments
			    (char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter
               result to an appropriate error message on error.  Otherwise
               the procedure returns TCL_OK and sets the interpreter result
	       to (1) the empty string if the agent's state has just been 
	       saved to the disk file or (2) "JUMPED" if the disk file has 
	       been transferred to a server and the agent has been restored.
*/

int Agent_DiskCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int mode;
    Tcl_Channel channel;

	/* assertions */

    assert (interp != NULL);
    assert (argv != NULL);

	/* check the number of command arguments */

    if (argc != 2) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " <fd>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the file channel */

    argc -= 1;
    argv += 1;

    if ((channel = Tcl_GetChannel (interp, argv[0], &mode)) == NULL) {
	return TCL_ERROR;
    } else if (!(mode & TCL_WRITABLE)) {
        Tcl_AppendResult(interp, "channel \"", argv[0], "\" wasn't opened for writing", (char *) NULL);
        return TCL_ERROR;
    }

	/* check for extraneous arguments */

    argc -= 1;
    argv += 1;

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* save the state to disk */

    if (Agent_Disk (interp, channel) != TCL_OK) {
	return TCL_ERROR;
    }

    Tcl_ResetResult (interp);
    return TCL_OK;
}
       
/* Agent_JumpCmd

   TCL Syntax: agent_jump machine [-time seconds]

      Purpose: Jump to another machine

        Input: dummy  = client data (unused)
               interp = the current interpreter
               argc   = number of command arguments
               argv   = the command arguments 

       Output: The procedure returns TCL_ERROR and sets the interpreter
               result to an appropriate error message if it is unable to 
               jump to the remote machine.  The procedure returns TCL_OK
               and sets the interpreter result to "SAME" if the remote
               machine is the same as the current machine.  Otheriwse the  
               procedure calls exit() to terminate the program since the 
               agent has successfully transported to a different machine.
               The agent will resume execution on this machine with the
               interpreter result set to "JUMPED".
*/

int Agent_JumpCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int code;
    double seconds;

	/* check the number of arguments */

    if (argc == 2) {
   	 seconds = DEFAULT_CONNECT_SECONDS;
    } else if (argc != 4) {
   	 Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " machine [-time seconds]\"", (char *) NULL);
   	 return TCL_ERROR;
    } else if (parseShortTimeout (interp, seconds, argc, argv, 2) < 0) {
   	 return TCL_ERROR;
    }

	/* jump and exit if successful */

    MachineId machine (argv[1], UNKNOWN_IP);

    if ((code = Agent_Jump (interp, seconds, &machine)) == TCL_OK) {

#ifdef REUSE_INTERPRETER
	return TCL_REWIND;
#else
	Tcl_Eval (interp, "exit");
	abort_with_message ("should never get here");   // should never get here
#endif

    } else if (code == TCL_CONTINUE) {

   	 Tcl_SetResult (interp, "SAME", TCL_STATIC);
   	 code = TCL_OK;
    }

    return (code); 
}

/* Agent_ForkCmd

   TCL syntax: agent_fork machine [-time seconds]

      Purpose: Fork a copy of the current agent

        Input: dummy  = client data (unused)
               interp = Tcl interpreter
               argc   = number of arguments
               argv   = the arguments   
 
       Output: The procedure returns TCL_ERROR if it is unable to fork
               the agent.  Otherwise the procedure returns TCL_OK and 
               sets interp -> result as follows: result for the parent agent
               will be set to the 4-element identification vector of the child
               agent; result for the child agent will be set to "CHILD".
*/

int Agent_ForkCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
  int code;
  double seconds;
  AgentId *id = NULL;

    /* check the arguments */

  if (argc == 2) {
    seconds = DEFAULT_CONNECT_SECONDS;
  } else if (argc != 4) {
    Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " machine [-time seconds]\"", (char *) NULL);
    return TCL_ERROR;
  } else if (parseShortTimeout (interp, seconds, argc, argv, 2) < 0) {
    return TCL_ERROR;
  }

    /* fork the agent */

  MachineId machine (argv[1], UNKNOWN_IP);

  if ((id = Agent_Fork (interp, seconds, &machine)) == NULL) {

    code = TCL_ERROR;

  } else {

    Tcl_DString dstring;
    Tcl_DStringInit (&dstring);
    Agent_IdToTclList (id, &dstring);
    Tcl_DStringResult (interp, &dstring);
    Tcl_DStringFree (&dstring);
    code = TCL_OK;
  }

    /* clean up */

  delete (id);
  return (code);
}

/* Agent_ScriptCmd

   Tcl syntax: agent_script
			[-vars varname ...]
			[-procs procname ...]
			[-varlist <varname ...>]
			[-proclist <varname ...>]
			[-time time]
			-script script
	
       Purpose: Construct the Tcl script that defines the specified variables
	        and the specified procedures and then executes the specified
	        (sub)script

         Input: clientData = unused client data 
			     (ClientData)

		interp     = the current Tcl interpreter
			     (Tcl_Interp *)

		argc       = the number of command arguments
			     (int)

		argv       = the command arguments themselves
			     (char **)

        Output: The procedure returns TCL_ERROR and sets the interpreter
		result to an appropriate error message on error.  Otherwise
	  	the procedure returns TCL_OK and sets the interpreter result
		to the constructed script.
*/

static int findListEnd (int i, int argc, char **argv)
{
    int count = 0;

    for (i++; i < argc; i++) {

	if (strcmp(argv[i], "-time") && 
	    strcmp(argv[i], "-vars") && 
	    strcmp(argv[i], "-varlist") && 
	    strcmp(argv[i], "-proclist") && 
	    strcmp(argv[i], "-procs") && 
	    strcmp(argv[i], "-script") && 
	    strcmp(argv[i],"-language")) {

	    count++;

	} else {

	    break; 
	}
    }

    return count;
}

static int parseOptions (
    Tcl_Interp *interp, int argc, char **argv, int startArgc,
    double &seconds, 
    int &numVariables, char **&variables, BOOLEAN &freeVariables,
    int &numProcedures, char **&procedures, BOOLEAN &freeProcedures,
    char *&script, char *&language)
{
    int i;
    
	/* defaults */

    seconds	   = DEFAULT_CONNECT_SECONDS;
    variables      = NULL;
    procedures     = NULL;
    script         = NULL;
    language       = NULL;
    numVariables   = 0;
    numProcedures  = 0;
    freeVariables  = e_FALSE;
    freeProcedures = e_FALSE;

	/* get the arguments */

    for (i = startArgc; i < argc; i++) {

	if (!strcmp(argv[i], "-time")) {

	    if ((i = parseShortTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR;
	    }

	    i -= 1;	// we're going to increment again in the loop

	} else if (!strcmp(argv[i], "-vars")) {

	    if (variables != NULL) {
		Tcl_AppendResult (interp, "can not have more than one variable list", (char *) NULL);
		return TCL_ERROR;
	    } 

	    if ((numVariables = findListEnd (i, argc, argv)) == 0) {
		Tcl_AppendResult (interp, "\"-vars\" must be followed by at least one variable name", (char *) NULL);
		return TCL_ERROR;
	    }

	    variables = &argv[i] + 1;
	    i        += numVariables;

	} else if (!strcmp(argv[i], "-varlist")) {

	    if (variables != NULL) {
		Tcl_AppendResult (interp, "can not have more than one variable list", (char *) NULL);
		return TCL_ERROR;
	    } 

	    if (++i == argc) {
		Tcl_AppendResult (interp, "\"-varlist\" must be followed by a single Tcl list", (char *) NULL);
		return TCL_ERROR;
	    }

	    int listArgc;
	    char **listArgv;

	    if (Tcl_SplitList (interp, argv[i], &listArgc, &listArgv) != TCL_OK) {
		Tcl_AppendResult (interp, ": argument after \"-varlist\" must be a valid Tcl list", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (listArgc == 0) {
		Tcl_AppendResult (interp, ": list after \"-varlist\" must contain at least one element", (char *) NULL);
		return TCL_ERROR;
	    }

	    numVariables  = listArgc;
	    variables     = listArgv;
	    freeVariables = e_TRUE;

	} else if (!strcmp(argv[i], "-proclist")) {

	    if (procedures != NULL) {
		Tcl_AppendResult (interp, "can not have more than one procedure list", (char *) NULL);
		return TCL_ERROR;
	    } 

	    if (++i == argc) {
		Tcl_AppendResult (interp, "\"-proclist\" must be followed by a single Tcl list", (char *) NULL);
		return TCL_ERROR;
	    }

	    int listArgc;
	    char **listArgv;

	    if (Tcl_SplitList (interp, argv[i], &listArgc, &listArgv) != TCL_OK) {
		Tcl_AppendResult (interp, ": argument after \"-proclist\" must be a valid Tcl list", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (listArgc == 0) {
		Tcl_AppendResult (interp, ": list after \"-proclist\" must contain at least one element", (char *) NULL);
		return TCL_ERROR;
	    }

	    numProcedures  = listArgc;
	    procedures     = listArgv;
	    freeProcedures = e_TRUE;

	} else if (!strcmp(argv[i], "-procs")) {

	    if (procedures != NULL) {
		Tcl_AppendResult (interp, "can not have more than one procedure list", (char *) NULL);
		return TCL_ERROR;
	    } 

	    if ((numProcedures = findListEnd (i, argc, argv)) == 0) {
		Tcl_AppendResult (interp, "\"-procs\" must be followed by at least one procedure name", (char *) NULL);
		return TCL_ERROR;
	    }

	    procedures = &argv[i] + 1;
	    i         += numProcedures;
 
	} else if (!strcmp(argv[i],"-language")) {

	    if (language != NULL) {
		Tcl_AppendResult (interp, "can not specify more than one language", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (++i == argc) {
		Tcl_AppendResult (interp, "\"-language\" must be followed by a language name", (char *) NULL);
		return TCL_ERROR;
	    }

	    language = argv[i];

	} else if (!strcmp(argv[i], "-script")) {

	    if (script != NULL) {
		Tcl_AppendResult (interp, "can not have more than one script", (char *) NULL);
		return TCL_ERROR;
	    } 

	    if (++i >= argc) {
		Tcl_AppendResult (interp, ": need a script after \"-script\"", (char *) NULL);
		return TCL_ERROR; 
	    }

	    script = argv[i];

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\"", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we have script */

    if (script == NULL) {
	Tcl_AppendResult (interp, "must specify a script with \"-script\"", (char *) NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

int Agent_ScriptCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    char *script;
    char *language;
    double seconds;
    int numVariables;
    char **variables = NULL;
    BOOLEAN freeVariables = e_FALSE;
    int numProcedures;
    char **procedures = NULL;
    BOOLEAN freeProcedures = e_FALSE;
    char *newScript;
    char temp[24];

	/* assertions */

    assert (interp != NULL);
    assert (argv != NULL);

	/* 
	 * check the number of arguments
	 */

    if (argc <= 2) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " [-time seconds] [-language language] [-vars varname ...] [-procs procname ...] [-time seconds] -script script\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* 
	 * parse the arguments, construct the script and put the script into
	 * the interpreter result
	 */
 
    if (parseOptions (interp, argc, argv, 1, seconds, numVariables, variables, freeVariables, numProcedures, procedures, freeProcedures, script, language) != TCL_OK) {

	rc = TCL_ERROR;

    } else if ((newScript = Agent_Script (interp, numProcedures, procedures, numVariables, variables, script)) == NULL) {

	rc = TCL_ERROR;

    } else {

	sprintf (temp, "%0.6f", seconds);
	Tcl_SetResult (interp, temp, TCL_VOLATILE);
	Tcl_AppendElement (interp, newScript);
	rc = TCL_OK;
    }

	/*
	 * cleanup
	 */

    if (freeVariables) {
	assert (variables != NULL);
	free (variables);
    }

    if (freeProcedures) {
	assert (procedures != NULL);
	free (procedures);
    }
 
    return (rc);
}

/* Agent_SubmitCmd

   Tcl syntax: agent_submit machine 
			[-time seconds]
			[-language language]
			[-vars varname ...]
			[-procs procname ...]
			[-varlist <varname ...>]
			[-proclist <procname ...>]
			-script script
  
      Purpose: Submit an agent to the server

        Input: clientData = client data (unused)
               interp     = Tcl interpreter
               argc       = number of arguments
               argv       = arguments

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result to an appropriate error message on error.  Otherwise
	       the procedure returns TCL_OK and sets the interpreter result
	       to the 4-element identification of the new child.
*/

int Agent_SubmitCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{ 
    int code;
    double seconds;
    char **variables = NULL;
    char **procedures = NULL;
    char *script;
    char *language;
    int numVariables;
    int numProcedures;
    BOOLEAN freeVariables = e_FALSE;
    BOOLEAN freeProcedures = e_FALSE;
    AgentId *id = NULL;
    Version *version = NULL;
    MachineId *machine = NULL;

	/* check the number of arguments */

    if (argc <= 3) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " machine [-time seconds] [-language language] [-vars varname ...] [-procs procname ...] -script script\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* construct the script and then try to submit the script */

    if (parseOptions (interp, argc, argv, 2, seconds, numVariables, variables, freeVariables, numProcedures, procedures, freeProcedures, script, language) != TCL_OK) {

	code = TCL_ERROR;

    } else {

	machine = new MachineId (argv[1], UNKNOWN_IP);

	if (language != NULL) {
	    version = new Version (language, 0, 0, Version::e_ALPHA);
	}

	if ((id = Agent_Submit (interp, seconds, machine, version, numProcedures, procedures, numVariables, variables, script)) == NULL) {

	    code = TCL_ERROR;

	} else {

	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_IdToTclList (id, &dstring);
	    Tcl_DStringResult (interp, &dstring);
	    Tcl_DStringFree (&dstring);
	    code = TCL_OK;
	}
    }

	/* cleanup */

    if (freeVariables) {
	assert (variables != NULL);
	free (variables);
    }

    if (freeProcedures) {
	assert (procedures != NULL);
	free (procedures);
    }

    delete (version);
    delete (machine);
    delete (id); 
    return (code);
}         

/* Agent_GetEventCmd

   Tcl syntax: agent_getevent tag string <-blocking | -time seconds | -nonblocking>

      Purpose: Check to see if any events are available for the agent

        Input: clientData = client data (unused)
               interp     = Tcl interpreter
               argc       = number of arguments
               argv       = the arguments

       Output: The procedure returns TCL_ERROR on error.  The procedure 
               returns TCL_OK and sets the interpreter result to -1 if no 
	       event arrives during the timeout interval.  Otherwise the 
               procedure returns TCL_OK, sets the interpreter result to 
               the sender identification, sets the "tag" variable to the
               event tag and sets the "string" variable to the event string.                
*/

int Agent_GetEventCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int i;
    BOOLEAN timeFound = e_FALSE;    /* e_TRUE if timeout found        */
    double seconds;                 /* number of seconds for time out */
    int returnCode;                 /* return code from the procedure */
    INCOMING_EVENT *event;          /* an event from an agent         */
    char *scstring = NULL;	    /* security information string    */
    char *securityVariable = NULL;  /* security variable              */

	/* check the number of arguments and agent registration */

    if (argc < 4) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " tag string [-security secure] <-blocking | -time seconds | -nonblocking>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the flags */

    i = 3;

    while (i < argc) {

	if (!strcmp (argv[i], "-security")) {

	    if (++i >= argc) {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (argv[i][0] == '-') {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }
		
	    if (securityVariable != NULL) {
		Tcl_AppendResult (interp, "\"-security\" can only appear once", (char *) NULL);
		return TCL_ERROR;
	    } 

	    securityVariable = argv[i++];

	} else if ((!strcmp(argv[i], "-blocking")) || (!strcmp(argv[i], "-nonblocking")) || (!strcmp(argv[i], "-time"))) {

	    if ((i = parseTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR; 
	    }

	    if (timeFound == e_TRUE) {
		Tcl_AppendResult (interp, "only one timeout can be specified", (char *) NULL);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\": expected -security, -blocking, -nonblocking or -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }
 
	/* make sure that we had a timeout option */

    if (timeFound == e_FALSE) {
	Tcl_AppendResult (interp, "must specify a timeout with -blocking, -nonblocking or -time", (char *) NULL);
	return TCL_ERROR;
    }
  
	/* receive the event */
 
    if (Agent_GetEvent (interp, seconds, &event) == TCL_ERROR) {
	return TCL_ERROR; 
    } else if (event == NULL) {
	Tcl_SetResult (interp, "-1", TCL_STATIC);
	return TCL_OK;
    }

	/* set the interpreter result and the variables */

    if (Tcl_SetVar (interp, argv[1], event -> tag, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set tag variable", (char *) NULL);
	returnCode = TCL_ERROR;

    } else if (Tcl_SetVar (interp, argv[2], event -> string, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set string variable", (char *) NULL); 
	returnCode = TCL_ERROR;

    } else {

	returnCode = TCL_OK;

	if (securityVariable != NULL) {

	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_SecurityToTclList (event -> security, &dstring);

	    if (Tcl_SetVar (interp, securityVariable, Tcl_DStringValue(&dstring), TCL_LEAVE_ERR_MSG) == NULL) {
		Tcl_AppendResult (interp, ": unable to set security variable", (char *) NULL); 
		returnCode = TCL_ERROR;
	    }

	    Tcl_DStringFree (&dstring);
	}
 
	if (returnCode == TCL_OK) {
	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_IdToTclList (event -> id, &dstring);
	    Tcl_DStringResult (interp, &dstring);
	    Tcl_DStringFree (&dstring);
	}
    }

    /* clean up */

  delete (scstring);
  delete (event); 
  return returnCode;
}

/* Meeting_GetfileCmd

   Tcl syntax: meeting_getfile <handle> <file handle> <-nonblocking | -time seconds | -blocking>

      Purpose: Receive a file from a meeting connection

	Input: clientData = client data (unused)
		    	    (ClientData)

	       interp     = the current interpreter
			    (struct Tcl_Interp *)

	       argc       = the number of command arguments
			    (int)
	
	       argv       = the command arguments
			    (char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result to an appropriate error message on error.  Otherwise
	       the procedure returns TCL_OK and sets the interpreter result
               to the number of bytes that were read.
*/

int Meeting_GetfileCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int i;
    int mode;
    int handle;
    char temp[16];
    int errorCode;
    UINT_32 bytes;
    Tcl_Channel channel;
    BOOLEAN timeFound = e_FALSE;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* assertions on the parameters */

    assert (interp != NULL);
    assert (argv != NULL);

	/* check the number of arguments */

    if (argc < 4) {
	Tcl_AppendResult (interp, "wrong # of args: should be \"", argv[0], " <handle> <file handle> <-blocking | -time seconds | -nonblocking>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the timeout option */ 

    i = 3;

    while (i < argc) {

	if ((!strcmp(argv[i], "-blocking")) || (!strcmp(argv[i], "-nonblocking")) || (!strcmp(argv[i], "-time"))) {

	    if ((i = parseTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR; 
	    }

	    if (timeFound) {
		Tcl_AppendResult (interp, "only one timeout can be specified", (char *) NULL);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\": expected -security, -blocking, -nonblocking or -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we had a timeout option */

    if (timeFound == e_FALSE) {
	Tcl_AppendResult (interp, "must specify a timeout with -blocking, -nonblocking or -time", (char *) NULL);
	return TCL_ERROR;
    }

    argc  = 3;

	/* split out the meeting handle */ 

    argc -= 1;
    argv += 1; 

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the file channel */

    argc -= 1;
    argv += 1;

    if ((channel = Tcl_GetChannel (interp, argv[0], &mode)) == NULL) {
	return TCL_ERROR;
    } else if (!(mode & TCL_WRITABLE)) {
        Tcl_AppendResult(interp, "channel \"", argv[0], "\" wasn't opened for writing", (char *) NULL);
        return TCL_ERROR;
    }

	/* check for extraneous arguments */

    argc -= 1;
    argv += 1;

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* receive the file from the meeting */

    if ((errorCode = Meeting_Getfile (interp, seconds, (UINT_32) handle, channel, bytes)) == TCL_BREAK) {
	Tcl_SetResult (interp, "-1", TCL_STATIC);
	return TCL_OK;
    } else if (errorCode != TCL_OK) {
	return TCL_ERROR;
    }

	/* set the result to the number of bytes read */

    fastIntegerToAscii (bytes, temp);
    Tcl_SetResult (interp, temp, TCL_VOLATILE);
    return TCL_OK; 
}

/* Meeting_ReceiveCmd

   Tcl syntax: meeting_receive handle code string <-blocking | -time seconds | -nonblocking>

      Purpose: Receive a message from a meeting

        Input: clientData = client data (unused)
			    (ClientData)
	
	       interp     = the current interpreter
			    (Tcl_Interp *)

	       argc       = the number of command arguments
			    (int)

	       argv       = the command arguments
			    (char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result to an appropriate error message on error.  The 
	       procedure returns TCL_OK and sets the intepreter result to
	       "-1" if the timeout expires before a message arrives.  
	       Otherwise the procedure returns TCL_OK and sets the variable 
	       with name "code" to the message code, the variable with name
	       "string" to the message string, and the interpreter result to
	       the empty string.
*/

int Meeting_ReceiveCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    int i;
    int handle;
    UINT_32 code;
    char *string;
    char temp[16];                    /* scratch area for message code  */
    double seconds;                   /* number of seconds for time out */
    int returnCode;                  /* return code from the procedure */
    BOOLEAN timeFound = e_FALSE;      /* e_TRUE if timeout found        */

	/* check the number of arguments */

    if (argc < 5) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " handle codevar stringvar <-blocking | -time seconds | -nonblocking>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the flags */

    i = 4;

    while (i < argc) {

	if ((!strcmp(argv[i], "-blocking")) || (!strcmp(argv[i], "-nonblocking")) || (!strcmp(argv[i], "-time"))) {

	    if ((i = parseTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR; 
	    }

	    if (timeFound) {
		Tcl_AppendResult (interp, "only one timeout can be specified", (char *) NULL);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\": expected -security, -blocking, -nonblocking or -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }
 
	/* make sure that we had a timeout option */

    if (timeFound == e_FALSE) {
	Tcl_AppendResult (interp, "must specify a timeout with -blocking, -nonblocking or -time", (char *) NULL);
	return TCL_ERROR;
    }
 
	/* get the meeting handle */

    if ((Tcl_GetInt (interp, argv[1], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[1], "\"", (char *) NULL);
	return TCL_ERROR;
    }
   
	/* receive the message */

    if ((rc = Meeting_Receive (interp, seconds, (UINT_32) handle, &code, &string)) == TCL_BREAK) {
	Tcl_SetResult (interp, "-1", TCL_STATIC);
	return TCL_OK;
    } else if (rc != TCL_OK) {
	return TCL_ERROR;
    }

	/* set the interpreter result and the variables */

    fastIntegerToAscii (code, temp);
    Tcl_ResetResult (interp);

    if (Tcl_SetVar (interp, argv[2], temp, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set code variable", (char *) NULL);
	returnCode = TCL_ERROR;

    } else if (Tcl_SetVar (interp, argv[3], string, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set string variable", (char *) NULL); 
	returnCode = TCL_ERROR; 

    } else {

	returnCode = TCL_OK;
    }

	/* clean up */

    delete (string);

    return (returnCode);
}

/* Agent_ReceiveCmd

   Tcl syntax: agent_receive code value [-security varname] <-blocking | -time seconds | -nonblocking>

      Purpose: Check to see if any results are available for the agent

        Input: clientData = client data (unused)
               interp     = Tcl interpreter
               argc       = number of arguments
               argv       = the arguments

       Output: The procedure returns TCL_ERROR and sets the interpreter
	       result on error.  The procedure returns TCL_OK and sets
	       the interpreter result to "-1" if no message arrived before
	       the timeout.  Otherwise the procedure returns TCL_OK, sets
	       the variable of name "code" to the message code, sets
	       the variable of name "string" to the message string, sets
	       the optional variable of name "varname" to the security
	       information, and sets the intepreter result to the empty
	       string.
*/

int Agent_ReceiveCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int i;
    char temp[16];                    /* scratch area for message code  */
    double seconds;                   /* number of seconds for time out */
    int returnCode;                  /* return code from the procedure */
    INCOMING_MESSAGE *message;        /* an incoming message            */
    BOOLEAN timeFound = e_FALSE;      /* e_TRUE if timeout found        */
    char *securityVariable = NULL;    /* variable for security info     */

	/* check the number of arguments */

    if (argc < 4) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " code string [-security varname] <-blocking | -time seconds | -nonblocking>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the flags */

    i = 3;

    while (i < argc) {

	if (!strcmp (argv[i], "-security")) {

	    if (++i >= argc) {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (argv[i][0] == '-') {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }
		
	    if (securityVariable != NULL) {
		Tcl_AppendResult (interp, "\"-security\" can only appear once", (char *) NULL);
		return TCL_ERROR;
	    } 

	    securityVariable = argv[i++];

	} else if ((!strcmp(argv[i], "-blocking")) || (!strcmp(argv[i], "-nonblocking")) || (!strcmp(argv[i], "-time"))) {

	    if ((i = parseTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR; 
	    }

	    if (timeFound == e_TRUE) {
		Tcl_AppendResult (interp, "only one timeout can be specified", (char *) NULL);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\": expected -security, -blocking, -nonblocking or -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }
 
	/* make sure that we had a timeout option */

    if (timeFound == e_FALSE) {
	Tcl_AppendResult (interp, "must specify a timeout with -blocking, -nonblocking or -time", (char *) NULL);
	return TCL_ERROR;
    }
 
	/* receive the message */

    if (Agent_Receive (interp, seconds, &message) != TCL_OK) {
	return TCL_ERROR; 
    } else if (message == NULL) {
	Tcl_SetResult (interp, "-1", TCL_STATIC);
	return TCL_OK;
    }

	/* set the interpreter result and the variables */

    sprintf (temp, "%d", message -> code);

    if (Tcl_SetVar (interp, argv[1], temp, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set code variable", (char *) NULL);
	returnCode = TCL_ERROR;

    } else if (Tcl_SetVar (interp, argv[2], message -> string, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set string variable", (char *) NULL); 
	returnCode = TCL_ERROR; 

    } else {

	returnCode = TCL_OK;

	if (securityVariable != NULL) {

	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_SecurityToTclList (message -> security, &dstring);

	    if (Tcl_SetVar (interp, securityVariable, Tcl_DStringValue(&dstring), TCL_LEAVE_ERR_MSG) == NULL) {
		Tcl_AppendResult (interp, ": unable to set security variable", (char *) NULL); 
		returnCode = TCL_ERROR;
	    }

	    Tcl_DStringFree (&dstring);
	}
 
	if (returnCode == TCL_OK) {
	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_IdToTclList (message -> id, &dstring);
	    Tcl_DStringResult (interp, &dstring);
	    Tcl_DStringFree (&dstring);
	}
    }

	/* clean up */

    delete (message);
    return returnCode;
}

#ifdef TCL8

int Agent_ReceiveObjCmd (ClientData /*dummy*/, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    double seconds = 0.0;
    BOOLEAN timeFound = e_FALSE;
    Tcl_Obj *securityVariableObject = (Tcl_Obj *) NULL;
    INCOMING_MESSAGE *message = (INCOMING_MESSAGE *) NULL;

	// check the number of arguments

    if (objc < 4) {
	Tcl_WrongNumArgs (interp, 1, objv, "varName varName ?-security varName? <-blocking | -time seconds | -nonblocking>");
	return TCL_ERROR;
    }

	// parse the flags

    int i = 3;

    while (i < objc) {

	char *arg;
	int argLen;

	arg = Tcl_GetStringFromObj (objv[i], &argLen);

	if (!strcmp (arg, "-security")) {

	    if (++i >= objc) {
		Tcl_AppendToObj (Tcl_GetObjResult(interp),
		    "\"-security\" must be followed by a variable name", -1);
		return TCL_ERROR;
	    }

	    arg = Tcl_GetStringFromObj (objv[i], &argLen);

	    if (arg[0] == '-') {
		Tcl_AppendToObj (Tcl_GetObjResult(interp),
		    "\"-security\" must be followed by a variable name", -1);
		return TCL_ERROR;
	    }
		
	    if (securityVariableObject != (Tcl_Obj *) NULL) {
		Tcl_AppendToObj (Tcl_GetObjResult(interp),
		    "\"-security\" can only appear once", -1);
		return TCL_ERROR;
	    }

	    securityVariableObject = objv[i++];

	} else if ((!strcmp(arg, "-blocking")) || (!strcmp(arg, "-nonblocking")) || (!strcmp(arg, "-time"))) {

	    if ((i = parseTimeoutObj (interp, seconds, objc, objv, i)) < 0) {
		return TCL_ERROR;
	    }

	    if (timeFound == e_TRUE) {
		Tcl_AppendToObj (Tcl_GetObjResult(interp),
		    "only one timeout can be specified", -1);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendStringsToObj (Tcl_GetObjResult(interp), 
		"unknown option \"",
		arg,
	        "\": expected -security, -blocking, -nonblocking or -time",
		(char *) NULL);
	    return TCL_ERROR;
	}
    }

	// make sure that we had a timeout option

    if (!timeFound) {
	Tcl_AppendToObj (Tcl_GetObjResult(interp),
	    "must specify a timeout with -blocking, -nonblocking or -time", -1);
	return TCL_ERROR;
    }
 
	// receive the message

    if (Agent_Receive (interp, seconds, &message) != TCL_OK) {
	return TCL_ERROR; 
    } else if (message == NULL) {
	Tcl_SetObjResult (interp, Tcl_NewIntObj(-1));
	return TCL_OK;
    }

	// set the interpreter result and the code, string and security 
	// variables

    int returnCode;
    Tcl_Obj *codeObject   = Tcl_NewIntObj    (message -> code);
    Tcl_Obj *stringObject = Tcl_NewStringObj (message -> string, -1);

    if (Tcl_ObjSetVar2 (interp, objv[1], (Tcl_Obj *) NULL, codeObject, 
	    TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1) == (Tcl_Obj *) NULL) {

	Tcl_DecrRefCount (codeObject);
	Tcl_DecrRefCount (stringObject);
	Tcl_AppendToObj (Tcl_GetObjResult(interp),
	    ": unable to set code variable", -1);
	returnCode = TCL_ERROR;

    } else if (Tcl_ObjSetVar2 (interp, objv[2], (Tcl_Obj *) NULL, stringObject,
	    TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1) == (Tcl_Obj *) NULL) {

	Tcl_DecrRefCount (stringObject);
	Tcl_AppendToObj (Tcl_GetObjResult(interp),
	    ": unable to set string variable", -1);
	returnCode = TCL_ERROR;

    } else {

	returnCode = TCL_OK;

	if (securityVariableObject != (Tcl_Obj *) NULL) {

	    Tcl_Obj *securityObject =
		Agent_SecurityToTclListObj (message -> security);

	    if (Tcl_ObjSetVar2 (interp, securityVariableObject, 
		    (Tcl_Obj *) NULL, securityObject, 
		    TCL_LEAVE_ERR_MSG | TCL_PARSE_PART1) == (Tcl_Obj *) NULL) {
		Tcl_DecrRefCount (securityObject);
		Tcl_AppendToObj (Tcl_GetObjResult(interp),
	    	    ": unable to set security variable", -1);
		returnCode = TCL_ERROR;
	    }
	}
 
	if (returnCode == TCL_OK) {
	    Tcl_SetObjResult (interp, Agent_IdToTclListObj (message -> id));
	}
    }

	// cleanup

    delete (message);
    return (returnCode);
}

#endif

/* Meeting_CloseCmd

   Tcl syntax: meeting_close {handle}

      Purpose: Close a meeting with another agent

	Input: dummy  = client data (unused)
			(ClientData)

	       interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)

       Output: The procedure returns TCL_ERROR and sets the interpreter result
	       to an appropriate error message on error.  Otherwise the 
	       procedure returns TCL_OK and terminates the meeting.
*/

int Meeting_CloseCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    INT_32 handle;

	/* check the number of arguments */

    if (argc != 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {handle}\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the handle */ 

    argc -= 1;
    argv += 1;

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* close the meeting */

    if ((rc = Meeting_Close (interp, (UINT_32) handle)) == TCL_OK) {
	Tcl_ResetResult (interp);
    }

    return (rc);
}

/* Agent_AcceptAndWaitCmd

   Tcl syntax: agent_accept {handle} [-time seconds]

      Purpose: Accept a meeting with another agent (and wait until the
	       meeting connection is complete)

	Input: dummy  = client data (unused)
		  	(ClientData)

	       interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)

 	Output: The procedure returns TCL_ERROR and sets the interpreter
		result to an appropriate error message on error.  Otherwise
		the procedure returns TCL_Ok and accepts the meeting with
		the specified handle.
*/

#ifdef FIX_LATER
    /* Agent_AcceptAndWaitCmd and Agent_AcceptCmd are almost identical */
#endif

int Agent_AcceptAndWaitCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    INT_32 handle;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {handle} [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the handle */ 

    argc -= 1;
    argv += 1;

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the timeout if one is specified */

    argc -= 1;
    argv += 1;

    if (argc != 0) {

        if (!strcmp(argv[0], "-time")) {

	    if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	        return TCL_ERROR;
	    } else {
		argc -= 2;
		argv += 2;
	   }

        } else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[0], "\": should be -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we do not have extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the meeting acceptance */

    if ((rc = Agent_AcceptAndWait (interp, seconds, (UINT_32) handle)) == TCL_OK) {
	Tcl_ResetResult (interp);
    }

    return (rc);
}

/* Agent_AcceptCmd

   Tcl syntax: accept_meeting {handle} [-time seconds]

      Purpose: Accept a meeting with another agent

	Input: dummy  = client data (unused)
		  	(ClientData)

	       interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)

 	Output: The procedure returns TCL_ERROR and sets the interpreter
		result to an appropriate error message on error.  Otherwise
		the procedure returns TCL_Ok and accepts the meeting with
		the specified handle.
*/

int Agent_AcceptCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    INT_32 handle;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {handle} [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the handle */ 

    argc -= 1;
    argv += 1;

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the timeout if one is specified */

    argc -= 1;
    argv += 1;

    if (argc != 0) {

        if (!strcmp(argv[0], "-time")) {

	    if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	        return TCL_ERROR;
	    } else {
		argc -= 2;
		argv += 2;
	   }

        } else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[0], "\": should be -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we do not have extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the meeting acceptance */

    if ((rc = Agent_Accept (interp, seconds, (UINT_32) handle)) == TCL_OK) {
	Tcl_ResetResult (interp);
    }

    return (rc);
}

/* Agent_RejectCmd

   Tcl syntax: agent_reject {handle} [-time seconds]

      Purpose: Reject a meeting with another agent

	Input: dummy  = client data (unused)
		  	(ClientData)

	       interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)

 	Output: The procedure returns TCL_ERROR and sets the interpreter
		result to an appropriate error message on error.  Otherwise
		the procedure returns TCL_Ok and rejects the meeting with
		the specified handle.
*/

int Agent_RejectCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    INT_32 handle;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {handle} [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the handle */ 

    argc -= 1;
    argv += 1;

    if ((Tcl_GetInt (interp, argv[0], &handle) != TCL_OK) || (handle < 0)) {
	Tcl_AppendResult (interp, ": invalid meeting handle \"", argv[0], "\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the timeout if one is specified */

    argc -= 1;
    argv += 1;

    if (argc != 0) {

        if (!strcmp(argv[0], "-time")) {

	    if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	        return TCL_ERROR;
	    } else {
		argc -= 2;
		argv += 2;
	   }

        } else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[0], "\": should be -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we do not have extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the meeting acceptance */

    if ((rc = Agent_Reject (interp, seconds, (UINT_32) handle)) == TCL_OK) {
	Tcl_ResetResult (interp);
    }

    return (rc);
}

/* Agent_MeetCmd

   Tcl syntax: agent_meet {destination} [-time seconds]
  
      Purpose: Meet with another agent

        Input: dummy  = client data (unused)
			(ClientData)

               interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)
 
       Output: The procedure returns TCL_ERROR on error.  Otherwise the
               procedure meets with the specified agent and returns TCL_OK.
*/

#ifdef FIX_LATER
    /* Agent_MeetCmd and Agent_RequestCmd are almost identical */
#endif

int Agent_MeetCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    char temp[16];
    SmartIdPtr id;
    UINT_32 handle;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {destination} [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the agent identification */

    argc -= 1;
    argv += 1;

    if ((id = Agent_SplitAgentId (interp, argv[0])) == (AgentId *) NULL) {
	return TCL_ERROR;
    }

	/* get the timeout if one is specified */

    argc -= 1;
    argv += 1;

    if (argc != 0) {

        if (!strcmp(argv[0], "-time")) {

	    if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	        return TCL_ERROR;
	    } else {
		argc -= 2;
		argv += 2;
	   }

        } else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[0], "\": should be -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we do not have extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the meeting request */

    if ((rc = Agent_Meet (interp, seconds, id, &handle)) == TCL_OK) {
	sprintf (temp, "%u", handle);
	Tcl_SetResult (interp, temp, TCL_VOLATILE);
    }

    return (rc);
}
 
/* Agent_RequestCmd

   Tcl syntax: agent_request {destination} [-time seconds]

      Purpose: Request a meeting with another agent

        Input: dummy  = client data (unused)
			(ClientData)

               interp = the current interpreter
			(struct Tcl_Interp *)

               argc   = number of command arguments
			(int)

               argv   = the command arguments
			(char **)
 
       Output: The procedure returns TCL_ERROR on error.  Otherwise the
               procedure sends the meeting request and returns TCL_OK.
*/

int Agent_RequestCmd (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int rc;
    char temp[16];
    SmartIdPtr id;
    UINT_32 handle;
    double seconds = DEFAULT_CONNECT_SECONDS;

	/* check the number of arguments */

    if (argc < 2) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], "\" {destination} [-time seconds]\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* split out the agent identification */

    argc -= 1;
    argv += 1;

    if ((id = Agent_SplitAgentId (interp, argv[0])) == (AgentId *) NULL) {
	return TCL_ERROR;
    }

	/* get the timeout if one is specified */

    argc -= 1;
    argv += 1;

    if (argc != 0) {

        if (!strcmp(argv[0], "-time")) {

	    if (parseShortTimeout (interp, seconds, argc, argv, 0) < 0) {
	        return TCL_ERROR;
	    } else {
		argc -= 2;
		argv += 2;
	   }

        } else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[0], "\": should be -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }

	/* make sure that we do not have extraneous arguments */

    if (argc != 0) {
	Tcl_AppendResult (interp, "extraneous arguments", (char *) NULL);
	return TCL_ERROR;
    }

	/* send the meeting request */

    if ((rc = Agent_Request (interp, seconds, id, &handle)) == TCL_OK) {
	sprintf (temp, "%u", handle);
	Tcl_SetResult (interp, temp, TCL_VOLATILE);
    }

    return (rc);
}

/* Agent_GetReqCmd 

   Tcl syntax: agent_getreq handle [-security varname] <-blocking | -time seconds | -nonblocking>

      Purpose: Get a meeting request

        Input: dummy  = client data (unused)
               interp = the current interpreter
               argc   = number of command arguments
               argv   = the command arguments

       Output: The procedure returns TCL_ERROR on error.  The procedure
               returns TCL_OK and sets the interpreter result to -1 if there
               are no pending meeting requests for the agent.  Otherwise
               the procedure returns TCL_OK and sets the interpreter result
               to the three-element list {server name id} which specifies
               the identity of the agent that is requesting the meeting.  In
               addition the procedure sets the status and port variables.
*/

int Agent_GetReqCmd  (ClientData, Tcl_Interp *interp, int argc, char **argv)
{
    int i;   
    char temp[32];
    double seconds;
    int returnCode;
    BOOLEAN timeFound = e_FALSE;
    char *scstring = NULL;
    char *securityVariable = NULL;
    struct INCOMING_MEETING *meeting = NULL;

	/* check the number of arguments */

    if (argc < 3) {
	Tcl_AppendResult (interp, "wrong # of arguments: should be \"", argv[0], " handle [-security varname] <-blocking | -time seconds | -nonblocking>\"", (char *) NULL);
	return TCL_ERROR;
    }

	/* parse the flags */

    i = 2;

    while (i < argc) {

	if (!strcmp (argv[i], "-security")) {

	    if (++i >= argc) {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }

	    if (argv[i][0] == '-') {
		Tcl_AppendResult (interp, "\"-security\" must be followed by a variable name", (char *) NULL);
		return TCL_ERROR;
	    }
		
	    if (securityVariable != NULL) {
		Tcl_AppendResult (interp, "\"-security\" can only appear once", (char *) NULL);
		return TCL_ERROR;
	    } 

	    securityVariable = argv[i++];

	} else if ((!strcmp(argv[i], "-blocking")) || (!strcmp(argv[i], "-nonblocking")) || (!strcmp(argv[i], "-time"))) {

	    if ((i = parseTimeout (interp, seconds, argc, argv, i)) < 0) {
		return TCL_ERROR; 
	    }

	    if (timeFound == e_TRUE) {
		Tcl_AppendResult (interp, "only one timeout can be specified", (char *) NULL);
		return TCL_ERROR;
	    }

	    timeFound = e_TRUE;

	} else {

	    Tcl_AppendResult (interp, "unknown option \"", argv[i], "\": expected -security, -blocking, -nonblocking or -time", (char *) NULL);
	    return TCL_ERROR;
	}
    }
 
	/* make sure that we had a timeout option */

    if (timeFound == e_FALSE) {
	Tcl_AppendResult (interp, "must specify a timeout with -blocking, -nonblocking or -time", (char *) NULL);
	return TCL_ERROR;
    }

	/* get the meeting request */

    if (Agent_GetReq (interp, seconds, &meeting) != TCL_OK) {
	return TCL_ERROR;
    } else if (meeting == NULL) {
	Tcl_SetResult (interp, "-1", TCL_STATIC);
	return TCL_OK; 
    }
 
	/* set the interpreter result and the variables */

    sprintf (temp, "%u", meeting -> localHandle);

    if (Tcl_SetVar (interp, argv[1], temp, TCL_LEAVE_ERR_MSG) == NULL) {

	Tcl_AppendResult (interp, ": unable to set handle variable", (char *) NULL);
	returnCode = TCL_ERROR;

    } else {

	returnCode = TCL_OK;

	if (securityVariable != NULL) {

	    Tcl_DString dstring; 
	    Tcl_DStringInit (&dstring);
	    Agent_SecurityToTclList (meeting -> security, &dstring); 

	    if (Tcl_SetVar (interp, securityVariable, Tcl_DStringValue(&dstring), TCL_LEAVE_ERR_MSG) == NULL) {
		Tcl_AppendResult (interp, ": unable to set security variable", (char *) NULL); 
		returnCode = TCL_ERROR;
	    }

	    Tcl_DStringFree (&dstring);
	}
 
	if (returnCode == TCL_OK) {
	    Tcl_DString dstring;
	    Tcl_DStringInit (&dstring);
	    Agent_IdToTclList (meeting -> id, &dstring);
	    Tcl_DStringResult (interp, &dstring);
	    Tcl_DStringFree (&dstring);
	}
    }

	/* clean up */

    delete (meeting);
    delete (scstring);
    return (returnCode);
}

/* Agent_Init

   Purpose: Perform intialization of the Agent Tcl core 

     Input: interp   = the current interpreter
		       (struct Tcl_Interp *)

            language = symbolic name for the language
		       (char *)

 	    major    = major version number 
		       (int)

	    minor    = minor version number
		       (int)

	    type     = ALPHA, BETA or PRODUCTION
		       (int)
 
    Output: The procedure returns TCL_ERROR and sets the interpreter
	    result to an appropriate error message on error.  Otherwise
	    the procedure returns TCL_OK.
*/

static void agentExit (ClientData clientData)
{
     struct timeval stop = {DEFAULT_CONNECT_SECONDS, 0};

	/* end the agent */

     AGENT_TCL *agent = (AGENT_TCL *) clientData;
     (void) agent -> agentEnd (stop, AGENT::e_END_CLEANUP);
     
	/* get rid of the exit handler */

     Tcl_DeleteExitHandler (agentExit, clientData);
}


static void agentCleanup (ClientData clientData, Tcl_Interp *)
{
	/* do the exit handler routines */

    agentExit (clientData);

	/* get rid of the agent */

    AGENT_TCL *agent = (AGENT_TCL *) clientData;
    delete (agent);
}

static void Agent_CommandInit (Tcl_Interp *interp)
{
    COMMAND_INFO *ptr;

	/* assertions on the parameters */

    assert (interp != NULL);

	/* install the agent commands */

    for (ptr = agent_commands; ptr -> name != NULL; ptr++) {
#ifdef TCL8
	if (ptr -> objProc != NULL) {
	    Tcl_CreateObjCommand (interp, ptr -> name, ptr -> objProc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
	} else {
	    Tcl_CreateCommand (interp, ptr -> name, ptr -> proc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
	}
#else
	if (ptr -> interruptable) {
	    Tcl_CreateIntCommand (interp, ptr -> name, ptr -> proc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
	} else {
	    Tcl_CreateCommand (interp, ptr -> name, ptr -> proc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
	}
#endif
    }
}

   /* 
    * Command that will perform normal initialization
    */

static char initCmd[] =
"proc tclInit {} {\n\
    global tcl_library tcl_version tcl_patchLevel env\n\
    rename tclInit {}\n\
    set dirs {}\n\
    if [info exists env(TCL_LIBRARY)] {\n\
	lappend dirs $env(TCL_LIBRARY)\n\
    }\n\
    lappend dirs [info library]\n\
    foreach d $dirs {\n\
	set tcl_library $d\n\
	if ![file exists $d/agent.tcl] {\n\
	    continue\n\
	}\n\
	set fd [open $d/agent.tcl r]\n\
	set code [read $fd]\n\
	close $fd\n\
	uplevel #0 $code\n\
	if {[info command interp] != \"\"} {\n\
	   set slaves [interp slaves]\n\
	   foreach s $slaves {\n\
		$s eval [list uplevel #0 set tcl_library $tcl_library]\n\
		$s eval [list uplevel #0 $code]\n\
	   }\n\
	}\n\
	return\n\
    }\n\
    set msg \"Can't find agent.tcl in the following directories: \n\"\n\
    append msg \"    $dirs\n\"\n\
    append msg \"This probably means that Agent Tcl wasn't installed properly.\n\"\n\
    error $msg\n\
}\n\
tclInit";

int Agent_Init (Tcl_Interp *interp, const Version& version)
{
    int code;
    AGENT_TCL *agent; 
    Tcl_Interp *masterInterp;

	/* 
	 * Assertions on the parameters
	 */

    assert (interp != NULL);

	/* 
	 * Get the master interpreter -- this could be NULL.
	 */ 

    masterInterp = Tcl_GetMaster (interp);

	/* 
	 * Create the AGENT structure if necessary
	 */ 

    if ((agent = AGENT_TCL::get_agent (interp)) == NULL) {
	agent = new AGENT_TCL (interp, masterInterp, version);
    }

	/*
	 * Set up the exit and deletion callbacks
	*/

    Tcl_CallWhenDeleted (interp, agentCleanup, (ClientData) agent);
    Tcl_CreateExitHandler (agentExit, (ClientData) agent);

	/* 
         * If we have a master interpreter, then we are a slave interpreter 
	 * and must perform safe initialization rather than normal 
	 * initialization.					
 	 */

    if (masterInterp != NULL) {

	    /*
	     * Perform command initialization for both the master and slave 
	     */

	Agent_CommandInit (interp);
	Agent_CommandInit (masterInterp);

	    /* 
	     * Evaluate the initialization script 
	     */

	if ((string_agentScript != NULL) && (*string_agentScript != '\0')) {

#ifdef FIX_LATER
	    /*
	     * we copy the script since Tcl_ParseVar can modify the
	     * script in place (by temporarily replacing the character at
	     * the end of a variable name with a \0 -- we should not have to 
	     * do this
	     */
#endif

	    char *script = new char [strlen(string_agentScript) + 1];
	    strcpy (script, string_agentScript);

	    if ((code = Tcl_GlobalEval (masterInterp, script)) != TCL_OK) {

		Tcl_TransferResult (interp, masterInterp);

	    } else {

		if ((code = Tcl_GlobalEval (interp, script)) == TCL_OK) {

		    char *libraryValue = Tcl_GetVar2 (masterInterp, "tcl_library", (char *) NULL, TCL_GLOBAL_ONLY);
		
		    if (libraryValue != NULL) {
			Tcl_SetVar2 (interp, "tcl_library", (char *) NULL, libraryValue, TCL_GLOBAL_ONLY);
		    }
		}
	    }

	    delete (script);

	} else {

	    if ((code = Tcl_GlobalEval (masterInterp, initCmd)) != TCL_OK) {
		Tcl_TransferResult (interp, masterInterp);
	    }
	}

    } else {

	    /*
	     * Perform command initialization for the interpreter
	     */

	Agent_CommandInit (interp);

	    /*
	     * Evaluate the initialization script
	     */ 

	if ((string_agentScript != NULL) && (*string_agentScript != '\0')) {

#ifdef FIX_LATER
	    /*
	     * we copy the script since Tcl_ParseVar can modify the
	     * script in place (by temporarily replacing the character at
	     * the end of a variable name with a \0 -- we should not have to 
	     * do this
	     */
#endif

	    char *script = new char [strlen(string_agentScript) + 1];
	    strcpy (script, string_agentScript);
	    code = Tcl_GlobalEval (interp, script);
	    delete (script);

	} else {

	    code = Tcl_GlobalEval (interp, initCmd);
	}
    }

    return (code);
}
