/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */
#include <api_oam_sdk/common/types/inc/linked_list.h>
#include <api_oam_sdk/policer_mef10p2/inc/policer_mef10p2.h>
#include <api_oam_sdk/service_bwp/src/svc_bwp_types.h>
#include <api_oam_sdk/service_cos/src/svc_cos_types.h>
#include <api_oam_sdk/service_interface/src/svc_iface_types.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include "stdio.h"
#include "string.h"
#include "egl_types.h"
#include "egl_log.h"
#include "egl_assert.h"
#include "osal.h"

static bwpGrpEntry_t	bwpGrpArray[MAX_NUM_BWP];
static bwpEntry_t		bwpArray[MAX_NUM_BWP];
static uint16_t			log_handle				= 0;		// handle used to log messages for this module
static osMutex_t		bwpListLock				= NULL;
static bool				bwpInitialised			= false;
static bwpGrpIndex_t	bwpGrpNextIndex 		= 1;
static bwpNum_t			bwpGrpCount;
static bwpNum_t			bwpCount;
static TAILQ_HEAD(bwpGrpHeadEntry_s,struct bwpGrpEntry_s,)	llBwpGrpHead;

#define LOCK_BWP_LIST(timeout)	osRecursiveMutexTake(bwpListLock,timeout)
#define UNLOCK_BWP_LIST()		osRecursiveMutexGive(bwpListLock)

/*
 * 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 setBwpLogLevel(log_level_t logLevel)
{
	setModuleLogLevel(log_handle,logLevel);
}

/*
 * @brief	This sets the bwpEntry Group values to their default
 *
 * In most cases this should only be done with bwpLock enabled
 * to ensure thread safe operation
 */
static void setBwpGrpDefaults(bwpGrpEntry_t *bwpGrpEntry)
{
	memset(bwpGrpEntry,0,sizeof(bwpGrpEntry_t));
	bwpGrpEntry->bwpNextIndex = 1;
	TAILQ_INIT(&bwpGrpEntry->llBwpHead);
}

/*
 * @brief	This sets the bwpEntry values to their default
 *
 * In most cases this should only be done with bwpLock enabled
 * to ensure thread safe operation
 */
static void setBwpDefaults(bwpEntry_t *bwpEntry)
{
	memset(bwpEntry,0,sizeof(bwpEntry_t));
	bwpEntry->bwpCtxt.bwpNum 	= INVALID_BWP_NUM;
	bwpEntry->cfg.cir			= 1000000;
	bwpEntry->cfg.cbs			= 12;
	bwpEntry->cfg.cm			= colorBlind;
}

/*
 * @brief Clears the BWP list
 *
 * This should be called to initialise the BWP list.
 * Any BWPs on the list will be lost.
 */
static void clearBwpList(void)
{
	uint16_t	k;

	bwpGrpNextIndex	= 1;
	bwpGrpCount		= 0;
	bwpCount		= 0;

	TAILQ_INIT(&llBwpGrpHead);

	// Initialise all entries in the MD store to defaults
	for (k = 0; k < MAX_NUM_BWP; k++)
	{
		setBwpGrpDefaults(&bwpGrpArray[k]);
		setBwpDefaults(&bwpArray[k]);
	}
	DO_LOG_DEBUG(log_handle,"Initialised the BWP List");
}

/*
 * @brief	Called at device startup to initialise BWP module
 *
 * Safe to call repeatedly.
 */
void bwpInit(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("BWP");

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

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

		DO_LOG_DEBUG(log_handle,"Preparing the BWP List for first use");
		cosInit();
		LOCK_BWP_LIST(0);
		clearBwpList();
		UNLOCK_BWP_LIST();
		bwpInitialised = true;
	}
}

/*
 * @brief Convert an bwpNum into a MD MIB index
 */
