/*******************************************************************************
 * Copyright (c) 2005, 2009 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 API and Implementation
 *
 * $Id: ClientCTL.c,v 1.55 2009/08/26 14:59:58 jwest Exp $
 *
 *******************************************************************************/ 

#ifdef __linux__
	#include <sys/types.h>
	#include <sys/socket.h>
#endif

#include <string.h>
#include "tptp/TPTPTypes.h"
#include "tptp/TPTPSupportTypes.h"
#include "tptp/TransportSupport.h"
#include "tptp/TPTPBaseTL.h"
#include "tptp/BaseTLLog.h"
#include "ClientCTL.h"
#include "RACClientSupport.h"
#include "Connect2AC.h"
#include "java.h"
#include "tptp/dime.h"

/* Declaration from CCTLServer.c */
extern THREAD_USER_FUNC_RET_TYPE processClientRequest(LPVOID args);

static TID startProcessPollingThread(tl_state_data_t* stateData);

#ifdef _WIN32
extern DWORD WINAPI processPollingThread(LPVOID args);
#else
extern void *processPollingThread(void *args);
#endif

THREAD_USER_FUNC_RET_TYPE startNativeFileServer(LPVOID args);
extern int stopNativeFileServer();

/**
 *********************************************************
 *
 * @brief
 *    common interface to instantiate a transport layer object
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 createTransportListener( tptp_object* cmo, transport_layer_data_t * pTransportData, tptp_object* tlo )
{
	tptp_int32	 rc;
	BOOL		 jvmCreated;
	TID			 _fileServerTID;
	TID			 _secsvrtid;
	tptp_string *isDataMultiplexed;
	tptp_string *processPolling;
	tptp_string *securityEnabled;
	tptp_string *fileServer;

	rc = baseTL_createTransportListener( cmo, pTransportData, tlo, "Client Compatibility TL" );
	if ( ( rc == 0 ) && (tlo != NULL) && (tlo->data != NULL) )
	{
		cctl_state_data_t*  cctlData;
		tl_state_data_t*    stateData = (tl_state_data_t*)tlo->data;

		/* Allocate implementation specific state data */
		stateData->implData = tptp_malloc( sizeof(cctl_state_data_t) );
		if ( stateData->implData == NULL )
		{
			baseTL_destroyTransportListener( tlo );
			return TPTP_ERROR_OUT_OF_MEMORY;
		}
		cctlData = (cctl_state_data_t*)stateData->implData;
		
		initSocketAccept(&(cctlData->sockAccept));

		/* Read the TL configuration */
		rc = getXmlFragmentElementString( pTransportData->configurationData,"Configuration", "IsDataMultiplexed", &isDataMultiplexed);
		
		if ( rc == 0 )
		{
#ifdef _WIN32
#ifdef _WIN64
			if ( 0 == strcasecmp(isDataMultiplexed, "true") )
#else
			if ( 0 == strcmpi(isDataMultiplexed, "true") )
#endif
#else
			if ( 0 == strcasecmp(isDataMultiplexed, "true") )
#endif
			{
				cctlData->isDataMultiplexed = TRUE;
			}
			else
			{
				cctlData->isDataMultiplexed = FALSE;
			}
			tptp_free( isDataMultiplexed );
		}
		else
		{
			cctlData->isDataMultiplexed = FALSE;
		}
		rc = getXmlFragmentElementInt( pTransportData->configurationData, "Configuration", "Port", &cctlData->port );
		if (rc == -1) 
		{
			cctlData->port = RA_CTL_PORT_NUM_SERVER;
		}
		
		rc = getXmlFragmentElementInt( pTransportData->configurationData, "Configuration", "FilePort", &cctlData->filePort );
		if (rc == -1) 
		{
			cctlData->filePort = RA_FILE_PORT_NUM_SERVER;
		}
		rc = getXmlFragmentElementInt( pTransportData->configurationData,"Configuration", "ACProtocolPort", &cctlData->acProtocolPort );
		if (rc == -1) 
		{
			// The default AC port is 10006
			// In this case the config file does not have the ACProtocolPort entry.
			cctlData->acProtocolPort = 10006;
		}

		rc = getXmlFragmentElementString( pTransportData->configurationData, "Configuration","SecurityEnabled", &securityEnabled );
		if ( rc == 0 )
		{
#ifdef _WIN32
#ifdef _WIN64
			if ( 0 == strcasecmp(securityEnabled, "true") )
#else
			if ( 0 == strcmpi(securityEnabled, "true") )
#endif
#else
			if ( 0 == strcasecmp(securityEnabled, "true") )
#endif
			{
				cctlData->securityEnabled = 1;
			}
			else
			{
				cctlData->securityEnabled = 0;
			}

			tptp_free( securityEnabled );
		}
		else
		{
			cctlData->securityEnabled = 0;
		}

		rc = parseHostList(pTransportData->configurationData, &cctlData->network_list);
		if ( rc == -1 )
		{
			cctlData->network_list = NULL;
		}


		/* Set up the process list */
		ra_initializeDataStructures();
		cctlData->processList = ra_getActiveProcessList();
		/* The process list can knows how to navigate back to the ccb via state data, this was
		 * necessary for commands such as scrubProcess */
		cctlData->processList->stateData = stateData;

		rc = getXmlFragmentElementString(pTransportData->configurationData,"Configuration", "ProcessPolling", &processPolling);
		if ( rc == 0 )
		{
#ifdef _WIN32
#ifdef _WIN64
			if ( 0 == strcasecmp(processPolling, "true") )
#else
			if ( 0 == strcmpi(processPolling, "true") )
#endif
#else
			if ( 0 == strcasecmp(processPolling, "true") )
#endif
			{
				cctlData->pollingThread = startProcessPollingThread( stateData );
				cctlData->isPolling = TRUE;
			}
			else
			{
				#ifdef MVS
					memset(&cctlData->pollingThread, 0, sizeof(TID));
				#else
					cctlData->pollingThread = (TID)0;
				#endif
				cctlData->isPolling = FALSE;
			}

			tptp_free( processPolling );
		}
		else
		{
			cctlData->pollingThread = startProcessPollingThread( stateData );
			cctlData->isPolling = TRUE;
		}

		rc = getXmlFragmentElementString(pTransportData->configurationData, "Configuration", "JavaUnsecuredFileServer", &fileServer);
		if (rc ==0 ) {
#ifdef _WIN32
#ifdef _WIN64
			if (0 == strcasecmp(fileServer, "true"))
#else
			if (0 == strcmpi(fileServer, "true"))
#endif
#else
			if (0 == strcasecmp(fileServer, "true"))
#endif
			{
				cctlData->isJavaUnsecuredFileServer = TRUE;
			}
			else {
				cctlData->isJavaUnsecuredFileServer = FALSE;
			}

			tptp_free(fileServer);
		}
		else {
				cctlData->isJavaUnsecuredFileServer = FALSE;
		}

		/* Assume NO Security for now !!! */

		/* Create JVM */
		if (cctlData->isJavaUnsecuredFileServer || cctlData->securityEnabled) {
			if (tptpCreateJavaVitualMachine(stateData, NULL) == 0) {
				jvmCreated = TRUE;
			}		
			else {
				jvmCreated = FALSE;
				TPTP_LOG_ERROR_MSG(stateData, "Cannot create JVM");
			}
		}
		else {
			jvmCreated = FALSE;		// skip jvm
		}

		/* Start createFileServer */
		if (jvmCreated)
		{
			/* Start secured server if needed */
			if(cctlData->securityEnabled) {
				BOOL isValid = TRUE;

				/* Start the secured connection server */
				rc = getXmlFragmentElementInt(pTransportData->configurationData,"Configuration", "SecuredPort", &cctlData->securedPort);
				if ( rc == -1 )
				{
					TPTP_LOG_ERROR_MSG( stateData, "Cannot retrieve the secured port from configuration file." );
					isValid = FALSE;
				}

				rc = getXmlFragmentElementString(pTransportData->configurationData, "Configuration", "Keystore", &cctlData->keystore);
				if ( rc == -1 )
				{
					TPTP_LOG_ERROR_MSG( stateData, "Cannot retrieve the security keystore from configuration file." );
					isValid = FALSE;
				}

				rc = getXmlFragmentElementString(pTransportData->configurationData, "Configuration","KeystorePassword", &cctlData->keystorePassword);
				if ( rc == -1 )
				{
					TPTP_LOG_ERROR_MSG( stateData, "Cannot retrieve the security keystorePassword from configuration file." );
					isValid = FALSE;
				}

				rc = parseUserList(pTransportData->configurationData, &cctlData->user, &cctlData->userlist);				
				if ( rc == -1 )
				{
					TPTP_LOG_ERROR_MSG( stateData, "Cannot retrieve the allowed user from configuration file." );
					isValid = FALSE;
				}

				if(isValid) {
					/* Start secure java file server... this needs to be done after vairiables are read from config */
					startSecuredFileServer(stateData, &_fileServerTID);
					/* Start the secured file server */
					startSecuredServer(stateData, &_secsvrtid);
				}
				else {
					TPTP_LOG_ERROR_MSG( stateData, "Cannot start secured server. Some required parameters are missing from the configuration file" );
				}
			}
			else {
				/* Start the insecured java file server */
				startFileServer(stateData, &_fileServerTID);
			}
		}
	}

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to send to the given destination the given message
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 sendMessage( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 size, tptp_string* pCmdBlock)
{
	tl_state_data_t*          stateData;
	tl_control_connection_t*  connectionEntry;
	client_connection_block_t* client;

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	/* retrieve the corresponding client information */
	connectionEntry = (tl_control_connection_t*) tableGet(stateData->controlConnectionTable, connectionID) ;
	if ( connectionEntry == NULL )
	{
		/* Unrecognized connection */
		/* TODO: Log an error */
		return -1;
	}

	client = (client_connection_block_t*) connectionEntry->implData;

	if ( client == NULL )
	{
		/* Missing client data */
		/* TODO: Log an error */
		return -1;
	}

	/* Call our handler to parse, translate and process this message */
	return handleOutgoingMessage( stateData, client, pCmdBlock );

}


