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

#include <api_oam_sdk/common/error_codes/inc/mr_error_codes.h>
#include <api_oam_sdk/continuity_check/src/cc_types.h>
#include <api_oam_sdk/mep/src/mep_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <api_oam_sdk/table_config/inc/table_config.h>
#include <api_oam_sdk/tunnel/inc/tunnel.h>
#include "string.h"
#include "egl_types.h"
#include "osal.h"
#include "egl_assert.h"
#include "egl_log.h"

static uint16_t		log_handle				= 0;		// handle used to log messages for this module

char		tlvOrder[5] = "PIOS";				//!< This string can be used to vary the order of TLVs in Tx Templates


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

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

/*
 * @brief Add or Update (including remove) a TLV to be sent in CCM packets
 *
 * If the length and value parameters are valid for given tlvType then that
 * TLV is added or it's value updated. If the length is set to 0 for Sender
 * or Org TLVs that TLV will be removed. Also if Port or Interface Status
 * TLV values are changed to psNoPortStatusTLV or isNoInterfaceStatusTLV
 * they will be removed. For PS and IS, length is ignored. If a value
 * is invalid, the length invalid or the tlvType unknown then the
 * CCMs TLVs are not updated.
 */
void ccmAddUpdateTlv(lMepNum_t lMepNum,tlvType_t type,uint8_t length,void *value)
{
	ccmTlvByteArray_t	ccmTlvByteArray;
	ccmTlv_t 			ccmTlv;
	uint16_t 			tlvLen;
	bool				reEnable = false;
	psValue_t			*psValuePtr;
	isValue_t			*isValuePtr;

	// Read the current TLV - if any.
	readCfmTlvInTxTempl(lMepNum, ccmTlvByteArray, &tlvLen, CCM_TEMPL);

	// Convert byte array to a Type, length, value
	_extractTlvsFromArray(ccmTlvByteArray, &ccmTlv);

	// Add/update the required TLV
	switch(type)
	{
		case senderIdTlvType:
			if (length > sizeof(ccmTlv.sidTlv.value))
			{
				// The length is too long for the TLV
				DO_LOG_WARNING(log_handle,"Sender ID TLV value is too large. TLV not updated for lMepNum = %d",lMepNum);
				return;
			}
			// Add/update the ccmTLV...
			ccmTlv.sidTlv.present = (length == 0 ? false : true);
			ccmTlv.sidTlv.length = length;
			memcpy(ccmTlv.sidTlv.value,value,length);
			break;
		case organizationSpecificTlvType:
			if (length > sizeof(ccmTlv.orgTlv.value))
			{
				// The length is too long for the TLV
				DO_LOG_WARNING(log_handle,"Organization specific TLV value is too large. TLV not updated for lMepNum = %d",lMepNum);
				return;
			}
			// Add/update the ccmTLV...
			ccmTlv.orgTlv.present = (length == 0 ? false : true);
			ccmTlv.orgTlv.length = length;
			memcpy(ccmTlv.orgTlv.value,value,length);
			break;
		case portStatusTlvType:
			psValuePtr = (psValue_t*)value;
			if ((*psValuePtr < psNoPortStatusTLV) || (*psValuePtr > psUp))
			{
				// The length is invalid
				DO_LOG_WARNING(log_handle,"Port Status TLV value invalid value %d. TLV not updated for lMepNum = %d",*psValuePtr, lMepNum);
				return;
			}
			if (*psValuePtr  == psNoPortStatusTLV)
			{
				ccmTlv.psTlv.present = false;
			}
			else
			{
				ccmTlv.psTlv.present = true;
			}
			ccmTlv.psTlv.value = *psValuePtr;
			break;
		case interfaceStatusTlvType:
			isValuePtr = (isValue_t*)value;
			if ((*isValuePtr < isNoInterfaceStatusTLV) || (*isValuePtr > isLowerLayerDown))
			{
				// The length is invalid
				DO_LOG_WARNING(log_handle,"Interface Status TLV value invalid value %d. TLV not updated for lMepNum = %d",*isValuePtr, lMepNum);
				return;
			}
			if (*isValuePtr  == isNoInterfaceStatusTLV)
			{
				ccmTlv.isTlv.present = false;
			}
			else
			{
				ccmTlv.isTlv.present = true;
			}
			ccmTlv.isTlv.value = *isValuePtr;
			break;
		default:
			DO_LOG_WARNING(log_handle,"Invalid TLV type specified for lMepNum = %d", lMepNum);
			return;
	}

	// Convert the Type, length, value back to a byte array
	_generateTlvArray(&ccmTlv, ccmTlvByteArray, &tlvLen);

	// Stop transmit CCMs on this MEP temporarily (if it's transmitting) - to avoid transmitting a corrupted packet.
	if (isCcmTxEnabled(lMepNum))
	{
		cciDisable(lMepNum);
		reEnable = true;
	}

	//write the new TLV array back into the CCM Template
	writeTlvInTxTempl(lMepNum, tlvLen, ccmTlvByteArray, CCM_TEMPL);

	// Restart CCM transmission
	if (reEnable)
	{
		cciEnable(lMepNum);
	}
}