bwpIndex_t _bwpNumToMibIndex(bwpNum_t bwpNum)
{
	bwpIndex_t bwpIndex  = 0;

	LOCK_BWP_LIST(0);
	if (bwpArray[bwpNum].bwpCtxt.inUse)
	{
		bwpIndex = bwpArray[bwpNum].bwpCtxt.bwpIndex;
	}
	UNLOCK_BWP_LIST();
	return (bwpIndex);
}

static bwpGrpEntry_t *getBwpGrpWithIndex(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t		*bwpGrpEntryPtr;

	// Start searching valid BWP Groups at the head of the list
	bwpGrpEntryPtr = TAILQ_FIRST(&llBwpGrpHead);
	while (bwpGrpEntryPtr != NULL)
	{
		if (bwpGrpEntryPtr->bwpGrpCtxt.bwpGrpIndex == bwpGrpIndex)
		{
			return (bwpGrpEntryPtr);	//found it
		}
		bwpGrpEntryPtr = TAILQ_NEXT(bwpGrpEntryPtr,ll);
	}
	// if the bwp group index is not found return NULL
	return (NULL);
}

static bool isBwpGrpIndexValid(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t *bwpGrpEntry;

	bwpGrpEntry = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntry == NULL)
	{
		return false;
	}
	return true;
}

/*
 * @brief Convert a bwp index into an bwpNum
 */
static bwpNum_t	bwpIndexToBwpNumNoLocking(bwpGrpEntry_t *bwpGrpEntry, bwpIndex_t bwpIndex)
{
	bwpEntry_t		*bwpEntryPtr;
	bwpNum_t		bwpNum = -1;

	// Start searching valid MDs at the head of the list
	bwpEntryPtr = TAILQ_FIRST(&bwpGrpEntry->llBwpHead);
	while (bwpEntryPtr != NULL)
	{
		if (bwpEntryPtr->bwpCtxt.bwpIndex == bwpIndex)
		{
			bwpNum = bwpEntryPtr->bwpCtxt.bwpNum;
			return (bwpNum);	//found it
		}
		bwpEntryPtr = TAILQ_NEXT(bwpEntryPtr,ll);
	}
	return -1;	// bwpIndex not found
}

/*
 * @brief Convert a MD MIB index into an bwpNum
 */
bwpNum_t	_bwpIndexToBwpNum(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex)
{
	bwpGrpEntry_t 	*bwpGrpEntryPtr;
	bwpNum_t 		bwpNum;

	LOCK_BWP_LIST(0);
	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		LOCK_BWP_LIST(0);
		return -1;	// bwpIndex not found
	}

	bwpNum = bwpIndexToBwpNumNoLocking(bwpGrpEntryPtr, bwpIndex);
	UNLOCK_BWP_LIST();
	return bwpNum;
}

/*
 * @brief Read the next entry in the BWP List
 *
 * @param[in] bwpIndex of previous entry or 0
 * @return	bwpIndex of the next entry or 0 if no more entries
 *
 * Read the next entry in the BWP List. If prevBwpIndex is 0 it
 * returns the bwpIndex of the first BWP entry or 0 if there are
 * no BWP entries. If a valid bwpIndex is provided it returns
 * the bwpIndex of the next BWP in the list.
 */
