/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <api_oam_sdk/continuity_check/src/cc_types.h>
#include <api_oam_sdk/fault_notification_generator/src/fng_types.h>
#include <api_oam_sdk/notifications/inc/egl_notifications.h>
#include <api_oam_sdk/rmep_db/src/rmep_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include "string.h"
#include <stdlib.h>

#include "egl_assert.h"
#include "egl_log.h"
#include "osal.h"

#ifndef FREERTOS
#include <unistd.h>
#endif

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

typedef void (* fngTimeoutHandler_t)(void *arg, uint32_t lMepNum);

struct fngTimeo_s
{
  struct fngTimeo_s		*next;
  struct timespec		time;
  fngTimeoutHandler_t	h;
  void 					*arg;
  uint32_t				hwNum;
};

struct fngTimerMem_s
{
	bool				inUse;
	struct fngTimeo_s	timer;
};

static struct fngTimerMem_s	fngTimerMem[FNG_MAX_TIMERS];
static fngEntry_t			fngArray[MAX_NUM_MEPS];
static uint16_t				log_handle				= 0;		// handle used to log messages for this module
osMutex_t			fngListLock				= NULL;
static osQueueHandle_t		fngMsgQ 				= NULL;
static bool					fngInitialised			= false;
static struct fngTimeo_s	*nextTimeOut			= NULL;

#if 1
#define LOCK_FNG_LIST(timeout)	osRecursiveMutexTake(fngListLock,timeout)
#define UNLOCK_FNG_LIST()		osRecursiveMutexGive(fngListLock)
#else
#define LOCK_FNG_LIST(timeout)	//lockMDBranch(0,timeout)	//osRecursiveMutexTake(fngListLock,timeout)
#define UNLOCK_FNG_LIST()		//unlockMDBranch(0)		//osRecursiveMutexGive(fngListLock)
#endif

void usedToVerifyStructSizes(void)
{

    /* check that you didn't screw up mystruct */
    BUILD_BUG_ON(sizeof(struct eventTLVsize_s) % 4);
    BUILD_BUG_ON(sizeof(fngConnInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngXConInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngRMepErrInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngSaChngInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngRdiChngInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngTlvChngInfo_t) % 4);
    BUILD_BUG_ON(sizeof(fngPktProbeInfo_t) % 4);

}

/*
 * This function is used to change the lowest level severity to log for
 * this module. Values of eLOG_LEVEL_DEBUG (lowest) to eLOG_LEVEL_ERROR
 * can be selected. Levels with greater severity than eLOG_LEVEL_ERROR
 * will default to eLOG_LEVEL_ERROR since severity of eLOG_LEVEL_ERROR
 * and higher are non maskable.
 */
void setFNGLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

/*
 * @brief	This sets the mepEntry values to their default
 *
 * In most cases this should only be done with mepLock enabled
 * to ensure thread safe operation
 */
static void fngSetDefaults(lMepNum_t lMepNum)
{
	memset(&fngArray[lMepNum],0,sizeof(fngEntry_t));
	fngArray[lMepNum].mepFngInfo.fngState = fngReset;
	fngArray[lMepNum].mepFngInfo.lowPrDef = macRemErrXcon;
	fngArray[lMepNum].mepFngInfo.fngAlarmTime = 250;
	fngArray[lMepNum].mepFngInfo.fngResetTime = 1000;
}

/*
 * @brief Clears the Mep list
 *
 * This should be called to initialise the MA list.
 * Any MAs on the list will be lost.
 */
static void clearFngList(void)
{
	lMepNum_t	k;

	// Initialise all entries in the MA array to defaults
	for (k = 0; k < MAX_NUM_MEPS; k++)
	{
		fngSetDefaults(k);
	}
	DO_LOG_DEBUG(log_handle,"Initialised the FNG List");
}

static void initTimer(void)
{
	int k;

	for (k = 0; k < FNG_MAX_TIMERS; k++)
	{
		fngTimerMem[k].inUse = false;
	}
}

static struct fngTimeo_s* getFreeTimer(void)
{
	int k;

	for (k = 0; k < FNG_MAX_TIMERS; k++)
	{
		if (fngTimerMem[k].inUse == false)
		{
			fngTimerMem[k].inUse = true;
			return (&fngTimerMem[k].timer);
		}
	}
	return NULL;
}

static void freeTimer(struct fngTimeo_s* timer)
{
	int k;

	for (k = 0; k < FNG_MAX_TIMERS; k++)
	{
		if (&fngTimerMem[k].timer == timer)
		{
			fngTimerMem[k].inUse = false;
			return;
		}
	}
	EGL_ASSERT(0,1);			// We shouldn't get to here!
}

static void fngAddTimeOut(uint32_t msecs, fngTimeoutHandler_t handler, void *arg , uint32_t hwNum)
{
	struct fngTimeo_s *timeout, *t;

	timeout = getFreeTimer();
	if (timeout == NULL)
	{
	    EGL_ASSERT(timeout != NULL,1);
		return;
	}
	timeout->next = NULL;
	timeout->h = handler;
	timeout->arg = arg;
	clock_gettime_plus_ms_delay(&timeout->time,msecs);
	timeout->hwNum = hwNum;

	// Check are there other pending timeouts...
	if (nextTimeOut == NULL)
	{
		// if not add this to the head of the list
		nextTimeOut = timeout;
		DO_LOG_DEBUG(log_handle,"Adding a new timer (%lums) (t=%lu.%lu) ",msecs,timeout->time.tv_sec,timeout->time.tv_nsec);
		return;
	}
	// There are timeouts pending
	// does the fist on the list expire after this new one?
	if (timeAisAfterTimeB(&nextTimeOut->time, &timeout->time))
	{
		// yes, so add this new timeout to the start of the list
		timeout->next = nextTimeOut;
		nextTimeOut = timeout;
		DO_LOG_DEBUG(log_handle,"Adding timer @sol (%lums) (t=%lu.%lu) ",msecs,timeout->time.tv_sec,timeout->time.tv_nsec);
	}
	else
	{
		// Search the list to find where the new timeout fits...
		for(t = nextTimeOut; t != NULL; t = t->next)
		{
			if (t->next == NULL || timeAisAfterTimeB(&t->next->time, &timeout->time))
			{
				timeout->next = t->next;
				t->next = timeout;
				DO_LOG_DEBUG(log_handle,"Adding timer to list (%lums) (t=%lu.%lu) ",msecs,timeout->time.tv_sec,timeout->time.tv_nsec);
				break;
			}
		}
	}
}