/*
 * @brief Remove (stop sending) a TLV of the specified type in CCM packets
 */
void ccmRemoveTlv(lMepNum_t lMepNum,tlvType_t type)
{
	//TODO - write ccm Remove TLV
}

/*
 * @brief Provide a means to control the order that TLVs are added to CCM packets
 */
void ccmSetTlvOrder(const char *order)
{
	//TODO - write ccmSetTlvOrder
}

/*
 * @brief converts the structured ccmTlv data type containing TLV information into a simple byte array that can be placed in HW RAM
 *
 * It can only support Port Status, Interface Status, Organisation and Sender ID TLVs.
 * The order in which TLVs are encoded is determined by the global string parameter
 * tlvOrder which defaults to "PIOS". The characters order determines the TLVs order.
 * 'P' = port status, 'I' = interface status, 'O' = Org TLV and 'S' = Sender ID TLV.
 */
int _generateTlvArray(ccmTlv_t *ccmTlv,uint8_t ccmTlvByteArray[],uint16_t *tlvLen)
{
	uint16_t	length = 0;
	int			i;
	int			k;

	ccmTlvByteArray[0] = 0;				 // This ensures that the End TLV is set even if no other TLVs are present

	for (k=0;k<4;k++)
	{
		switch(tlvOrder[k])
		{
			case 'P' :
			{
				if (ccmTlv->psTlv.present == True)
				{
					ccmTlvByteArray[length] = MR_PS_TLV_TYPE;
					ccmTlvByteArray[length+1] = 0;
					ccmTlvByteArray[length+2] = 1;
					ccmTlvByteArray[length+3] = (uint8_t) ccmTlv->psTlv.value;
					length += 4;
				}
				break;
			}
			case 'I' :
			{
				if (ccmTlv->isTlv.present == True)
				{
					ccmTlvByteArray[length] = MR_IS_TLV_TYPE;
					ccmTlvByteArray[length+1] = 0;
					ccmTlvByteArray[length+2] = 1;
					ccmTlvByteArray[length+3] = (uint8_t) ccmTlv->isTlv.value;
					length += 4;
				}
				break;
			}
			case 'O' :
			{
				if (ccmTlv->orgTlv.present == True)
				{
					ccmTlvByteArray[length] = MR_ORG_TLV_TYPE;
					ccmTlvByteArray[length+1] = (uint8_t) (ccmTlv->orgTlv.length >> 8);
					ccmTlvByteArray[length+2] = (uint8_t) (ccmTlv->orgTlv.length & 0xFF);
					for (i=0;i<ccmTlv->orgTlv.length;i++)
						ccmTlvByteArray[length+3+i] = (uint8_t) ccmTlv->orgTlv.value[i];
					length += ccmTlv->orgTlv.length + 3;
				}
				break;
			}
			case 'S' :
			{
				if (ccmTlv->sidTlv.present == True)
				{
					ccmTlvByteArray[length] = MR_SID_TLV_TYPE;
					// TODO : add full Sender ID TLV encoder
					ccmTlvByteArray[length+1] = (uint8_t) (ccmTlv->sidTlv.length >> 8);
					ccmTlvByteArray[length+2] = (uint8_t) (ccmTlv->sidTlv.length & 0xFF);
					for (i=0;i<ccmTlv->sidTlv.length;i++)
						ccmTlvByteArray[length+3+i] = (uint8_t) ccmTlv->sidTlv.value[i];
					length += ccmTlv->sidTlv.length + 3;
				}
				break;
			}
		}
	}

#if 0	// - the end TLV is automatically added by the transmit template
	// Add the end TLV
	ccmTlvByteArray[length] = MR_END_TLV_TYPE;
	length += 1;
#endif

	*tlvLen = length;
	return(NO_ERR);
}