bwpGrpIndex_t svcBwpGrpReadList(bwpGrpIndex_t prevBwpGrpIndex)
{
	bwpGrpIndex_t	theIndex;
	bwpGrpEntry_t	*bwpGrpEntry;

	LOCK_BWP_LIST(0);
	if (prevBwpGrpIndex == 0)
	{
		if (!TAILQ_EMPTY(&llBwpGrpHead))
		{
			theIndex = TAILQ_FIRST(&llBwpGrpHead)->bwpGrpCtxt.bwpGrpIndex;
			UNLOCK_BWP_LIST();
			return theIndex;
		}
	}
	else
	{
		bwpGrpEntry = TAILQ_FIRST(&llBwpGrpHead);
		while (bwpGrpEntry != NULL)
		{
			if (bwpGrpEntry->bwpGrpCtxt.bwpGrpIndex == prevBwpGrpIndex)
			{
				if (TAILQ_NEXT(bwpGrpEntry,ll) != NULL)
				{
					theIndex = TAILQ_NEXT(bwpGrpEntry,ll)->bwpGrpCtxt.bwpGrpIndex;
					UNLOCK_BWP_LIST();
					return theIndex;
				}
				else
				{
					UNLOCK_BWP_LIST();
					return 0;
				}
			}
			bwpGrpEntry = TAILQ_NEXT(bwpGrpEntry,ll);
		}
	}
	UNLOCK_BWP_LIST();
	return 0;
}

/*
 * @brief Read the next entry in the BWP List
 *
 * @param[in] bwpIndex of previous entry or 0
 * @return	bwpIndex of the next entry or 0 if no more entries
 *
 * Read the next entry in the BWP List. If prevBwpIndex is 0 it
 * returns the bwpIndex of the first BWP entry or 0 if there are
 * no BWP entries. If a valid bwpIndex is provided it returns
 * the bwpIndex of the next BWP in the list.
 */
bwpIndex_t svcBwpReadList(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t prevBwpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;
	bwpNum_t 		nextBwpNum;
	bwpIndex_t		theIndex;

	LOCK_BWP_LIST(0);

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		return 0;	// bwpIndex not found
	}

	if (prevBwpIndex == 0)
	{
		if (!TAILQ_EMPTY(&bwpGrpEntryPtr->llBwpHead))
		{
			theIndex = TAILQ_FIRST(&bwpGrpEntryPtr->llBwpHead)->bwpCtxt.bwpIndex;
			UNLOCK_BWP_LIST();
			return theIndex;
		}
	}
	else
	{
		nextBwpNum = bwpIndexToBwpNumNoLocking(bwpGrpEntryPtr, prevBwpIndex);
		if (nextBwpNum != -1)
		{
			if (TAILQ_NEXT(&bwpArray[nextBwpNum],ll) != NULL)
			{
				theIndex = TAILQ_NEXT(&bwpArray[nextBwpNum],ll)->bwpCtxt.bwpIndex;
				UNLOCK_BWP_LIST();
				return theIndex;
			}
		}
	}
	UNLOCK_BWP_LIST();
	return 0;
}

/*
 * @brief	Find the next free BWP Entry
 */
