/*******************************************************************************
 * (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/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/system/inc/system.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 svcCosEntry_t	cosArray[MAX_NUM_COS];
static uint16_t			log_handle				= 0;		// handle used to log messages for this module
static osMutex_t		cosListLock				= NULL;
static bool				cosInitialised			= false;
static cosIndex_t		cosNextIndex = 1;
static cosNum_t			cosCount;
static TAILQ_HEAD(cosHeasEntry_s,struct svcCosEntry_s,)	llCosHead;

#define LOCK_COS_LIST(timeout)	osRecursiveMutexTake(cosListLock,timeout)
#define UNLOCK_COS_LIST()		osRecursiveMutexGive(cosListLock)

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

/*
 * @brief	This sets the cosEntry values to their default
 *
 * In most cases this should only be done with cosLock enabled
 * to ensure thread safe operation
 */
static void setCosDefaults(svcCosEntry_t *cosEntry)
{
	memset(cosEntry,0,sizeof(svcCosEntry_t));
	cosEntry->cosCtxt.cosNum 		= INVALID_COS_NUM;
	cosEntry->cfg.cosType			= cosType_pcp;
	cosEntry->cfgEgl.colourAware	= true;
	cosEntry->cfgEgl.colourForward	= true;
	cosEntry->cfgEgl.colourId		= col_dei;
	sprintf(cosEntry->cfg.identifierList,"0:7");	// Map all prio in this COS
	sprintf(cosEntry->cfgEgl.colourMap,"0'7");		// all prio are Green
}

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

	cosNextIndex	= 1;
	cosCount		= 0;

	TAILQ_INIT(&llCosHead);

	// Initialise all entries in the MD store to defaults
	for (k = 0; k < MAX_NUM_COS; k++)
	{
		setCosDefaults(&cosArray[k]);
	}
	DO_LOG_DEBUG(log_handle,"Initialised the COS List");
}

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

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

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

		DO_LOG_DEBUG(log_handle,"Preparing the COS List for first use");
		LOCK_COS_LIST(0);
		clearCosList();
		UNLOCK_COS_LIST();
		cosInitialised = true;
	}
}

/*
 * @brief Convert an cosNum into a MD MIB index
 */
cosIndex_t _cosNumToMibIndex(cosNum_t cosNum)
{
	cosIndex_t cosIndex  = 0;

	LOCK_COS_LIST(0);
	if (cosArray[cosNum].cosCtxt.inUse)
	{
		cosIndex = cosArray[cosNum].cosCtxt.cosIndex;
	}
	UNLOCK_COS_LIST();
	return (cosIndex);
}


/*
 * @brief Convert a MD MIB index into an cosNum
 */
static cosNum_t	cosIndexToCosNumNoLocking(cosIndex_t cosIndex)
{
	svcCosEntry_t	*cosEntryPtr;
	cosNum_t		cosNum = -1;

	// Start searching valid MDs at the head of the list
	cosEntryPtr = TAILQ_FIRST(&llCosHead);
	while (cosEntryPtr != NULL)
	{
		if (cosEntryPtr->cosCtxt.cosIndex == cosIndex)
		{
			cosNum = cosEntryPtr->cosCtxt.cosNum;
			return (cosNum);	//found it
		}
		cosEntryPtr = TAILQ_NEXT(cosEntryPtr,ll);
	}
	return -1;	// cosIndex not found
}

/*
 * @brief Convert a MD MIB index into an cosNum
 */
cosNum_t	_cosIndexToCosNum(cosIndex_t cosIndex)
{
	cosNum_t cosNum;

	LOCK_COS_LIST(0);
	cosNum = cosIndexToCosNumNoLocking(cosIndex);
	UNLOCK_COS_LIST();
	return cosNum;
}

/*
 * @brief Read the next entry in the COS List
 *
 * @param[in] cosIndex of previous entry or 0
 * @return	cosIndex of the next entry or 0 if no more entries
 *
 * Read the next entry in the COS List. If prevCosIndex is 0 it
 * returns the cosIndex of the first COS entry or 0 if there are
 * no COS entries. If a valid cosIndex is provided it returns
 * the cosIndex of the next COS in the list.
 */
