/*******************************************************************************
 * Copyright (c) 2004, 2008 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
 *******************************************************************************/


// NamedPipeTLTest.c : Defines the entry point for the DLL application.
//

#include "NamedPipeTLTest.h"
#include "TPTPCommon.h"
#include "TPTPUtils.h"
#include <stdio.h>
#include <string.h>

#ifndef _WIN32
#include <sys/types.h>
#include <unistd.h>
#endif

/* Forward declaration */
#ifdef _WIN32
DWORD WINAPI SimThreadW32( LPVOID param );
DWORD WINAPI SimDataW32( LPVOID param );
#endif
void* SimThread(void* param);
void* SimData(void* param);

/* Someting hard-coded for testing purposes */
#define BIND_DATA_CONTEXT  201

/* Data connection helpers */
#define MAX_DATA_CONNECTIONS  5
typedef struct
{
    unsigned int    connectionID;
	unsigned int    partnerID;
	tptp_object*    partner;
	unsigned int    flags;
	unsigned int    sourceID;
	unsigned int    context;
    sendData_ptr_t  sendData;
} dataConnectionInfo;

typedef struct
{
	tptp_object*                     cmo;
	addConnectionEntry_ptr_t         addConnection;
	removeConnectionEntry_ptr_t      removeConnection;
	addDataConnectionEntry_ptr_t     addDataConnection;
    removeDataConnectionEntry_ptr_t  removeDataConnectionEntry;
    logErrorEntry_ptr_t              logErrorEntry;
} connection_manager;

typedef struct
{
	tptp_object*          nexto;
	processMessage_ptr_t  processMessage;
} pipeline_object;

typedef enum { STARTED, STOPPED } listener_state;

typedef struct
{
	int                 transportID;
	dataConnectionInfo  dataConn[MAX_DATA_CONNECTIONS];
	int					lastDataConnection;
	TID                 threadID;
#ifdef _WIN32
	HANDLE              hThread;
#endif
	listener_state      state;
	connection_manager  cm;
	pipeline_object     next;
} state_data;

int processMessage( state_data* tlData, char* msg );

tptp_int32 createTransportListener( tptp_object* cmo, transport_layer_data_t * pTransportData, tptp_object* tlo )
{
	state_data*  tlData;

	if ( 0 == tlo )
	{
		printf( "Bad pointer passed to createTransportLayer.\n" );
		return -1;
	}

	tlData = tptp_malloc( sizeof(state_data) );
	if ( 0 == tlData )
		return -1;

	memset( tlData, 0, sizeof(state_data) );

	tlData->cm.cmo                       = cmo;
	tlData->cm.addConnection             = pTransportData->addConnectionEntry;
	tlData->cm.addDataConnection         = pTransportData->addDataConnectionEntry;
	tlData->cm.removeConnection          = pTransportData->removeConnectionEntry;
	tlData->cm.removeDataConnectionEntry = pTransportData->removeDataConnectionEntry;
	tlData->cm.logErrorEntry             = pTransportData->logErrorEntry;

	tlData->transportID = pTransportData->transportID;
	tlData->lastDataConnection = -1;
	tlData->state = STOPPED;

	tlo->data     = tlData;
	tlo->objectID = tlData->transportID;

	return 0;
}

tptp_int32 destroyTransportListener( tptp_object* tlo )
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to destroyTransportLayer.\n" );
		return -1;
	}

	tlData = tlo->data;

	tptp_free( tlData );

	tlo->data = 0;
	tlo->objectID = -1;

	return 0;
}

tptp_int32 setProcessMessageFunc(tptp_object* tlo, tptp_object* nexto, processMessage_ptr_t func )
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to setProcessMessageFunc.\n" );
		return -1;
	}

	tlData = tlo->data;

	tlData->next.nexto = nexto;
	tlData->next.processMessage = func;

	return 0;
}

tptp_int32 startTransportListener( tptp_object* tlo )
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to startTransportListener.\n" );
		return -1;
	}

	tlData = tlo->data;

	if ( tlData->state == STARTED )
		printf( "Error: startTransportListener called when listen is already started." );

	/* Start a new thread to simulate activity */
#ifdef _WIN32
	tlData->hThread = CreateThread( NULL, 0, SimThreadW32, tlData, 0, &(tlData->threadID) );
#else
	pthread_create( &(tlData->threadID), NULL, SimThread, tlData );
#endif
	return 0;
}

tptp_int32 stopTransportListener( tptp_object* tlo ) 
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to stopTransportListener.\n" );
		return -1;
	}

	tlData = tlo->data;

	if ( tlData->state == STOPPED )
		printf( "Warning: startTransportListener called when listen is already stopped." );

	/* We're just a test program. We can be harsh. */
#ifdef _WIN32
	TerminateThread( tlData->hThread, 0 );
#else
	pthread_cancel( &(tlData->threadID) );
#endif

	tlData->state = STOPPED;

	return 0;
}

