/**
* @file icss_learning.c
*
* @brief Main file for Learning/FDB implementation
*        Contains hash functions and other routines used to implement a Learning/FDB implementation
*
* \par
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
* \par
*/

/* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */
#include "icss_learning.h"


/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */
/**Two MAC Learning tables for two ports*/
HashTable macTable[2];
/**Callback to be registered to process protocol specific exceptions*/
static icssCallBackRet_t protException;

/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

/**
* @brief Hash function for Learning/FDB algorithm
* @details Assumption is that the MAC is in little endian format
*
* @param  macId Stack src MAC ID of device
*
* @retval key hash key derived from the MAC ID
*/
Uint8 hashFuncGPMAC(const Uint8* macId) {
	Uint8 mac[6];
	Uint8 key;

	unsigned int i;

	/*Extract the six bytes*/
	for(i = 0;i < 6; i++) {
		mac[i] = *(macId++);
	}
	/*get the key*/
	key = mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5];
	return key;
}
/**
* @brief Update the learning table used to map MAC Id's and ports
*
* @param  macId MACID which is to be learned
* @param  portNum port number of incoming packet
*
* @retval none
*/
void updateHashTable(Uint8* macId, Uint8 portNum) {
	Uint8 key;
	Uint8 i, oldestTimestamp, index, macFound;
	HashBucket * bucket;
	HashTable *table;

	if((macId[0] & 0x01) > 0) {	// MAC reversed - lowest bit indicates MC
		return;
	}

	// don't do anything for wrong port num
	if(portNum > HWPORT1) {
		return;
	}

	/*Call back for Protocol specific adaption*/
	if(protException != NULL) {
		if(!protException(macId, NULL)) {
			return;
		}
	}

	/*get the key*/
	key = hashFuncGPMAC(macId);
	table = &(macTable[portNum]);

	if(table->state == locked || table->state == notLearning) {
		return; //not allowed to make any changes in cases table is locked or if learning is disabled
	}
	/*go to first hash entry in the table*/
	bucket = &(table->entries[key]);
	macFound = 0; /*set to false*/


	/*If bucket is empty update the first entry else run through the bucket*/
	if (!bucket->numEntries) {
		// make sure the SLT has no entry for this MAC on the other port

		checkDuplicateMAC(key, macId, portNum);

    	// add entry
		//copyMAC((bucket->mac[0].macId), macId);
		copyMAC((bucket->mac[0].macId), macId);
		bucket->timerCount[0] = 0;
		bucket->numEntries++;
		table->totalNumEntries++;
	} else {
		oldestTimestamp = bucket->timerCount[0];
		for(i = 0; i < bucket->numEntries; i++) {
			/*Update all timer counts, for ageing*/
			if (COMPARE_MAC(macId, bucket->mac[i].macId)) {
				bucket->timerCount[i] = 0;
				macFound = 1;
				//early exit
				break;
			}

			/*if multiple MAC ID's hash to the same bucket, timer count is used to resolve the conflict*/
			if (oldestTimestamp <=  bucket->timerCount[i]) {
				oldestTimestamp = bucket->timerCount[i];
				index = i;
			}

		}
		if (!macFound) {
			// make sure the SLT has no entry for this MAC on the other port
			checkDuplicateMAC(key, macId, portNum);

			if(bucket->numEntries == MAX_NUM_ENTRIES) {
				/*replace an existing entry(the least hit)*/
				//bucket->MAC[index] = MAC;
				copyMAC(bucket->mac[index].macId, macId);
				bucket->timerCount[index] = 0;
			} else {
				//bucket->MAC[bucket->numEntries] = MAC;
				copyMAC(bucket->mac[bucket->numEntries].macId, macId);
				bucket->timerCount[bucket->numEntries] = 0;
				bucket->numEntries++;
				table->totalNumEntries++;
			}
		}

	}

}
/**
* @brief Clear the Learning table associated with a given port
*
* @param portNum Port number whose learning table is to be flushed
*
* @retval none
*/
void purgeTable(Uint8 portNum) {
	int i;
	/*don't do anything for wrong port num*/
	if(portNum > HWPORT1)
		return;
	HashTable *table;
	//table = tableAddr[portNum];
	table = &(macTable[portNum]);

	 //not allowed to make any changes in cases table is locked
	if(table->state == locked) {
		return;
	}
	/*make total entries to 0*/
	table->totalNumEntries = 0;
	/*Do a dirty deletion*/
	for(i=0; i<NUMBUCKETS;i++) {
		table->entries[i].numEntries = 0;
	}

}
/**
* @brief Remove old MAC entries from the table
*
* @param  portNum Portnumber whose learning table is to be flushed
*
* @retval none
*/
void ageingRoutine(Uint8 portNum) {

	int i, a, j = 0;
	int size, numEntriesRemoved;

	/*structure definitions, see header file*/
	HashTable *table;
	HashBucket * bucket;
	/*Right now this is hardcoded, modified only at compile time, can be added as an option later*/
	Uint8 ageingTime = AGEING_COUNT;



	// don't do anything for wrong port num
	if(portNum > HWPORT1)
		return;
	/*get the table for the corresponding port*/
	//table = tableAddr[portNum];
	table = &(macTable[portNum]);
	if(table->state == locked) {
		return; //not allowed to make any changes in cases table is locked
	}

	for(i=0; i < NUMBUCKETS; i++) {
		bucket = &(table->entries[i]);
		size = bucket->numEntries;
		numEntriesRemoved = size;
		a = 0;
		while(a < size) {
			if(ageingTime <= bucket->timerCount[a]){
				j = a;
				/*as we find hits, we decrement the size of the bucket*/
				size--;
				while(j < size) {
					/*copy the data from below once a hole is created in the bucket due to retiring an entry*/
					bucket->timerCount[j] = bucket->timerCount[j+1];
					copyMAC(bucket->mac[j].macId, bucket->mac[j+1].macId);
					j++;
				}

			} else {
				a++;
			}
		}
		/*Number of entries left*/
		bucket->numEntries = size;
		numEntriesRemoved -= size;

		//Update the total number of entries removed
		table->totalNumEntries -= numEntriesRemoved;
	}

}
/**
* @brief Find given a MAC ID which port it exists
*
* @param  macId MACID which is to be found
*
* @retval portNumber where the MAC ID exists, 0 means not found 1 means port 0 and 2 means port 1
*/
Uint8 findMAC(const Uint8 * macId) {
	Uint8 i, j;
	Uint8 key;
	Uint8 macFound[2] = {0, 0};
	Uint8 numPorts = NUM_PORTS;
	HashTable *table;
	HashBucket * bucket;
	/*get the key to lookup in the hash table*/
	key = hashFuncGPMAC(macId);
	/*Look in all port entries, break if found*/
	for(i = 0; i < numPorts; i++){
		//table = tableAddr[i];
		table = &(macTable[i]);
		bucket = &(table->entries[key]);
		for (j = 0; j < bucket->numEntries; j++ ) {
			if (COMPARE_MAC(bucket->mac[j].macId, macId)) {
				/*set the flag and exit*/
				macFound[i] = 1;
				break;
			}

		}

	}
	/*since a return val of 0 means MAC Id not found 1 is added to distinguish port 0 from mac id not found,
	 user to subtract 1 to get port number*/
	/*If MAC ID moves from port 1 to port 2, both ports will return false*/
	if(macFound[0]) {
		return 1;
	}
	if(macFound[1]) {
		return 2;
	}

	return 0;
}

