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


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

#include "TPTPCommon.h"
#include "SocketTLTest.h"
#include "ACCommon.h"
#include "TPTPUtils.h"
#include <stdio.h>
#include <string.h>

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

/* Someting hard-coded for testing purposes */
#define GET_AGENT_CONTEXT      101
#define HELLO_AGENT_CONTEXT    102
#define EST_DATA_PATH_CONTEXT1 103
#define EST_DATA_PATH_CONTEXT2 104
#define EST_DATA_PATH_CONTEXT3 105
#define START_DATA_CONTEXT     106
#define QUERY_AGENTS_CONTEXT   107

/* 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;
	HANDLE              dpeEvent;
#endif
	int                 agentID;
	listener_state      state;
	connection_manager  cm;
	pipeline_object     next;
} state_data;

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

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


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;

	/* 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 ( ret == 0 )
	{
		if ( context == QUERY_AGENTS_CONTEXT )
		{
//			char  getAgentByToeknFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><getAgentByToken iid=\"org.eclipse.tptp.agentManager\"><token>%ld</token></getAgentByToken></Cmd>";
//			int   token;

			char* agentInfo;

			getStringParam( "agentInfoList", paramList, &agentInfo );

//			sprintf( msg, getAgentByTokenFormat, connectionID, AGENT_MANAGER, GET_AGENT_CONTEXT2, token );
//			ret = processMessage( tlData, msg );
		}
		else if ( context == GET_AGENT_CONTEXT )
		{
			char  customFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><helloAgent iid=\"org.eclipse.tptp.prototype.test\"><message>Hello!</message></helloAgent></Cmd>";
			char  msg[256] = "";

			getIntegerParm( "agentID", paramList, &tlData->agentID );

			sprintf( msg, customFormat, connectionID, tlData->agentID, HELLO_AGENT_CONTEXT );

			ret = processMessage( tlData, msg );
		}
		else if ( context == HELLO_AGENT_CONTEXT )
		{
			int   dataConnID;
			char  estDataPathFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><establishDataPath iid=\"org.eclipse.tptp.dataProvider\"><dataConnectionID>%ld</dataConnectionID><flags>%ld</flags></establishDataPath></Cmd>";
			char  msg[256] = "";

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

			/* Start a data connection */
			ret = (tlData->cm.addDataConnection)( tlData->cm.cmo, tlData->transportID, DATA_PATH_SEND, &dataConnID );
			tlData->dataConn[0].connectionID = dataConnID;
			tlData->dataConn[0].flags        = DATA_PATH_SEND;

			/* Send the command */
			sprintf( msg, estDataPathFormat, connectionID, sourceID, EST_DATA_PATH_CONTEXT1, tlData->dataConn[0].connectionID, DATA_PATH_SEND );
			ret = processMessage( tlData, msg );

		}
		else if ( context == EST_DATA_PATH_CONTEXT1 )
		{
			int   dataConnID;
			char  estDataPathFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><establishDataPath iid=\"org.eclipse.tptp.dataProvider\"><dataConnectionID>%ld</dataConnectionID><flags>%ld</flags></establishDataPath></Cmd>";
			char  msg[256] = "";

			char  dataStr[] = "Hello agent.";

			printf( "Reponse received from establishDataPath(send)\n" );

			/* The data path has been established          */
			/* We should have a place to send our data now */
			if ( tlData->dataConn[0].sendData == 0 )
			{
				printf( "Unexpected response to establishDataPath: No data function set.\n" );
				return -1;
			}

			(tlData->dataConn[0].sendData)( tlData->dataConn[0].partner, tlData->dataConn[0].partnerID, strlen(dataStr), (void*)dataStr );

			/* Try this again with data going the other way */
			ret = (tlData->cm.addDataConnection)( tlData->cm.cmo, tlData->transportID, DATA_PATH_RECEIVE, &dataConnID );
			tlData->dataConn[1].connectionID = dataConnID;
			tlData->dataConn[1].flags        = DATA_PATH_RECEIVE;

			/* Send the command */
			sprintf( msg, estDataPathFormat, connectionID, sourceID, EST_DATA_PATH_CONTEXT2, tlData->dataConn[1].connectionID, DATA_PATH_RECEIVE );
			ret = processMessage( tlData, msg );
		}
		else if ( context == EST_DATA_PATH_CONTEXT2 )
		{
			printf( "Reponse received from establishDataPath(receive)\n" );

			if ( tlData->dataConn[1].sendData != 0 )
			{
				printf( "Unexpected response to establishDataPath: Data function set for receive-only connection.\n" );
				return -1;
			}
		}
		else if ( context == EST_DATA_PATH_CONTEXT3 )
		{
			printf( "Reponse received from establishDataPath(two-way)\n" );

			if ( tlData->dataConn[2].sendData == 0 )
			{
				printf( "Unexpected response to establishDataPath: Data function not set for two-way connection.\n" );
				return -1;
			}
#ifdef _WIN32
			SetEvent( tlData->dpeEvent );
#endif
		}
		else
		{
			printf( "Unexpected command received.\n" );
		}
	}

	// 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 agent: %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;
	TID         threadID;
	int         connectionID, connectionID2, connectionID3, connectionID4;
	int         dataConnID;
	char        queryRunningAgentsFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><queryRunningAgents iid=\"org.eclipse.tptp.agentManager\"></queryRunningAgents></Cmd>";
	char        getAgentFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><getAgent iid=\"org.eclipse.tptp.agentManager\"><agentName>org.eclipse.tptp.prototype.test</agentName></getAgent></Cmd>";
	char        startDataFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><startData iid=\"org.eclipse.tptp.prototype.test\"><targetConn>%ld</targetConn></startData></Cmd>";
	char        estDataPathFormat[] = "<Cmd src=\"%ld\" dest=\"%ld\" ctxt=\"%ld\"><establishDataPath iid=\"org.eclipse.tptp.dataProvider\"><dataConnectionID>%ld</dataConnectionID><flags>%ld</flags></establishDataPath></Cmd>";
	char        msg[256] = "";
	state_data* tlData = (state_data*)param;

	/* simulate two new connections */
	ret = (tlData->cm.addConnection)( tlData->cm.cmo, tlData->transportID, &connectionID );
	ret = (tlData->cm.addConnection)( tlData->cm.cmo, tlData->transportID, &connectionID2 );
	ret = (tlData->cm.addConnection)( tlData->cm.cmo, tlData->transportID, &connectionID3 );

	/* Wait... */
	SLEEP( 1000 );

	ret = (tlData->cm.addConnection)( tlData->cm.cmo, tlData->transportID, &connectionID4 );

	/* Remove the extra connections */
	ret = (tlData->cm.removeConnection)( tlData->cm.cmo, connectionID );
	ret = (tlData->cm.removeConnection)( tlData->cm.cmo, connectionID3 );
	ret = (tlData->cm.removeConnection)( tlData->cm.cmo, connectionID4 );

	/* Send the queryRunningAgents command */
	sprintf( msg, queryRunningAgentsFormat, connectionID2, AGENT_MANAGER, QUERY_AGENTS_CONTEXT );
	ret = processMessage( tlData, msg );

	/* Send the getAgent command */
	sprintf( msg, getAgentFormat, connectionID2, AGENT_MANAGER, GET_AGENT_CONTEXT );
	ret = processMessage( tlData, msg );

	/* Start a two-way data connection */
	ret = (tlData->cm.addDataConnection)( tlData->cm.cmo, tlData->transportID, DATA_PATH_TWOWAY, &dataConnID );
	tlData->dataConn[2].connectionID = dataConnID;
	tlData->dataConn[2].flags        = DATA_PATH_TWOWAY;

#ifdef _WIN32
	tlData->dpeEvent = CreateEvent( NULL, TRUE, FALSE, "DataPathEstablished" );
#endif

	/* Send the getAgent command */
	sprintf( msg, estDataPathFormat, connectionID2, tlData->agentID, EST_DATA_PATH_CONTEXT3, tlData->dataConn[2].connectionID, DATA_PATH_TWOWAY );
	ret = processMessage( tlData, msg );

#ifdef _WIN32
	/* Given the way the test is coded, this won't actually wait */
	/* But if there were something out-of-proc on the other end it would */
	WaitForSingleObject( tlData->dpeEvent, 5000 );
#endif

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

	/* Ask the agent to start pumping data too */
	sprintf( msg, startDataFormat, connectionID, tlData->agentID, START_DATA_CONTEXT, dataConnID );
	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;
	state_data* tlData = (state_data*)param;

	if ( tlData->dataConn[2].sendData == 0 )
	{
		printf( "Unexpected: send function not set on client side!\n" );
		return 0;
	}

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

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

		(tlData->dataConn[2].sendData)( tlData->dataConn[2].partner, tlData->dataConn[2].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 );
}
