/*******************************************************************************
 * (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/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/service_bwp/src/svc_bwp_types.h>
#include <api_oam_sdk/service_uni/src/svc_uni_types.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>

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

static uniEntry_t		uniArray[MAX_NUM_UNIS];
static uint16_t			log_handle				= 0;		// handle used to log messages for this module
static osMutex_t		uniListLock				= NULL;
static bool				uniInitialised			= false;

#define LOCK_UNI_LIST(timeout)	osRecursiveMutexTake(uniListLock,timeout)
#define UNLOCK_UNI_LIST()		osRecursiveMutexGive(uniListLock)

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

/*
 * @brief	This sets the uniEntry values to their default
 *
 * In most cases this should only be done with uniLock enabled
 * to ensure thread safe operation
 */
static void setUniDefaults(uniEntry_t *uniEntry)
{
	memset(uniEntry,0,sizeof(uniEntry_t));
	uniEntry->cfg.bundlingMultiplex	= allToOne;
	uniEntry->cfg.ceVlanUntagged	= 1;
}

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

	// Initialise all entries in the UNI store to defaults
	for (k = 0; k < MAX_NUM_UNIS; k++)
	{
		setUniDefaults(&uniArray[k]);
	}
	DO_LOG_DEBUG(log_handle,"Initialised the UNI List");
}

/*
 * @brief	Called at device startup to initialise UNI module
 *
 * Safe to call repeatedly.
 */
void uniInit(void)
{
	pVidEntry_t	pVidEntry;

	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("UNI");

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

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

		DO_LOG_DEBUG(log_handle,"Preparing the UNI List for first use");
		LOCK_UNI_LIST(0);
		clearUniList();
		UNLOCK_UNI_LIST();

		// Ensure that the HW is also set to the default values
		pVidEntry.de = 0;
		pVidEntry.prio = uniArray[wanPort].cfg.cePriorityUntaged;
		pVidEntry.vlan = uniArray[wanPort].cfg.ceVlanUntagged;
		writePVidTable((mrPort_t) wanPort, &pVidEntry);
		pVidEntry.prio = uniArray[lanPort].cfg.cePriorityUntaged;
		pVidEntry.vlan = uniArray[lanPort].cfg.ceVlanUntagged;
		writePVidTable((mrPort_t) lanPort, &pVidEntry);

		uniInitialised = true;
	}
}


/*
 * @brief Test if the supplied UNI Index is valid
 */
static bool isUniValid(iFace_t uni)
{
	if ((uni != wanPort) && (uni != lanPort))
	{
		DO_LOG_DEBUG(log_handle,"The UNI Index (%lu) was invalid",uni);
		return (false);
	}
	return(true);
}

/*
 * @brief Create a new service UNI.
 */
int svcUniCreate(iFace_t uni, svcUniCfg_t *svcUniCfg)
{
	pVidEntry_t pVidEntry;

	// Test is the uni valid and not already created
	if ((!isUniValid(uni)) || (uniArray[uni].created == true))
	{
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent UNI index (%lu)",uni);
		return (1);
	}

	if (svcUniCfg == NULL)
	{
		DO_LOG_INFO(log_handle,"Operation rejected on UNI %d due to NULL value for svcUniCfg",uni);
		return (2);
	}

	if ((svcUniCfg->bundlingMultiplex < allToOne) || (svcUniCfg->bundlingMultiplex > bundlingMultiplex))
	{
		DO_LOG_INFO(log_handle,"Operation rejected on UNI %d due to invalid bundlingMultiplex value %d",uni,svcUniCfg->bundlingMultiplex);
		return (2);
	}

	if ((svcUniCfg->ceVlanUntagged == 0) || (svcUniCfg->ceVlanUntagged > 4095))
	{
		DO_LOG_INFO(log_handle,"Operation rejected on UNI %d due to invalid ceVlanUntagged value=%d",uni,svcUniCfg->ceVlanUntagged);
		return (3);
	}

	if (svcUniCfg->cePriorityUntaged > prio7)
	{
		DO_LOG_INFO(log_handle,"Operation rejected on UNI %d due to invalid cePriorityUntaged value=%d",uni,svcUniCfg->cePriorityUntaged);
		return (4);
	}

	LOCK_UNI_LIST(0);
	if (svcUniCfg != NULL)
	{
		memcpy(&uniArray[uni].cfg, svcUniCfg, sizeof(svcUniCfg_t));
	}
#if 0 //ND - the PVid settings are no longer controlled by the API - Netconf directly maintains them.
	pVidEntry.de = 0;
	pVidEntry.prio = svcUniCfg->cePriorityUntaged;
	pVidEntry.vlan = svcUniCfg->ceVlanUntagged;
	writePVidTable((mrPort_t) uni, &pVidEntry);
#endif
	uniArray[uni].created = true;

	UNLOCK_UNI_LIST();
	DO_LOG_NOTICE(log_handle,"Created UNI (%lu)",uni);
	return (5); // Operation accepted
}