cosIndex_t svcCosReadList(cosIndex_t prevCosIndex)
{
	cosNum_t 	nextCosNum;
	cosIndex_t	theIndex;

	LOCK_COS_LIST(0);
	if (prevCosIndex == 0)
	{
		if (!TAILQ_EMPTY(&llCosHead))
		{
			theIndex = TAILQ_FIRST(&llCosHead)->cosCtxt.cosIndex;
			UNLOCK_COS_LIST();
			return theIndex;
		}
	}
	else
	{
		nextCosNum = cosIndexToCosNumNoLocking(prevCosIndex);
		if (nextCosNum != -1)
		{
			if (TAILQ_NEXT(&cosArray[nextCosNum],ll) != NULL)
			{
				theIndex = TAILQ_NEXT(&cosArray[nextCosNum],ll)->cosCtxt.cosIndex;
				UNLOCK_COS_LIST();
				return theIndex;
			}
		}
	}
	UNLOCK_COS_LIST();
	return 0;
}

/*
 * @brief	Find the next free COS Entry
 */
static svcCosEntry_t* getNextFreeCosEntry(void)
{
	cosNum_t	k;

	LOCK_COS_LIST(0);
	for (k = 0; k < MAX_NUM_COS; k++)
	{
		if (!cosArray[k].cosCtxt.inUse)
		{
			cosArray[k].cosCtxt.cosNum = k;
			cosArray[k].cosCtxt.inUse = true;
			UNLOCK_COS_LIST();
			return (&cosArray[k]);
		}
	}
	return NULL;
}

/*
 * @brief	Add a new cosEntry into the COS List
 */
static void addCosToList(svcCosEntry_t *cosEntry)
{
	ASSERT_NOT_NULL(cosEntry);

	TAILQ_INSERT_TAIL(&llCosHead,cosEntry,ll);
	cosCount++;

	// The COS index can be specified by the user. So
	// it is necessary to keep the cosNextIndex aligned
	if (cosNextIndex <= cosEntry->cosCtxt.cosIndex)
	{
		cosNextIndex = cosEntry->cosCtxt.cosIndex + 1;
	}
}

/*
 * @brief	Remove the COS entry, pointed to by cosEntry, from the COS List
 */
static void removeCosFromList(svcCosEntry_t *cosEntry)
{
	ASSERT_NOT_NULL(cosEntry);

	TAILQ_REMOVE(&llCosHead,cosEntry,ll);
	setCosDefaults(cosEntry);
	cosCount--;
}


/*
 * @brief Returns the next available COS index
 */
cosIndex_t	svcCosGetNextIndex(void)
{
	return cosNextIndex;
}

/*
 * @brief Test if the supplied COS Index is valid
 */
static bool isCosIndexValid(cosIndex_t cosIndex, cosNum_t *cosNum)
{
	cosNum_t	theCosNum;

	if ((cosIndex == 0) || (cosIndex >= cosNextIndex))
	{
COS_INDEX_INVALID:
		DO_LOG_DEBUG(log_handle,"The COS Index (%lu) was invalid",cosIndex);
		return false;
	}

	// Test is the cosIndex known and get it's associated cosNum
	theCosNum = cosIndexToCosNumNoLocking(cosIndex);
	if (theCosNum != -1)		// If the COS index is found
	{
		if (cosNum != NULL)
		{
			*cosNum = theCosNum;
		}
		return(true);
	}
	goto COS_INDEX_INVALID;
}

/*
 * @brief Test if the supplied COS Index is valid
 */
bool svcCosIsCosIndexValid(cosIndex_t cosIndex)
{
	bool retVal;

	LOCK_COS_LIST(0);
	retVal = isCosIndexValid(cosIndex, NULL);
	UNLOCK_COS_LIST();
	return(retVal);
}

/*
 * @brief Create a new service COS.
 */