/**
 * Go through timeout list (for this task only) and remove the first matching
 * entry, even though the timeout has not triggered yet.
 *
 * @note This function only works as expected if there is only one timeout
 * calling 'handler' in the list of timeouts.
 *
 * @param handler callback function that would be called by the timeout
 * @param arg callback argument that would be passed to handler
*/
static void fngCancelTimeOut(fngTimeoutHandler_t handler, void *arg, uint32_t hwNum)
{
	struct fngTimeo_s *prev_t, *t;

	if (nextTimeOut == NULL)
	{
		return;
	}

	for (t = nextTimeOut, prev_t = NULL; t != NULL; prev_t = t, t = t->next)
	{
		if ((t->h == handler) && (t->hwNum == hwNum)) //&& (t->arg == arg)
		{
			// We have a match
			if (t == nextTimeOut)
			{
				// The timer being cancelled it already running so all we do is set the event handler to NULL and release the args
				ts_free(t->arg);
				t->h = NULL;
				DO_LOG_DEBUG(log_handle,"Cancelling active timer");
			}
			else
			{
				// Unlink from previous in list
				if (prev_t == NULL)
				{
					nextTimeOut = t->next;
				}
				else
				{
					prev_t->next = t->next;
				}
				ts_free(t->arg);
				t->h = NULL;
				freeTimer(t);
				DO_LOG_DEBUG(log_handle,"Cancelling scheduled timer");
			}
			return;
		}
	}
	return;
}

static void xmitFaultAlarm(struct mibRMepIndices_s *mepIndices ,fngDefect_t fngDefect, lMepNum_t lMepNum)
{
	sendCfmFaultNotification(lMepNum, mepIndices->mdIndex,mepIndices->maIndex,mepIndices->lMepId,mepIndices->rMepId, log_handle, fngDefect);
	DO_LOG_ERROR(log_handle,"FNG Transmit Alarm: MEP Index %lu.%lu.%d.%d (lMepNum=%d) has the %s(%d) defect",
			mepIndices->mdIndex,mepIndices->maIndex,mepIndices->lMepId,mepIndices->rMepId, lMepNum, fngDefectToStr[fngDefect],fngDefect);
#if 0  // TODO: enable code when
		retVal = ssSendPushMsg(eventBuf,dataSize);
		if (retVal == -1)
			ts_printf("Push Msg Queue full\n");
#endif
}

static void fngReportDefectAlarm(void *arg, lMepNum_t lMepNum)
{
	struct mibRMepIndices_s *mepIndices = (struct mibRMepIndices_s *)arg;

	fngArray[lMepNum].mepFngInfo.fngState	= fngReportDefect;
	fngArray[lMepNum].fngPriority			= fngArray[lMepNum].mepFngInfo.hiPrDefect;
	fngArray[lMepNum].fngDefect				= fngArray[lMepNum].mepFngInfo.hiPrDefect;

	xmitFaultAlarm(mepIndices, fngArray[lMepNum].fngDefect, lMepNum);

	_setMepLastDefectSentStatus(lMepNum, fngArray[lMepNum].fngDefect);

	fngArray[lMepNum].mepFngInfo.fngState = fngDefectReported;
}

static void fngDefectAlarmTimeoCB(void *arg, uint32_t lMepNum)
{
	struct timespec tp;

	#ifdef FREERTOS
	    //VR missing function
		//getTimeOfDay (tp);
	#else
		clock_gettime(CLOCK_REALTIME,&tp);
	#endif
	DO_LOG_DEBUG(log_handle,"Alarm Timeout Callback (t=%lu.%lu)",tp.tv_sec,tp.tv_nsec);
	fngReportDefectAlarm(arg, (lMepNum_t)lMepNum);
	ts_free(arg);
}

static void fngDefectResetTimeoCB(void *arg, uint32_t lMepNum)
{
	struct timespec tp;

	#ifdef FREERTOS
	    //VR missing function
		//getTimeOfDay (tp);
	#else
		clock_gettime(CLOCK_REALTIME,&tp);
	#endif
	DO_LOG_DEBUG(log_handle,"Defect Reset Timeout Callback (t=%lu.%lu)",tp.tv_sec,tp.tv_nsec);
	fngArray[(lMepNum_t)lMepNum].mepFngInfo.fngState = fngReset;
}

/*
 *
 */