static bwpGrpEntry_t* getNextFreeBwpGrpEntry(void)
{
	bwpNum_t	k;

	for (k = 0; k < MAX_NUM_BWP; k++)
	{
		if (!bwpGrpArray[k].bwpGrpCtxt.inUse)
		{
			bwpGrpArray[k].bwpGrpCtxt.inUse = true;
			return (&bwpGrpArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief	Find the next free BWP Entry
 */
static bwpEntry_t* getNextFreeBwpEntry(void)
{
	bwpNum_t	k;

	for (k = 0; k < MAX_NUM_BWP; k++)
	{
		if (!bwpArray[k].bwpCtxt.inUse)
		{
			bwpArray[k].bwpCtxt.bwpNum = k;
			bwpArray[k].bwpCtxt.inUse = true;
			return (&bwpArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief	Add a new bwpEntry into the BWP List
 */
static void addBwpGrpToList(bwpGrpEntry_t *bwpGrpEntry)
{
	ASSERT_NOT_NULL(bwpGrpEntry);

	TAILQ_INSERT_TAIL(&llBwpGrpHead,bwpGrpEntry,ll);
	bwpGrpCount++;

	// The BWP Grp index can be specified by the user. So
	// it is necessary to keep the bwpNextIndex aligned
	if (bwpGrpNextIndex <= bwpGrpEntry->bwpGrpCtxt.bwpGrpIndex)
	{
		bwpGrpNextIndex = bwpGrpEntry->bwpGrpCtxt.bwpGrpIndex + 1;
	}
}

/*
 * @brief	Add a new bwpEntry into the BWP List
 */
static void addBwpToList(bwpGrpEntry_t *bwpGrpEntry, bwpEntry_t *bwpEntry)
{
	ASSERT_NOT_NULL(bwpEntry);

	TAILQ_INSERT_TAIL(&bwpGrpEntry->llBwpHead,bwpEntry,ll);
	bwpCount++;

	// The BWP index can be specified by the user. So
	// it is necessary to keep the bwpNextIndex aligned
	if (bwpGrpEntry->bwpNextIndex <= bwpEntry->bwpCtxt.bwpIndex)
	{
		bwpGrpEntry->bwpNextIndex = bwpEntry->bwpCtxt.bwpIndex + 1;
	}
}

/*
 * @brief	Remove the BWP group entry, pointed to by bwpEntry, from the BWP List
 */
static void removeBwpGrpFromList(bwpGrpEntry_t *bwpGrpEntry)
{
	ASSERT_NOT_NULL(bwpGrpEntry);

	TAILQ_REMOVE(&llBwpGrpHead,bwpGrpEntry,ll);
	setBwpGrpDefaults(bwpGrpEntry);
	bwpGrpCount--;
}

/*
 * @brief	Remove the BWP entry, pointed to by bwpEntry, from the BWP List
 */
static void removeBwpFromList(bwpGrpEntry_t *bwpGrpEntry, bwpEntry_t *bwpEntry)
{
	ASSERT_NOT_NULL(bwpEntry);

	TAILQ_REMOVE(&bwpGrpEntry->llBwpHead,bwpEntry,ll);
	setBwpDefaults(bwpEntry);
	bwpCount--;
}

/*
 * @brief Returns the next available BWP Group index
 */
bwpIndex_t	svcBwpGrpGetNextIndex(void)
{
	return bwpGrpNextIndex;
}

/*
 * @brief Returns the next available BWP index
 */
bwpIndex_t	svcBwpGetNextBwpIndex(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntry;
	bwpIndex_t		theIndex;

	LOCK_BWP_LIST(0);
	bwpGrpEntry = TAILQ_FIRST(&llBwpGrpHead);
	while (bwpGrpEntry != NULL)
	{
		if (bwpGrpEntry->bwpGrpCtxt.bwpGrpIndex == bwpGrpIndex)
		{
			theIndex = bwpGrpEntry->bwpNextIndex;
			UNLOCK_BWP_LIST();
			return theIndex;
		}
		bwpGrpEntry = TAILQ_NEXT(bwpGrpEntry,ll);
	}
	UNLOCK_BWP_LIST();
	return 0;
}

/*
 * @brief Test if the supplied BWP Index is valid
 */
bool svcBwpGrpIsBwpGrpIndexValid(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;

	LOCK_BWP_LIST(0);
	// Validate the bwpIndex value provided

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_DEBUG(log_handle,"Operation rejected due to invalid BWP group Index (%lu)",bwpGrpIndex);
		return (false);
	}
	UNLOCK_BWP_LIST();
	return(true);
}

/*
 * @brief Test if the supplied BWP Index is valid
 */
static bool isBwpIndexValid(bwpGrpEntry_t *bwpGrpEntry, bwpIndex_t bwpIndex, bwpNum_t *bwpNum)
{
	bwpNum_t	theBwpNum;

	if ((bwpIndex == 0) || (bwpIndex >= bwpGrpEntry->bwpNextIndex))
	{
BWP_INDEX_INVALID:
		DO_LOG_DEBUG(log_handle,"The BWP Index (%lu) was invalid",bwpIndex);
		return false;
	}

	// Test is the bwpIndex known and get it's associated bwpNum
	theBwpNum = bwpIndexToBwpNumNoLocking(bwpGrpEntry,bwpIndex);
	if (theBwpNum != -1)		// If the BWP index is found
	{
		if (bwpNum != NULL)
		{
			*bwpNum = theBwpNum;
		}
		return(true);
	}
	goto BWP_INDEX_INVALID;
}

/*
 * @brief Test if the supplied BWP Index is valid
 */
bool svcBwpIsBwpIndexValid(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex)
{
	bool retVal;
	bwpGrpEntry_t	*bwpGrpEntryPtr;

	LOCK_BWP_LIST(0);
	// Validate the bwpIndex value provided

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr != NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_DEBUG(log_handle,"Operation rejected due to invalid BWP group Index (%lu)",bwpGrpIndex);
		return (false);
	}

	retVal = isBwpIndexValid(bwpGrpEntryPtr, bwpIndex, NULL);
	UNLOCK_BWP_LIST();
	return(retVal);
}

/*
 * @brief Create a new BWP Group.
 */
int svcBwpGrpCreate(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;

	LOCK_BWP_LIST(0);
	// Validate the bwpIndex value provided
	if ((bwpGrpIndex == 0) || isBwpGrpIndexValid(bwpGrpIndex))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP group Index (%lu)",bwpGrpIndex);
		return (1);
	}

	bwpGrpEntryPtr = getNextFreeBwpGrpEntry();
	if (bwpGrpEntryPtr != NULL)
	{
		TAILQ_INIT(&bwpGrpEntryPtr->llBwpHead);

		bwpGrpEntryPtr->bwpGrpCtxt.bwpGrpIndex = bwpGrpIndex;
		// add the new BWP to the list
		addBwpGrpToList(bwpGrpEntryPtr);
	}
	else
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		return (-1);
	}
	UNLOCK_BWP_LIST();
	DO_LOG_NOTICE(log_handle,"Created BWP Group with index = %lu",bwpGrpIndex);
	return (2); // Operation accepted.
}

/**
 * @brief	Delete the BWP Group with the supplied bwpIndex.
 *
 */
int svcBwpGrpDelete(bwpGrpIndex_t bwpGrpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntry;
	svcIfaceCfg_t 	svcIFaceCfg;
	bwpIndex_t		bwpIndex;
	bwpIndex_t		bwpIndexToDelete;

	LOCK_BWP_LIST(0);
	bwpGrpEntry = TAILQ_FIRST(&llBwpGrpHead);
	while (bwpGrpEntry != NULL)
	{
		if (bwpGrpEntry->bwpGrpCtxt.bwpGrpIndex == bwpGrpIndex)
		{
		    EGL_ASSERT(svcIfaceRead(wanPort, &svcIFaceCfg,NULL,NULL) == 2, 1);
			if (svcIFaceCfg.ingressBwpGrpIndex == bwpGrpIndex)
			{
BWP_GRP_INUSE:
				UNLOCK_BWP_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected because BWP group (index = %lu) is in use",bwpGrpIndex);
				return 2;
			}
			EGL_ASSERT(svcIfaceRead(lanPort, &svcIFaceCfg,NULL,NULL) == 2, 1);
			if (svcIFaceCfg.ingressBwpGrpIndex == bwpGrpIndex)
			{
				goto BWP_GRP_INUSE;
			}
			if (_evcIsBwpGrpInUse(bwpGrpIndex))
			{
				goto BWP_GRP_INUSE;
			}

			// Remove all associated BWPs...
			bwpIndex = svcBwpReadList(bwpGrpIndex,0);
			while(bwpIndex !=0)
			{
				bwpIndexToDelete = bwpIndex;
				bwpIndex = svcBwpReadList(bwpGrpIndex,bwpIndex);
				svcBwpDelete(bwpGrpIndex, bwpIndexToDelete);
			}

			removeBwpGrpFromList(bwpGrpEntry);
			UNLOCK_BWP_LIST();
			DO_LOG_NOTICE(log_handle,"Deleted BWP Group with index = %lu",bwpGrpIndex);
			return 3;
		}
		bwpGrpEntry = TAILQ_NEXT(bwpGrpEntry,ll);
	}

	UNLOCK_BWP_LIST();
	DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent BWP group (index = %lu)",bwpGrpIndex);
	return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
}

/*
 * @brief Create a new service BWP.
 */
int svcBwpCreate(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex, svcBwpCfg_t *svcBwpCfg)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;
	bwpEntry_t		*bwpEntryPtr;

	LOCK_BWP_LIST(0);

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP Group Index (%lu)",bwpGrpIndex);
		return (1);
	}

	// Validate the bwpIndex value provided
	if ((bwpIndex == 0) || isBwpIndexValid(bwpGrpEntryPtr,bwpIndex,NULL)) //|| (bwpIndex > bwpNextIndex))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP Index (%lu.%lu)",bwpGrpIndex,bwpIndex);
		return (1);
	}

	if (svcBwpCfg == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to NULL svcBwpCfg pointer");
		return (-1);
	}

	bwpEntryPtr = getNextFreeBwpEntry();
	if (bwpEntryPtr != NULL)
	{
		bwpEntryPtr->bwpCtxt.bwpIndex = bwpIndex;
		// Set the mibIdx to the one provided and update the other values
		memcpy(&bwpEntryPtr->cfg, svcBwpCfg, sizeof(svcBwpCfg_t));
		// add the new BWP to the list
		addBwpToList(bwpGrpEntryPtr, bwpEntryPtr);
	}
	else
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		return (-2);
	}
	UNLOCK_BWP_LIST();
	DO_LOG_NOTICE(log_handle,"Created BWP with index %lu.%lu",bwpGrpIndex,bwpIndex);
	DO_LOG_DEBUG(log_handle,"BWP %lu.%lu; CIR=%d,EIR=%d,CBS=%d,EBS=%d",bwpGrpIndex,bwpIndex, bwpEntryPtr->cfg.cir,bwpEntryPtr->cfg.eir,bwpEntryPtr->cfg.cbs,bwpEntryPtr->cfg.ebs);
	return (2); // Operation accepted.
}