int svcCosCreate(cosIndex_t cosIndex, svcCosCfg_t *svcCosCfg, svcCosEglCfg_t *svcCosEglCfg)
{
	svcCosEntry_t	*cosEntryPtr;
	uint16_t		colMaplistLen;
	uint16_t		identListLen;

	LOCK_COS_LIST(0);
	// Validate the cosIndex value provided
	if (cosIndex == 0 || isCosIndexValid(cosIndex, NULL)) //|| (cosIndex > cosNextIndex))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid COS Index (%lu)",cosIndex);
		return (1);
	}

	if ((svcCosCfg == NULL) || (svcCosEglCfg == NULL))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid config pointer");
		return(-1); //Operation rejected due to invalid config pointer
	}

	//Begin testing the fields of the entry...
	// Test the COS type
	if ((svcCosCfg->cosType > cosType_l2cp) || (svcCosCfg->cosType < cosType_interface))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid COS type (%d)",svcCosCfg->cosType);
		return(2); //Operation rejected due to invalid MD Level
	}

	if (!getListFromStringMap(svcCosCfg->identifierList, NULL,0,7,&identListLen))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid COS identifier list (\"%s\")",svcCosCfg->identifierList);
		return(3); //Operation rejected due to invalid identifierList
	}

	if (!getListFromStringMap(svcCosEglCfg->colourMap, NULL,0,1,&colMaplistLen))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid colourMap list (\"%s\")",svcCosEglCfg->colourMap);
		return(4); //Operation rejected due to invalid identifierList
	}

	// Verify that the list lengths are not too long and that they are equal
	if ((identListLen > 8) || (colMaplistLen > 8) || (identListLen != colMaplistLen))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid colourMap or COS identifier list length (\"%s\")",svcCosEglCfg->colourMap);
		if (identListLen > 7)
		{
			return(3); //Operation rejected due to invalid identifierList length
		}
		else
		{
			return(4); //Operation rejected due to invalid colourMap List length
		}
	}

	cosEntryPtr = getNextFreeCosEntry();
	if (cosEntryPtr != NULL)
	{
		// Set the mibIdx to the one provided and update the other values
		cosEntryPtr->cosCtxt.cosIndex = cosIndex;

		memcpy(&cosEntryPtr->cfg, svcCosCfg, sizeof(svcCosCfg_t));
		memcpy(&cosEntryPtr->cfgEgl, svcCosEglCfg, sizeof(svcCosEglCfg_t));

		// add the new COS to the list
		addCosToList(cosEntryPtr);

		// TODO: Call function to modify hardware - if appropriate!?
	}
	else
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to insufficient resources");
		return (-2);
	}
	UNLOCK_COS_LIST();
	DO_LOG_NOTICE(log_handle,"Created COS with index = %lu",cosIndex);
	return (5); // Operation accepted.
}

/*
 * @brief This function returns true if the specified cosIndex is in use by any EVC or Interface
 */
static bool isCosInUse(cosIndex_t cosIndex)
{
	return(_bwpIsCosIndexInUse(cosIndex));
}

/**
 * @brief	Delete the COS with the supplied cosIndex.
 *
 */
int svcCosDelete(cosIndex_t cosIndex)
{
	cosNum_t	cosNum;

	LOCK_COS_LIST(0);
	// Test is the cosIndex known and get it's associated cosNum
	if (!isCosIndexValid(cosIndex, &cosNum))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent COS (index = %lu)",cosIndex);
		return (1);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	if (isCosInUse(cosIndex))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected because COS (index = %lu) is in use by an EVC or Interface",cosIndex);
		return (2);	//Operation rejected due to invalid or nonexistent Maintenance Domain
	}

	removeCosFromList(&cosArray[cosNum]);
	UNLOCK_COS_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted COS with index = %lu",cosIndex);
	return (3); // Operation accepted
}

/*
 * @brief Read the service COS configuration.
 */
int svcCosRead(cosIndex_t cosIndex, svcCosCfg_t *svcCosCfg, svcCosEglCfg_t *svcCosEglCfg)
{
	cosNum_t		cosNum;

	LOCK_COS_LIST(0);
	// Test is the cosIndex known and get it's associated cosNum
	if (!isCosIndexValid(cosIndex, &cosNum))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent COS index (%lu)",cosIndex);
		return (1);
	}

	if (svcCosCfg != NULL)
	{
		memcpy(svcCosCfg, &cosArray[cosNum].cfg, sizeof(svcCosCfg_t));
	}

	if (svcCosEglCfg != NULL)
	{
		memcpy(svcCosEglCfg, &cosArray[cosNum].cfgEgl, sizeof(svcCosEglCfg_t));
	}

	UNLOCK_COS_LIST();
	DO_LOG_DEBUG(log_handle,"Read info for COS (%lu)",cosIndex);
	return (2); // Operation accepted
}

/**
 * @brief	Write a new value to the selected service COS parameter.
 */