static void fngStateMachine(void* arg, lMepNum_t lMepNum, rMepNum_t rMepNum)
{
	bool					presentRDI;
	bool					maDefectIndication;
	fngDefect_t 			highestDefect;
	fngDefect_t				priMask;
	struct mibRMepIndices_s *rMepIndices;
	rMepState_t				rMepState  = rMepIdle;
	bool					rdi;

	// First assess if the RDI bit in the transmit CCM packet needs to be updated
	// The RDI is to be set if any of these defects are present
	presentRDI = (fngArray[lMepNum].mepFngInfo.defects & (DEF_XCON_CCM_MASK | DEF_ERROR_CCM_MASK | SOME_RMEP_CCM_DEFECT_MASK | SOME_MAC_STATUS_DEFECT_MASK)) ? true : false;
	// Update the RDI bit appropriately
	writeRdiInTxTempl(lMepNum, presentRDI, CCM_TEMPL);

	// Keep this status bit up-to-date
	_setMepRdiTransmitStatus(lMepNum,presentRDI);

	// Next determine the new value for maDefectIndication
	switch(fngArray[lMepNum].mepFngInfo.lowPrDef)
	{
		case allDef:
		{
			maDefectIndication = fngArray[lMepNum].mepFngInfo.defects ? true : false;
			break;
		}
		case macRemErrXcon:						// (default value)
		{
			maDefectIndication = presentRDI;
			break;
		}
		case remErrXcon:
		{
			maDefectIndication = (fngArray[lMepNum].mepFngInfo.defects & (DEF_XCON_CCM_MASK | DEF_ERROR_CCM_MASK | SOME_RMEP_CCM_DEFECT_MASK)) ? true : false;
			break;
		}
		case errXcon:
		{
			maDefectIndication = (fngArray[lMepNum].mepFngInfo.defects & (DEF_XCON_CCM_MASK | DEF_ERROR_CCM_MASK )) ? true : false;
			break;
		}
		case xcon:
		{
			maDefectIndication = (fngArray[lMepNum].mepFngInfo.defects & (DEF_XCON_CCM_MASK)) ? true : false;
			break;
		}
		case noXcon:
		{
			maDefectIndication = false;
			break;
		}
	}

	// Determine the highest priority defect that is present
	if (maDefectIndication)
	{
		highestDefect = xcon;
		priMask = DEF_XCON_CCM_MASK;
		// Scan the currently set defects from highest to lowest
		// and stop when we find the highest one set
		while ((priMask != 0) && ((fngArray[lMepNum].mepFngInfo.defects & priMask) == 0))
		{
			priMask = priMask >> 1;
			highestDefect--;
		}
		fngArray[lMepNum].mepFngInfo.hiPrDefect = highestDefect;
	}
	else
	{
		fngArray[lMepNum].mepFngInfo.hiPrDefect = defNone;
	}

	DO_LOG_DEBUG(log_handle,"FNG old state = %d, lMepNum = %d",fngArray[lMepNum].mepFngInfo.fngState,lMepNum);
	// The FNG State Machine...
	switch(fngArray[lMepNum].mepFngInfo.fngState)
	{
		case fngReset:
		{
			if(maDefectIndication)
			{
				// Change to the Defect state
				fngArray[lMepNum].mepFngInfo.fngState = fngDefect;

				// The MEP indices must be preserved before passing them to the timer Callback
				rMepIndices = ts_malloc(sizeof (struct mibRMepIndices_s));
				memcpy(rMepIndices,arg,sizeof (struct mibRMepIndices_s));

				// Start the alarm Timeout
				fngAddTimeOut(fngArray[lMepNum].mepFngInfo.fngAlarmTime * 10,fngDefectAlarmTimeoCB,rMepIndices,(uint32_t)lMepNum);
			}
			break;
		}
		case fngDefect:
		{
			if(!maDefectIndication)
			{
				// All defects have been cleared so change to the Reset state
				fngArray[lMepNum].mepFngInfo.fngState = fngReset;
				fngCancelTimeOut(fngDefectAlarmTimeoCB, NULL, (uint32_t)lMepNum);
			}
			break;
		}
		case fngReportDefect:
		{
			// The action for this state is executed by the fngDefectAlarmTimeoCB function
			break;
		}
		case fngDefectReported:
		{
			if(maDefectIndication)
			{
				if (fngArray[lMepNum].mepFngInfo.hiPrDefect > fngArray[lMepNum].fngPriority)
				{
					fngReportDefectAlarm(arg, lMepNum);
				}
			}
			else
			{
				// The defects have been cleared so change to the DefectClearing state
				fngArray[lMepNum].mepFngInfo.fngState = fngDefectClearing;
				// Start the reset Timeout
				fngAddTimeOut(fngArray[lMepNum].mepFngInfo.fngResetTime * 10,fngDefectResetTimeoCB,NULL,(uint32_t)lMepNum);
			}
			break;
		}
		case fngDefectClearing:
		{
			if(maDefectIndication)
			{
				// A defect has re-appeared before the reset timeout so change to the Defect Reported state
				fngArray[lMepNum].mepFngInfo.fngState = fngDefectReported;
				fngCancelTimeOut(fngDefectResetTimeoCB, NULL, (uint32_t)lMepNum);
			}
			break;
		}
	}
	DO_LOG_DEBUG(log_handle,"FNG new state = %d, lMepNum = %d",fngArray[lMepNum].mepFngInfo.fngState,lMepNum);

	if (fngArray[lMepNum].mepFngInfo.defects ^ fngArray[lMepNum]._previousDefects)
	{
		rMepIndices = (struct mibRMepIndices_s *)arg;
		if (rMepIndices->rMepId != 0 && rMepNum > -1)
		{
			rMepState = readRMepState(rMepNum, &rdi);
		}
		// If any defect has changed the a MEP Defect Notification should be sent
		sendMepDefectNotification(lMepNum, rMepIndices->mdIndex, rMepIndices->maIndex, rMepIndices->lMepId, rMepIndices->rMepId, log_handle,
			fngArray[lMepNum].mepFngInfo.defects, _getMepLastDefectSentStatus(lMepNum), rMepState);
		// Save the current defects value so a delta can be done next time to detect any change
		fngArray[lMepNum]._previousDefects = fngArray[lMepNum].mepFngInfo.defects;
	}


}
//VR Needs RPC support
/*
static void handleSaEvent(struct eventTLVextract_s	*eventTlv)
{
	fngSaChngInfo_t	*saChngInfo;

	// point to the SA Change data struct
	saChngInfo = (fngSaChngInfo_t*)&eventTlv->data;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_rMepNumIsValid(saChngInfo->rMepNum))
	{
		return;
	}

	// TODO: May need to take further action, such as update Service Templates? Usually latest RMEP MAC is read from Hardware before use.
	_rMepSetMacValid(saChngInfo->rMepNum,true);

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: saChngInfo->rMepNum is changed by this function. Do NOT use after this function!
	_rMepGetParentsIndices(saChngInfo->rMepNum, &saChngInfo->rMepIndices);

	DO_LOG_INFO(log_handle,"Detected SA Change on remote MEP with indices: %d.%d.%d.%d (New SA = %02X:%02X:%02X:%02X:%02X:%02X)",
			saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex,
			saChngInfo->rMepIndices.lMepId,saChngInfo->rMepIndices.rMepId,
			saChngInfo->value[0],saChngInfo->value[1],saChngInfo->value[2],saChngInfo->value[3],saChngInfo->value[4],saChngInfo->value[5]);

	// The following code is used to automatically start any PM sessions on the MEP that are waiting to learn the target Remote MEP's MAC address...
	pmIndex_t pmIndex;
	//VR should this be rpc_lossMeasurementReadList
	pmIndex = lossMeasurementReadList(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, 0);
	while (pmIndex != 0)
	{
		if (isLmWaitingForMac(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId,pmIndex))
		{
			lossMeasurementEnable(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, pmIndex, true);
		}
		pmIndex = lossMeasurementReadList(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, pmIndex);
	}
	pmIndex = delayMeasurementReadList(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, 0);
	while (pmIndex != 0)
	{
		if (isDmWaitingForMac(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId,pmIndex))
		{
			delayMeasurementEnable(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, pmIndex, true);
		}
		pmIndex = delayMeasurementReadList(saChngInfo->rMepIndices.mdIndex, saChngInfo->rMepIndices.maIndex, saChngInfo->rMepIndices.lMepId, pmIndex);
	}
}
*/
static void handleRDIEvent(struct eventTLVextract_s	*eventTlv)
{
	fngRdiChngInfo_t	*rdiChngInfo;
	lMepNum_t			lMepNum;
	rMepNum_t			rMepNum;

	// point to the RDI Change data struct
	rdiChngInfo = (fngRdiChngInfo_t*)&eventTlv->data;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_rMepNumIsValid(rdiChngInfo->rMepNum))
	{
		return;
	}

	// Save the new state to context so we can use it later;
	_rMepSetRxRdiStatus(rdiChngInfo->rMepNum,rdiChngInfo->rdiIsSet);

	// get the lMepNum of this remote MEPs parent MEP
	lMepNum = _rMepGetParentLMepNum(rdiChngInfo->rMepNum);
	rMepNum = rdiChngInfo->rMepNum;

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: rdiChngInfo->rMepNum is changed by this function. Do NOT use after this function!
	_rMepGetParentsIndices(rdiChngInfo->rMepNum, &rdiChngInfo->rMepIndices);

	// Check if the RDI is set on any (enabled) remote MEP and update the 'someRDIdefect' parameter accordingly
	if (_lMepIsAnyRxRdiSet(rdiChngInfo->rMepIndices.mdIndex, rdiChngInfo->rMepIndices.maIndex, rdiChngInfo->rMepIndices.lMepId))
	{
		// Regardless of priority, the defect bit for 'someRDIdefect' must be set if any Rx RDI is set
		fngArray[lMepNum].mepFngInfo.defects |= SOME_RDI_CCM_DEFECT_MASK;
	}
	else
	{
		fngArray[lMepNum].mepFngInfo.defects &= ~SOME_RDI_CCM_DEFECT_MASK;
	}
	DO_LOG_INFO(log_handle,"Detected RDI %s on remote MEP with indices: %d.%d.%d.%d",
					rdiChngInfo->rdiIsSet ? "set" : "cleared",
					rdiChngInfo->rMepIndices.mdIndex, rdiChngInfo->rMepIndices.maIndex,
					rdiChngInfo->rMepIndices.lMepId, rdiChngInfo->rMepIndices.rMepId);

	// Call the FNG State Machine to process the new event
	fngStateMachine(&rdiChngInfo->rMepIndices, lMepNum, rMepNum);
}