/**
 * @brief	Delete the BWP with the supplied bwpIndex.
 *
 */
int svcBwpDelete(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;
	bwpNum_t		bwpNum;

	LOCK_BWP_LIST(0);

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP Group Index (%lu)",bwpGrpIndex);
		return (1);
	}

	// Test is the bwpIndex known and get it's associated bwpNum
	if (!isBwpIndexValid(bwpGrpEntryPtr,bwpIndex, &bwpNum))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent BWP (index = %lu.%lu)",bwpGrpIndex,bwpIndex);
		return (1);	//Operation rejected due to invalid or nonexistent BWP index
	}

	if (_evcIsBwpGrpInUse(bwpGrpIndex))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected because BWP (index = %lu.%lu) is in use",bwpGrpIndex,bwpIndex);
		return (2);	//Operation rejected due to invalid or nonexistent BWP index
	}

	removeBwpFromList(bwpGrpEntryPtr, &bwpArray[bwpNum]);
	UNLOCK_BWP_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted BWP with index = %lu.%lu",bwpGrpIndex,bwpIndex);
	return (3); // Operation accepted
}

/*
 * @brief Read the service BWP configuration.
 */
int svcBwpRead(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex, svcBwpCfg_t *svcBwpCfg)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;
	bwpNum_t		bwpNum;

	LOCK_BWP_LIST(0);

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP Group Index (%lu)",bwpGrpIndex);
		return (1);
	}

	// Test is the bwpIndex known and get it's associated bwpNum
	if (!isBwpIndexValid(bwpGrpEntryPtr, bwpIndex, &bwpNum))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent BWP index %lu.%lu",bwpGrpIndex,bwpIndex);
		return (1);
	}

	if (svcBwpCfg != NULL)
	{
		memcpy(svcBwpCfg, &bwpArray[bwpNum].cfg, sizeof(svcBwpCfg_t));
	}

	UNLOCK_BWP_LIST();
	DO_LOG_DEBUG(log_handle,"Read info for BWP %lu.%lu",bwpGrpIndex,bwpIndex);
	return (2); // Operation accepted
}