/**
 *********************************************************
 *
 * @brief
 *    common interface to terminate the given connection
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 terminateConnection(tptp_object* tlo, tptp_uint32  connectionID)
{
	tl_state_data_t* stateData;
	tl_control_connection_t*  connectionEntry;

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	/* retrieve the corresponding client information */
	connectionEntry = (tl_control_connection_t*) tableGet(stateData->controlConnectionTable, connectionID) ;
	if ( connectionEntry == NULL )
	{
		/* Unrecognized connection */
		/* TODO: Log an error */
		return -1;
	}

	/* TODO: Use the connectionEntry->implData to terminate the conneciton */

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to send data to the given destination 
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 sendData( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 size, void * pDataBlock)
{
	tl_state_data_t*       stateData;
	cctl_state_data_t*     cctlData;
	tl_data_connection_t*  connectionEntry;
	tptp_int32 rc = -1;
	client_data_connection_block_t* dcb;
	#ifdef MVS
		char* nativeBuffer = 0;
	#endif

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	cctlData = (cctl_state_data_t*)stateData->implData;

	/* retrieve the corresponding client information */
	connectionEntry = (tl_data_connection_t*) tableGet(stateData->dataConnectionTable, connectionID) ;
	if ( connectionEntry == NULL )
	{
		/* Unrecognized connection */
		/* TODO: Log an error */
		// TPTP_LOG_DEBUG_MSG( stateData, "sendData: connectionEntry == NULL, so error apparently" );
		return -1;
	}

	dcb = (client_data_connection_block_t*) connectionEntry->implData;
	if (dcb == NULL) 
	{
		/* TODO: Log an error */
		// TPTP_LOG_DEBUG_MSG( stateData, "sendData: dcb is NULL" );
		return -1;
	}

	if ( cctlData->isDataMultiplexed )
	{
		ra_message_t *message;
		ra_command_t *command;

		if ( ( dcb->pid == 0) || (dcb->ccb == NULL) )
		{
			/* TODO: Log an error */
			// TPTP_LOG_DEBUG_MSG( stateData, "sendData: pid or ccb" );
			return -1;
		}

		message = ra_createMessage(RA_CONTROL_MESSAGE, 0);
		command = ra_addCommandToMessage(message, NULL);

		command->tag = RA_BINARY_CUSTOM_COMMAND;
		command->info.custom_command.context = dcb->context;
		command->info.custom_command.processId = dcb->pid;
		ra_copyRASTRING(&command->info.custom_command.agent, &dcb->agentName);

		command->info.custom_command.message.length = size; /* add 4 bytes magic number */
		command->info.custom_command.message.data = (char*)tptp_malloc(sizeof(char) * command->info.custom_command.message.length);

		memcpy( command->info.custom_command.message.data, pDataBlock, size );

		ra_forwardMessage(message, dcb->ccb);

		TPTP_LOG_DEBUG_MSG1( stateData, "%d data bytes written to control channel", size );

		ra_destroyMessage(message, FALSE);
	}
	else
	{
		DIME_HEADER_PTR_T  pDime = (DIME_HEADER_PTR_T) pDataBlock ;
		tptp_uint32 dimeLength;

		// We need to strip out the dime header 
		// If we get here we KNOW this is console output
		// The data channel is handled by direct flush to fd.
		//
		MAKE_DIME_HEADER((char *)pDime);
		dimeLength = GET_DIME_LENGTH(pDime) ;
		if (dimeLength > size) {
			// TPTP_LOG_DEBUG_MSG( stateData, "sendData: dime > size" );
			return -1;
		}
		size -= dimeLength ;
		pDataBlock = (void *)((char *)pDataBlock + dimeLength);
		#ifdef MVS
			native2unicode(&nativeBuffer, (char *)pDataBlock, size);
			size = strlen(nativeBuffer);
			rc = writeToSocket(dcb->sock, nativeBuffer, size);
			if(nativeBuffer != 0) {
				tptp_free(nativeBuffer);
			}
		#else
			rc = writeToSocket(dcb->sock, (char *)pDataBlock, size);
		#endif
	}

	return rc;
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to terminate a given data connection
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 terminateDataConnection(tptp_object* tlo, tptp_uint32  connectionID)
{
	tl_state_data_t*       stateData;
	tl_data_connection_t*  connectionEntry;
	client_data_connection_block_t* dcb;

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	/* retrieve the corresponding client information */
	connectionEntry = (tl_data_connection_t*) tableGet(stateData->dataConnectionTable, connectionID) ;
	if ( connectionEntry == NULL )
	{
		/* Unrecognized connection */
		/* TODO: Log an error */
		return -1;
	}

	/* TODO: Use the connectionEntry->implData to terminate the conneciton */
	dcb = (client_data_connection_block_t*) connectionEntry->implData;

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to destroy a transport layer object
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 destroyTransportListener( tptp_object* tlo )
{
	return baseTL_destroyTransportListener( tlo );
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to start an underlying transport mechanism
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 startTransportListener(tptp_object* tlo)
{
	int                 rc = 0;
	tl_state_data_t*    stateData;
	cctl_state_data_t*  cctlData;
	TID                 threadId;
	HANDLE              threadHandle;
	SOCKET * socketList;
	
	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	cctlData = (cctl_state_data_t*)stateData->implData;

	if ( cctlData == NULL )
	{
		TPTP_LOG_ERROR_MSG(stateData, "Error: Internal socket data is missing") ;
		return -1; // TODO: Use a more specific error
	}

	/* Create a pseudo-connection to monitor agent register events */
	startAgentRegistrationListener( stateData );

	socketList = (SOCKET *)tptp_malloc(sizeof(SOCKET) * FD_SETSIZE);
	
	cctlData->sockets = socketList;
	
	/* create and initialize the server socket */
	// cctlData->sock = getTheSocket( cctlData->port, &myServerSockAddr );

	/* create and initialize the server socket */
	rc = getTheSocket(cctlData->port, cctlData->sockets, &(cctlData->numSockets)); 
	
	if ( rc < 0 || cctlData->numSockets == 0 )
	{
		TPTP_LOG_ERROR_MSG(stateData, "Error: unable to create the server socket.") ;
		return -1; // TODO: Use a more specific error
	}
	
	if ((!cctlData->isJavaUnsecuredFileServer) && !cctlData->securityEnabled) {
		rc = tptpStartThread(startNativeFileServer, (LPVOID)stateData, &threadId, &threadHandle);
		CLOSE_THREAD_HANDLE(threadHandle);
		if (rc == 0) {
			TPTP_LOG_INFO_MSG1(stateData, "Native file server started listening on port %d", cctlData->filePort);
		}
		else {
			TPTP_LOG_ERROR_MSG(stateData, "Native file server could not start");
		}
	}			

	return baseTL_startTransportListener( tlo, serveRequests );
}

/**
 *********************************************************
 *
 * @brief
 *    common interface to retrieve peer monitoring info
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 getPeerConnectionInfo(tptp_object* tlo, tptp_string* type, tptp_string** ci )
{
	char   ciFormat[] = "<%s><transportType>TPTP_CCTL</transportType><host>%s</host><port>%d</port></%s>";
	tl_state_data_t*    stateData;
	cctl_state_data_t*  cctlData;
	int                 port;
	char*               ipAddrStr;

	if ( ci == NULL )
	{
		return TPTP_UNEXPECTED_NULL_ARG;
	}

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
	{
		return TPTP_ERROR_INVALID_PARAMETER;
	}

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	cctlData = (cctl_state_data_t*)stateData->implData;

	if ( cctlData == NULL )
	{
		TPTP_LOG_ERROR_MSG(stateData, "Error: Internal socket data is missing") ;
		return -1; // TODO: Use a more specific error
	}

	if ( cctlData->usePeerAttachPort )
	{
		port = cctlData->peerAttachPort;
	}
	else
	{
		port = cctlData->port;
	}

	/* Get the local host IP address */
	if ( cctlData->usePeerAttachAddress )
	{
		int ret;
		
		ret = convertIPAddressUlongToStringIPv4M( cctlData->peerAttachAddress, &ipAddrStr );
		if ( ret != 0 )
		{
			return ret;
		}
	}
	else
	{
		/* NOTE: This has problems if there are multiple IP addresses, but in
			     the case of compatibility mode, this isn't really used anyway */
		if (getSocketIPStringIPv4M( (SOCKET)NULL, &ipAddrStr) < 0) {
			TPTP_LOG_ERROR_MSG(stateData, "Unable to get local IP address");
			return -1;
		}
	}
	
	*ci = (char *)tptp_malloc( (2*strlen(type)) + strlen(ciFormat) + 11 + strlen(ipAddrStr) + 1 );
	if ( *ci == NULL )
	{
		return TPTP_SYS_NO_MEM;
	}

	sprintf( *ci, ciFormat, type, ipAddrStr, cctlData->port, type );

	/* We're done with this now, so let's free it */
	tptp_free( ipAddrStr ); 

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to establish a connection with
 *    another Agent Controller
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 createPeerConnection( tptp_object* tlo, tptp_string* sourceConnectionInfo, tptp_uint32* connectionID )
{
	tl_state_data_t* stateData;
	tptp_string*     ipAddrStr;
	unsigned long    ipAddr;
	unsigned int     port;
	SOCKET           sock;
	tptp_int32       rc;
	TID              threadId;
	HANDLE           threadHandle;

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
		return TPTP_ERROR_INVALID_PARAMETER;

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	/* Extract the peer address from the sourceConnectionInfo fragment */
	rc = getXmlFragmentElementInt( sourceConnectionInfo, "sourceConnectionInfo", "port", &port );
	if ( rc != 0 )
	{
		/* If this failed, check for the shutdown case */
		rc = getXmlFragmentElementInt( sourceConnectionInfo, SHUTDOWNCONNECTION_XML_NAME, "port", &port );
		if ( rc == 0 )
		{
			client_connection_block_t* clientConnectionBlock;

			/* We don't actually need to connect in this case because we will send the message through a named pipe */
			TPTP_LOG_DEBUG_MSG( stateData, "Short-circuiting connection for shutdown in CCTL" );

			/* Ask the AC for a new connection */
			rc = stateData->ac.addConnection( stateData->ac.cmo, stateData->transportID, connectionID );

			/* set up the data block for our fake connection */
			clientConnectionBlock = createClientConnectionBlock((SOCKET)NULL, stateData);
			clientConnectionBlock->clientID = *connectionID;
			clientConnectionBlock->connectionType = CCTL_SHUTDOWN;
			clientConnectionBlock->secured = FALSE;

			/* Add this connection to a list maintained by the base TL implementation */
			baseTL_addControlConnectionEntry( stateData, *connectionID, (void*)clientConnectionBlock );

			return 0;
		}
		TPTP_LOG_ERROR_MSG1( stateData, "Unable to find port in sourceConnectionInfo: %s", sourceConnectionInfo );
		return rc;	
	}
	rc = getXmlFragmentElementString( sourceConnectionInfo, "sourceConnectionInfo", "host", &ipAddrStr );
	if ( rc != 0 )
	{
		TPTP_LOG_ERROR_MSG1( stateData, "Unable to find host in sourceConnectionInfo: %s", sourceConnectionInfo );
		return rc;	
	}
	rc = convertIPAddressStringToUlongIPv4M( ipAddrStr, &ipAddr );
	if ( rc != 0 )
	{
		TPTP_LOG_ERROR_MSG1( stateData, "Unable to convert IP address string to unsigned long: %s", ipAddrStr );
		tptp_free( ipAddrStr );
		return rc;	
	}

	/* TODO: Check to see if this is local */

	/* Connect to the remote AC */
	rc = connectToTCPServerIPv4M( ipAddr, (unsigned short)port, &sock);
	if ( rc != 0 )
	{
		TPTP_LOG_ERROR_MSG2( stateData, "Unable to connect to peer: %s, port %d", ipAddrStr, port );
		return rc;
	}

	/* Start a thread to handle responses */
	if (isSocketValid(sock) == 0)
	{
		TPTP_LOG_ERROR_MSG(stateData, "Accept() received an invalid socket request." );
	}
	else
	{
		client_connection_block_t* clientConnectionBlock;

		setHandleInherited((HANDLE) sock) ;

		/* set up the data block for each request */
		clientConnectionBlock = createClientConnectionBlock(sock, stateData);
		clientConnectionBlock->connectionType = CCTL_NATIVE_SOCKET;

		/* Ask the AC for a new connection */
		rc = stateData->ac.addConnection( stateData->ac.cmo, stateData->transportID, connectionID );
		if ( rc != 0 )
		{
			TPTP_LOG_ERROR_MSG1( stateData, "Error adding Agent Controller connection for socket: %u", (unsigned int)sock );
			return rc;
		}

		/* Add this connection to a list maintained by the base TL implementation */
		baseTL_addControlConnectionEntry( stateData, *connectionID, (void*)clientConnectionBlock );

		/* Save this ID for later use */
		clientConnectionBlock->clientID = *connectionID;
		clientConnectionBlock->connectionType = CCTL_NATIVE_SOCKET;
		clientConnectionBlock->secured = FALSE;

		/* go create a new thread to process each incoming connection request */
		rc = tptpStartThread(processClientRequest, 
			(LPVOID)clientConnectionBlock, &threadId, &threadHandle) ;
		CLOSE_THREAD_HANDLE(threadHandle);
	}

	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    common interface to stop the transport mechanism
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 setPeerAttachInfo(tptp_object* tlo, PeerAttachInfo_t* peerInfo)
{
	tl_state_data_t*    stateData;
	cctl_state_data_t*  cctlData;

	if ( peerInfo == NULL )
	{
		return TPTP_UNEXPECTED_NULL_ARG;
	}

	if ( !isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)) )
	{
		return TPTP_ERROR_INVALID_PARAMETER;
	}

	stateData = tlo->data; /* This was verified not to be NULL by isValidTPTPBlock */

	cctlData = (cctl_state_data_t*)stateData->implData;

	if ( cctlData == NULL )
	{
		TPTP_LOG_ERROR_MSG(stateData, "Error: Internal socket data is missing") ;
		return -1; // TODO: Use a more specific error
	}

	/* Copy the information into our local data structure */
	cctlData->usePeerAttachPort    = peerInfo->usePeerAttachPort;
	cctlData->peerAttachPort       = peerInfo->peerAttachPort;
	cctlData->usePeerAttachAddress = peerInfo->usePeerAttachAddress;
	cctlData->peerAttachAddress    = peerInfo->peerAttachAddress;

	return 0;
}

#ifdef __linux__
int closeSocket (int socket) {
	struct linger linger;
	int rc;

	linger.l_onoff = 1;
	linger.l_linger = 0;

	rc = setsockopt(socket, SOL_SOCKET, SO_LINGER, (const char*) &linger, 
				sizeof(linger));

	close (socket);
	
	return rc;
}
#else
int closeSocket (int socket) {
	return closeThisSocket(socket);
}
#endif

int closeConnections (tptp_object* tlo) {
	tl_control_connection_t* con;
	client_connection_block_t* client;
	tl_state_data_t* stateData;
	tptp_uint32* list;
	int i, tabSize;
	SOCKET sock;

	if (!isValidTPTPBlock(tlo, TPTP_BASETL_MAKE_OBJECT_ID(tlo)))
		return TPTP_ERROR_INVALID_PARAMETER;
		
	stateData = tlo->data;
	tabSize = tableSize(stateData->controlConnectionTable);
	if (tabSize <= 0) return 0;
	
	list = (tptp_uint32*) malloc (tabSize*sizeof(tptp_uint32));
	tableKeysAsList(stateData->controlConnectionTable, list, tabSize);
		
	for (i=0; i<tabSize; i++) {
		tptp_uint32 cid = list[i];
		con = (tl_control_connection_t*) tableGet (stateData->controlConnectionTable, cid);
		if (con == NULL) continue;
		
		client = (client_connection_block_t*) con->implData;
		if (client == NULL) continue;
		
		sock = client->sock;
		if (sock > 0) closeSocket (sock);
	}		

	free (list);
	
	return 0;			
}

/**
 *********************************************************
 *
 * @brief
 *    common interface to stop the transport mechanism
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/

tptp_int32 stopTransportListener(tptp_object* tlo)
{
	closeConnections(tlo);
	
	return baseTL_stopTransportListener( tlo );
}


/**
 *********************************************************
 *
 * @brief
 *    common interface to set the function and data block 
 * for forwarding messages
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 setProcessMessageFunc(tptp_object* tlo, tptp_object* nexto, processMessage_ptr_t func )
{
	return baseTL_setProcessMessageFunc( tlo, nexto, func );
}

/**
 *********************************************************
 *
 * @brief
 *    common interface to set up the data path between source and destination
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 setIncomingDataFunc( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 partnerID, tptp_object* partner, sendData_ptr_t newDataFunc )
{
	return baseTL_setIncomingDataFunc( tlo, connectionID, partnerID, partner, newDataFunc );
}

/**
 *********************************************************
 *
 * @brief
 *    This function establishes a pseudo-connection to 
 *      the AC and registers that pseudo-connection to
 *      receive agent registraiton events
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/


tptp_int32 startAgentRegistrationListener( tl_state_data_t* stateData )
{
	cctl_state_data_t*  cctlData = (cctl_state_data_t*)stateData->implData;
	tptp_uint32         connectionID;
	tptp_int32          ret;
	char                addListenerFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><addEventListener iid=\"org.eclipse.tptp.eventProvider\"><interfaceID>%s</interfaceID><listenerID>-1</listenerID></addEventListener></Cmd>";
	char                addListenerCmd[8192];
	client_connection_block_t* clientConnectionBlock;

	/* Add a connection so that we can act as a client */
	ret = stateData->ac.addConnection( stateData->ac.cmo, stateData->transportID, &connectionID );
	if ( ret != 0 )
		return ret;
	cctlData->selfConnectionID = connectionID;

	/* set up the data block for our selfConnection client */
	clientConnectionBlock = createClientConnectionBlock((SOCKET)NULL, stateData);
	baseTL_addControlConnectionEntry( stateData, connectionID, (void*)clientConnectionBlock );
	clientConnectionBlock->clientID = connectionID;
	clientConnectionBlock->connectionType = CCTL_AGENT_LISTENER;
	clientConnectionBlock->secured = FALSE;


	/* Build a command to request agent registration events */
	sprintf( addListenerCmd, addListenerFormat, connectionID, AGENT_MANAGER, baseTL_getNextContextID(), AGENTMGR_AGENT_EVENTS_IID ); 

	/* Check to make sure it's safe to send commands */
	if ( stateData->controlPartner.processMessage == NULL )
	{
		/* The message pipeline isn't complete yet.  This is unexpected */
		/* TODO: Do something to report this error */
		return -1;
	}

	/* Send the command */
	stateData->controlPartner.processMessage( stateData->controlPartner.nexto, strlen(addListenerCmd), addListenerCmd );

	return 0;
}

/*
 * Thread to keep polling the registered processes and see if they are still alive
 */
static TID startProcessPollingThread(tl_state_data_t* stateData) {
	TID tid;
	HANDLE handle;
	cctl_state_data_t* cctlData = (cctl_state_data_t*)stateData->implData;
#if defined(_WIN32)
	handle = CreateThread(NULL,			/* default security attributes */
						0,						/* same stack size as current thread */
						processPollingThread,	/* thread entry point */
						(LPVOID)cctlData,		/* thread params */
						0,						/* start executing immediately */
						&tid);					/* the thread ID */
	CLOSE_THREAD_HANDLE(handle);
#elif defined(_AIX)
	pthread_attr_t thread_attr;

	pthread_attr_init(&thread_attr);
	pthread_attr_setstacksize( &thread_attr, 4194304 );
	pthread_create(&tid,
		&thread_attr,
		processPollingThread,
		(void *)cctlData);
#else
	pthread_create(&tid,
		NULL,
		processPollingThread,
		(void *)cctlData);
#endif
	TPTP_LOG_DEBUG_MSG1( stateData, "Process polling thread created, tid = %d", tid );

	return tid;
}