static void handleTlvEvent(struct eventTLVextract_s	*eventTlv)
{
	fngTlvChngInfo_t	*tlvChngInfo;
	lMepNum_t			lMepNum;
	rMepNum_t			rMepNum;

	// point to the Tlv Change Info data struct
	tlvChngInfo = (fngTlvChngInfo_t*)&eventTlv->data;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_rMepNumIsValid(tlvChngInfo->rMepNum))
	{
		return;
	}

	// get the lMepNum of this remote MEPs parent MEP
	lMepNum = _rMepGetParentLMepNum(tlvChngInfo->rMepNum);

	// preserve the rMepNum before calling _rMepGetParentsIndices()
	rMepNum = tlvChngInfo->rMepNum;

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: tlvChngInfo->rMepNum is changed by this function. Do NOT use after this function!
	_rMepGetParentsIndices(rMepNum, &tlvChngInfo->rMepIndices);

	// If the Port Status value was present and has changed then...
	if (tlvChngInfo->changeMask & MR_PS_TLV_CHANGE_MASK)
	{
		if (tlvChngInfo->newTlv.psTlv.present)
		{
			// Save the new state to context so we can use it later;
			_rMepSetPsValue(rMepNum, tlvChngInfo->newTlv.psTlv.value);
		}
		else
		{
			// Save the new state to context so we can use it later;
			_rMepSetPsValue(rMepNum, psNoPortStatusTLV);
		}
		DO_LOG_INFO(log_handle,"Detected a change in Port Status TLV on remote MEP with indices: %lu.%lu.%d.%d. newValue = %d",
				tlvChngInfo->rMepIndices.mdIndex, tlvChngInfo->rMepIndices.maIndex,
				tlvChngInfo->rMepIndices.lMepId, tlvChngInfo->rMepIndices.rMepId,
				tlvChngInfo->newTlv.psTlv.present ? tlvChngInfo->newTlv.psTlv.value : psNoPortStatusTLV);
	}

	// If the Interface Status value was present and has changed then...
	if (tlvChngInfo->changeMask & MR_IS_TLV_CHANGE_MASK)
	{
		if (tlvChngInfo->newTlv.isTlv.present)
		{
			// Save the new state to context so we can use it later;
			_rMepSetIsValue(rMepNum, tlvChngInfo->newTlv.isTlv.value);
		}
		else
		{
			// Save the new state to context so we can use it later;
			_rMepSetIsValue(rMepNum, isNoInterfaceStatusTLV);
		}
		DO_LOG_INFO(log_handle,"Detected a change in Interface Status TLV on remote MEP with indices: %lu.%lu.%d.%d. newValue = %d",
				tlvChngInfo->rMepIndices.mdIndex, tlvChngInfo->rMepIndices.maIndex,
				tlvChngInfo->rMepIndices.lMepId, tlvChngInfo->rMepIndices.rMepId,
				tlvChngInfo->newTlv.isTlv.present ? tlvChngInfo->newTlv.isTlv.value : isNoInterfaceStatusTLV);
	}

	// The someMACstatusDefect must be set if:
	// If any rMep has ((IS status != psUp) and (IS status != psNoTLV))
	// If all rMeps have ((PS status != psUp) and (PS status != psNoTLV))
	{
		if (_lMepAreAllPSNotUpOrAnyISNotUp(tlvChngInfo->rMepIndices.mdIndex, tlvChngInfo->rMepIndices.maIndex, tlvChngInfo->rMepIndices.lMepId))
		{
			// Regardless of priority, the defect bit for 'someMACstatusDefect' must be set
			fngArray[lMepNum].mepFngInfo.defects |= SOME_MAC_STATUS_DEFECT_MASK;
		}
		else
		{
			// All IS values are up and one or more PS value is up, so clear the defect bit
			fngArray[lMepNum].mepFngInfo.defects &= ~SOME_MAC_STATUS_DEFECT_MASK;
		}
	}

	// Call the FNG State Machine to process the new event
	fngStateMachine(&tlvChngInfo->rMepIndices, lMepNum, rMepNum);
}