/*
 * @brief Write a new value to the selected BWP parameter.
 */
int svcBwpWrite(bwpGrpIndex_t bwpGrpIndex, bwpIndex_t bwpIndex, bwpWrParam_t wrParam, void *paramValue)
{
	bwpGrpEntry_t	*bwpGrpEntryPtr;
	bwpNum_t		bwpNum;

	LOCK_BWP_LIST(0);

	bwpGrpEntryPtr = getBwpGrpWithIndex(bwpGrpIndex);
	if (bwpGrpEntryPtr == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid BWP Group Index (%lu)",bwpGrpIndex);
		return (1);
	}

	// Test is the bwpIndex known and get it's associated bwpNum
	if (!isBwpIndexValid(bwpGrpEntryPtr, bwpIndex, &bwpNum))
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent BWP index %lu.%lu",bwpGrpIndex,bwpIndex);
		return (1);
	}

	if (paramValue == NULL)
	{
		UNLOCK_BWP_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to NULL value");
		return (3);
	}

	switch(wrParam)
	{
		case bwpParam_cosIndex:
		{
			cosIndex_t	*cosIndexPtr = (cosIndex_t*)paramValue;

			if (*cosIndexPtr != 0)
			{
				if(!svcCosIsCosIndexValid(*cosIndexPtr))
				{
					UNLOCK_BWP_LIST();
					DO_LOG_INFO(log_handle,"Operation rejected due to unknown COS value");
					return (3);
				}
			}

			if (bwpArray[bwpNum].cfg.cosIndex != *cosIndexPtr)
			{
				// TODO: If cos index changes on BWP, do I need to update all UNI and EVC-per-Uni that use this BWG?

				// Write the new value into the EVC
				bwpArray[bwpNum].cfg.cosIndex = *cosIndexPtr;
				DO_LOG_NOTICE(log_handle,"On BWP with index %lu.%lu the cosIndex value was changed to %lu",bwpGrpIndex,bwpIndex,*cosIndexPtr);
			}
			break;
		}
		default:
		{
			UNLOCK_BWP_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_BWP_LIST();
	return (4); // Operation accepted
}

/*
 * @brief Add/updates hardware tables for the BWP Group on an EVC or Interface
 */
int svcBwpGrpAddToEvc(evcNum_t evcNum, iFace_t iFace, bwpGrpIndex_t bwpGrpIndex)
{
	bwpIndex_t		bwpIndex;
	bwpNum_t		bwpNum;
	uint8_t			prio;
	uint8_t			k;
	uint16_t 		cosIdList[8];
	uint16_t		idListLen;


	if (bwpGrpIndex == 0)
	{
		// There is no BWP Group... so reset the flow num table to defaults
		setFlowTableDefaults(evcNum, iFace, false);
		return (0);
	}
	LOCK_BWP_LIST(0);

	// configure the policer for the EVC
	// TODO : must verify this is correct
	// Might need to initialise the policer values for all Prio before mapping in the new ones - TODO: check is this necessary?
	for (prio = 0; prio < 8; prio++)
	{
		if (polSetQOSCfg(evcNum, prio,0,0,0,0,0,0))
		{
			UNLOCK_BWP_LIST();
			return(-1);
		}
	}

	bwpIndex = svcBwpReadList(bwpGrpIndex,0);
	while (bwpIndex != 0)
	{
		// go through each BWP in the group and configure the HW

		// Get the bwpNum (Note this is also the flowNum value)
		bwpNum = _bwpIndexToBwpNum(bwpGrpIndex,bwpIndex);
		if (bwpNum != -1)
		{
			if (bwpArray[bwpNum].cfg.cosIndex != 0)
			{
				if (svcCosAddToEvc(bwpArray[bwpNum].cfg.cosIndex, bwpNum, evcNum, iFace) != 0)
				{
					UNLOCK_BWP_LIST();
					return(-1);
				}
			}

			// configure the policer for the EVC
			if (iFace == lanPort)
			{
				// TODO : must verify this is correct

				// Get a list of the incoming COS from the associated COS profile.
				_getCosListFromProfile(bwpArray[bwpNum].cfg.cosIndex, false, cosIdList, &idListLen);

				// The current BWP profile should be applied to each valid incoming COS
				for (k = 0; k < idListLen; k++)
				{

					// Clear the policer stats
					polSetStats(evcNum, cosIdList[k],0, 0, 0, 0);
					// Clear the associated context RAM
					polSetQOSCtxt(evcNum, cosIdList[k],0, 0, 0);

					// TODO: Do I need to check flowNumEntry.pcpDiscard to ensure flowNumEntry.mapPcpPrio is valid?
					if (polSetQOSCfg(evcNum, cosIdList[k],(uint16_t)bwpArray[bwpNum].cfg.ebs,(uint16_t)bwpArray[bwpNum].cfg.cbs,bwpArray[bwpNum].cfg.eir,bwpArray[bwpNum].cfg.cir,bwpArray[bwpNum].cfg.cf,bwpArray[bwpNum].cfg.cm))
					{
						UNLOCK_BWP_LIST();
						return(-1);
					}
					// If there is any valid BWP then make sure the policer is enabled.
					polEnPolicing(enable);
				}

			}

		}
		bwpIndex = svcBwpReadList(bwpGrpIndex,bwpIndex);
	}
	UNLOCK_BWP_LIST();
	return (0);
}

bool _bwpIsCosIndexInUse(cosIndex_t cosIndex)
{
	bwpGrpIndex_t	bwpGrpIndex;
	bwpIndex_t		bwpIndex;
	bwpNum_t		bwpNum;
	svcIfaceCfg_t	svcIfaceCfg;

	LOCK_BWP_LIST(0);
	// Get the group index
	bwpGrpIndex = svcBwpGrpReadList(0);
	while (bwpGrpIndex != 0)
	{
		// The BWP Group has been selected, now check all BWPs...
		bwpIndex = svcBwpReadList(bwpGrpIndex,0);
		while (bwpIndex != 0)
		{
			bwpNum = _bwpIndexToBwpNum(bwpGrpIndex,bwpIndex);
			EGL_ASSERT(bwpNum != -1,1);
			if (bwpArray[bwpNum].cfg.cosIndex == cosIndex)
			{
				// Check is the BWP group in use on either Interface
			    EGL_ASSERT(svcIfaceRead(wanPort, &svcIfaceCfg,NULL,NULL) == 2, 1);
				if (svcIfaceCfg.ingressBwpGrpIndex == bwpGrpIndex)
				{
					UNLOCK_BWP_LIST();
					return (true);
				}

				EGL_ASSERT(svcIfaceRead(lanPort, &svcIfaceCfg,NULL,NULL) == 2, 1);
				if (svcIfaceCfg.ingressBwpGrpIndex == bwpGrpIndex)
				{
					UNLOCK_BWP_LIST();
					return (true);
				}
				if (_evcIsBwpGrpInUse(bwpGrpIndex))
				{
					UNLOCK_BWP_LIST();
					return(true);
				}
			}
			bwpIndex = svcBwpReadList(bwpGrpIndex,bwpIndex);
		}

		bwpGrpIndex = svcBwpGrpReadList(bwpGrpIndex);
	}
	UNLOCK_BWP_LIST();
	return(false);
}