/*
 * @brief convert the CCM TLV byte array into a structured TLV data type (ccmTlv_t)
 *
 * It can only extract Port Status, Interface Status Organisation and Sender ID TLVs.
 * Other TLVs will be skipped (ignored)
 */
int _extractTlvsFromArray(uint8_t ccmTlvByteArray[], ccmTlv_t *ccmTlv)
{
	int	     k;
	int	     i;
	uint16_t  length;

	// initially assume no TLVs are present
	ccmTlv->psTlv.present	= False;
	ccmTlv->psTlv.value		= psBlocked;
	ccmTlv->isTlv.present	= False;
	ccmTlv->isTlv.value		= isDown;
	ccmTlv->orgTlv.present	= False;
	ccmTlv->sidTlv.present	= False;

	for (k=0;k<sizeof(ccmTlvByteArray_t);k++)
	{
		switch(ccmTlvByteArray[k])
		{
			case MR_END_TLV_TYPE:
			{
				// This marks the end of the TLVs valid bytes
				k = sizeof(ccmTlvByteArray_t);
				break;
			}
			case MR_PS_TLV_TYPE :
			{
				length = (((uint16_t) ccmTlvByteArray[k+1]) << 8) + (uint16_t) ccmTlvByteArray[k+2];
				if (length != 1)
				{
					return(ERR_INVALID_TLV);
				}

				ccmTlv->psTlv.value  = ccmTlvByteArray[k+3];
				if ((ccmTlv->psTlv.value != psBlocked) && (ccmTlv->psTlv.value != psUp))
				{
					DO_LOG_DEBUG(log_handle, "Invalid Port Status TLV value detected (%d)",(ccmTlv->psTlv.value));
					return (1);
				}
				ccmTlv->psTlv.present = True;
				k += 3;	 // this will move k on by 4 since the for loop will also add 1
				break;
			}
			case MR_IS_TLV_TYPE :
			{
				length = (((uint16_t) ccmTlvByteArray[k+1]) << 8) + (uint16_t) ccmTlvByteArray[k+2];
				if (length != 1)
				{
					return(ERR_INVALID_TLV);
				}

				ccmTlv->isTlv.value  = ccmTlvByteArray[k+3];
				if ((ccmTlv->isTlv.value < isUp) || (ccmTlv->isTlv.value > isLowerLayerDown))
				{
					DO_LOG_DEBUG(log_handle, "Invalid Interface Status TLV value detected (%d)",(ccmTlv->isTlv.value));
					return (2);
				}
				ccmTlv->isTlv.present = True;
				k += 3;	 // this will move k on by 4 since the for loop will also add 1
				break;
			}
			case MR_ORG_TLV_TYPE :
			{
				// ensure that there is space left for the Length and Values (4 => assumes 1 byte for value!)
				if (k > (sizeof(ccmTlvByteArray_t) - 4))
				{
					return(ERR_INVALID_TLV);
				}

				// extract the length (2 bytes, MSB is first)
				length = (((uint16_t) ccmTlvByteArray[k+1]) << 8) + (uint16_t) ccmTlvByteArray[k+2];
				if (length == 0)
				{
					return(ERR_INVALID_TLV);
				}
				ccmTlv->orgTlv.length = length;

				for (i=0;i<ccmTlv->orgTlv.length;i++)
				{
					if (k+i+3 > sizeof(ccmTlvByteArray_t))
					{
						return(ERR_INVALID_TLV);
					}

					ccmTlv->orgTlv.value[i] = ccmTlvByteArray[k+i+3];
				}
				k += length + 2;		// this will move k on by length + 3 since the for loop will also add 1
				ccmTlv->orgTlv.present = True;

				break;
			}
			case MR_SID_TLV_TYPE :
			{
				// ensure that there is space left for the Length and Values (4 => assumes 1 byte for value!)
				if (k > (sizeof(ccmTlvByteArray_t) - 4))
				{
					return(ERR_INVALID_TLV);
				}

				// extract the length (2 bytes, MSB is first)
				length = (((uint16_t) ccmTlvByteArray[k+1]) << 8) + (uint16_t) ccmTlvByteArray[k+2];
				if (length == 0)
				{
					return(ERR_INVALID_TLV);
				}
				ccmTlv->sidTlv.length = length;

				// TODO : add full Sender ID TLV encoder

				for (i=0;i<ccmTlv->sidTlv.length;i++)
				{
					if (k+i+3 > sizeof(ccmTlvByteArray_t))
					{
						return(ERR_INVALID_TLV);
					}

					ccmTlv->sidTlv.value[i] = ccmTlvByteArray[k+i+3];
				}
				k += length + 2;		// this will move k on by length + 3 since the for loop will also add 1
				ccmTlv->sidTlv.present = True;
				break;
			}
			default :
			{
				// unknown TLV type - skip it
				// extract the length (2 bytes, MSB is first)
				length = (((uint16_t) ccmTlvByteArray[k+1]) << 8) + (uint16_t) ccmTlvByteArray[k+2];
				k += length + 2;		// this will move k on by length + 3 since the for loop will also add 1
				break;
			}
		}
	}
	return(NO_ERR);
}