/**
* @brief Find given a MAC ID and remove it from the table
*
* @param  macId  MAC which is to be found
*
* @retval pass/fail. 0 means operation failed (mac not found)
*/
Uint8 removeMAC(Uint8 * macId) {
	Uint8 i, j = 0;
	Uint8 key;
	Uint8 macFound[2] = {0, 0};
	Uint8 macIndex[2];
	Uint8 numPorts = NUM_PORTS;
	HashTable *table;
	HashBucket * bucket;
	key = hashFuncGPMAC(macId);
	/*Look in all port entries, exit if true*/
	for(i = 0; i < numPorts; i++){
		//table = tableAddr[i];
		table = &(macTable[i]);
		if(table->state == locked) {
			continue; //not allowed to make any changes in cases table is locked
		}
		bucket = &(table->entries[key]);
		for (j = 0; j < bucket->numEntries; j++ ) {
			if (COMPARE_MAC(bucket->mac[j].macId, macId)) {
				macFound[i] = 1;
				macIndex[i] = j;
				break;
			}

		}

	}

	if(macFound[0]) {
		/*Remove for PORT 0*/
		//table = tableAddr[HWPORT0];
		table = &(macTable[HWPORT0]);
		bucket = &(table->entries[key]);

		for(j = macIndex[HWPORT0]; j < bucket->numEntries - 1; j++) {
			bucket->timerCount[j] = bucket->timerCount[j+1];
			copyMAC(bucket->mac[j].macId, bucket->mac[j+1].macId);
		}
		bucket->numEntries--;
		table->totalNumEntries--;
		return 1;
	}
	if(macFound[1]) {
		/*Remove for PORT 1*/
		//table = tableAddr[HWPORT1];
		table = &(macTable[HWPORT1]);
		bucket = &(table->entries[key]);

		for(j = macIndex[HWPORT1]; j < bucket->numEntries - 1; j++) {
			bucket->timerCount[j] = bucket->timerCount[j+1];
			copyMAC(bucket->mac[j].macId, bucket->mac[j+1].macId);
		}
		bucket->numEntries--;
		table->totalNumEntries--;
		return 1;
	}

	return 0;

}