tptp_int32 terminateConnection(tptp_object* tlo, tptp_uint32 connectionID)
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to terminateConnection.\n" );
		return -1;
	}

	tlData = tlo->data;

	return 0;
}

tptp_int32 terminateDataConnection(tptp_object* tlo, tptp_uint32 connectionID)
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to terminateDataConnection.\n" );
		return -1;
	}

	tlData = tlo->data;

	return 0;
}

tptp_int32 sendMessage(tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 size, tptp_string* pCmdBlock)
{
	/* Since we're only a test stub, we can process this message here */
	int            ret;
	int            sourceID;
	int            context;
	char*          interfaceID = 0;
	char*          cmdName = 0;
	tptp_list_t*   paramList;
	state_data*    tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to sendMessage.\n" );
		return -1;
	}

	tlData = tlo->data;

	/* Parse the XML into useful blocks */
	ret = parseCommand( pCmdBlock, &sourceID, &context, &interfaceID, &cmdName, &paramList );

	if ( isEqualString(cmdName, "helloAgent") )
	{
		char customFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><helloResponse iid=\"org.eclipse.tptp.prototype.test\"><message>Hi!</message></helloResponse></Cmd>";
		char msg[256] = "";

		printf( "The client says hello.\n" );

		sprintf( msg, customFormat, connectionID, sourceID, context );

		ret = processMessage( tlData, msg );
	}
	else if ( isEqualString(cmdName, "establishDataPath") )
	{
		char bindDataConnectionsFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><bindDataConnections iid=\"org.eclipse.tptp.agentManager\"><dataConnection1>%ld</dataConnection1><dataConnection2>%ld</dataConnection2><flags>%ld</flags></bindDataConnections></Cmd>";
		char msg[256] = "";
		int partnerID;
		int dataConnID;
		int flagsIn, flagsOut;

		getIntegerParam( "dataConnectionID", paramList, &partnerID );
		getIntegerParam( "flags", paramList, &flagsIn );

		/* Prepare inverse flags */
		flagsOut = 0;
		if ( flagsIn & DATA_PATH_SEND )
			flagsOut |= DATA_PATH_RECEIVE;
		if ( flagsIn & DATA_PATH_RECEIVE )
			flagsOut |= DATA_PATH_SEND;

		(tlData->cm.addDataConnection)( tlData->cm.cmo, tlData->transportID, flagsOut, &dataConnID );

		/* Save what we know */
		tlData->lastDataConnection++;
		tlData->dataConn[tlData->lastDataConnection].connectionID = dataConnID;
		tlData->dataConn[tlData->lastDataConnection].partnerID    = partnerID;
		tlData->dataConn[tlData->lastDataConnection].flags        = flagsOut;
		tlData->dataConn[tlData->lastDataConnection].sourceID     = sourceID;
		tlData->dataConn[tlData->lastDataConnection].context      = context;

		sprintf( msg, bindDataConnectionsFormat, connectionID, AGENT_MANAGER, BIND_DATA_CONTEXT, dataConnID, partnerID, flagsOut );
		ret = processMessage( tlData, msg );
	}
	else if ( isEqualString(cmdName, "dataConnectionsBound") )
	{
		dataConnectionInfo* dci;
		char dataPathEstablishedFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><dataPathEstablished iid=\"org.eclipse.tptp.dataProvider\"></dataPathEstablished></Cmd>";
		char msg[256] = "";

		char  dataStr[] = "Hello client.";

		printf( "Data path established.\n" );

		/* Note that the use of lastDataConnection here relies on */
		/*    a very simple, sequential use of data connections   */
		dci = &(tlData->dataConn[tlData->lastDataConnection]);

		/* Send the response */
		sprintf( msg, dataPathEstablishedFormat, connectionID, dci->sourceID, dci->context );
		ret = processMessage( tlData, msg );

		if ( dci->flags & DATA_PATH_SEND )
		{
			if ( dci->sendData == 0 )
			{
				printf( "Unexpected response to bindDataConnecitons: No data function set.\n" );
				return -1;
			}

			(dci->sendData)( tlData->dataConn[tlData->lastDataConnection].partner,
				             tlData->dataConn[tlData->lastDataConnection].partnerID, 
							 strlen(dataStr), (void*)dataStr );
		}
		else
		{
			if ( dci->sendData != 0 )
			{
				printf( "Unexpected response to bindDataConnecitons: Data function set for receive-only connection.\n" );
				return -1;
			}
		}
	}
	else if ( isEqualString(cmdName, "dataConnectionsBindingFailed") )
	{
		char dataPathUnavailableFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><dataPathEstablished iid=\"org.eclipse.tptp.dataProvider\"><reason>-1</reason></dataPathEstablished></Cmd>";
		char msg[256] = "";
		dataConnectionInfo* dci;

		printf( "Data path could not be established.\n" );

		/* Note that the use of lastDataConnection here relies on */
		/*    a very simple, sequential use of data connections   */
		dci = &(tlData->dataConn[tlData->lastDataConnection]);
		sprintf( msg, dataPathUnavailableFormat, connectionID, dci->sourceID, dci->context );
		ret = processMessage( tlData, msg );
	}
	else if ( isEqualString(cmdName, "startData") )
	{
		TID                 threadID;
		unsigned int        partnerID = 0;
		dataConnectionInfo* conn = 0;

		getIntegerParam( "targetConn", paramList, &partnerID );

		if ( tlData->dataConn[tlData->lastDataConnection].partnerID == partnerID )
		{
			conn = &(tlData->dataConn[tlData->lastDataConnection]);
		}
		else
		{
			int idx;
			for ( idx = 0; idx < MAX_DATA_CONNECTIONS; idx++ )
			{
				if ( tlData->dataConn[idx].partnerID == partnerID )
				{
					conn = &(tlData->dataConn[idx]);
					break;
				}
			}

			if ( conn == 0 )
			{
				printf( "Data connection not found" );
				return -1;
			}
		}


#ifdef _WIN32
		/* Start a new thread to simulate data */
		CreateThread( NULL, 0, SimDataW32, (LPVOID)conn, 0, &threadID );
#else
		pthread_create( &threadID, NULL, SimData, (void*)conn );
#endif

	}

	// Free space allocated by parseCommand()
	tptp_free(interfaceName);
	tptp_free(cmdName);
	tptp_list_clear(paramList);
	tptp_free(paramList);


	return 0;
}