static void handleCcmEvent(struct eventTLVextract_s	*eventTlv)
{
	fngConnInfo_t	*connIntInfo;
	lMepNum_t		lMepNum;
	rMepNum_t		rMepNum;

	// point to the connectivity data struct
	connIntInfo = (fngConnInfo_t*)&eventTlv->data;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_rMepNumIsValid(connIntInfo->rMepNum))
	{
		return;
	}

	// get the lMepNum of this remote MEPs parent MEP
	lMepNum = _rMepGetParentLMepNum(connIntInfo->rMepNum);

	// The Connectivity for this Remote MEP when Up or Down, record the time
	_rMepSetFailedOkTime(connIntInfo->rMepNum, osGetTickCount());

	// Save the new state to context so we can use it later;
	_rMepSetLinkStatus(connIntInfo->rMepNum,!connIntInfo->connIsDown);

	rMepNum = connIntInfo->rMepNum;

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: connIntInfo->rMepNum is changed by this function. Do NOT use after this function!
	_rMepGetParentsIndices(rMepNum, &connIntInfo->rMepIndices);

	// Check is connectivity down on any (enabled) remote MEPs
	if (_lMepGetConnStatus(connIntInfo->rMepIndices.mdIndex, connIntInfo->rMepIndices.maIndex, connIntInfo->rMepIndices.lMepId) == connActive)
	{
		// CCMs links to all remote MEPs are up...clear the 'someRMEPCCMdefect' bit
		fngArray[lMepNum].mepFngInfo.defects &= ~SOME_RMEP_CCM_DEFECT_MASK;
	}
	else
	{
		// Regardless of priority, the defect bit for 'someRMEPCCMdefect' must be set if one or more CCM link is down
		fngArray[lMepNum].mepFngInfo.defects |= SOME_RMEP_CCM_DEFECT_MASK;
	}
	DO_LOG_INFO(log_handle,"Detected CCM link %s on remote MEP with indices: %lu.%lu.%d.%d",
					connIntInfo->connIsDown ? "down" : "up",
					connIntInfo->rMepIndices.mdIndex, connIntInfo->rMepIndices.maIndex,
					connIntInfo->rMepIndices.lMepId, connIntInfo->rMepIndices.rMepId);

	// Call the FNG State Machine to process the new event
	fngStateMachine(&connIntInfo->rMepIndices, lMepNum, rMepNum);
}



static void handleRMepEvent(struct eventTLVextract_s	*eventTlv)
{
	fngRMepErrInfo_t	*rMepIntInfo;
	lMepNum_t			lMepNum;

	// point to the Cross Connect data struct
	rMepIntInfo = (fngRMepErrInfo_t*)&eventTlv->data;

	// Take a copy of this as we loose it when we call _lMepGetParentsIndices
	lMepNum = rMepIntInfo->lMepNum;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_lMepNumIsValid(lMepNum))
	{
		return;
	}

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: rMepIntInfo->lMepNum is changed by this function. Do NOT use after this function!
	_lMepGetParentsIndices(lMepNum, &rMepIntInfo->rMepIndices);
	rMepIntInfo->rMepIndices.rMepId = 0;			// The remote MEPID is not relevant/known for this error

	if (rMepIntInfo->errDetected)
	{
		// Regardless of priority, the defect bit for 'errorCCMdefect' must be set if a remote MEP Error is detected
		fngArray[lMepNum].mepFngInfo.defects |= DEF_ERROR_CCM_MASK;
		// Copy the captured packet associated with the error
		memcpy(&fngArray[lMepNum].mepFngInfo.errCcmLastFailure, &rMepIntInfo->captPkt, sizeof(packetBuffer_t));
	}
	else
	{
		fngArray[lMepNum].mepFngInfo.defects &= ~DEF_ERROR_CCM_MASK;
	}
	DO_LOG_INFO(log_handle,"Detected Remote MEP Error %s on local MEP with indices: %lu.%lu.%d (reason code = %d)",
					rMepIntInfo->errDetected ? "set" : "cleared",
					rMepIntInfo->rMepIndices.mdIndex, rMepIntInfo->rMepIndices.maIndex, rMepIntInfo->rMepIndices.lMepId,
					rMepIntInfo->defectReason);

	// Call the FNG State Machine to process the new event
	fngStateMachine(&rMepIntInfo->rMepIndices, lMepNum, -1);
}

static void handleXConnEvent(struct eventTLVextract_s	*eventTlv)
{
	fngXConInfo_t	*xConIntInfo;
	lMepNum_t		lMepNum;

	// point to the Cross Connect data struct
	xConIntInfo = (fngXConInfo_t*)&eventTlv->data;

	// Take a copy of this as we loose it when we call _lMepGetParentsIndices
	lMepNum = xConIntInfo->lMepNum;

	// Due to delays in the message queue, it is possible to get an
	// interrupt for a MEP that is not valid (i.e. MEP was deleted just
	// after the interrupt event was queued). So validate the mepNum
	// before proceeding.
	if (!_lMepNumIsValid(lMepNum))
	{
		return;
	}

	// Get the MIB indices for all the parents of the remote MEP in question
	// *** IMPORTANT: xConIntInfo->lMepNum is changed by this function. Do NOT use after this function!
	_lMepGetParentsIndices(lMepNum, &xConIntInfo->rMepIndices);
	xConIntInfo->rMepIndices.rMepId = 0;			// The remote MEPID is not relevant/known for this error

	if (xConIntInfo->errDetected)
	{
		// Regardless of priority, the defect bit for 'xconCCMdefect' must be set if a xConn is detected
		fngArray[lMepNum].mepFngInfo.defects |= DEF_XCON_CCM_MASK;
		// Copy the captured packet associated with the error
		memcpy(&fngArray[lMepNum].mepFngInfo.xconCcmLastFailure, &xConIntInfo->captPkt, sizeof(packetBuffer_t));
	}
	else
	{
		fngArray[lMepNum].mepFngInfo.defects &= ~DEF_XCON_CCM_MASK;
	}
	DO_LOG_INFO(log_handle,"Detected XConnect %s on local MEP with indices: %lu.%lu.%d (reason code = %d)",
					xConIntInfo->errDetected ? "error" : "cleared",
					xConIntInfo->rMepIndices.mdIndex, xConIntInfo->rMepIndices.maIndex,
					xConIntInfo->rMepIndices.lMepId, xConIntInfo->defectReason);

	// Call the FNG State Machine to process the new event
	fngStateMachine(&xConIntInfo->rMepIndices, lMepNum, -1);
}