/*
 * @brief add a CCM service to a local MEP
 *
 * It will add a new txTemplate for the CCM and it will add an entry in the txLMepNumRam.
 */
static int addCCMSvcToLMep(lMepNum_t lMepNum, ccmInterval_t ccmInterval, txTemplateEntry_t *txCCMTempl, iFace_t iFace, uint16_t tlvLen,uint8_t tlv[])
{
	int		retVal;

	// Add a new txTemplate entry at the location determined by lMepNum
	if ((retVal = writeTxTempl(lMepNum,txCCMTempl,CCM_TEMPL,tlvLen,tlv)))
		return(retVal);

	// Add a new LMEP to the TX NumRam
	if ((retVal = addLMep2TxNumRam(lMepNum, ccmInterval, iFace)))
		return(retVal);
	return(0);
}

/*
 * @brief Set the Transmit RDI bit in the CCM packet for this local MEP
 */
void _ccSetRdi(lMepNum_t lMepNum)
{
	writeRdiInTxTempl(lMepNum, SET, CCM_TEMPL);
	_mepRdiStatusUpdate(lMepNum, true);
	DO_LOG_DEBUG(log_handle,"RDI set for lMepNum = %d",lMepNum);
}

/*
 * @brief Clear the Transmit RDI bit in the CCM packet for this local MEP
 */
void _ccClearRdi(lMepNum_t lMepNum)
{
	writeRdiInTxTempl(lMepNum, CLR, CCM_TEMPL);
	_mepRdiStatusUpdate(lMepNum, false);
	DO_LOG_DEBUG(log_handle,"RDI cleared for lMepNum = %d",lMepNum);
}

/*
 * @brief initialises a txTemplateEntry_t structure for use as a CCM transmit template
 *
 * @param[in] lMepNum
 * @param[in] mdLevel
 * @param[in] sTag
 * @param[in] cTag
 * @param[out] txTemplateEntry
 * @return NO_ERR if no error occurred, a non-zero error code otherwise
 */