tptp_int32 sendData(tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 size, void* pDataBlock)
{
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to sendData.\n" );
		return -1;
	}

	tlData = tlo->data;

	printf( "Data received from the client: %s\n", (char *)pDataBlock );
    return 0;
}

tptp_int32 setIncomingDataFunc( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 partnerID, tptp_object* partner, sendData_ptr_t newDataFunc )
{
	int i;
	state_data* tlData;

	if ( (0 == tlo) || (0 == tlo->data) )
	{
		printf( "Bad pointer passed to setIncomingDataFunc.\n" );
		return -1;
	}

	tlData = tlo->data;


	for ( i = 0; i < MAX_DATA_CONNECTIONS; i++ )
	{
		if ( tlData->dataConn[i].connectionID == connectionID )
		{
			tlData->dataConn[i].partner   = partner;
			tlData->dataConn[i].partnerID = partnerID;
			tlData->dataConn[i].sendData  = newDataFunc;
			return 0;
		}
	}

	printf( "Unexpected data connection ID on setIncomingDataFunc.\n" );

    return -1;
}

#ifdef _WIN32
DWORD WINAPI SimThreadW32( LPVOID param )
{
	SimThread( param );
	return 0;
}
#endif

void* SimThread( void* param )
{
	int         ret;
	int         connectionID;
	char        registerAgentFormat[] = "<Cmd src=\"%d\" dest=\"%d\" ctxt=\"%d\"><registerAgent iid=\"org.eclipse.tptp.agentManager\"><agentID>%d</agentID><processID>%lu</processID><agentName>org.eclipse.tptp.prototype.test</agentName></registerAgent></Cmd>";
	char        msg[1024] = "";
	state_data* tlData = (state_data*)param;

	SLEEP( 50 );

	/* simulate a new connection */
	ret = (tlData->cm.addConnection)( tlData->cm.cmo, tlData->transportID, &connectionID );

	/* Send the gtAgent command */
#ifdef _WIN32
	sprintf( msg, registerAgentFormat, connectionID, AGENT_MANAGER, 100, connectionID, GetCurrentProcessId() );
#else
	sprintf( msg, registerAgentFormat, connectionID, AGENT_MANAGER, 100, connectionID, getpid() );
#endif
	ret = processMessage( tlData, msg );

	return 0;
}


#ifdef _WIN32
DWORD WINAPI SimDataW32( LPVOID param )
{
	SimData( param );
	return 0;
}
#endif

void* SimData( void* param )
{
	int idx = 0;

	dataConnectionInfo* conn = (dataConnectionInfo*)param;

	if ( conn == NULL )
	{
		printf( "Unexpected: No data connection on agent side!\n" );
		return 0;
	}

	if ( conn->sendData == 0 )
	{
		printf( "Unexpected: send function not set on agent side!\n" );
		return 0;
	}

	while ( idx < 50 )
	{
		char  data[64];
		int   dataLen;

		sprintf( data, "This is data block number %d from the agent.", idx++ );
		dataLen = strlen( data );

		(conn->sendData)( conn->partner, conn->partnerID, dataLen, (void*)data );

		SLEEP( 50 );
	}

	return 0;
}

int processMessage( state_data* tlData, char* msg )
{
	int msglen = strlen(msg);

	if ( 0 == tlData->next.processMessage )
	{
		printf( "Error: processMessage pointer need but not set.\n" );
		return -1;
	}

	return (tlData->next.processMessage)( tlData->next.nexto, msglen, msg );
}