void processEventMessage(fngMsg_t *fngMsg)
{
	struct eventFrame_s 		*eventFrame;
	struct eventTLVextract_s	*eventTLV;
	uint8_t						*nextTlv;
	uint8_t						*endTlv;
	uint8_t 					*arrayPtr;
	uint16_t 					arrayLen;

	arrayPtr = fngMsg->payload;
	arrayLen = fngMsg->payloadSize;

	// Check the frame signature
	eventFrame = (struct eventFrame_s *)arrayPtr;
	if (eventFrame->signature != EVENT_TLV_SIG)
	{
		DO_LOG_ERROR(log_handle,"Mal-formed event frame. Incorrect signature expected 0x%08lX but got 0x%08lX", EVENT_TLV_SIG,eventFrame->signature);
		return;
	}

	// Check that there is an END TLV where we expect it
	endTlv = arrayPtr + eventFrame->length + sizeof(struct eventFrame_s) - 1;
	if (*endTlv != 0)
	{
		DO_LOG_ERROR(log_handle,"Mal-formed event frame. Missing End TLV.");
		return;
	}

	// the data array looks good so start extracting data from it
	//isrRpt->intTime = eventFrame->timeStamp;
	//isrRpt->intMask = eventFrame->intMask;

	// point to the first TLV
	nextTlv = arrayPtr + sizeof(struct eventFrame_s);
	eventTLV  = (struct eventTLVextract_s*)nextTlv;

	DO_LOG_DEBUG(log_handle,"Processing FNG event type = %d", eventTLV->type);
	lockMDBranch(0,0);
	LOCK_FNG_LIST(0);
	// Loop until the END TLV is found
	while ((eventTLV->type != 0) && (nextTlv < endTlv))
	{
		switch(eventTLV->type)
		{
			case EVENT_TLV_CONN:
				handleCcmEvent(eventTLV);
				break;
			case EVENT_TLV_XCONN:
				handleXConnEvent(eventTLV);
				break;
			case EVENT_TLV_RMEP_ERR:
				handleRMepEvent(eventTLV);
				break;
			case EVENT_TLV_SA_CNG:
				// Copy the TLV data into the appropriate struct in the isrRpt
				//VR - Not supported for now
			    //handleSaEvent(eventTLV);
				break;
			case EVENT_TLV_RDI_CNG:
				// Copy the TLV data into the appropriate struct in the isrRpt
				handleRDIEvent(eventTLV);
				break;
			case EVENT_TLV_ANY_TLV_CNG:
				// Copy the TLV data into the appropriate struct in the isrRpt
				handleTlvEvent(eventTLV);
				break;
			default:
				DO_LOG_NOTICE(log_handle,"Unknown Event TLV (%d)", eventTLV->type);
				break;
		}
		// Move to the next TLV
		nextTlv = nextTlv + (sizeof(struct eventTLVsize_s)) + eventTLV->length;

		if (nextTlv > (arrayPtr + arrayLen))
		{
			DO_LOG_WARNING(log_handle,"FNG TLV is malformed - unexpected - investigate!!!!!!! array length=%d, eventFrame length=%d, intMask=%#4lX, sofstruct=%d, eventTlv-len=%d \n",arrayLen, eventFrame->length, eventFrame->intMask, sizeof(struct eventTLVextract_s), eventTLV->length);
			UNLOCK_FNG_LIST();
			unlockMDBranch(0);
			return;
		}
		eventTLV  = (struct eventTLVextract_s*)nextTlv;
	}
	UNLOCK_FNG_LIST();
	unlockMDBranch(0);
	DO_LOG_DEBUG(log_handle,"FNG event complete");

#if 0  // TODO: enable code to send event info to the GUI
		retVal = ssSendPushMsg(fngMsg->payload,fngMsg->payloadSize);
		if (retVal == -1)
			DO_LOG_INFO(log_handle,"Failed to send Event info");
#endif
}
/*
 * Blocks the thread until a message arrives in the mailbox, but does
 * not block the thread longer than "timeout" milliseconds (similar to
 * the sys_arch_sem_wait() function).
 * The return values are the Number of milliseconds spent waiting
 * or OS_TIMEOUT if there was a timeout.
 */
static uint32_t fngMessageFetch(fngMsg_t *msg, struct timespec *tm)
{
	fngMsg_t	dummyMsg;
	fngMsg_t	*msgPtr;

    if(msg == NULL)
    {
    	msgPtr = &dummyMsg;
    }
    else
    {
    	msgPtr = msg;
    }

    if(!((tm->tv_sec == 0) && (tm->tv_nsec ==0)))
    {
    	// wait for 'tm' time for a message
        if(osQueueReceive( fngMsgQ, msgPtr, tm))
        {
			// received a message before timer expired
            return 0;
        }
        else
        {
        	// timeout occurred before message received
        	msg->msgID = fng_invalidMsg;
            return OS_TIMEOUT;
        }
    }
    else // block forever for a message.
    {
        while(!osQueueReceive( fngMsgQ, msgPtr, tm)); // time is arbitrary
        return 0;
    }
}

/**
 * Wait (forever) for a message to arrive in an mbox.
 * While waiting, timeouts are processed.
 *
 * @param mbox the mbox to fetch the message from
 * @param msg the place to store the message
 */
static void fngTimeOutsMeassageFetch(fngMsg_t *msg)
{
	uint32_t				timeNeeded;
	struct fngTimeo_s 		*tmptimeout;
	fngTimeoutHandler_t		handler;
	uint32_t				hwNum;
	void					*arg;
	struct timespec			tm	= {0,0};

	again:
	if (nextTimeOut == NULL)
	{
		tm.tv_sec	= 0;
		tm.tv_nsec	= 0;
		timeNeeded = fngMessageFetch(msg, &tm);
	}
	else
	{
		timeNeeded = fngMessageFetch(msg, &nextTimeOut->time);
		if (timeNeeded == OS_TIMEOUT)
		{
			// If time == OS_TIMEOUT, a timeout occurred before a message
			// could be fetched. We should now call the timeout handler and
			// deallocate the memory allocated for the timeout.
			tmptimeout = nextTimeOut;
			nextTimeOut = tmptimeout->next;
			handler = tmptimeout->h;
			freeTimer(tmptimeout);
			if (handler != NULL)
			{
				arg = tmptimeout->arg;
				hwNum = tmptimeout->hwNum;
				handler(arg,hwNum);
			}
			DO_LOG_DEBUG(log_handle,"timer timeout occurred");
			// We try again to fetch a message from the mbox
			goto again;
		}
		else
		{
			// If time != OS_TIMEOUT, a message was received before the timeout
			// occurred. The time variable is set to the number of
			// milliseconds we waited for the message.
			DO_LOG_DEBUG(log_handle,"Event message received");
		}
	}
}

/*
 *
 */