/*
 * @brief Delete the specified service UNI.
 */
int svcUniDelete(iFace_t uni)
{
	pVidEntry_t	pVidEntry;
	evcIndex_t	nextEvcIndex;
	evcIndex_t	evcToDelete;
	evcUniCfg_t evcUniCfg;
	int			retVal;

	LOCK_UNI_LIST(0);
	// Test is the uni known and get it's associated uni
	if (!isUniValid(uni) || (uniArray[uni].created == false))
	{
		UNLOCK_UNI_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent UNI index (%lu)",uni);
		return (1);
	}

	nextEvcIndex = svcEvcReadList(0);
	while (nextEvcIndex != 0)
	{
		// first check if this UNI is associated with this EVC...
		retVal = svcEvcUniRead(uni, nextEvcIndex, &evcUniCfg);
		if ((retVal == 2) && (evcUniCfg.evcUniType != eu_unknown))
		{
			evcToDelete = nextEvcIndex;
			// Get the next EVC Index. BEFORE deleting the current one
			nextEvcIndex = svcEvcReadList(nextEvcIndex);

			if (svcEvcDelete(evcToDelete) != 2)
			{
				UNLOCK_UNI_LIST();
				// TODO: see if this can happen and if it can should the error be higher severity?
				DO_LOG_CRITICAL(log_handle,"Failed to delete EVC with index = %lu",evcToDelete);
				return (-1); // Not all child objects were deleted
			}
		}
		else
		{
			nextEvcIndex = svcEvcReadList(nextEvcIndex);
		}
	}
	setUniDefaults(&uniArray[uni]);
#if 0 //ND - the PVid settings are no longer controlled by the API - Netconf directly maintains them.
	pVidEntry.de = 0;
	pVidEntry.prio = uniArray[uni].cfg.cePriorityUntaged;
	pVidEntry.vlan = uniArray[uni].cfg.ceVlanUntagged;
	writePVidTable((mrPort_t) uni, &pVidEntry);
#endif
	UNLOCK_UNI_LIST();
	DO_LOG_NOTICE(log_handle,"Deleted the UNI (%lu)",uni);
	return (2); // Operation accepted
}

/*
 * @brief Read the service UNI configuration.
 */
int svcUniRead(iFace_t uni, svcUniCfg_t *svcUniCfg)
{

	LOCK_UNI_LIST(0);
	// Test is the UNI known
	if (!isUniValid(uni) || (uniArray[uni].created == false))
	{
		UNLOCK_UNI_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent UNI index (%lu)",uni);
		return (1);
	}

	if (svcUniCfg != NULL)
	{
		memcpy(svcUniCfg, &uniArray[uni].cfg, sizeof(svcUniCfg_t));
	}

	UNLOCK_UNI_LIST();
	DO_LOG_DEBUG(log_handle,"Read info for UNI (%lu)",uni);
	return (2); // Operation accepted
}



#if 0 		// UNI attributes are set at creation!
/**
 * @brief	Write a new value to the selected service UNI parameter.
 */