static void generateCCMTemplate(
		lMepNum_t lMepNum,
		iFace_t iFace,
		mepId_t mepId,
		uint8_t mdLevel,
		ccmInterval_t ccmInterval,
		tagEntry_t *innerTag,
		tagEntry_t *outerTag,
		txTemplateEntry_t *txCCMTempl)
{
	// Set the DA to the default CCM broadcast DA
	txCCMTempl->DA[0] = 0x01;							   // DA[0] is the first byte on the wire, then DA[1],DA[2],etc.
	txCCMTempl->DA[1] = 0x80;
	txCCMTempl->DA[2] = 0xC2;
	txCCMTempl->DA[3] = 0x00;
	txCCMTempl->DA[4] = 0x00;
	txCCMTempl->DA[5] = 0x30 + mdLevel;

	generateBasicCfmTemplate(lMepNum,iFace,104,innerTag,outerTag,txCCMTempl);		// for CCMs the pktSize field is ignored

	txCCMTempl->comCfmHdr[0]	= mdLevel << 5;
	txCCMTempl->comCfmHdr[1]	= MR_CCM_OPCODE;
	txCCMTempl->comCfmHdr[2]	= ccmInterval;
	txCCMTempl->comCfmHdr[3]	= MR_CCM_1ST_OFF;
	txCCMTempl->mepID 			= mepId;
}

/*
 * @brief Called when the primary vlan for a local MEP is updated
 *
 * This will update the Vlan in the CCM packet tags
 */
void ccmUpdatePrimaryVlan(
	lMepNum_t	lMepNum,
	uint16_t 	vLan,
	prio_t		prio,
	tagType_t	tagType,
	iFace_t		iFace,
	bool		enable)
{
	tagEntry_t	outerTag;
	tagEntry_t	innerTag;
	uint8_t		eType[2];
	uint16_t	cfmType;

	// get the necessary tags for the CCM template
	generatePacketTags(&innerTag,&outerTag,vLan,prio,0,tagType,iFace);

	cfmType = getCfmTypeId();
	eType[0] = (uint8_t)((cfmType >> MR_BYTE1_OFFSET) & MR_BYTE0_MASK);	// ether Type field - typically eType[0]=0x89,eType[1]=0x02
	eType[1] = (uint8_t)(cfmType & MR_BYTE0_MASK);

	// Stop transmitting, if not already stopped, so a corrupted packet is not sent.
	cciDisable(lMepNum);
	writeTagsInTxTempl(lMepNum,&outerTag,&innerTag,eType,CCM_TEMPL);
	if (enable)
	{
		// The MEP was enabled for TX so re-enable it...
		cciEnable(lMepNum);
	}
}
/*
 * @ brief Add a CCM (transmit) service to a local MEP
 *
 * IMPORTANT: mdIndex, maIndex and mepId must be validate before calling this function
 */