/**
* @brief This takes in a hash key and the port number and removes the MAC ID if found in the other port
* @details This function contains an optimized subset of removeMAC. Used to remove duplicate MAC ID's
*
* @param  key  hash key for the MAC
* @param  macId  MAC which is to be removed from the other port
* @param  portNum  port number of the table which is being currently updated
*
* @retval pass or fail 0 means operation failed (mac not found)
*/
Uint8 checkDuplicateMAC(Uint8 key, Uint8* macId, Uint8 portNum) {

	HashBucket * bucket;
	HashTable *table;
	Uint8 i, j, macFound = 0;
	/*don't do anything for wrong port num*/
	if(portNum > HWPORT1) {
		return 0;
	}
	//Take the other port
	portNum = !(portNum);
	table = &(macTable[portNum]);

	bucket = &(table->entries[key]);
	for (j = 0; j < bucket->numEntries; j++ ) {
		if (COMPARE_MAC(bucket->mac[j].macId, macId)) {
			macFound = 1;
			for(i = j; i < bucket->numEntries - 1; i++) {
				bucket->timerCount[i] = bucket->timerCount[i+1];
				copyMAC(bucket->mac[i].macId, bucket->mac[i+1].macId);
			}
			bucket->numEntries--;
			table->totalNumEntries--;
			break;
		}

	}

	return macFound;

}
/**
* @brief This takes in a hash key and the port number and removes the MAC ID if found in the other port
*
* @retval none
*/
void incrementCounter(void) {
	int i,j;
	HashTable *table;
	HashBucket * bucket;

	/*For Port 0*/
	table = &(macTable[HWPORT0]);
	if(table->state == locked) {
		return; //not allowed to make any changes in cases table is locked
	}

	for(i=0; i<NUMBUCKETS;i++) {
		bucket = &(table->entries[i]);
		for(j=0; j < bucket->numEntries; j++) {
			if(bucket->timerCount[j] < AGEING_COUNT) {
				bucket->timerCount[j]++;
			}
		}
	}

	/*For Port 1*/
	table = &(macTable[HWPORT1]);

	for(i=0; i<NUMBUCKETS; i++) {
		bucket = &(table->entries[i]);
		for(j=0; j < bucket->numEntries; j++) {
			if(bucket->timerCount[j] < AGEING_COUNT) {
				bucket->timerCount[j]++;
			}
		}
	}

}
/**
* @brief This takes in a hash key and the port number and removes the MAC ID if found in the other port
*
* @retval none
*/
void initLearningTable() {
	HashTable *table;
	
	table = &(macTable[HWPORT0]);
	purgeTable(HWPORT0);
	table->state = learning;

	table = &(macTable[HWPORT1]);
	purgeTable(HWPORT1);
	table->state = learning;
}

/*Port State Change Functions*/

/**
* @brief Lock down the port from any changes, from NDK compatibility perspective
*
* @param  portNum port number
*
* @retval none
*/
void lockPort(Uint8 portNum) {
	HashTable *table;

	/*don't do anything for wrong port num*/
	if(portNum > HWPORT1) {
		return;
	}

	table = &(macTable[portNum]);
	table->state = locked;

}

/**
* @brief port is not learning any new addresses, deletions are allowed however
*         This is from NDK compatibility perspective
*
* @param  portNum port number
*
* @retval none
*/
void portNotLearning(Uint8 portNum) {
	HashTable *table;

	/*don't do anything for wrong port num*/
	if(portNum > HWPORT1) {
		return;
	}

	table = &(macTable[portNum]);
	table->state = notLearning;

}
/**
* @brief change the state to Learning. Port is now learning new addresses, this is the default state
*        This is from NDK compatibility perspective
*
* @param  portNum port number
*
* @retval none
*/
void portLearning(Uint8 portNum) {
	HashTable *table;

	/*don't do anything for wrong port num*/
	if(portNum > HWPORT1) {
		return;
	}

	table = &(macTable[portNum]);
	table->state = learning;

}

/**
* @brief Register the EIP callback for Supervisor learning exception
*
* @param  callBack call back function name
*
* @retval none
*/
void RegisterLearningCallBack(icssCallBackRet_t callBack)
{
	protException = callBack;
}

/**
* @brief API to copy Macaddress
*
* @param dst destination address
 * @param src source address
*
* @retval none
*/
void copyMAC(uint8_t *dst, uint8_t *src) {
	uint8_t i;
	for(i = 0; i < 6; i++) {
		*(dst++) = *(src++);
	}
}