int svcCosWrite(cosIndex_t cosIndex, cosWrParam_t wrParam, void *paramValue)
{
	cosNum_t		cosNum;
	uint16_t		listLen;

	LOCK_COS_LIST(0);
	// Test is the cosIndex known and get it's associated cosNum
	if (!isCosIndexValid(cosIndex, &cosNum))
	{
		UNLOCK_COS_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent COS index (%lu)",cosIndex);
		return (1);
	}

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

	switch(wrParam)
	{
	// TODO: add support for writing to svcCosEglCfg parameters - if appropriate to do so.
		case cosParam_cosIdentifierList:
		{
			char	*identifierListPtr = (char*)paramValue;

			if (isCosInUse(cosIndex))
			{
				UNLOCK_COS_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected because COS %lu is in use by an EVC or Interface");
				return (2);
			}

			//check the string length
			if (strlen(identifierListPtr) > 256)
			{
				UNLOCK_COS_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid string length for identifierListPtr");
				return (3);
			}

			// see can we parse the string
			if (!getListFromStringMap(identifierListPtr,NULL,0,7,&listLen))
			{
				UNLOCK_COS_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid string content for identifierListPtr");
				return (3);
			}

			if (listLen > 8)
			{
				UNLOCK_COS_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid string content for identifierListPtr");
				return (3);
			}

			if (strcmp(cosArray[cosNum].cfg.identifierList,identifierListPtr) != 0)
			{
				// Write the new value into the COS
				strcpy(cosArray[cosNum].cfg.identifierList,identifierListPtr);
				DO_LOG_NOTICE(log_handle,"On COS with index %lu the identifier List value was changed to \"%s\"",cosIndex,identifierListPtr);
			}
			break;
		}
		default:
		{
			UNLOCK_COS_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_COS_LIST();
	return (4); // Operation accepted

}

int	svcCosAddToEvc(cosIndex_t cosIndex, bwpNum_t bwpNum, evcNum_t evcNum, iFace_t iFace)
{
	cosNum_t		cosNum;
	uint16_t		cosIdList[64];
	uint16_t		cosColourList[64];
	uint16_t 		idListLen;
	uint16_t 		colListLen;
	uint16_t		k;
	flowNumEntry_t	flowNumEntry;
	evcTblEntry_t	evcTblEntry;

	cosNum = _cosIndexToCosNum(cosIndex);
	if (cosNum != -1)
	{
		readEvcTbl(evcNum, &evcTblEntry, iFace);
		if (cosArray[cosNum].cfg.cosType == cosType_dscp)
		{
			evcTblEntry.cosDscp 		= (uint8_t)true;		// ND 4 April 2016 - This bit is unused - bits 2 and 3 in dev_config_reg are used to enable/disable cosDSCP per port if DSCP is configured on that port
			evcTblEntry.usePcpEvcCol	= (uint8_t)false;
		}
		else
		{
			evcTblEntry.cosDscp 		= (uint8_t)false;		// ND 4 April 2016 - This bit is unused - bits 2 and 3 in dev_config_reg are used to enable/disable cosDSCP per port if DSCP is configured on that port

			if (cosArray[cosNum].cfgEgl.colourId == col_dei)
			{
				evcTblEntry.usePcpEvcCol	= (uint8_t)false;
			}
			else
			{
				evcTblEntry.usePcpEvcCol	= (uint8_t)true;
			}
		}

		if (!getListFromStringMap(cosArray[cosNum].cfg.identifierList,cosIdList, 0, (cosArray[cosNum].cfg.cosType == cosType_dscp ? 64 : 7), &idListLen))
		{
			return (-1);
		}
		if (!getListFromStringMap(cosArray[cosNum].cfgEgl.colourMap,cosColourList, 0, (cosArray[cosNum].cfg.cosType == cosType_dscp ? 64 : 7), &colListLen))
		{
			return (-1);
		}
		if (idListLen != colListLen)
		{
			return (-1);
		}
		writeEvcTbl(evcNum, &evcTblEntry, iFace);
		for (k=0;k<idListLen;k++)
		{
			flowNumEntry.flowNum	= bwpNum;
			flowNumEntry.pcpEvcCol	= cosColourList[k];
			flowNumEntry.colAware	= cosArray[cosNum].cfgEgl.colourAware;
			flowNumEntry.colFwd		= cosArray[cosNum].cfgEgl.colourForward;
			flowNumEntry.pcpDiscard	= (cosColourList[k] == 0xFFFF ? 1 : 0);
			flowNumEntry.mapPcpPrio	= cosArray[cosNum].cfgEgl.outerTagCos;
			writeFlowNumTbl((evcNum * 8) + cosIdList[k], iFace, &flowNumEntry);
		}
	}
	return (0);
}

void _getCosListFromProfile(cosIndex_t cosIndex, bool dscp_list, uint16_t *cosIdList, uint16_t *idListLen)
{
	cosNum_t		cosNum;

	cosNum = _cosIndexToCosNum(cosIndex);
	if (cosNum != -1)
	{

		if (!getListFromStringMap(cosArray[cosNum].cfg.identifierList,cosIdList, 0, (dscp_list ? 64 : 7), idListLen))
		{
			*idListLen = 0;
			return;
		}
	}
	else
	{
		*idListLen = 0;
	}
}