void  ccmAdd(
	lMepNum_t			lMepNum,
	mepId_t				mepId,
	uint16_t 			vLan,
	prio_t				prio,
	tagType_t			tagType,
	mdLevel_t			mdLevel,
	ccmInterval_t		ccmInterval,
	iFace_t				iFace,
	ccmTlv_t			*ccmTlv)
{
	tagEntry_t			outerTag;
	tagEntry_t			innerTag;
	txTemplateEntry_t 	txCCMTempl;
	ccmTlvByteArray_t	ccmTlvByteArray;
	uint16_t			tlvLen,i;
#ifdef DEBUG_PRINT_L2
	ts_printf("||||||||||Adding CCM...");
	ts_printf("ccmAdd: lMepNum = %d,mepId = %d,vLan = %d,prio=%d,tagType=%d,mdLevel=%d,ccmInterval=%d,iFace=%d\n\r", lMepNum,mepId,vLan,prio,tagType,mdLevel,ccmInterval,iFace);
#endif
    //osSleepMS(20000);
	// get the necessary tags for the CCM template
	generatePacketTags(&innerTag,&outerTag,vLan,prio,0,tagType,iFace);

	// Convert the ccmTlv structure into a simple byte array
	_generateTlvArray(ccmTlv,ccmTlvByteArray,&tlvLen);

	// Generate the template structure
	generateCCMTemplate(lMepNum,iFace,mepId, mdLevel,ccmInterval, &innerTag,&outerTag,&txCCMTempl);

	addCCMSvcToLMep(lMepNum,ccmInterval,&txCCMTempl,iFace,tlvLen,ccmTlvByteArray);					// TLV length = 0

	// the RDI should be set by default TODO confirm this is true!
	_ccSetRdi(lMepNum);

	//DO_LOG_DEBUG(log_handle,"Added new CCM service to lMep with lMepNum=%d;port=%d,level=%d,vLan=%d,prio=%d",lMepNum,iFace,mdLevel,vLan,prio);
#ifdef DEBUG_PRINT_L2
	ts_printf("Added new CCM service to lMep with lMepNum=%d;port=%d,level=%d,vLan=%d,prio=%d",lMepNum,iFace,mdLevel,vLan,prio);
	ts_printf("Printing ccmTlvByteArray:\n\r");
#endif
#ifdef DEBUG_PRINT_L1
    ts_printf("Added new CCM service to local MEP Sucessfully.. ");

#endif
#ifdef DEBUG_PRINT_L2
	for(i=0;i<56;i++)
    {
	    ts_printf("%0x ",ccmTlvByteArray[i]);
    }
	ts_printf("txCCMTempl\n\r");
	ts_printf("%0x %0x %0x %0x %0x %0x ",txCCMTempl.DA[0],txCCMTempl.DA[1],txCCMTempl.DA[2],txCCMTempl.DA[3],txCCMTempl.DA[4],txCCMTempl.DA[5]);
	ts_printf("%0x %0x %0x %0x %0x %0x ",txCCMTempl.SA[0],txCCMTempl.SA[1],txCCMTempl.SA[2],txCCMTempl.SA[3],txCCMTempl.SA[4],txCCMTempl.SA[5]);
#endif
}

/*
 * @brief Remove a CCM (transmit) service from a Local MEP
 */
void ccmRemove(lMepNum_t lMepNum)
{
	deleteCcm(lMepNum);
	disableXConnSM(lMepNum);
	disableRMepErrSM(lMepNum);
	ts_printf("Removed the CCM service from lMep with lMepNum=%d",lMepNum);
}

/*
 * @brief Enable CCM transmit on the local MEP
 */
void cciEnable(lMepNum_t lMepNum)
{
   // ts_printf("||||||||||Enabling CCM Tx...");
    //osSleepMS(20000);
	enableCcmTx(lMepNum);
#ifdef DEBUG_PRINT_L2
	ts_printf("Enabled the CCM Tx service on lMep with lMepNum=%d",lMepNum);
#endif
#ifdef DEBUG_PRINT_L1
    ts_printf("Enabled the CCM Tx service");
#endif
}

/*
 * @brief Disable CCM transmit on the local MEP
 */
void cciDisable(lMepNum_t lMepNum)
{
	disableCcmTx(lMepNum);
	ts_printf("Disabled the CCM Tx service on lMep with lMepNum=%d",lMepNum);
}

/*
 * @brief Enable CCM receive processing State Machines on the local MEP
 */
void ccEnableLMep(lMepNum_t lMepNum, ccmInterval_t ccmInterval)
{
    //ts_printf("||||||||||Enabling CCM Rx...");
    //osSleepMS(20000);
    enableXConnSM(lMepNum, ccmInterval);
	enableRMepErrSM(lMepNum, ccmInterval);
#ifdef DEBUG_PRINT_L2
	ts_printf("Enabled the CCM Rx service on lMep with lMepNum=%d;interval=%d",lMepNum,ccmInterval);
#endif
#ifdef DEBUG_PRINT_L1
    ts_printf("Enabled the CCM Rx service ");
#endif
}

/*
 * @brief Disable CCM receive processing State Machines on the local MEP
 */
void ccDisableLMep(lMepNum_t lMepNum)
{
	disableXConnSM(lMepNum);
	disableRMepErrSM(lMepNum);
	ts_printf("Disabled the CCM Rx service on lMep with lMepNum=%d",lMepNum);
}



