/*******************************************************************************
 * Copyright (c) 2005, 2007 Intel Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Andy Kaylor, Intel - Initial re-implementation of Agent Controller
 * $Id: AgentManager.c,v 1.64 2007/06/12 17:15:52 samwai Exp $
 *******************************************************************************/ 



#include "tptp/TPTPUtils.h"
#include "tptp/TPTPCommon.h"
#include "tptp/TPTPErrorCode.h"
#include "AgentManager.h"
#include "AgentManagerPrivate.h"
#include "ConnectionManager.h"
#include "ConfigurationManager.h"
#include "tptp/ProcCtlUtils.h"
#include "AgentManagerLog.h"
#include <stdio.h>
#include <string.h>

#ifdef _WIN32
#define PROCESS_CONTROLLER_EXENAME "tptpProcessController.exe"
#else
#define PROCESS_CONTROLLER_EXENAME "tptpProcessController"
#endif

/* Default metadata is used when an agent registers but we don't have       */
/*    metadata corresponding to its agent name                              */
agent_metadata_t  defaultMetadata = {
                                     NULL,         /* No known name         */
                                     { 0, 0, 0, 0, 0}, /* No known interfaces   */
                                     1,            /* Single instance       */
                                     1,            /* Bound (can't launch)  */
                                     -1,           /* Unlimited controllers */
                                     -1,           /* Unlimited observers   */
                                     NULL,         /* No launch info        */
                                     NULL,         /* No client data        */
                                     NULL,         /* No full metadata      */
                                     { 0, 0, 0, 0, 0}, /* options */
				     NULL,	   /* dataChannelSize */
				     NULL,	   /* logFile */
				     NULL,	   /* client */
                                    };

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Initialize resources used by the Agent Manager and perform any
 *    necessary startup tasks
 *                                                       
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_init( AgentManager_t* am )
{
	tptp_initializeLock( &(am->agentListLock) );

	tptp_list_init( &(am->agentList) );
	tptp_list_init( &(am->availableAgentList) );
	tptp_list_init( &(am->pendingAgentList) );
	tptp_list_init( &(am->eventListenerList) );

	tptp_list_init( &defaultMetadata.options );

	am->nextContextID = 100000;
	am->processControllerAgent = 0;

	/* This should get replaced as we read the config file */
	strcpy( am->defProcCtrlName, PROCESS_CONTROLLER_EXENAME );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Free all resources used by the Agent Manager in preparation 
 *    for shutdown
 *                                                       
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_cleanup( AgentManager_t* am )
{
	if ( am->processControllerAgent != NULL )
	{
			char terminateFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><terminate iid=\"org.eclipse.tptp.Agent\"></terminate></Cmd>";
			char terminateCmd[256];

			/* Ask this agent to go away */
			sprintf( terminateCmd, terminateFormat, AGENT_MANAGER, am->processControllerAgent->agentID, -1 );
			connectionManager_sendMessage( am->connectionManager, am->processControllerAgent->agentID, terminateCmd );
	}

	tptp_list_clear( &(am->agentList) );
	tptp_list_clear( &(am->availableAgentList) );
	tptp_list_clear( &(am->pendingAgentList) );
	tptp_list_clear( &(am->eventListenerList) );

	tptp_deleteLock( &(am->agentListLock) );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Load all agents that are configured to load at startup
 *                                                       
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_startAutoloadAgents( AgentManager_t* am )
{
	/* This should not be called from init() because the 		*/
	/*    Connection Manager won't have started its servers yet */
	agentManager_startProcessController( am );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    In this wrapper around the actual comand processing we parse 
 *    the command and, after it is processed, free any resources
 *    allocated during the parsing
 *                                                       
 * @param cmd  XML fragment describing the command to be invoked
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_processCommand( AgentManager_t* am, const char* cmd )
{
	tptp_int32     ret;
	tptp_int32     sourceID;
	tptp_int32     context;
	char*          interfaceID = 0;
	char*          cmdName = 0;
	tptp_list_t*   paramList;

	/* Parse the XML into useful blocks */
	ret = parseCommand( cmd, &sourceID, &context, 
	                    &interfaceID, &cmdName, &paramList );
	if ( ret != 0 )
	{
		/* We can't respond (because we don't know where to send it)   */
		/*    so log it as an error                                    */
		TPTP_LOG_ERROR_MSG1( am, "Error parsing incoming command: %s", cmd );
		return ret;
	}
	else
	{
		ret = agentManager_processCommandImpl( am,
		                                       cmd, 
		                                       sourceID, 
		                                       context,
		                                       interfaceID, 
		                                       cmdName, 
											   paramList );

		/* Free the resources allocated by parseCommand */
		tptp_free( interfaceID );
		tptp_free( cmdName );
		tptp_list_clear( paramList );
		tptp_free( paramList );

		return ret;
	}
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Having previously parsed the command's contents, we process it here
 *                                                       
 * @param cmd  XML fragment describing the command to be invoked
 * @param sourceID    ID of the object sending the command
 * @param context     Context ID of the command (used for reply)
 * @param interfaceID Identifier providing scope for the command name
 * @param cmdName     Name of the command being invoked
 * @param flags       List of parameters as name-value string pairs
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_processCommandImpl( AgentManager_t* am, 
                                            const char*     cmd, 
                                            tptp_int32      sourceID, 
                                            tptp_int32      context,
                                            const char*     interfaceID, 
                                            const char*     cmdName, 
                                            tptp_list_t*	paramList )
{
	tptp_int32 ret;

	/* Find the right interface */
	if ( isEqualString( interfaceID, "org.eclipse.tptp.agentManager" ) )
	{
		/* Find the right command */
		if ( isEqualString( cmdName, "registerAgent" ) )
		{
			tptp_int32   agentID;
			PID   processID;
			char* agentName;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
			ret = getPIDParam( "processID", paramList, &processID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
			ret = getStringParam( "agentName", paramList, &agentName );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_registerAgent( am,
			                                  sourceID, 
			                                  context, 
											  agentID,
											  processID,
			                                  agentName ); 

			/* free the agent name string */
			tptp_free( agentName );

			return ret;
		}
		else if ( isEqualString( cmdName, "deregisterAgent" ) )
		{
			tptp_int32   agentID;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_deregisterAgent( am,
			                                    sourceID, 
			                                    context, 
			                                    agentID );

			return ret;
		}
		else if ( isEqualString( cmdName, "getAgent" ) )
		{
			char* agentName;
			tptp_int32   flags;

			ret = getStringParam( "agentName", paramList, &agentName );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Optional parameter, failure is acceptable */
			if (0 != getIntegerParam( "flags", paramList, &flags ) )
				flags = 0;

			/* Process this command */
			ret = agentManager_getAgent( am, sourceID, context, agentName, flags );

			/* free the agent name string */
			tptp_free( agentName );

			return ret;
		}
		else if ( isEqualString( cmdName, "getAgentByID" ) )
		{
			tptp_int32  agentID;
			tptp_int32  flags;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Optional parameter, failure is acceptable */
			if (0 != getIntegerParam( "flags", paramList, &flags ) )
				flags = 0;

			/* Process this command */
			ret = agentManager_getAgentByID( am, sourceID, context, agentID, flags );

			return ret;
		}
		else if ( isEqualString( cmdName, "getAgentByToken" ) )
		{
			tptp_int32  token;
			tptp_int32  flags;

			ret = getIntegerParam( "token", paramList, &token );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Optional parameter, failure is acceptable */
			if (0 != getIntegerParam( "flags", paramList, &flags ) )
				flags = 0;

			/* Process this command */
			ret = agentManager_getAgentByToken( am, sourceID, context, token, flags );

			return ret;
		}
		else if ( isEqualString( cmdName, "releaseAgent" ) )
		{
			tptp_int32 agentID;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_releaseAgent( am, sourceID, context, agentID );

			return ret;
		}
		else if ( isEqualString( cmdName, "bindDataConnections" ) )
		{
			tptp_int32 dataConn1, dataConn2;

			ret = getIntegerParam( "dataConnection1", paramList, &dataConn1 );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
			ret = getIntegerParam( "dataConnection2", paramList, &dataConn2 );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_bindDataConnections( am, sourceID, context, dataConn1, dataConn2 );

			return ret;
		}
		else if ( isEqualString( cmdName, "queryAvailableAgents" ) )
		{
			tptp_list_t interfaces;
			
			tptp_list_init( &interfaces );
			
			/* This parameter is optional.  If it's missing, we work with the empty list */
			getStringListParam( "interface", paramList, &interfaces );
			
			/* Process this command */
			ret = agentManager_queryAvailableAgents( am, sourceID, context, &interfaces );			
			
			tptp_list_clear( &interfaces );
			
			return ret;
		}
		else if ( isEqualString( cmdName, "queryRunningAgents" ) )
		{
			PID         processID = 0; /* default to all agents */
			tptp_list_t interfaces;
			
			tptp_list_init( &interfaces );
			
			/* We have initialized these variables, and both are optional,         */
			/* such that if they are not found this code can proceeed without them */
			getPIDParam( "processID", paramList, &processID );
			getStringListParam( "interface", paramList, &interfaces );
			
			/* Process this command */
			ret = agentManager_queryRunningAgents( am, sourceID, context, processID, &interfaces );			
			
			tptp_list_clear( &interfaces );
			
			return ret;
		}
		else if ( isEqualString( cmdName, "getAgentMetadata" ) )
		{
			char* agentName;

			ret = getStringParam( "agentName", paramList, &agentName );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_getAgentMetadata( am, sourceID, context, agentName );			

			/* free the agent name string */
			tptp_free( agentName );

			return ret;
		}
		else if ( isEqualString( cmdName, "getApplicationAliases" ) )
		{
			return agentManager_getApplicationAliases( am, sourceID, context );
		}
		else if ( isEqualString( cmdName, "requestControl" ) )
		{
			tptp_int32  agentID;
			tptp_int32  flags;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Optional parameter, failure is acceptable */
			if (0 != getIntegerParam( "flags", paramList, &flags ) )
				flags = 0;

			/* Process this command */
			ret = agentManager_requestControl( am, sourceID, context, agentID, flags );

			return ret;
		}
		else if ( isEqualString( cmdName, "releaseControl" ) )
		{
			tptp_int32  agentID;

			ret = getIntegerParam( "agentID", paramList, &agentID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			/* Process this command */
			ret = agentManager_releaseControl( am, sourceID, context, agentID );

			return ret;
		}
        else if ( isEqualString( cmdName, "getPropertyList" ) )
        {
            tptp_string* name;
            tptp_string* type;

			ret = getStringParam( "name", paramList, &name );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
			ret = getStringParam( "type", paramList, &type );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
    
            ret = agentManager_getPropertyList( am, sourceID, context, name, type );

            tptp_free( name );
            tptp_free( type );

            return ret;
        }
        else if ( isEqualString( cmdName, "shutdown" ) )
        {
			/* Invoke a global function to handle shutdown */
            return agentController_shutdown();
		}
		else
		{
			/* Report that this is an unrecognized command */
			agentManager_sendErrorResponse( am, TPTP_CMD_UNKNOWN, sourceID, context );
			return -1;
		}
	}
	else if ( isEqualString( interfaceID, "org.eclipse.tptp.ac.peerMonitoring" ) )
	{
		if ( isEqualString( cmdName, "getPeerMonitoringInfo" ) )
		{
			tptp_string* connectionType;

			ret = getStringParam( "connectionType", paramList, &connectionType );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = agentManager_getPeerConnectionInfo( am, connectionType, sourceID, context );

			tptp_free( connectionType );

			return ret;
		}
		else if ( isEqualString( cmdName, "agentRequestMonitor" ) )
		{
			tptp_string* sci;
			tptp_string* sai;

			ret = getXmlFragmentParam( "sourceConnectionInfo", paramList, &sci );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = getXmlFragmentParam( "sourceAgentInfo", paramList, &sai );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = agentManager_requestMonitor( am, sci, sai, sourceID, context  );

			tptp_free( sci );
			tptp_free( sai );

			return ret;
		}
		/* This won't actually be called in the backward compatibility case 
		   so it's here as a place marker for future development 

		else if ( isEqualString( cmdName, "peerRequestMonitor" ) )
		{
			tptp_string* tci;
			tptp_string* tai;
			tptp_string* sai;

			ret = getStringParam( "targetConnectionInfo", paramList, &tci );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = getStringParam( "targetAgentInfo", paramList, &tai );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = getStringParam( "sourceAgentInfo", paramList, &sai );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = agentManager_peerRequestMonitor( am->connectionManager, tci, tai, sai, sourceID, context );

			tptp_free( tci );
			tptp_free( tai );
			tptp_free( sai );

			return ret;
		}

		*/
		else
		{
			/* Report that this is an unrecognized command */
			agentManager_sendErrorResponse( am, TPTP_CMD_UNKNOWN, sourceID, context );
			return -1;
		}
	}
	else if ( isEqualString( interfaceID, "org.eclipse.tptp.eventProvider" ) )
	{
		if ( isEqualString( cmdName, "addEventListener" ) )
		{			
			char*       eventIID = NULL;
			tptp_uint32 listenerID = 0;

			/* Check for a specified event interface */
			ret = getStringParam( "interfaceID", paramList, &eventIID );
			if ( ret != 0 )
			{	
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = getIntegerParam( "listenerID", paramList, &listenerID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}
			
			ret = agentManager_addAgentEventListener(am, eventIID, listenerID, sourceID, context);

			return ret;
		}
		else if ( isEqualString( cmdName, "removeEventListener" ) )
		{
			char*       eventIID = NULL;
			tptp_uint32 listenerID = 0;

			/* Check for a specified event interface */
			ret = getStringParam( "interfaceID", paramList, &eventIID );
			if ( ret != 0 )
			{	
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = getIntegerParam( "listenerID", paramList, &listenerID );
			if ( ret != 0 )
			{
				agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = agentManager_removeAgentEventListener(am, eventIID, listenerID, sourceID, context);

			return ret;
		}
		else
		{
			/* Report that this is an unrecognized command */
			agentManager_sendErrorResponse( am, TPTP_CMD_UNKNOWN, sourceID, context );
			return -1;
		}
	}
	else if ( isEqualString( interfaceID, "org.eclipse.tptp.processController" ) )
	{
		if ( isEqualString( cmdName, "processStarted" ) )
		{
			PID processID;

			ret = getPIDParam( "processID", paramList, &processID );
			if ( ret != 0 )
			{
				TPTP_LOG_ERROR_MSG( am, "The process started response arrived without a process ID" );
				return TPTP_CMD_MISSING_ARG;
			}

			ret = agentManager_handleProcessStarted( am, context, processID );

			return ret;
		}
		else if ( isEqualString( cmdName, "processExitedEvent" ) )
		{
			PID        processID;
			tptp_int32 exitCode;

			ret = getPIDParam( "processID", paramList, &processID );
			if ( ret != 0 )
			{
				TPTP_LOG_ERROR_MSG( am, "The process started response arrived without a process ID" );
				return TPTP_CMD_MISSING_ARG;
			}
			ret = getIntegerParam( "exitCode", paramList, &exitCode );
			if ( ret != 0 )
			{
				/* This parameter isn't critical, so we can use a default value */
				exitCode = 0;
			}

			ret = agentManager_handleProcessExited( am, context, processID, exitCode );

			return ret;
		}
		else
		{
			/* Report that this is an unrecognized command */
			agentManager_sendErrorResponse( am, TPTP_CMD_UNKNOWN, sourceID, context );
			return -1;
		}
	}
	else if ( isEqualString( interfaceID, "org.eclipse.tptp.Error" ) )
	{
		if ( isEqualString( cmdName, "TPTP_Error" ) )
		{
			tptp_node_t*     node;
			pending_agent_t* pendingAgent;

			/* We use -1 as the context for commands that we aren't interested in a response to */
			if ( context == -1 )
			{
				return 0;
			}

			/* Search through the pending agent launches to see if this matches one of their context IDs */
			for (node = am->pendingAgentList.head; node != 0; node = node->next )
			{
				pendingAgent = (pending_agent_t*)node->data;
				if ( pendingAgent->processLaunchContext == context )
				{
					/* This is a case where we tried to launch an agent, but it failed       */
					/* We need to send a response to the component that requested this agent */
					agentManager_sendErrorResponse( am, TPTP_AM_AGENT_LAUNCH_FAILED, pendingAgent->requestorID, pendingAgent->requestContext );
					return 0;
				}
			}

			/* If we got here, we don't recognize this context ID.  The best we can do is log it. */
			TPTP_LOG_WARNING_MSG1( am, "Agent Manager received an unexpected error response %s", cmdName );
			return -1;
		}
		else
		{
			/* We're already in error handling code, but we don't recognize the error command
			       nothing we do here is likely to be helpful, but we'll log this just so someone
			       knows we got here */
			TPTP_LOG_WARNING_MSG1( am, "Agent Manager received an unrecognized error command: %s", cmdName );
			return -1;
		}
	}
	else
	{
		/* Report that this is an unrecognized interface */
		agentManager_sendErrorResponse( am, TPTP_INTERFACE_UNKNOWN, sourceID, context );
		return -1;
	}
}

void agentManager_setProcessMonitor(AgentManager_t* am, agent_t* agent) {
	int contextId;
	agent_t*          processController;
	char cmd[256];
	char cmdFormat[] = 
	  "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><monitorProcessState iid=\"org.eclipse.tptp.processController\"><processID>%d</processID></monitorProcessState></Cmd>";

	contextId = am->nextContextID++;

	/* Get the process controller agent */
	processController = agentManager_getProcessController(am);

	sprintf (cmd, cmdFormat, AGENT_MANAGER, processController->agentID, contextId, agent->processID);
	connectionManager_sendMessage(am->connectionManager, processController->agentID, cmd);
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to request an instance of an agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param agentName Unique identifier of the type of agent being requested
 * @param flags     Flags indicating how the new instance is to be shared
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getAgent( AgentManager_t* am, 
                                  tptp_int32      sourceID, 
                                  tptp_int32      context, 
                                  const char*     agentName, 
                                  tptp_int32      flags )
{
	agent_t*          agent;
	agent_metadata_t* agentMD;

	TPTP_LOG_DEBUG_MSG1( am, "getAgent( %s ) called", agentName );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgent waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );
	
	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgent has write lock" );

	/* If the caller didn't specifically request a new instance, */
	/*    see if we can use an existing agent                    */
	if ( !(flags & TPTP_CREATE_INSTANCE) )
	{
		agent = agentManager_findActiveAgentByName( am, agentName );
		if ( agent != 0 )
		{
			/* Check to see if the agent is available for the requested access */
			if ( agentManager_checkAccess( am, agent, flags ) )
			{
				/* Count this reference and send a response indicating the agent we found */
				agentManager_grantAgentReference( am, sourceID, context, agent, flags );

				agentManager_setProcessMonitor(am, agent);
					
				TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgent releasing write lock" );
				
				/* Allow access to agent lists */
				tptp_releaseWriteLock( &(am->agentListLock) );

				return 0;
			}
		}
	}

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgent releasing write lock" );

	/* Allow access to agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	if ( !(flags & TPTP_RUNNING_AGENTS_ONLY) )
	{
		/* If we get here, the agent isn't loaded yet */
		/*   See if we can launch the requested agent */
		agentMD = agentManager_findAgentMetadata( am, agentName );

		if ( (agentMD != NULL) && (!agentMD->bound) )
		{
			tptp_int32 ret;

			ret = agentManager_launchAgent( am, agentMD->launchInfo, agentName, sourceID, context );

			if ( ret == 0 )
			{
				/* We've started the gears turning.  The agent should be here soon */
				return 0;
			}
			else
			{
				/* Report this error */
				agentManager_sendErrorResponse( am, TPTP_AM_ERROR_LAUNCHING_AGENT, sourceID, context );
				return -1;
			}
		}
	}

	/* Report an error */
	agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

	return -1;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to request a reference to a specific agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param agentID   ID of the agent being requested
 * @param flags     Flags indicating how the agent is to be shared
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getAgentByID( AgentManager_t* am, 
                                      tptp_int32      sourceID, 
                                      tptp_int32      context, 
                                      tptp_int32      agentID, 
                                      tptp_int32      flags )
{
	agent_t*  agent;

	TPTP_LOG_DEBUG_MSG1( am, "getAgentByID( %d ) called", agentID );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID has write lock" );

	/* Find the agent structure in our list */
	agent = agentManager_findActiveAgentByID( am, agentID, TRUE );
	if ( NULL == agent )
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID releasing write lock" );
	
		/* Allow access to the agent lists */
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

		return -1;
	}

	/* If the agent is locked, no one else may access it */
	if ( agent->locked )
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID releasing write lock" );
		
		/* Allow access to the agent lists */
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_IS_LOCKED, sourceID, context );

		return -1;
	}

	if ( flags & TPTP_CONTROLLER_ACCESS )
	{
		tptp_int32 numControllers = agent->controllers.count;

		/* Check the agent's metadata */
		if ( (agent->metadata->maxControllers != -1) &&
			 (agent->metadata->maxControllers <= numControllers) )
		{
			TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID releasing write lock" );
			
			/* Allow access to the agent lists */
			tptp_releaseWriteLock( &(am->agentListLock) );

			/* Report an error */
			agentManager_sendErrorResponse( am, TPTP_AM_CONTROLLER_ACCESS_DENIED, sourceID, context );

			return -1;
		}
	}
	else /* Default to TPTP_OBSERVER_ACCESS */
	{
		tptp_int32 numObservers = agent->observers.count;

		/* Check the agent's metadata */
		if ( (agent->metadata->maxObservers != -1) &&
			 (agent->metadata->maxObservers <= numObservers) )
		{
			/* Allow access to the agent lists */
			tptp_releaseWriteLock( &(am->agentListLock) );

			/* Report an error */
			agentManager_sendErrorResponse( am, TPTP_AM_OBSERVER_ACCESS_DENIED, sourceID, context );

			return -1;
		}
	}

	/* Send a response indicating the agent we found */
	agentManager_grantAgentReference( am, sourceID, context, agent, flags );
	agentManager_setProcessMonitor(am, agent);

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "getAgentByID releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to request access to a specific agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param token     Token referencinf the agent being requested
 * @param flags     Flags indicating how the agent is to be shared
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getAgentByToken( AgentManager_t* am, 
                                         tptp_int32      sourceID, 
                                         tptp_int32      context, 
                                         tptp_int32      token, 
                                         tptp_int32      flags )
{
	tptp_int32  agentID = (token & (~AGENT_TOKEN_MASK));

	TPTP_LOG_DEBUG_MSG1( am, "getAgentByToken( %d ) called", token );

	return agentManager_getAgentByID( am, sourceID, context, agentID, flags );
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to request control of an agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param agentID   ID of the agent for which control is requested
 * @param flags     Flags indicating how the agent is to be shared
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_requestControl( AgentManager_t* am, 
                                        tptp_int32      sourceID, 
                                        tptp_int32      context, 
                                        tptp_int32      agentID, 
                                        tptp_int32      flags )
{
	agent_t*     agent;
	tptp_node_t* node;
	char         responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><controlGranted iid=\"org.eclipse.tptp.agentManager\"></controlGranted></Cmd>";
	char         responseCmd[256];

	TPTP_LOG_DEBUG_MSG1( am, "requestControl( %d ) called", agentID );

	/* Get the agent for which control is requested */
	agent = agentManager_findActiveAgentByID( am, agentID, TRUE );
	if ( 0 == agent )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );
		return -1;
	}

	/* Before we dco anything else, check to see if this client   */
	/*    is already a controller.  We have to do this somewhere  */
	/*    so, we may as well do it up front                       */
	for ( node = agent->controllers.head; node != 0; node = node->next )
	{
		tptp_int32* cid = (tptp_int32*)node->data;

		if ( *cid == sourceID )
		{
			/* The caller already has control. A positive response seems most likely 
			   to lead to working code on the other end, so lets send that */
			sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
			connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
			return 0;
		}
	}

	/* If we are able to give control, do so */
	if ( agent->controllers.count < agent->metadata->maxControllers )
	{
		tptp_int32*  refID = 0;
		tptp_int32   sourceWasObserver = 0;

		/* Remove this client from the observer list */
		for ( node = agent->observers.head; node != 0; node = node->next )
		{
			tptp_int32* cid = (tptp_int32*)node->data;

			if ( *cid == sourceID )
			{
				tptp_list_remove( &agent->observers, node );
				sourceWasObserver = 1;
				break;
			}
		}

		/* Only observers can become controllers, others should use getAgent */
		if ( !sourceWasObserver )
		{
			/* Report an error */
			agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_REFERENCED, sourceID, context );

			return -1;
		}

		/* Add this client to the list of controllers */
		refID = tptp_malloc( sizeof(tptp_int32) );
		*refID = sourceID;
		tptp_list_add( &agent->controllers, refID );

		/* Send the positive response */
		sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
		connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
	}
	else
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_CONTROLLER_ACCESS_DENIED, sourceID, context );

		return -1;
	}

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to release control of an agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param agentID   ID of the agent for which control is released
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_releaseControl( AgentManager_t* am, 
                                        tptp_int32      sourceID, 
                                        tptp_int32      context, 
                                        tptp_int32      agentID )
{
	agent_t*     agent;
	tptp_node_t* node;
	tptp_int32   sourceWasObserver = 0;

	TPTP_LOG_DEBUG_MSG1( am, "releaseControl( %d ) called", agentID );

	/* Get the agent for which control is requested */
	agent = agentManager_findActiveAgentByID( am, agentID, TRUE );
	if ( 0 == agent )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

		return -1;
	}

	/* Find this client in the controller list */
	sourceWasObserver = 0;
	for ( node = agent->observers.head; node != 0; node = node->next )
	{
		tptp_int32* cid = (tptp_int32*)node->data;

		if ( *cid == sourceID )
		{
			/* This shouldn't happen */
			TPTP_LOG_WARNING_MSG( am, "An agent observer sent the releaseControl command (but observers don't have control)." );
			sourceWasObserver = 1;
		}
	}

	/* If we are able to give control, do so */
	if ( agent->observers.count < agent->metadata->maxObservers )
	{
		tptp_int32*  refID = 0;
		tptp_int32   sourceWasController = 0;

		/* Remove this client from the observer list */
		for ( node = agent->observers.head; node != 0; node = node->next )
		{
			tptp_int32* cid = (tptp_int32*)node->data;

			if ( *cid == sourceID )
			{
				tptp_list_remove( &agent->controllers, node );
				sourceWasController = 1;
			}
		}

		/* Only controllers can release control */
		if ( !sourceWasController )
		{
			/* Report an error */
			agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_CONTROLLED, sourceID, context );

			return -1;
		}

		/* We wouldn't expect the source to already be in the observer list */
		/*    but it's cheap to check and will avoid trouble                */
		if ( !sourceWasObserver )
		{
			/* Add this client to the list of controllers */
			refID = tptp_malloc( sizeof(tptp_int32) );
			*refID = sourceID;
			tptp_list_add( &agent->observers, refID );
		}

		/* We're done now.  There is no response to this command. */
	}
	else
	{
		/* Although it will probably be rare, it is possible that an agent will    */
		/*    have a limit on observers so that a controller can't release control */

		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_OBSERVER_ACCESS_DENIED, sourceID, context );

		return -1;
	}

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Clients call this method to release a reference to an agent
 *                                                       
 * @param sourceID  ID of the object requesting the agent
 * @param context   Context ID of the getAgent command (used for reply)
 * @param agentID   ID of the agent to be released
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_releaseAgent( AgentManager_t* am, 
                                      tptp_int32      sourceID, 
                                      tptp_int32      context, 
                                      tptp_int32      agentID )
{
	agent_t*     agent;

	TPTP_LOG_DEBUG_MSG1( am, "releaseAgent( %d ) called", agentID );

	/* Get the agent for which control is requested */
	agent = agentManager_findActiveAgentByID( am, agentID, TRUE );
	if ( 0 == agent )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

		return -1;
	}

	agentManager_removeReference( am, agent, sourceID );

	/* We're done now.  There is no response to this command */

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Informs the Agent Manager of a new active agent
 *                                                       
 * @param sourceID  ID of the object the requested this binding (used for reply)
 * @param context   Context ID of the bindDataConnections command (used for reply)
 * @param agentID   ID of the agent being registered
 * @param processID ID of the process hosting this agent
 * @param agentName Unique identifier of the type of agent of which this is an instance
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_registerAgent( AgentManager_t* am, 
                                       tptp_int32      sourceID, 
                                       tptp_int32      context, 
                                       tptp_int32      agentID,
                                       PID             processID,
                                       const char*     agentName )
{
	char             responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentRegistered iid=\"org.eclipse.tptp.agentManager\"></agentRegistered></Cmd>";
	char             responseFormat2[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentRegistered iid=\"org.eclipse.tptp.agentManager\">%s</agentRegistered></Cmd>";
	char             responseFormat3[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentRegistered iid=\"org.eclipse.tptp.agentManager\">%s%s</agentRegistered></Cmd>";
	char             responseCmd[8192];
	agent_t*         agent;
	tptp_node_t*     node;
	pending_agent_t* pendingAgent;
	char             agentConfigFormat[] = "<agentConfigData><logFile>%s</logFile><dataChannelSize>%s</dataChannelSize><client>%s</client></agentConfigData>";
	char*            agentConfigStr = NULL;


	TPTP_LOG_DEBUG_MSG2( am, "registerAgent( %s, %d ) called", agentName, agentID );


	/* Create a new agent structure */
	agent = tptp_malloc( sizeof(agent_t) );
	if ( agent == 0 )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );

		return -1;
	}
	agent->agentID = agentID;
	agent->processID = processID;
	strcpy( agent->agentName, agentName );
	 
	/*  The agent is initially a self-referenced.  If we discovered below   */
	/*    that this is an agent that we launched and are waiting for, we'll */
	/*    add an owner but won't increment the reference count (i.e. we'll  */
	/*    give away this reference).                                        */
	agent->refCount = 1; 
	agent->locked = 0;
	tptp_list_init( &agent->controllers );
	tptp_list_init( &agent->observers );
	agent->metadata = agentManager_findAgentMetadata( am, agentName );
	if ( 0 == agent->metadata )
	{
		agent->metadata = &defaultMetadata;
	}
	
	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent waiting for write lock" );
	
	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent has write lock" );

	/* Add the agent to our list of agents */
	tptp_list_add( &(am->agentList), (void*)agent );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	//If the dataChannelSize is set then we want to allow the TL's to try to configure
	//base upon metadata. Put together an agentConfigData xml fragment to pass back on registration.
	//This is mainly target towards BC but it doesn't need to be. (ie if other agents make use of this 
	//attribute then the config data will be passed back on registration.
	if (agent->metadata->dataChannelSize != NULL) {
		agentConfigStr = (char*) tptp_malloc(strlen(agent->metadata->logFile) + strlen(agent->metadata->dataChannelSize) + strlen(agent->metadata->client)+ strlen(agentConfigFormat) + 4);
		sprintf( agentConfigStr, agentConfigFormat, agent->metadata->logFile, agent->metadata->dataChannelSize, agent->metadata->client);
	}
	
	/* Send a response to the agent */
	if ( agent->metadata->options.count != 0 )
	{
	    tptp_node_t* node;
		char*        optionsStr = NULL;
	    int          optionsLen = 0;

	    for ( node = agent->metadata->options.head; node != NULL; node = node->next )
		{
	        char           optFormat[] = "<propertyListEntry><property><name>%s</name><type>%s</type><value>%s</value></property></propertyListEntry>";
	        char*          optStr;
            int            optSize;
			tptp_option_t* opt = (tptp_option_t*)node->data;

            optSize = strlen(optFormat) + strlen(opt->name) + strlen(opt->type) + strlen(opt->value);

			/* Allocate memory for our match */
			optStr = (char*)tptp_malloc( optSize );
			if ( optStr != NULL )
			{
	            sprintf( optStr, optFormat, opt->name, opt->type, opt->value );

				if ( optionsLen == 0 )
				{
					optionsLen = strlen( optStr ) + 1;
					optionsStr = tptp_malloc( optionsLen );
					if ( optionsStr != NULL )
					{
		                strcpy( optionsStr, optStr );
					}
					else
					{
						optionsLen = 0;
					}
	            }
		        else
			    {
				    char *temp;

					optionsLen += strlen( optStr );
					temp = tptp_realloc( optionsStr, optionsLen );
	                if ( temp != NULL )
		            {
	                    optionsStr = temp;
						strcat( optionsStr, optStr );
		            }
			    }

				tptp_free( optStr );
	        }
		}

	        //all of these responses are made much difficult by the possible option entries and
		//config data that may be passed back.
		if ( optionsLen > 0 )
		{
			if (agentConfigStr) 
			{
				sprintf( responseCmd, responseFormat3, AGENT_MANAGER, sourceID, context, optionsStr, agentConfigStr );
				tptp_free( agentConfigStr );
			} else {
				sprintf( responseCmd, responseFormat2, AGENT_MANAGER, sourceID, context, optionsStr );
			}
			tptp_free( optionsStr );
		}
		else
		{
			if (agentConfigStr) 
			{
				sprintf( responseCmd, responseFormat2, AGENT_MANAGER, sourceID, context, agentConfigStr );
				tptp_free( agentConfigStr );
			} else {
				sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
			}
		}
	}
	else
	{
		if (agentConfigStr) 
		{
			sprintf( responseCmd, responseFormat2, AGENT_MANAGER, sourceID, context, agentConfigStr );
			tptp_free( agentConfigStr );
		} else {
			sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
		}
	}
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

	/* See if this is the Process Controller agent we launch at startup */
	if ( (am->processControllerAgent != NULL ) && 
	     (am->processControllerAgent->processID == processID) )
	{
		am->processControllerAgent->agentID = agentID;
		strcpy( am->processControllerAgent->agentName, agentName );
	}
	else
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent waitng for write lock again" );
	
		/* Synchronize access to agent lists */
		tptp_getWriteLock( &(am->agentListLock) );

		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent has write lock" );
			/* Look to see if this matches an agent we just launched */
		for (node = am->pendingAgentList.head; node != 0; node = node->next )
		{
			pendingAgent = (pending_agent_t*)node->data;
			if ( (pendingAgent->processID == processID) && 
				 isEqualString(agentName, pendingAgent->agentName) )
			{
				/* Release the self-reference, since the AM launched this agent */
				agent->refCount--;

				/* This agent was launched for a client.  Send the response now */
				agentManager_grantAgentReference( am, pendingAgent->requestorID, pendingAgent->requestContext, agent, pendingAgent->requestFlag );

				/* Stop looking for this agent */
				tptp_list_remove( &(am->pendingAgentList), pendingAgent );

				break;
			}
		}

		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "registerAgent releasing write lock" );
	
		/* Allow access to the agent lists */
		tptp_releaseWriteLock( &(am->agentListLock) );	
	}

	/* Notify an event listeners that an agent has registered */
	agentManager_fireAgentEvent( am, "agentRegistered", agent->agentName, 
	                             (agent->agentID | AGENT_TOKEN_MASK), agent->processID );


	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Informs the Agent Manager that an agent is shutting down
 *                                                       
 * @param sourceID  ID of the object the requested this binding (used for reply)
 * @param context   Context ID of the bindDataConnections command (used for reply)
 * @param agentID   ID of the agent being deregistered
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_deregisterAgent( AgentManager_t* am, 
                                         tptp_int32      sourceID, 
                                         tptp_int32      context, 
                                         tptp_int32      agentID )
{
	tptp_int32       ret = 0;
    char      responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentDeregistered iid=\"org.eclipse.tptp.agentManager\"></agentDeregistered></Cmd>";
	char      responseCmd[256];
	agent_t*  agent;

	TPTP_LOG_DEBUG_MSG1( am, "deregisterAgent( %d ) called", agentID );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "deregisterAgent waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "deregisterAgent has write lock" );

	/* Find the agent structure in our list */
	agent = agentManager_findActiveAgentByID( am, agentID, FALSE );
	if ( NULL != agent )
	{
		/* Notify anyone who might be using this agent  */
		agentManager_notifyAgentUsers( am, agent, "agentDeregistered" );

		/* Notify any event listeners that an agent has registered */
		agentManager_fireAgentEvent( am, "agentDeregistered", agent->agentName, 
	                                 (agent->agentID | AGENT_TOKEN_MASK), agent->processID );

		agentManager_releaseResources( am, agentID );
		tptp_list_remove( &(am->agentList), agent );

		/* Send a response to the agent */
		sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
		connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

		ret = 0;
	}
	else
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

		ret = -1;
	}

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "deregisterAgent releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	return ret;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Builds a list of agents known to the Agent Manager, whether active or not
 *                                                       
 * @param sourceID  ID of the object the requested this list (used for reply)
 * @param context   Context ID of the queryAvailableAgent command (used for reply)
 * @param interface List of interfaces used to filter the list of available agent
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_queryAvailableAgents( AgentManager_t* am, 
                                              tptp_int32      sourceID, 
                                              tptp_int32      context, 
                                              tptp_list_t*    interfaces )
{
	char              responsePrefix[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><availableAgents iid=\"org.eclipse.tptp.agentManager\"><agentNames>";
	char              responseSuffix[] = "</agentNames></availableAgents></Cmd>";
	char*             responseCmd;
	tptp_int32        responseSize;
	tptp_node_t*      node;
	agent_metadata_t* md;

	TPTP_LOG_DEBUG_MSG( am, "queryAvailableAgents called" );

	/* Estimate how much memory our response will take -- guess high */
	responseSize = 256 + (256 * am->availableAgentList.count);

	/* Allocate the message block */
	responseCmd = tptp_malloc( responseSize );
	if ( responseCmd == NULL )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );

		return -1;
	}

	/* Start building the response */
	sprintf( responseCmd, responsePrefix, AGENT_MANAGER, sourceID, context );

	/* Walk through the list of available agents */
	for (node = am->availableAgentList.head; node != 0; node = node->next )
	{
		md = (agent_metadata_t*)node->data;

		/* If this agent supports the requested interfaces, add it */
		if ( (0 == interfaces->count) ||
		     agentManager_checkInterfaces( am, interfaces, &md->interfaces ) )
		{
			strcat( responseCmd, md->agentName );

			if ( node->next != 0 )
			{
				strcat( responseCmd, " " );
			}
		}
	}

	/* Finished the command */
	strcat( responseCmd, responseSuffix );

	/* Send the command */
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
	
	return 0;
}			

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Builds a list of agents known to the Agent Manager, whether active or not
 *                                                       
 * @param sourceID  ID of the object the requested this list (used for reply)
 * @param context   Context ID of the queryAvailableAgent command (used for reply)
 * @param processID Optional filter, returns only agents in the specified process
 * @param interface List of interfaces used to filter the list of available agent
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_queryRunningAgents( AgentManager_t* am, 
                                            tptp_int32      sourceID, 
                                            tptp_int32      context, 
                                            PID             processID, 
                                            tptp_list_t*    interfaces )
{
	char          responsePrefix[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><runningAgents iid=\"org.eclipse.tptp.agentManager\"><agentInfoList>";
	char          responseSuffix[] = "</agentInfoList></runningAgents></Cmd>";
	char          agentInfoFormat[] = "<agentInfo token=\"%d\" name=\"%s\" pid=\"%lu\"></agentInfo>";
	char*         responseCmd;
	tptp_int32    responseSize;
	agent_t*      agent;
	tptp_node_t*  node;

	TPTP_LOG_DEBUG_MSG( am, "queryRunningAgents called" );

	/* Estimate how much memory our response will take -- guess high */
	responseSize = 256 + (256 * am->agentList.count);

	/* Allocate the message block */
	responseCmd = tptp_malloc( responseSize );
	if ( responseCmd == NULL )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );

		return -1;
	}

	/* Start building the message */
	sprintf( responseCmd, responsePrefix, AGENT_MANAGER, sourceID, context );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "queryRunningAgents waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "queryRunningAgents has write lock" );

	/* Get rid of any inactive agents */
	agentManager_purgeAgentList( am );

	/* Walk through the list of available agents */
	for (node = am->agentList.head; node != 0; node = node->next )
	{
		char  agentInfoStr[256];

		agent = (agent_t*)node->data;

		/* If this agent supports the requested interfaces, add it */
		/* If 0 was passed in for the processID, check all processes. */
		if ( ( (0 == processID) || (processID == agent->processID) ) &&
		     ( (0 == interfaces->count) ||
		       agentManager_checkInterfaces( am, interfaces, &agent->metadata->interfaces ) ) )
		{
			/* Add this agent info to the list */
			sprintf( agentInfoStr, agentInfoFormat, (agent->agentID | AGENT_TOKEN_MASK), agent->agentName, (unsigned long)agent->processID);
			strcat( responseCmd, agentInfoStr );
		}
	}

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "queryRunningAgents releasing write lock" );
	
	/* Allow access to agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	/* Finish the command */
	strcat( responseCmd, responseSuffix );

	/* Send the command */
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

	/* Release the memory we allocated here */
	tptp_free( responseCmd );

	return 0;
}			

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Delegates data connection binding to the Connection Manager
 *                                                       
 * @param sourceID  ID of the object the requested this binding (used for reply)
 * @param context   Context ID of the bindDataConnections command (used for reply)
 * @param dataConn1 ID of the first data connection to be bound
 * @param dataConn2 ID of the second data connection to be bound
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_bindDataConnections( AgentManager_t* am,  
                                             tptp_int32      sourceID, 
                                             tptp_int32      context, 
                                             tptp_int32      dataConn1, 
                                             tptp_int32      dataConn2 )
{
	return connectionManager_bindDataConnections( am->connectionManager, sourceID, context, dataConn1, dataConn2 );
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Retrieves the metadata for an agent specified by name
 *                                                       
 * @param sourceID  ID of the object the requested this information
 * @param context   Context ID of the command used to make the request
 * @param agentName Name of the agent for which metadata is requested
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getAgentMetadata( AgentManager_t* am, 
                                          tptp_int32      sourceID, 
                                          tptp_int32      context, 
                                          const char*     agentName )
{
	char              responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentMetadata iid=\"org.eclipse.tptp.agentManager\"><metadata>%s</metadata></agentMetadata></Cmd>";
	char              responseCmd[8192];
	agent_metadata_t* md;

	TPTP_LOG_DEBUG_MSG1( am, "getAgentMetadata( %s ) called", agentName );

	md = agentManager_findAgentMetadata( am, agentName );
	if ( 0 == md )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );

		return -1;
	}

	if ( md->fullMetadata == NULL )
	{
		agentManager_sendErrorResponse( am, TPTP_AM_NO_METADATA, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}
	
	if (strlen(md->fullMetadata) > (8192 - strlen(responseFormat)) )
	{
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, md->fullMetadata );
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Retrieves an XML fragment containing application aliases which
 *    was previously read from serviceconfig.xml and associated files
 *                                                       
 * @param sourceID  ID of the object the requested this information
 * @param context   Context ID of the command used to make the request
 * @param agentName Name of the agent for which metadata is requested
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getApplicationAliases( AgentManager_t* am, 
                                               tptp_int32      sourceID, 
                                               tptp_int32      context )
{
	tptp_string* aliases;
	tptp_string  emptyString = '\0';
	tptp_int32   rc;
	char         responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><applicationAliases iid=\"org.eclipse.tptp.agentManager\"><aliases>%s</aliases></applicationAliases></Cmd>";
	char*        responseCmd;
	tptp_int32   responseSize;

	TPTP_LOG_DEBUG_MSG( am, "getApplicationAliases called" );

	/* Get the requested information */
	rc = configurationManager_getApplicationAliases( am->configurationManager, &aliases );
	if ( ( rc != 0 ) || (aliases == NULL) )
	{
		/* An empty response is more useful than an error */
		aliases = &emptyString;
	}

	/* Calculate the size of buffer we need 
           -- 24 is 3*(the maximum integer length - format specifier size) 
              where 3 is the number of formatted elements (src, dest, context) */
	responseSize = strlen(responseFormat) + 24 + strlen(aliases);

	/* Allocate the buffer */
	responseCmd = (tptp_string*)tptp_malloc( responseSize );

	TPTP_LOG_DEBUG_MSG( am, "getApplicationAliases sending response" );
	
	/* Build our reply command and send it */
	sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, aliases );
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Retrieves an XML fragment describing how other ACs may contact
 *    this AC.
 *                                                       
 * @param sourceID  ID of the object the requested this information
 * @param context   Context ID of the command used to make the request
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getPeerConnectionInfo( AgentManager_t* am, 
                                               tptp_string*    connectionType,
                                               tptp_int32      sourceID, 
                                               tptp_int32      context )
{
	tptp_string* ci;
	tptp_int32   rc;
	char         responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><connectionInfo iid=\"org.eclipse.tptp.ac.peerMonitoring\">%s</connectionInfo></Cmd>";
	char*        responseCmd;
	tptp_int32   responseSize;

	/* Get the requested information */
	rc = agentManager_buildConnectionInfo( am, connectionType, &ci );
	if ( rc != 0 )
	{
		agentManager_sendErrorResponse( am, rc, sourceID, context );
		return rc;
	}

	/* Calculate the size of buffer we need 
           -- 33 is 3*(the maximum integer length) 
              where 3 is the number of formatted elements (src, dest, context) */
	responseSize = strlen(responseFormat) + 33 + strlen(ci) + 1;

	/* Allocate the buffer */
	responseCmd = (tptp_string*)tptp_malloc( responseSize );
	if ( responseCmd == NULL )
	{
		tptp_free( ci );
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	/* Build our reply command and send it */
	sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, ci );
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

	tptp_free( ci );
	tptp_free( responseCmd );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Builds an XML fragment describing how other ACs may contact
 *    this AC.
 *                                                       
 * @param pmi  A pointer to a string pointer to receive the fragment
 *             The caller is responsible for freeing the string returned
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_buildConnectionInfo( AgentManager_t* am, 
                                             tptp_string*    connectionType,
                                             tptp_string**   ci )
{
	tptp_string* pmt;
	tptp_int32   rc;

	/* Get the transport type that handles this */
	rc = configurationManager_getPeerConnectionTransport( am->configurationManager, &pmt );
	if ( ( rc != 0 ) || (pmt == NULL) )
	{
		return TPTP_AM_NO_PMI;
	}

	/* Get the requested information */
	rc = connectionManager_getPeerConnectionInfo( am->connectionManager, pmt, connectionType, ci );
	if ( ( rc != 0 ) || (*ci == NULL) )
	{
		tptp_free( pmt );
		return TPTP_AM_NO_PMI;
	}

	tptp_free( pmt );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Transforms an agentRequestMonitor command into a requestMonitor
 *    command and forwards it to the appropriate client
 *    
 *                                                       
 * @param targetConnectionInfo   XML fragment describing how to connect
 *                               to this AC
 * @param targetAgent            The agent that we would like the client
 *                               to start monitoring.  This agent will
 *                               be described in the peerAgentInfo to be
 *                               sent to the client
 * @param sourceAgentInfo        XML fragment describing the agent that
 *                               initiated the peer monitoring process.
 *                               The client should already be monitoring
 *                               this agent.
 * @param sourceID               ID of the agent that sent this command
 * @param context                Context ID of the command
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/

tptp_int32 agentManager_requestMonitorLocal( AgentManager_t* am, 
                                             tptp_string*    targetConnectionInfo,
                                             agent_t*        targetAgent,
                                             tptp_string*    sourceAgentInfo,
                                             tptp_int32      sourceID,
                                             tptp_int32      context )
{
	/* Transform the request and forward it to the client controlling the given agent */
	char         requestFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><requestMonitor iid=\"org.eclipse.tptp.client.peerMonitoring\">%s%s</peerRequestMonitor></Cmd>";
	char*        requestCmd;
	tptp_int32   requestSize;
	char         taiFormat[] = "<targetAgentInfo><agentName>%s</agentName><pid>%s</pid></targetAgentInfo>";
	tptp_int32   taiSize;
	tptp_string* targetAgentInfo;
	tptp_string* srcAgentName;
	PID          srcAgentPID;
	tptp_node_t* node;
	agent_t*     sourceAgent;
	tptp_int32   ret;

	/* Unpack the old peer agent info */
	ret = getXmlFragmentElementPID( sourceAgentInfo, "sourceAgentInfo", "pid", &srcAgentPID );
	if ( ret != 0 )
	{
		agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
		return TPTP_CMD_MISSING_ARG;
	}
	ret = getXmlFragmentElementString( sourceAgentInfo, "sourceAgentInfo", "agentName", &srcAgentName );
	if ( ret != 0 )
	{
		agentManager_sendErrorResponse( am, TPTP_CMD_MISSING_ARG, sourceID, context );
		return TPTP_CMD_MISSING_ARG;
	}

	/* Find the agent that was previously marked as the peer */
	sourceAgent = agentManager_findActiveAgentByNameAndProcessID( am, srcAgentName, srcAgentPID );
	if ( sourceAgent == NULL )
	{
		/* If we didn't find a matching agent, go no further */
		tptp_free( srcAgentName );
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );
		return TPTP_AM_AGENT_NOT_FOUND;
	}

	/* Free this, we're done with it */
	tptp_free( srcAgentName );
	srcAgentName = NULL;

	if ( sourceAgent->controllers.count <= 0 )
	{
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_CONTROLLED, sourceID, context );
		return TPTP_AM_AGENT_NOT_CONTROLLED;
	}

	/* Calculate the size of buffer we need for sourceAgentInfo
           -- 11 is number of digits we're reserving for the pid */
	taiSize = strlen(taiFormat) + 11 + strlen(targetAgent->agentName) + 1;

	/* Allocate the buffer */
	targetAgentInfo = (tptp_string*)tptp_malloc( taiSize );
	if ( targetAgentInfo == NULL )
	{
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	/* Build the peer agent info */
	sprintf( targetAgentInfo, taiFormat, targetAgent->agentName, targetAgent->processID );

	/* Calculate the size of buffer we need for our request
		   -- 33 is 3*(the maximum integer length) 
			  where 3 is the number of formatted elements (src, dest, context) */
	requestSize = strlen(requestFormat) + 33 
						+ strlen(targetConnectionInfo)
						+ strlen(targetAgentInfo)	+ 1;

	/* Allocate the buffer */
	requestCmd = (tptp_string*)tptp_malloc( requestSize );
	if ( requestCmd == NULL )
	{
		tptp_free( targetAgentInfo );
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	/* Forward the request to all of controlling clients */
	for ( node = sourceAgent->controllers.head; node != NULL; node = node->next )
	{
		tptp_uint32* controllerID = (tptp_uint32*)node->data;

		if ( controllerID != NULL )
		{
			/* Build our reply command and send it */
			sprintf( requestCmd, requestFormat, AGENT_MANAGER, *controllerID, context, targetConnectionInfo, targetAgentInfo );
			connectionManager_sendMessage( am->connectionManager, *controllerID, requestCmd );
		}
	}

	tptp_free( targetAgentInfo );
	tptp_free( requestCmd );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Connects to a peer AC and sends a peerRequestMonitor command
 *    
 * This function handles the agentRequestMonitor command.  Two XML
 *    fragments are passed into this function, sourceConnectionInfo and
 *    sourceAgentInfo.  
 * 
 * The sourceConnectionInfo fragment describes how to connect to the 
 *    Agent Controller that owns the agent which initiated this process.
 *    The sourceAgentInfo described the agent which initiated this 
 *    process.
 *
 * The client we are looking for will already be connected to the
 *    AC described by the sourceConnectionInfo and monitoring the agent
 *    described by sourceAgentInfo.  We need to connect to that AC and
 *    ask it to forward a request to the client monitoring the source 
 *    agent, asking that client to attach to this AC and start 
 *    monitoring the agent that sent us this command (target agent).
 *
 * In the message that we send, targetConnecitonInfo will describe how to
 *    connect to THIS AC, targetAgentInfo will describe the agent to be
 *    monitored, and sourceAgentInfo will describe the agent that
 *    initiated the process
 *
 * In addition, if we discover that the source AC is actually this AC,
 *    we will short circuit the process and handle the request here.
 *
 *                                                       
 * @param sourceConnectionInfo   XML fragment describing how to connect
 *                               to the AC that owns the source agent
 * @param sourceAgentInfo        XML fragment describing the agent that
 *                               initiated the peer monitoring process.
 *                               The client should already be monitoring
 *                               this agent.
 * @param sourceID               ID of the agent that sent this command.
 *                               This will be the agent to be monitored
 * @param context                Context ID of the command
 *
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/

tptp_int32 agentManager_requestMonitor( AgentManager_t* am, 
                                        tptp_string*    sourceConnectionInfo,
                                        tptp_string*    sourceAgentInfo,
                                        tptp_int32      sourceID,
                                        tptp_int32      context )
{
	tptp_uint32  connectionID;
	tptp_int32   rc;
	char*        targetConnectionInfo;
	char         requestFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><peerRequestMonitor iid=\"org.eclipse.tptp.ac.peerMonitoring\">%s%s%s</peerRequestMonitor></Cmd>";
	char*        requestCmd;
	tptp_int32   requestSize;
	char         taiFormat[] = "<targetAgentInfo><agentName>%s</agentName><pid>%u</pid></targetAgentInfo>";
	tptp_int32   taiSize;
	tptp_string* targetAgentInfo;
	agent_t*     targetAgent;

	/* Find the agent that sent us this command.  It's the target agent. */
	targetAgent = agentManager_findActiveAgentByID( am, sourceID, TRUE );
	if ( targetAgent == NULL )
	{
		/* If we didn't find a matching agent, go no further */
		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_FOUND, sourceID, context );
		return TPTP_AM_AGENT_NOT_FOUND;
	}

	/* Establish a connection with the other AC */
    rc = connectionManager_connectToPeer( am->connectionManager, sourceConnectionInfo, &connectionID );
	if ( rc != 0 )
	{
		if ( rc == TPTP_REQUEST_IS_LOCAL )
		{
			return agentManager_requestMonitorLocal( am, sourceConnectionInfo, targetAgent, sourceAgentInfo, sourceID, context );
		}
		else
		{
			/* If we couldn't connect, send an error response */
			agentManager_sendErrorResponse( am, TPTP_AM_CANT_CONNECT_TO_PEER, sourceID, context );
			return TPTP_AM_CANT_CONNECT_TO_PEER;
		}
	}

	/* Calculate the size of buffer we need for targetAgentInfo
           -- 11 is number of digits we're reserving for the pid */
	taiSize = strlen(taiFormat) + 11 + strlen(targetAgent->agentName) + 1;

	/* Allocate the buffer */
	targetAgentInfo = (tptp_string*)tptp_malloc( taiSize );
	if ( targetAgentInfo == NULL )
	{
		/* Drop the peer connection */
		connectionManager_dropPeerConnection( am->connectionManager, connectionID );

		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	/* Build the target agent info */
	sprintf( targetAgentInfo, taiFormat, targetAgent->agentName, targetAgent->processID );


	/* Build the target connection info */
	rc = agentManager_buildConnectionInfo( am, "targetConnectionInfo", &targetConnectionInfo );
	if ( rc != 0 )
	{
		/* Drop the peer connection */
		connectionManager_dropPeerConnection( am->connectionManager, connectionID );

		tptp_free( targetAgentInfo );

		agentManager_sendErrorResponse( am, TPTP_AM_AGENT_NOT_CONTROLLED, sourceID, context );
		return rc;
	}

	/* Calculate the size of buffer we need for our request
           -- 33 is 3*(the maximum integer length) 
              where 3 is the number of formatted elements (src, dest, context) */
	requestSize = strlen(requestFormat) + 33 
		                + strlen(targetConnectionInfo)
						+ strlen(targetAgentInfo)
						+ strlen(sourceAgentInfo) + 1;

	/* Allocate the buffer */
	requestCmd = (tptp_string*)tptp_malloc( requestSize );
	if ( requestCmd == NULL )
	{
		tptp_free( targetAgentInfo );
		tptp_free( targetConnectionInfo );

		/* Drop the peer connection */
		connectionManager_dropPeerConnection( am->connectionManager, connectionID );

		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}


	/* Build our reply command and send it */
	sprintf( requestCmd, requestFormat, AGENT_MANAGER, connectionID, context, targetConnectionInfo, targetAgentInfo, sourceAgentInfo );
	rc = connectionManager_sendMessage( am->connectionManager, connectionID, requestCmd );

	/* Free resources before checking for errors */
	tptp_free( targetAgentInfo );
	tptp_free( targetConnectionInfo );
	tptp_free( requestCmd );

	if ( rc == 0 )
	{
		char        responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><monitoringRequested iid=\"org.eclipse.tptp.ac.peerMonitoring\"></monitoringRequested></Cmd>";
		char        responseCmd[256]; /* This isn't a variable sized command, so the fixed buffer is OK */

		/* Build our reply command and send it */
		sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
		connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

		/* We'll drop the peer connection when we get a response */
	}
	else
	{
		/* Drop the peer connection */
		connectionManager_dropPeerConnection( am->connectionManager, connectionID );

		/* Send an error response */
		agentManager_sendErrorResponse( am, TPTP_AM_ERR_SENDING_PEER_REQUEST, sourceID, context );
		return TPTP_AM_ERR_SENDING_PEER_REQUEST;
	}

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Retrieves a list of properties from the global options list
 *    which match the specified criteria
 *                                                       
 * @param sourceID  ID of the object the requested this information
 * @param context   Context ID of the command used to make the request
 * @param name      Name of the property being requested
 * @param type      Type of the property being requested
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_getPropertyList( AgentManager_t* am, 
                                         tptp_int32      sourceID, 
                                         tptp_int32      context, 
                                         tptp_string*    name, 
                                         tptp_string*    type )
{
    tptp_node_t* node;
    int          numMatches = 0;
	char*        matchesStr = NULL;
    int          matchLen = 0;
    BOOL         nameIsWildcard;
    BOOL         typeIsWildcard;

    nameIsWildcard = isEqualString( name, "*" );
    typeIsWildcard = isEqualString( type, "*" );

    for ( node = am->globalOptionsList.head; node != NULL; node = node->next )
    {
        tptp_option_t* opt = (tptp_option_t*)node->data;

        if ( (nameIsWildcard || isEqualString( name, opt->name )) && 
             (typeIsWildcard || isEqualString( type, opt->type )) )
        {
	        char  matchFormat[] = "<propertyListEntry><property><name>%s</name><type>%s</type><value>%s</value></property></propertyListEntry>";
	        char* matchStr;
            int   matchSize;

            matchSize = strlen(matchFormat) + strlen(opt->name) + strlen(opt->type) + strlen(opt->value);

			/* Allocate memory for our match */
			matchStr = (char*)tptp_malloc( matchSize );
			if ( matchStr == NULL )
			{
				if ( matchesStr != NULL )
				{
					tptp_free( matchesStr );
				}
				agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
				return TPTP_SYS_NO_MEM;
			}

            sprintf( matchStr, matchFormat, opt->name, opt->type, opt->value );

            if ( matchLen == 0 )
            {
                matchLen = strlen( matchStr ) + 1;
                matchesStr = tptp_malloc( matchLen );
                if ( matchesStr == NULL )
                {
                    agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
                    return TPTP_SYS_NO_MEM;
                }
                strcpy( matchesStr, matchStr );
            }
            else
            {
                char *temp;

                matchLen += strlen( matchStr );
                temp = tptp_realloc( matchesStr, matchLen );
                if ( temp == NULL )
                {
                    tptp_free( matchesStr );
                    agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
                    return TPTP_SYS_NO_MEM;
                }
                else
                {
                    matchesStr = temp;
                }
                strcat( matchesStr, matchStr );
            }

			tptp_free( matchStr );

            numMatches++;
        }
    }

    if ( numMatches > 0 )
    {
    	char         responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><propertyList iid=\"org.eclipse.tptp.agentManager\">%s</propertyList></Cmd>";
	    char*        responseCmd;
    	tptp_int32   responseSize;

    	/* Calculate the size of buffer we need 
               -- 24 is 3*(the maximum integer length - format specifier size) 
                  where 3 is the number of formatted elements (src, dest, context) */
	    responseSize = strlen(responseFormat) + 24 + strlen(matchesStr);

        /* Allocate memory for our response */
        responseCmd = (char*)tptp_malloc( responseSize );
        if ( responseCmd == NULL )
        {
			agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
			return TPTP_SYS_NO_MEM;
        }

	    /* Build our reply command and send it */
        sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, matchesStr );
	    connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );

        /* Free our temporary string */
        tptp_free( matchesStr );
    }
    else
    {
        /* If there were no matches, we send an empty response */
    	char  responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><propertyList iid=\"org.eclipse.tptp.agentManager\"></propertyList></Cmd>";
	    char* responseCmd;
    	tptp_int32   responseSize;

    	/* Calculate the size of buffer we need 
               -- 24 is 3*(the maximum integer length - format specifier size) 
                  where 3 is the number of formatted elements (src, dest, context) */
	    responseSize = strlen(responseFormat) + 24;

        /* Allocate memory for our response */
        responseCmd = (char*)tptp_malloc( responseSize );
        if ( responseCmd == NULL )
        {
			agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
			return TPTP_SYS_NO_MEM;
        }

	    /* Build our reply command and send it */
        sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context );
	    connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
    }

    return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Registers a listener for an event interface
 *                                                       
 * @param eventsIID    Interface ID string of the events they want to hear about
 * @param listenerIID  Value to use for the "context" of the events when sent
 * @param sourceID     ID of the client registering for the events 
 * @param context      Context ID of the command used to make the request
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_addAgentEventListener( AgentManager_t* am, 
                                               const char*     eventIID, 
                                               const int       listenerID,
                                               const int       sourceID, 
                                               const int       context)
{
	tptp_listener_t* listener;
	char             responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><listenerAccepted iid=\"%s\"></listenerAccepted></Cmd>";
	char             responseCmd[8192];

	listener = (tptp_listener_t*)tptp_malloc( sizeof(tptp_listener_t) );
	if ( listener == 0 )
	{
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}

	if ( strlen(eventIID) > _MAX_PATH )
	{
		agentManager_sendErrorResponse( am, TPTP_CMD_BAD_ARG, sourceID, context );
		return TPTP_CMD_BAD_ARG;
	}

	listener->destID = sourceID;
	listener->listenerID = listenerID;
	listener->eventInterfaceID = (char *) tptp_malloc(strlen(eventIID) +1);
	strcpy( listener->eventInterfaceID, eventIID );

	if ( tptp_list_add(&am->eventListenerList, (void*)listener) != 0 )
	{
		/* Out of memory is the only cause for list_add to fail */
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );
		return TPTP_SYS_NO_MEM;
	}
	
	sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, EVENT_PROVIDER_IID );
	
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
	
	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Degisters a listener for an event interface
 *                                                       
 * @param eventsIID    Interface ID string of the events they want to hear about
 * @param listenerIID  Value to use for the "context" of the events when sent
 * @param sourceID     ID of the client registering for the events 
 * @param context      Context ID of the command used to make the request
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_removeAgentEventListener( AgentManager_t* am, 
                                                  const char*     eventIID, 
                                                  const int       listenerID,
                                                  const int       sourceID, 
                                                  const int       context)
{
	tptp_listener_t* listener;
	char             responseFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><listenerRemoved iid=\"%s\"></listenerRemoved></Cmd>";
	char             responseCmd[8192];
	tptp_node_t*     node;

	for (node = am->eventListenerList.head; node != 0; node = node->next )
	{
		listener = (tptp_listener_t*)node->data;

		if ( ( listener->listenerID == listenerID) && 
		     ( sourceID == listener->destID)       &&
		     ( isEqualString(eventIID, listener->eventInterfaceID) ) )
		{
			tptp_list_remove(&am->eventListenerList, listener);
		}
	}
	
	sprintf( responseCmd, responseFormat, AGENT_MANAGER, sourceID, context, EVENT_PROVIDER_IID );
	
	connectionManager_sendMessage( am->connectionManager, sourceID, responseCmd );
	
	return 0;

}


/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Sends an event command to all registered listeners
 *                                                       
 * @param eventsIID    Interface ID string of the event being fired
 * @param eventName    Name of the event being fired
 * @param agentName    Name of the agent to which this event refers
 * @param agentToken   Token ID of the agent to which this event refers
 * @param processID    PID of the process containing the agent
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
void agentManager_fireAgentEvent( AgentManager_t*     am, 
                                  const tptp_string*  eventName,
                                  const tptp_string*  agentName,
								  tptp_uint32         agentToken,
								  PID                 processID )
{
	tptp_node_t*     node;
	tptp_listener_t* listener;
	char             eventFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><%s iid=\"%s\"><agentToken>%d</agentToken><agentName>%s</agentName><processID>%lu</processID></%s></Cmd>";
	char             eventCmd[8192];

	if (am->eventListenerList.count == 0)
	{
		return;
	}

	for (node = am->eventListenerList.head; node != 0; node = node->next )
	{
		listener = (tptp_listener_t*)node->data;
		
		//Send the event data to each listener
		if ( isEqualString( listener->eventInterfaceID, AGENTMGR_AGENT_EVENTS_IID ) )
		{
			sprintf( eventCmd, eventFormat, AGENT_MANAGER, listener->destID, listener->listenerID, eventName, 
			                                AGENTMGR_AGENT_EVENTS_IID, agentToken, agentName, processID, eventName );
			connectionManager_sendMessage( am->connectionManager, listener->destID, eventCmd );
		}
	}
}


tptp_int32 copyInterfaceNode( const tptp_node_t* orig, tptp_node_t* copy )
{
	char* ifStr = (char *)orig->data;
	char* ifCopy = (char *)tptp_malloc( strlen(ifStr)+1 );

	if ( !ifCopy )
	{
		return -1;
	}

	strcpy( ifCopy, ifStr );

	copy->data = ifCopy;

	return 0;
}

tptp_int32 copyOptionsNode( const tptp_node_t* orig, tptp_node_t* copy )
{
	tptp_option_t* optOrig = (tptp_option_t *)orig->data;
	tptp_option_t* optCopy = (tptp_option_t *)tptp_malloc( sizeof(tptp_option_t) );

	if ( !optCopy )
	{
		return -1;
	}

    BZERO( optCopy, sizeof(tptp_option_t) );

    if ( optOrig->name )
    {
        optCopy->name = (char*)tptp_malloc( strlen(optOrig->name)+1 );
        strcpy( optCopy->name, optOrig->name );
    }

    if ( optOrig->type )
    {
        optCopy->type = (char*)tptp_malloc( strlen(optOrig->type)+1 );
        strcpy( optCopy->type, optOrig->type );
    }

    if ( optOrig->value )
    {
        optCopy->value = (char*)tptp_malloc( strlen(optOrig->value)+1 );
        strcpy( optCopy->value, optOrig->value );
    }

	copy->data = optCopy;

	return 0;
}

tptp_int32 destroyOptionsNode( tptp_node_t* node )
{
    tptp_option_t* opt = (tptp_option_t*)node->data;

    if ( opt->name )
    {
        tptp_free( opt->name );
    }
    if ( opt->type )
    {
        tptp_free( opt->type );
    }
    if ( opt->value )
    {
        tptp_free( opt->value );
    }

    return 0;
}

tptp_int32 destroyAgentMetadataNode( tptp_node_t* node )
{
	agent_metadata_t* md = (agent_metadata_t*)node->data;

	if ( md->agentName )
	{
		tptp_free( md->agentName );
	}
	if ( md->clientData )
	{
		tptp_free( md->clientData );
	}
	if ( md->fullMetadata )
	{
		tptp_free( md->fullMetadata );
	}
	if ( md->launchInfo )
	{
		tptp_free( md->launchInfo );
	}
	if ( md->client )
	{
		tptp_free( md->client );
	}
	if ( md->dataChannelSize )
	{
		tptp_free( md->dataChannelSize );
	}
	if ( md->logFile )
	{
		tptp_free( md->logFile );
	}

	tptp_list_setNodeDestructor( &md->options, destroyOptionsNode );

    tptp_list_clear( &md->options );
	tptp_list_clear( &md->interfaces );

	return 0;
}

tptp_int32 copyAgentMetadataNode( const tptp_node_t* orig, tptp_node_t* copy )
{
	agent_metadata_t* md = (agent_metadata_t*)orig->data;
	agent_metadata_t* copymd = (agent_metadata_t*)tptp_malloc( sizeof(agent_metadata_t) );

	BZERO( copymd, sizeof(agent_metadata_t ) );

	/* Copy strings */
	if ( md->agentName )
	{
		copymd->agentName = tptp_malloc( strlen(md->agentName)+1 );
		strcpy( copymd->agentName, md->agentName );
	}
	if ( md->clientData )
	{
		copymd->clientData = tptp_malloc( strlen(md->clientData)+1 );
		strcpy( copymd->clientData, md->clientData );
	}
	if ( md->fullMetadata )
	{
		copymd->fullMetadata = tptp_malloc( strlen(md->fullMetadata)+1 );
		strcpy( copymd->fullMetadata, md->fullMetadata );
	}
	if ( md->launchInfo )
	{
		copymd->launchInfo = tptp_malloc( strlen(md->launchInfo)+1 );
		strcpy( copymd->launchInfo, md->launchInfo );
	}
	if ( md->client )
	{
		copymd->client = tptp_malloc( strlen(md->client)+1 );
		strcpy( copymd->client, md->client );
	}
	if ( md->dataChannelSize )
	{
		copymd->dataChannelSize = tptp_malloc( strlen(md->dataChannelSize)+1 );
		strcpy( copymd->dataChannelSize, md->dataChannelSize );
	}
	if ( md->logFile )
	{
		copymd->logFile = tptp_malloc( strlen(md->logFile)+1 );
		strcpy( copymd->logFile, md->logFile );
	}

	/* copy simple data */
	copymd->bound          = md->bound;
	copymd->maxControllers = md->maxControllers;
	copymd->maxObservers   = md->maxObservers;
	copymd->singleInstance = md->singleInstance;

    /* copy the options list */
    tptp_list_init( &copymd->options );
	tptp_list_setNodeCopier( &md->options, copyOptionsNode );
	tptp_list_clone( &copymd->options, &md->options );

	/* copy the interface list */
	tptp_list_init( &copymd->interfaces );
	tptp_list_setNodeCopier( &md->interfaces, copyInterfaceNode );
	tptp_list_clone( &copymd->interfaces, &md->interfaces );

	/* store the copy */
	copy->data = (void*)copymd;

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Copies the internal table of available agents from external 
 *       configuration data
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_setAvailableAgentList( AgentManager_t* am, tptp_list_t* availList )
{
	/* Make sure the destructor is set, in case of error during the copy */
	tptp_list_setNodeDestructor( availList, destroyAgentMetadataNode );

	/* Tell the list how to copy node */
	tptp_list_setNodeCopier( availList, copyAgentMetadataNode );

	tptp_list_clone( &am->availableAgentList, availList );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Copies the internal table of global options from external 
 *       configuration data
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_setGlobalOptions( AgentManager_t* am, tptp_list_t* options )
{
	/* Make sure the destructor is set, in case of error during the copy */
	tptp_list_setNodeDestructor( options, destroyOptionsNode );

	/* Tell the list how to copy node */
	tptp_list_setNodeCopier( options, copyOptionsNode );

	tptp_list_clone( &am->globalOptionsList, options );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Launch an agent process and wait for it to register
 *
 * @param launchInfo  XML fragment describing how to launch the agent
 * @param agent       pointer to a pointer to receive the agent info
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_launchAgent( AgentManager_t* am, const char * launchInfo, const char * agentName, tptp_int32 sourceID, tptp_int32 context )
{
	agent_t*         processController = 0;
	pending_agent_t* pendingAgent = 0;
	char             startProcessFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><startProcess iid=\"org.eclipse.tptp.processController\">%s</startProcess></Cmd>";
	char             startProcessCmd[2048]; /* GN modified from 256 since the launchinfo can be quite a sz */

	/* Get the process controller agent */
	processController = agentManager_getProcessController( am );
	if ( processController == 0 )
	{
		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_AM_NO_PROCESS_CONTROLLER, sourceID, context );

		return -1;
	}

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "launchAgent waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "launchAgent has write lock" );

	/* Create a new pending agent structure */
	pendingAgent = tptp_malloc( sizeof(pending_agent_t) );
	if ( pendingAgent == 0 )
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "launchAgent releasing write lock" );
	
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Report an error */
		agentManager_sendErrorResponse( am, TPTP_SYS_NO_MEM, sourceID, context );

		return -1;
	}
	pendingAgent->processLaunchContext = am->nextContextID++;
	pendingAgent->processID = TPTP_INVALID_PID;
	strcpy( pendingAgent->agentName, agentName );
	pendingAgent->requestorID = sourceID;
	pendingAgent->requestContext = context;

	/* Add the agent to our list of pending agents */
	tptp_list_add( &(am->pendingAgentList), (void*)pendingAgent );

	/* Send a launch request to the process controller */		

	sprintf( startProcessCmd, startProcessFormat, AGENT_MANAGER, processController->agentID, pendingAgent->processLaunchContext, launchInfo );
	connectionManager_sendMessage( am->connectionManager, processController->agentID, startProcessCmd );
	
	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "launchAgent releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Handle the processStarted command, which is sent in response to
 *    a ProcessController::startProcess command.  This is where we
 *    attempt to match launched processes to requested agents.
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_handleProcessStarted( AgentManager_t* am, tptp_int32 context, PID processID )
{
	tptp_node_t*     node;
	pending_agent_t* pendingAgent;

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessStarted waiting for write lock" );

	/* Synchronize access to our agent lists */
	tptp_getWriteLock( &(am->agentListLock) );
	
	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessStarted has write lock" );

	/* Look to see if this matches an agent we just launched */
	for (node = am->pendingAgentList.head; node != 0; node = node->next )
	{
		pendingAgent = (pending_agent_t*)node->data;
		if ( pendingAgent->processLaunchContext == context )
		{
			tptp_node_t* agentNode;
			agent_t*     agent;

			/* Look for this agent in the active agent list */
			for (agentNode = am->agentList.head; agentNode != 0; agentNode = agentNode->next )
			{
				agent = (agent_t*)agentNode->data;
				if ( (agent->processID == processID) && 
					 isEqualString(agent->agentName, pendingAgent->agentName) )
				{
				 	/* Previously we would have considered this agent self referenced */
					/* Release the self-reference, since the AM launched this agent   */
					agent->refCount--;

					/* The agent has already registered.  Send the response here */
					agentManager_grantAgentReference( am, pendingAgent->requestorID, pendingAgent->requestContext, agent, pendingAgent->requestFlag );

					/* Stop looking for this agent */
					tptp_list_remove( &(am->pendingAgentList), pendingAgent );

					TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessStarted releasing write lock" );
					
					/* Allow access to the agent list */
					tptp_releaseWriteLock( &(am->agentListLock) );

					return 0;
				}
			}

			/* If we got here, we didn't find our agent.  It should be registering soon. */
			/* Set the process ID here, and we'll look for it in registerAgent           */
			pendingAgent->processID = processID;
			TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessStarted releasing write lock" );
			tptp_releaseWriteLock( &(am->agentListLock) );
			return 0;
		}
	}

	/* It doesn't look like we launched this.  What happened?  */
	/* Log an error */
	TPTP_LOG_WARNING_MSG( am, "Unexpected processLaunched response received" );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessStarted releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	return -1;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Handle the processExitedEvent command, which is sent when
 *    a process that was launched by the Agent Manager (we expect it
 *    to have been an agent) exits.  Here, we try to identify the
 *    agent and notify any client that may be using it.
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_handleProcessExited( AgentManager_t* am, tptp_int32 context, PID processID, tptp_int32 exitCode )
{
	tptp_node_t*     node;
	agent_t*         agent;

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessExited waiting for write lock" );

	/* Synchronize access to our agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessExited has write lock" );

	/* Look to see if this matches an agent we just launched */
	for (node = am->agentList.head; node != 0; node = node->next )
	{
		agent = (agent_t*)node->data;
		if ( agent->processID == processID )
		{
			/* Notify anyone using this agent that it is gone */
			agentManager_notifyAgentUsers( am, agent, "agentProcessExited" );

			agentManager_releaseResources( am, agent->agentID );
			tptp_list_remove( &(am->agentList), agent );

			TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessExited releasing write lock" );
			
			/* Allow access to the agent lists */
			tptp_releaseWriteLock( &(am->agentListLock) );
			return 0;
		}
	}

	/* It doesn't look like we launched this.  What happened?  */
	/* Log an error */
	TPTP_LOG_WARNING_MSG( am, "A processExitedEvent was received for which we have no corresponding agent." );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "handleProcessExited releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );

	return -1;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Sets the name of the default process controller agent
 *
 *********************************************************************/
void agentManager_setDefaultProcessController( AgentManager_t*am, char* procCtlrName )
{
	strcpy( am->defProcCtrlName, procCtlrName );
	return;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Return a pointer to the Process Controller agent. If necessary,
 *    the Process Controller agent is created.
 *
 * @return
 *	  Pointer to the agent_t structure for the Process Controller
 *    0 if error
 *
 *********************************************************************/
agent_t* agentManager_getProcessController( AgentManager_t* am )
{
	if ( (am->processControllerAgent != 0) && 
		 (am->processControllerAgent->agentID != -1) )
	{
		if ( !isProcessActive( am->processControllerAgent->processID ) )
		{
			tptp_free( am->processControllerAgent );
			am->processControllerAgent = 0;

			/* TODO: Should we try to restart it? */
		}

		return am->processControllerAgent;
	}

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Start the processController agent process.
 *
 *********************************************************************/
void agentManager_startProcessController( AgentManager_t* am )
{
	tptp_int32	rc = 0;
	tptp_process_t* proc = 0;
	PID			PCpid = 0;
	unsigned long errCode = 0;

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "startProcessController waiting for write lock" );

	/* Synchronize access to agent lists */
	tptp_getWriteLock( &(am->agentListLock) );

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "startProcessController has write lock" );

	/* Create process structure to be filled in by createProcess if it is successful */
	proc = initProcessT( -1, -1, am->defProcCtrlName, am->defProcCtrlName, NULL,
			am->configurationManager->configData.envConfig, 0, 0 );
	if ( !proc )
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "startProcessController releasing write lock" );
		
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Report an error */
		TPTP_LOG_ERROR_MSG( am, "Out of memory error when launching process controller" );
		return;
	}

	TPTP_LOG_DEBUG_MSG1( am, "Starting Process Controller:%s", am->defProcCtrlName );

	/* Start the process controller process */ 
	rc = createProc( proc, NULL, &errCode);
	if ( rc ) //Failed
	{
		TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "startProcessController releasing write lock" );
		
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Log an error */
		TPTP_LOG_ERROR_MSG1( am, "Failed to launch Process Controller: %d", rc );

		/* clean up process structure */
		destroyProcessT(proc);
		tptp_free(proc);

		return;
	}

	/* Delete process structure. Only the pid is needed by Agent Mangaer. */
	PCpid = proc->pid;
	destroyProcessT(proc);
	tptp_free(proc);

	TPTP_LOG_DEBUG_MSG1( am, "SUCCESSFULLY started Process Controller (pid=%lu)", (unsigned long)PCpid );

	/* Create a new agent structure */
	am->processControllerAgent = tptp_malloc( sizeof(agent_t) );
	if ( am->processControllerAgent == 0 )
	{
		tptp_releaseWriteLock( &(am->agentListLock) );

		/* Log an error */
		TPTP_LOG_ERROR_MSG( am, "Out of memory error when launching process controller" );
		return;
	}
	am->processControllerAgent->agentID = -1;
	am->processControllerAgent->processID = PCpid;
	strcpy( am->processControllerAgent->agentName, "pending");

	TPTP_LOG_DEBUG_WRITELOCK_MSG( am, "startProcessController releasing write lock" );
	
	/* Allow access to the agent lists */
	tptp_releaseWriteLock( &(am->agentListLock) );
}


/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Releases the resources owned by a client of agent that has gone away
 *
 * @param ownerID  OD of the client or agent that has gone away
 *
 * @return
 *	  Pointer to the agent_t structure for the agent
 *    0 if error
 *
 *********************************************************************/
tptp_int32 agentManager_releaseResources( AgentManager_t* am, tptp_int32 ownerID )
{
	/* TODO: Look through our agent list and release references */

	/* TODO: Notify the process controller */

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Find the entry for a given agent, if it has registered
 *
 * @param agentName  Unique identifier of the agent to be located
 * @param purgeInactive  boolean indicating if we should purge inactive agents from our list.
 *
 * @return
 *	  Pointer to the agent_t structure for the agent
 *    0 if error
 *
 *********************************************************************/
agent_t* agentManager_findActiveAgentByID( AgentManager_t* am, tptp_int32 agentID, BOOL purgeInactive )
{
	tptp_node_t*  node;
	agent_t*      agent;

	/* Get rid of any inactive agents */
	if (purgeInactive) 
	{
		agentManager_purgeAgentList( am );
	}
	
	for (node = am->agentList.head; node != 0; node = node->next )
	{
		agent = (agent_t*)node->data;
		if ( agent->agentID == agentID )
		{
			return agent;
		}
	}

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Find the entry for a given agent, if it has registered
 *
 * @param agentName  Unique identifier of the agent to be located
 *
 * @return
 *	  Pointer to the agent_t structure for the agent
 *    0 if error
 *
 *********************************************************************/
agent_t* agentManager_findActiveAgentByName( AgentManager_t* am, const char* agentName )
{
	tptp_node_t*  node;
	agent_t*      agent;

	/* Get rid of any inactive agents */
	agentManager_purgeAgentList( am );
	
	TPTP_LOG_DEBUG_MSG1( am, "findActiveAgentByName looking for %s", agentName );

	/* Look through thte list to see if it contains the requested agent */
	for (node = am->agentList.head; node != 0; node = node->next )
	{
		agent = (agent_t*)node->data;
		
		TPTP_LOG_DEBUG_MSG1( am, "findActiveAgentByName comparing to for %s", agent->agentName );
		
		if ( isEqualString( agent->agentName, agentName ) )
		{
			TPTP_LOG_DEBUG_MSG1( am, "findActiveAgentByName match %s", agent->agentName );
			return agent;
		}
	}

	TPTP_LOG_DEBUG_MSG1( am, "findActiveAgentByName couldn't find %s", agentName );

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Find the entry for a given agent, if it has registered
 *
 * @param agentName  Identifier of the agent to be located
 * @param processID  Process ID of the process in which the agent is running
 *
 * @return
 *	  Pointer to the agent_t structure for the agent
 *    0 if error
 *
 *********************************************************************/
agent_t* agentManager_findActiveAgentByNameAndProcessID( AgentManager_t* am, const char* agentName, PID processID )
{
	tptp_node_t*  node;
	agent_t*      agent;

	/* Get rid of any inactive agents */
	agentManager_purgeAgentList( am );

	/* Look through thte list to see if it contains the requested agent */
	for (node = am->agentList.head; node != 0; node = node->next )
	{
		agent = (agent_t*)node->data;
		if ( (agent->processID == processID) && isEqualString( agent->agentName, agentName ) )
		{
			return agent;
		}
	}

	return 0;
}


/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Remove any dead agents from the active agent list
 *
 *********************************************************************/
void agentManager_purgeAgentList( AgentManager_t* am )
{
	tptp_node_t*  node;
	agent_t*      agent;

	node = am->agentList.head;
	while ( node != 0 )
	{
		agent = (agent_t*)node->data;

		/* Increment this here, because we might be about to delete the current node */
		node = node->next;

		/* If the agent process is no longer active, get rid of this entry */
		if ( !isProcessActive( agent->processID ) )
		{
			tptp_list_remove( &am->agentList, agent );
		}
	}
}

/**
 *********************************************************************
 *                                                       
 * @brief                                                
 *    Find the metadata entry for a given agent, if it is known
 *
 * @param agentName  Unique identifier of the agent to be located
 *
 * @return
 *	  Pointer to the agent_metadata_t structure for the agent
 *    0 if error
 *
 *********************************************************************/
agent_metadata_t* agentManager_findAgentMetadata( AgentManager_t* am, const char* agentName )
{
	tptp_node_t*      node;
	agent_metadata_t* md;

	for (node = am->availableAgentList.head; node != 0; node = node->next )
	{
		md = (agent_metadata_t*)node->data;
		if ( isEqualString( md->agentName, agentName ) )
		{
			return md;
		}
	}

	return 0;
}

BOOL agentManager_checkInterfaces( AgentManager_t*    am,
                                   const tptp_list_t* required, 
                                   const tptp_list_t* supported )
{
	tptp_node_t* rnode;
	tptp_node_t* snode;
	tptp_string* reqif;
	tptp_string* supif;

	/* If none are required, then all are supported */
	if ( required->count == 0 )
		return TRUE;

	/* Otherwise, check for every required interface */
	for (rnode = required->head; rnode != 0; rnode = rnode->next )
	{
		tptp_int32 found = 0;

		reqif = (tptp_string*)rnode->data;

		for (snode = supported->head; snode != 0; snode = snode->next )
		{
			supif = (tptp_string*)snode->data;

			if ( isEqualString( reqif, supif ) )
			{
				found = 1;
				break;
			}
		}

		/* If we didn't find the interface, this isn't a match */
		if ( !found )
			return FALSE;
	}

	/* If we got here, we have all the required interfaces */
	return TRUE;
}

BOOL agentManager_checkAccess( AgentManager_t* am, agent_t* agent, tptp_int32 flags )
{
	/* If the agent is locked, no one else may access it */
	if ( agent->locked )
	{
		return FALSE;
	}

	if ( flags & TPTP_CREATE_INSTANCE )
	{
		/* We shouldn't get here if the client requested a new instance */
		/* This is here as a sanity check */
		TPTP_LOG_ERROR_MSG( am, "Internal error: checking access to an existing agent when a new instance was requested." );
		return FALSE;
	}

	if ( flags & TPTP_LOCK_AGENT )
	{
		if ( (agent->controllers.count != 0) || (agent->observers.count != 0) )
		{
			/* If there are currently controllers or observers, we can't lock this */
			return FALSE;
		}
		else
		{
			/* If there were no controllers or observers, we definitely have access */
			return TRUE;
		}
	}

	if ( flags & TPTP_CONTROLLER_ACCESS )
	{
		tptp_int32 numControllers = agent->controllers.count;

		/* Check the agent's metadata */
		if ( (agent->metadata->maxControllers != -1) &&
			 (agent->metadata->maxControllers <= numControllers) )
		{
			return FALSE;
		}
	}
	else /* Default to TPTP_OBSERVER_ACCESS */
	{
		tptp_int32 numObservers = agent->observers.count;

		/* Check the agent's metadata */
		if ( (agent->metadata->maxObservers != -1) &&
			 (agent->metadata->maxObservers <= numObservers) )
		{
			return FALSE;
		}
	}

	return TRUE;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *    Sends an agentReference to a client that requested one
 *                                                       
 * @param dest      ID of the object that requested this reference
 * @param context   Context ID of the command used to request the reference
 * @param agent     Local object representing the agent that is being referenced
 * @param flags     Access request flags associated with this request
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
void agentManager_grantAgentReference( AgentManager_t* am, tptp_int32 dest, tptp_int32 context, agent_t* agent, tptp_int32 flags )
{
	char agentRefFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><agentReference iid=\"org.eclipse.tptp.agentManager\"><agentID>%d</agentID><agentPID>%lu</agentPID></agentReference></Cmd>";
	char responseCmd[256];
	char notifyRefFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><notifyReference iid=\"org.eclipse.tptp.Agent\"><clientID>%d</clientID><flags>%d</flags></notifyReference></Cmd>";
	char notifyCmd[256];

	/* Count the reference */
	agentManager_addReference( am, agent, dest, flags );

	/* Send the agent notification of its new partner */
	/* This must be sent before we give the client the reference */
	sprintf( notifyCmd, notifyRefFormat, AGENT_MANAGER, agent->agentID, -1, dest, flags );
	connectionManager_sendMessage( am->connectionManager, agent->agentID, notifyCmd );

	/* Send a response indicating the agent we found */
	sprintf( responseCmd, agentRefFormat, AGENT_MANAGER, dest, context, agent->agentID, (unsigned long)agent->processID );
	connectionManager_sendMessage( am->connectionManager, dest, responseCmd );
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *   Update internal data to indicate that the given agent has been referenced
 *                                                       
 * @param agent     Local object representing the agent that is being referenced
 * @param sourceID  ID of the object getting the reference
 * @param flags     Access request flags associated with this request
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_addReference( AgentManager_t* am, agent_t* agent, tptp_int32 sourceID, tptp_int32 flags )
{
	tptp_int32*  refID = tptp_malloc( sizeof(tptp_int32) );

	if ( 0 == refID )
	{
		TPTP_LOG_ERROR_MSG( am, "Out of memory attempting to add agent reference" );
		return -1;
	}

	/* Count this reference */
	agent->refCount++;

	/* Update the agent ownership, etc. */
	*refID = sourceID;

	if ( flags & TPTP_CONTROLLER_ACCESS )
	{
		tptp_list_add( &agent->controllers, refID );

		if ( flags & TPTP_LOCK_AGENT )
		{
			agent->locked = 1;
		}
	}
	else
	{
		tptp_list_add( &agent->observers, refID );
	}

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *   Update internal data to indicate that an agent reference has been released
 *                                                       
 * @param agent     Local object representing the agent that is being referenced
 * @param sourceID  ID of the object releasing the reference
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_removeReference( AgentManager_t* am, agent_t* agent, tptp_int32 sourceID )
{
	BOOL         found = FALSE;
	tptp_node_t* node;

	/* Look for our object among the controllers */
	node = agent->controllers.head; 
	while ( node != 0 ) 
	{
		tptp_int32* cid = (tptp_int32*)node->data;

		/* Because we might be deleting the current node, we need to increment here */
		node = node->next;

		if ( *cid == sourceID )
		{
			tptp_list_remove( &agent->controllers, cid );
			found = TRUE;
		}
	}

	if ( ! found )
	{
		/* Look for our object among the observers */
		node = agent->observers.head; 
		while ( node != 0 ) 
		{
			tptp_int32* cid = (tptp_int32*)node->data;

			/* Because we might be deleting the current node, we need to increment here */
			node = node->next;

			if ( *cid == sourceID )
			{
				tptp_list_remove( &agent->observers, cid );
				found = TRUE;
			}
		}
	}

	if ( found )
	{
		char notifyFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><notifyDereference iid=\"org.eclipse.tptp.Agent\"><clientID>%d</clientID></notifyDereference></Cmd>";
		char notifyCmd[256];

		/* Send the agent notification that it lost a partner */
		sprintf( notifyCmd, notifyFormat, AGENT_MANAGER, agent->agentID, -1, sourceID );
		connectionManager_sendMessage( am->connectionManager, agent->agentID, notifyCmd );

		/* If the agent was locked, it should only have one controller/observer */
		if ( agent->locked )
			agent->locked = 0;

		/* Decrement the reference count and terminate the agent if it is now unreferenced */
		/* Agents that don't wish to terminate should have obtained a self-reference       */
		/* Agents that weren't launched through the AC automatically get a self-reference  */
		agent->refCount--;
		if ( 0 == agent->refCount )
		{
			char terminateFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><terminate iid=\"org.eclipse.tptp.Agent\"></terminate></Cmd>";
			char terminateCmd[256];

			/* Ask this agent to go away */
			sprintf( terminateCmd, terminateFormat, AGENT_MANAGER, agent->agentID, -1 );
			connectionManager_sendMessage( am->connectionManager, agent->agentID, terminateCmd );

			//Clean up the connection.
			//Calling dropPeerConnection will translate to terminateConnection.
			//The name dropPeerConnection does not signify that it is any way connected with peer monitoring.
			connectionManager_dropPeerConnection( am->connectionManager, agent->agentID );
		}
	}

	return 0;
}

/**
 *********************************************************************
 *                                                       
 * @brief                                  
 *   Notify controllers and observers of an agent of some agent state change (deregistered, processEnded)
 *                                                       
 * @param agent     Local object representing the agent that is being referenced
 * @param notifyCmd The specific command to be sent to the controllers and observers
 *
 * @return
 *    0 if success
 *    nonzero if error
 *
 *********************************************************************/
tptp_int32 agentManager_notifyAgentUsers( AgentManager_t* am, agent_t* agent, char* notifyCmdName )
{
    char         notifyFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><%s iid=\"org.eclipse.tptp.agentManager\"><agentID>%d</agentID></%s></Cmd>";
	char         notifyCmd[256];
	tptp_node_t* node;

	/* Notify any controllers */
	for ( node = agent->controllers.head; node != 0; node = node->next )
	{
		tptp_int32* cid = (tptp_int32*)node->data;

		/* Build the command for this controller and send it */
		sprintf( notifyCmd, notifyFormat, AGENT_MANAGER, *cid, -1, notifyCmdName, agent->agentID, notifyCmdName );
		connectionManager_sendMessage( am->connectionManager, *cid, notifyCmd );
	}

	/* Notify any observers */
	for ( node = agent->observers.head; node != 0; node = node->next )
	{
		tptp_int32* oid = (tptp_int32*)node->data;

		/* Build the command for this observer and send it */
		sprintf( notifyCmd, notifyFormat, AGENT_MANAGER, *oid, -1, notifyCmdName, agent->agentID, notifyCmdName );
		connectionManager_sendMessage( am->connectionManager, *oid, notifyCmd );
	}

	return 0;
}

void agentManager_sendErrorResponse( AgentManager_t* am, tptp_int32 errorCode, tptp_int32 sourceID, tptp_int32 contextID )
{
	tptp_string* cbeStr;
	tptp_int32   rc;
	char*        errorMsg;

	errorMsg = tptp_getErrorString( errorCode );

	TPTP_LOG_ERROR_MSG( am, errorMsg );

	rc = tptp_createErrorCmdCBE( "Agent Controller", "Agent Manager", AGENT_MANAGER, TPTP_CRITICAL, errorMsg, &cbeStr );

	if ( rc == 0 )
	{
		char errorFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><TPTP_Error iid=\"org.eclipse.tptp.Error\"><ErrCode>%d</ErrCode><ErrInfo>%s</ErrInfo></TPTP_Error></Cmd>";
		char errorCmd[8192];

		/* Ask this agent to go away */
		sprintf( errorCmd, errorFormat, AGENT_MANAGER, sourceID, contextID, errorCode, cbeStr );
		connectionManager_sendMessage( am->connectionManager, sourceID, errorCmd );
	}
	else
	{
		/* If we could build the CBE string, send the error with a raw error message */
		char errorFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><TPTP_Error iid=\"org.eclipse.tptp.Error\"><ErrCode>%d</ErrCode><ErrInfo>%s</ErrInfo></TPTP_Error></Cmd>";
		char errorCmd[8192];

		/* Ask this agent to go away */
		sprintf( errorCmd, errorFormat, AGENT_MANAGER, sourceID, contextID, errorCode, errorMsg );
		connectionManager_sendMessage( am->connectionManager, sourceID, errorCmd );
	}

	tptp_free( errorMsg );
}