void fngProcess(void)
{
	fngMsg_t				fngMsg;
	lMepNum_t				lMepNum;
	struct mibRMepIndices_s	*mepIndices;

	//DO_LOG_NOTICE(log_handle,"Started FNG process, PID = %d",getpid());

	for ( ;; )
	{
		fngMsg.msgID = fng_invalidMsg;
		// Wait forever to fetch a message and process timeouts while waiting..
#if 1
		fngTimeOutsMeassageFetch(&fngMsg);
#else
		const struct timespec tm = 
		{
			tv_sec = 0,
			tv_nsec = 0
		};
		if(osQueueReceive( fngMsgQ, &fngMsg, &tm))
#endif
		// A message was received so process it...
		switch(fngMsg.msgID)
		{

			case fng_cfmEventMsg:
			{
				//LOCK_FNG_LIST(0); - moved into the processEventMessage() function.
				processEventMessage(&fngMsg);
				//UNLOCK_FNG_LIST();
				break;
			}

			case fng_updateFngStateMsg:
			{
				LOCK_FNG_LIST(0);
				// Extract the local MEP number from the length field - payload is not used
				lMepNum = (lMepNum_t)fngMsg.payloadSize;
				// When re-evaluating the FNG SM the parent indices must be supplied
				mepIndices = (struct mibRMepIndices_s*)fngMsg.payload;

				// Call the FNG State Machine to re-evaluate it's state
				fngStateMachine(mepIndices, lMepNum, -1);
				UNLOCK_FNG_LIST();
				break;
			}
			case fng_resetSMForMepMsg:		// Put the SM in the reset state and clear any pending timeouts
			{
				LOCK_FNG_LIST(0);
				// Extract the local MEP number from the length field - payload is not used
				lMepNum = (lMepNum_t)fngMsg.payloadSize;
				fngCancelTimeOut(fngDefectAlarmTimeoCB, NULL, (uint32_t)lMepNum);
				fngCancelTimeOut(fngDefectResetTimeoCB, NULL, (uint32_t)lMepNum);
				fngArray[lMepNum].mepFngInfo.fngState	= fngReset;
				fngArray[lMepNum].mepFngInfo.defects	= defNone;
				fngArray[lMepNum].mepFngInfo.hiPrDefect	= defNone;
				fngArray[lMepNum].fngDefect				= defNone;
				fngArray[lMepNum].fngPriority			= defNone;
				fngArray[lMepNum]._previousDefects		= defNone;
				UNLOCK_FNG_LIST();
				break;
			}
			case fng_releaseSMForMepMsg:	// Release the SM and clear any pending timeouts
			{
				LOCK_FNG_LIST(0);
				// Extract the local MEP number from the length field - payload is not used
				lMepNum = (lMepNum_t)fngMsg.payloadSize;
				fngCancelTimeOut(fngDefectAlarmTimeoCB, NULL, (uint32_t)lMepNum);
				fngCancelTimeOut(fngDefectResetTimeoCB, NULL, (uint32_t)lMepNum);
				fngSetDefaults(lMepNum);
				UNLOCK_FNG_LIST();
				break;
			}
			default:
			{
				fngMsg.payload = NULL;
				break;
			}

		}
		ts_free(fngMsg.payload);
	}
}

/*
 * @brief	Called at device startup to initialise MA module
 *
 * Safe to call repeatedly.
 */
void fngInit(void)
{
	log_init();	// Initialises the logging system if it's not already initialised

	// Get a log handle if we don't already have one
	if (log_handle == 0)
		log_handle = log_register("FNG");

	// Create a mutex to protect the MA list if we don't already have one
	if (fngListLock == NULL)
	{
		fngListLock = osMutexCreate();
		ASSERT_NOT_NULL(fngListLock);
	}

	// If not already initialised clear the MA list for first use.
	if (!fngInitialised)
	{
		// Set the default logging level to NOTICE
		setModuleLogLevel(log_handle, eLOG_LEVEL_NOTICE);

		DO_LOG_NOTICE(log_handle,"Preparing the FNG S/M for first use");

		fngMsgQ = osCreateQueue( FNG_QUEUE_SIZE, sizeof( fngMsg_t ) );
		ASSERT_NOT_NULL(fngMsgQ);

		LOCK_FNG_LIST(0);
		clearFngList();
		initTimer();
		UNLOCK_FNG_LIST();
		fngInitialised = true;

		// Launch the task to take care of processing CFM interrupts
		osTaskCreate( (void (*)( void * ))fngProcess, "FNG Task    ", FNG_TASK_STACKSIZE, NULL, OS_STACK_THREAD_PRIO - 1, NULL );
	}
}


static int sendFngMsg( fngMsgId_t msgID, uint8_t *data, uint16_t length)
{
	fngMsg_t msg;

	ASSERT_NOT_NULL(fngMsgQ);

	msg.msgID 		= msgID;
	msg.payloadSize = length;
	msg.payload		= data;

	// Try to send the message, waiting for 5ms for the queue to have space
	if (osQueueSend(fngMsgQ, (void *)&msg, 5 * OS_TICKS_PER_MS) == 1)
	{
		return 0;
	}
	else
	{
		//fprintf(stderr, "[fng msg] Msg queue full");
		return -1;
	}
}

/*
 * @brief This function adds a CFM Event message to the FNG message Queue.
 *
 * If the FNG message Queue is full the function will continue to
 * try to send the message every 5ms, sleeping while waiting.
 */
void fngAddCfmEventMsgToQ( uint8_t *data, uint16_t length)
{
	uint16_t	loopCount = 100;
	bool		run_once = true;
#ifdef DEBUG_PRINT_L2
	ts_printf("fngAddCfmEventMsgToQ\n\r");
#endif

	while (sendFngMsg(fng_cfmEventMsg,data,length) != 0)
	{
		osSleepMS(10);	// wait 10ms before trying again.
		if(run_once)
		{
		    ts_printf("[cfm event] FNG Msg queue full...waiting\n\r");
			run_once = false;
		}
		if (loopCount-- == 0)
		{
		    ts_printf("[cfm event] FNG Msg queue still full after 1sec...Failed to send CFM event\n\r");
			loopCount = 100;
			break;
		}
	}
}

/*
 * @brief This function adds a message to the FNG message Queue.
 *
 * If the FNG message Queue is full the function will continue to
 * try to send the message every 5ms, sleeping while waiting.
 */
static void fngSendMsgWait(uint8_t *msg, uint16_t length, fngMsgId_t msgID)
{
	uint16_t	loopCount = 100;
	bool		run_once = true;

	while (sendFngMsg(msgID,msg,length) != 0)
	{
		osSleepMS(10);	// wait 10ms before trying again.
		if(run_once)
		{
			DO_LOG_WARNING(log_handle, "[SM update] FNG Msg queue full...waiting");
			run_once = false;
		}
		if (loopCount-- == 0)
		{
			DO_LOG_WARNING(log_handle, "[SM update] FNG Msg queue still full after 1sec...waiting");
			loopCount = 100;
		}
	}
}

