/*******************************************************************************
 * Copyright (c) 2005, 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 API and Implementation
 *
 * $Id: BaseTL.c,v 1.15 2008/04/29 19:19:26 aalexeev Exp $
 *
 *******************************************************************************/ 

#include "tptp/TransportSupport.h"
#include "tptp/TPTPBaseTL.h"
#include "tptp/BaseTLLog.h"

/**
 *********************************************************
 *
 * @brief
 *    base implementation to instantiate a transport layer object
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 baseTL_createTransportListener( tptp_object* cmo, transport_layer_data_t * pTransportData, tptp_object* tlo, tptp_string* displayName )
{
	tl_state_data_t* stateData = NULL;

	/* Check for the logging function, our subsequent error handling needs it */
	if ( pTransportData->logEventEntry == NULL )
	{
		return TPTP_ERROR_MISSING_INTERFACE_FUNCTIONS;
	}

	/* Check the incoming parameters */

	if ( ( pTransportData->addConnectionEntry        == NULL ) ||
	     ( pTransportData->removeConnectionEntry     == NULL ) ||
	     ( pTransportData->addDataConnectionEntry    == NULL ) ||
	     ( pTransportData->removeDataConnectionEntry == NULL ) )
	{


		/* In this case we can't use our standard macro because it 
		     depends on data we haven't allocated yet            */
		pTransportData->logEventEntry( cmo, displayName, pTransportData->transportID, __FILE__, __LINE__, TPTP_CRITICAL, "Required functions missing in call to initialize transport layer." );
		return TPTP_ERROR_MISSING_INTERFACE_FUNCTIONS;
	}

	/* Allocate our state data */
	tlo->data = tptp_malloc( sizeof(tl_state_data_t) );
	if ( tlo->data == NULL )
	{
		/* In this case we can't use our standard macro because it 
		     depends on the data we just failed to allocate            */
		pTransportData->logEventEntry( cmo, displayName, pTransportData->transportID, __FILE__, __LINE__, TPTP_CRITICAL, "Unable to allocate state memory." );
		return TPTP_ERROR_OUT_OF_MEMORY;
	}
	else
	{
		stateData = (tl_state_data_t*) tlo->data;
	}

	/* Store our transport ID */
	stateData->transportID = pTransportData->transportID;

	/* Store the Agent Controller entry points */
	stateData->ac.cmo                     = cmo;
	stateData->ac.addConnection           = pTransportData->addConnectionEntry;
	stateData->ac.removeConnection        = pTransportData->removeConnectionEntry;
	stateData->ac.addDataConnection       = pTransportData->addDataConnectionEntry;
	stateData->ac.addDirectDataConnection = pTransportData->addDirectDataConnectionEntry;
	stateData->ac.removeDataConnection    = pTransportData->removeDataConnectionEntry;
	stateData->ac.logEvent                = pTransportData->logEventEntry;

	/* Store the display name for logging */
	stateData->displayName = tptp_malloc( strlen(displayName) + 1 );
	if ( stateData->displayName )
	{
		strcpy( stateData->displayName, displayName );
	}

	/* Initialize our thread state */
	stateData->processingThreadState = TPTP_TL_THREAD_NOT_STARTED;

	/* Allocate connection tables */
	stateData->controlConnectionTable = tableCreate();
	stateData->dataConnectionTable = tableCreate();

	tptp_initializeLock( &stateData->dataConnectionLock );

	/* Allocate context data table */
	stateData->contextDataTable = tableCreate();


	/* Initialize the flag to recognize our component */
	tlo->objectID = TPTP_BASETL_MAKE_OBJECT_ID(tlo);

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    base implementation to destroy a transport layer object
 *
 * @return
 *    0 - Success
 *    nonzero - Error.
 *********************************************************/
tptp_int32 baseTL_destroyTransportListener( tptp_object* tlo )
{
	tl_state_data_t* stateData;

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

	/* Stop our processing thread */
	stateData->processingThreadState = TPTP_TL_THREAD_STOPPED;

	/* TODO: Wait for the thread to stop */

	tptp_deleteLock( &stateData->dataConnectionLock );

	/* Free connection tables */
	tableDelete( stateData->controlConnectionTable );
	tableDelete( stateData->dataConnectionTable );

	/* Free context data table */
	tableDelete( stateData->contextDataTable );

	/* Free our state data */
	tptp_free( stateData );

	/* Clear out the object data */
	tlo->data = 0;
	tlo->objectID = -1;

	return 0;
}


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

tptp_int32 baseTL_startTransportListener(tptp_object* tlo, RUN_FUNC_TYPE processingFunc)
{
	tl_state_data_t* stateData;
	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 */

	/* Start the server thread for this TL */
	tptpStartThread( processingFunc, (void*)stateData, &threadID, &threadHandle);
	CLOSE_THREAD_HANDLE(threadHandle);

	return 0;
}

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

tptp_int32 baseTL_stopTransportListener(tptp_object* tlo)
{
	tl_state_data_t* stateData;

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

	/* Stop our processing thread */
	stateData->processingThreadState = TPTP_TL_THREAD_STOPPED;

	return 0;
}


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

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

	stateData->controlPartner.nexto = nexto;
	stateData->controlPartner.processMessage = func;

	return 0;
}


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