int svcUniWrite(iFace_t uni, uniWrParam_t wrParam, void *paramValue)
{
	pVidEntry_t	pVidEntry;

	LOCK_UNI_LIST(0);
	// Test is the uni known and get it's associated uni
	if (!isUniValid(uni))
	{
		UNLOCK_UNI_LIST();
		DO_LOG_INFO(log_handle,"Operation rejected due to invalid or nonexistent UNI index (%lu)",uni);
		return (1);
	}

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

	switch(wrParam)
	{
		case uniParam_bundlingMultiplex:
		{
			bundlingMultiplex_t	*bundlingMultiplexPtr = (bundlingMultiplex_t*)paramValue;

			//check the value is one of the types supported
			switch(*bundlingMultiplexPtr)
			{
				case allToOne:
				case bundling:
				case multiplex:
				case bundlingMultiplex:
					break;
				default:
				{
					UNLOCK_UNI_LIST();
					DO_LOG_INFO(log_handle,"Operation rejected due to invalid UNI Bundling Multiplex Type %d",*bundlingMultiplexPtr);
					return (3);
				}
			}

			if (uniArray[uni].cfg.bundlingMultiplex != *bundlingMultiplexPtr)
			{
				// Write the new value into the UNI
				uniArray[uni].cfg.bundlingMultiplex = *bundlingMultiplexPtr;
				DO_LOG_NOTICE(log_handle,"On UNI with index %d the Bundling Multiplex Type was changed to %d",uni,*bundlingMultiplexPtr);

				//TODO: add code to carry out action of changing Bundling Multiplex Type on this interface
			}
			break;
		}
		case uniParam_ceVlanUntagged:
		{
			uint16_t	*ceVlanUntaggedPtr = (uint16_t*)paramValue;

			//check is the new value valid
			if ((*ceVlanUntaggedPtr = 0) || (*ceVlanUntaggedPtr > 4085))
			{
				UNLOCK_UNI_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid ceVlanUntagged value %d",*ceVlanUntaggedPtr);
				return (3);
			}

			if (uniArray[uni].cfg.ceVlanUntagged != *ceVlanUntaggedPtr)
			{
				// Write the new value into the UNI
				uniArray[uni].cfg.ceVlanUntagged = *ceVlanUntaggedPtr;
				DO_LOG_NOTICE(log_handle,"On UNI with index %d the ceVlanUntagged value was changed to %d",uni,*ceVlanUntaggedPtr);

				readPVidTable((mrPort_t) uni, &pVidEntry);
				pVidEntry.vlan = uniArray[uni].cfg.ceVlanUntagged;
				writePVidTable((mrPort_t) uni, &pVidEntry);
			}
			break;
		}
		case uniParam_cePriorityUntaged:
		{
			prio_t	*cePriorityUntagedPtr = (prio_t*)paramValue;

			//check is the index valid
			if (*cePriorityUntagedPtr > prio7)
			{
				UNLOCK_UNI_LIST();
				DO_LOG_INFO(log_handle,"Operation rejected due to invalid priority %d",*cePriorityUntagedPtr);
				return (3);
			}

			if (uniArray[uni].cfg.cePriorityUntaged != *cePriorityUntagedPtr)
			{
				// Write the new value into the UNI
				uniArray[uni].cfg.cePriorityUntaged = *cePriorityUntagedPtr;
				DO_LOG_NOTICE(log_handle,"On UNI with index %d the cePriorityUntaged value was changed to %d",uni,*cePriorityUntagedPtr);

				readPVidTable((mrPort_t) uni, &pVidEntry);
				pVidEntry.prio = uniArray[uni].cfg.cePriorityUntaged;
				writePVidTable((mrPort_t) uni, &pVidEntry);
			}
			break;
		}
		default:
		{
			UNLOCK_UNI_LIST();
			DO_LOG_INFO(log_handle,"Operation rejected due to the selected managed object being Read-Only");
			return (2);
			break;
		}
	}
	UNLOCK_UNI_LIST();
	return (4); // Operation accepted
}
#endif