void updateFngStateMsg(lMepNum_t lMepNum, struct mibRMepIndices_s *mepIndices)
{
	//ts_printf("*updt\n");
	LOCK_FNG_LIST(0);
	// Call the FNG State Machine to re-evaluate it's state
	fngStateMachine(mepIndices, lMepNum, -1);
	UNLOCK_FNG_LIST();
	//ts_printf("==\n");
}

void resetSMForMepMsg(lMepNum_t lMepNum)		// Put the SM in the reset state and clear any pending timeouts
{
	//ts_printf("*reset\n");
	LOCK_FNG_LIST(0);
	fngCancelTimeOut(fngDefectAlarmTimeoCB, NULL, (uint32_t)lMepNum);
	fngCancelTimeOut(fngDefectResetTimeoCB, NULL, (uint32_t)lMepNum);
	fngArray[lMepNum].mepFngInfo.fngState	= fngReset;
	fngArray[lMepNum].mepFngInfo.defects	= defNone;
	fngArray[lMepNum].mepFngInfo.hiPrDefect	= defNone;
	fngArray[lMepNum].fngDefect				= defNone;
	fngArray[lMepNum].fngPriority			= defNone;
	fngArray[lMepNum]._previousDefects		= defNone;
	UNLOCK_FNG_LIST();
	//ts_printf("==\n");
}

void releaseSMForMepMsg(lMepNum_t lMepNum)	// Release the SM and clear any pending timeouts
{
	//ts_printf("*rel\n");
	LOCK_FNG_LIST(0);
	fngCancelTimeOut(fngDefectAlarmTimeoCB, NULL, (uint32_t)lMepNum);
	fngCancelTimeOut(fngDefectResetTimeoCB, NULL, (uint32_t)lMepNum);
	fngSetDefaults(lMepNum);
	UNLOCK_FNG_LIST();
	//ts_printf("==\n");
}

/*
 * @brief Send a request to the FNG SM to re-evaluate the State of a particular MEP
 *
 * This should be called when a new remote MEP is added/removed or disabled for a Local MEP
 * It is possible that this function will be called during configuration before the
 * Local MEP is configured, so the validity of the lMepNum must be checked
 */
void fngReEvaluateSM(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, lMepNum_t lMepNum)
{

	struct mibRMepIndices_s *mepIndices;

	if ((lMepNum >= 0) && (lMepNum < MAX_NUM_MEPS))
	{
		if (_mepNumToMepId(lMepNum,NULL) != 0)
		{
			mepIndices = ts_malloc(sizeof(struct mibRMepIndices_s));
			if (mepIndices != NULL)
			{
				mepIndices->mdIndex	= mdIndex;
				mepIndices->maIndex	= maIndex;
				mepIndices->lMepId	= lMepId;
				mepIndices->rMepId	= 0;
			}
#if 0
			// We are encoding the lMepNum into the length field
			fngSendMsgWait((uint8_t*)mepIndices,(uint16_t)lMepNum,fng_updateFngStateMsg);
#else

			updateFngStateMsg(lMepNum, mepIndices);
#endif
		}
	}

}

/*
 * @brief Send a request to the FNG SM to reset the FNG state of a particular MEP
 *
 * This should be called when a MEP is Administratively disabled
 */
void fngResetSM(lMepNum_t lMepNum)
{
#if 0
	// We are encoding the lMepNum into the length field
	fngSendMsgWait(NULL,(uint16_t)lMepNum,fng_resetSMForMepMsg);
#else
	resetSMForMepMsg(lMepNum);
#endif
}

/*
 * @brief Send a request to the FNG SM to release the FNG state machine for a particular MEP
 *
 * This should be called when a MEP is deleted
 */
void fngReleaseSM(lMepNum_t lMepNum)
{
#if 0
	// We are encoding the lMepNum into the length field
	fngSendMsgWait(NULL,(uint16_t)lMepNum,fng_releaseSMForMepMsg);
#else
	releaseSMForMepMsg(lMepNum);
#endif
}

/*
 * @brief Read the FNG status information for the selected Local MEP
 */
void fngRead(mdIndex_t mdIndex, maIndex_t maIndex, mepId_t lMepId, mepFngInfo_t *mepFngInfo)
{
	lMepNum_t	lMepNum;

	lMepNum = _mepIdToMepNum(mdIndex, maIndex, lMepId);
	EGL_ASSERT(lMepNum >= 0, 1);	// Ensure we always get a valid lMepNum

	LOCK_FNG_LIST(0);
	memcpy(mepFngInfo,&fngArray[lMepNum].mepFngInfo,sizeof(mepFngInfo_t));
	UNLOCK_FNG_LIST();

	//DO_LOG_DEBUG(log_handle,"Read FNG information on MEPID %lu.%lu.%d", mdIndex, maIndex, lMepId);
}


lowPrDef_t _getFngLowPrDef(lMepNum_t lMepNum)
{
	lowPrDef_t theValue;
	LOCK_FNG_LIST(0);
	theValue = fngArray[lMepNum].mepFngInfo.lowPrDef;
	UNLOCK_FNG_LIST();
	return(theValue);
}

void _setFngLowPrDef(lMepNum_t lMepNum, lowPrDef_t newVal)
{
	LOCK_FNG_LIST(0);
	fngArray[lMepNum].mepFngInfo.lowPrDef = newVal;
	UNLOCK_FNG_LIST();
}

uint16_t _getFngAlarmTime(lMepNum_t lMepNum)
{
	uint16_t theValue;
	LOCK_FNG_LIST(0);
	theValue = fngArray[lMepNum].mepFngInfo.fngAlarmTime;
	UNLOCK_FNG_LIST();
	return(theValue);
}

void _setFngAlarmTime(lMepNum_t lMepNum, uint16_t newVal)
{
	LOCK_FNG_LIST(0);
	fngArray[lMepNum].mepFngInfo.fngAlarmTime = newVal;
	UNLOCK_FNG_LIST();
}

uint16_t _getFngResetTime(lMepNum_t lMepNum)
{
	uint16_t theValue;
	LOCK_FNG_LIST(0);
	theValue = fngArray[lMepNum].mepFngInfo.fngResetTime;
	UNLOCK_FNG_LIST();
	return(theValue);
}

void _setFngResetTime(lMepNum_t lMepNum, uint16_t newVal)
{
	LOCK_FNG_LIST(0);
	fngArray[lMepNum].mepFngInfo.fngResetTime = newVal;
	UNLOCK_FNG_LIST();
}