tptp_int32 baseTL_setIncomingDataFunc( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 partnerID, tptp_object* partner, sendData_ptr_t newDataFunc )
{
	tl_state_data_t*        stateData;
	tl_data_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 */

	connectionEntry = (tl_data_connection_t*)tableGet( stateData->dataConnectionTable, connectionID );
	if ( connectionEntry == NULL )
	{
		return TPTP_ERROR_CONNECTION_NOT_FOUND;
	}

	connectionEntry->dataPartner.nexto = partner;
	connectionEntry->dataPartner.connectionID = partnerID;
	connectionEntry->dataPartner.sendData = newDataFunc;
	connectionEntry->dataPartner.descriptor = 0;

	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    base implementation to set up a direct data connection between source and destination
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_setDataDescriptor( tptp_object* tlo, tptp_uint32 connectionID, tptp_uint32 partnerID, tptp_object* partner, tptp_uint32 descriptor )
{
	tl_state_data_t*        stateData;
	tl_data_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 */

	connectionEntry = (tl_data_connection_t*)tableGet( stateData->dataConnectionTable, connectionID );
	if ( connectionEntry == NULL )
	{
		return TPTP_ERROR_CONNECTION_NOT_FOUND;
	}

	connectionEntry->dataPartner.nexto = partner;
	connectionEntry->dataPartner.connectionID = partnerID;
	connectionEntry->dataPartner.sendData = NULL;
	connectionEntry->dataPartner.descriptor = descriptor;

	return 0;
}



/**
 *********************************************************
 *
 * @brief
 *    helper method to organize control connections
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_addControlConnectionEntry(tl_state_data_t* stateData, tptp_uint32 connectionID, void* implData)
{
	tl_control_connection_t*  connectionEntry;

	/* Allocate a connection entry block */
	connectionEntry = tptp_malloc( sizeof(tl_data_connection_t) );
	if ( connectionEntry == NULL )
	{
		return TPTP_ERROR_OUT_OF_MEMORY;
	}

	/* Initialize the connection entry */
	connectionEntry->connectionID = connectionID;
	connectionEntry->implData = implData;

	/* Save the connection in our table */
	tablePut(stateData->controlConnectionTable, connectionID, (Entry_value_ptr_t)connectionEntry);

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    helper method to organize data connections
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_addDataConnectionEntry(tl_state_data_t* stateData, tptp_uint32 connectionID, void* implData)
{
	tl_data_connection_t*  connectionEntry;

	/* Allocate a connection entry block */
	connectionEntry = tptp_malloc( sizeof(tl_data_connection_t) );
	if ( connectionEntry == NULL )
	{
		return TPTP_ERROR_OUT_OF_MEMORY;
	}

	/* Initialize the connection entry */
	connectionEntry->connectionID = connectionID;
	connectionEntry->implData = implData;

	/* Save the connection in our table */
	tablePut(stateData->dataConnectionTable, connectionID, (Entry_value_ptr_t)connectionEntry);

	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    helper method to organize data connections
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_removeControlConnectionEntry(tl_state_data_t* stateData, tptp_uint32 connectionID)
{
	tableRemove( stateData->controlConnectionTable, connectionID );
	return 0;
}


/**
 *********************************************************
 *
 * @brief
 *    helper method to organize data connections
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_removeDataConnectionEntry(tl_state_data_t* stateData, tptp_uint32 connectionID)
{
	tableRemove( stateData->dataConnectionTable, connectionID );
	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    helper method to manage context mapped responses
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_storeContextData(tl_state_data_t* stateData, tptp_int32 contextID, void* contextData)
{
	tablePut(stateData->contextDataTable, contextID, (Entry_value_ptr_t)contextData);
	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    helper method to manage context mapped responses
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_getContextData(tl_state_data_t* stateData, tptp_int32 contextID, void** contextData)
{
	*contextData = tableGet( stateData->contextDataTable, contextID);
	if ( *contextData == NULL )
		return -1;
	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    helper method to manage context mapped responses
 *
 * @return
 *    0 - Success
 *    negative - Not supported
 *********************************************************/

tptp_int32 baseTL_releaseContextData(tl_state_data_t* stateData, tptp_int32 contextID)
{
	tableRemove( stateData->contextDataTable, contextID );
	return 0;
}


/*******************************************************************************/ 

THREAD_USER_FUNC_RET_TYPE commandTimeoutThread(LPVOID args)
{
	generic_wait_context_t *contextData = (generic_wait_context_t *)args;

	if ( contextData == NULL )
	{
		return -1;
	}

	SLEEP(contextData->timeout);

	if ( contextData->status == BASETL_STATUS_PENDING )
	{
		contextData->status = BASETL_STATUS_TIMEOUT;
		tptp_postSemaphore( &contextData->completionSemaphore );
	}
	else
	{
		/* If the command has already been handled, we need to clean up here */
		tptp_deleteSemaphore( &contextData->completionSemaphore );
		//We can't call tptp_free on this context... it corrupts the table.
		//We need to investigate how to delete this so race conditions do not occur.
		//tptp_free( contextData );
	}

	return 0;
}

/**
 *********************************************************
 *
 * @brief
 *    helper method to setup timeout for command responses
 *
 * @return
 *    context ID
 *********************************************************/

void baseTL_startTimeoutThread( generic_wait_context_t *contextData )
{
	TID            threadID;
	HANDLE         threadHandle;

	tptpStartThread( commandTimeoutThread, (void*)contextData, &threadID, &threadHandle);
	CLOSE_THREAD_HANDLE(threadHandle);
}

/**
 *********************************************************
 *
 * @brief
 *    helper method to manage context IDs
 *
 * @return
 *    context ID
 *********************************************************/

static tptp_uint32  _nextContextID = 1000;

tptp_uint32 baseTL_getNextContextID()
{
	return _nextContextID++;
}

