#include "fec.h"
#include "hamming.h"
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <iostream>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include "crc.h"
#include "fec_decoder.h"

//#define TIME
#define K 2 
#define MAX_output_messages    1024
#define FEC_HEADER_SIZE		8
#define DEFAULT_FEC_SCHEMA_ID	0xA
#define CHUNK_SIZE		8
#define PARITY_BYTE_PER_CHUNK	1
#define MAX_NUMBER_OF_FRAGMENTS 8

extern void RS_code(unsigned int fragLen, std::vector<const unsigned char*>& fragments);

/*
 * ====================================================================================
 *
 *       Filename:  fec.cpp
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  04/07/2011 12:58:52 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Cesar Prados Boda (cp), c.prados@gsi.de
 *        Company:  GSI
 *
 * =====================================================================================
 */

static uint32_t outgoing_msgID = 0;
struct State 
{
    unsigned int msize;
    unsigned int divsize;
    unsigned int recovered_fragments_received;
    bool recovered_succesfully;
    std::vector<std::string> received;
    fec_decoder_msg_raport_t report;
};

typedef std::map<uint32_t, State> Map;
typedef std::queue<uint32_t> Queue;

static Map cache;
static Queue inCache;
static std::string result;

void fec_open()
{
    srand(time(0));
    outgoing_msgID = rand();
}

void fec_close()
{
}

static unsigned int fec_chopchop(unsigned int msize) {
    // !!! Do something intelligent!
    return 2;
}
/*
 * calculate FEC parameters based on the size (mSize) of the original message. 
 * 
 * Inputs:
 * 	mSize	- size of the original message 
 * Outputs:
 * 	divSize	- size of the fragment of the original message to be encoded, in the multiples of 
 * 		  this size we cut the original message
 * 	rsinSize- input to R-S encoder (padded to make the size of the input message a multiple of
 * 		  8 byte chunk
 * 	fragSize- size of the output encoded (with R-S and hamming) message, we call it fragment.
 * 		  the size includes FEC header
 * 	fragNumber- number of fragments (encoded messages)
 *
 */
void fec_parameters(unsigned int mSize, unsigned int* divSize, unsigned int* rsinSize,
		    unsigned int* fragSize, unsigned int* fragNumber) 
{
    *divSize    = (mSize + 1) / fec_chopchop(mSize);    // where to cut the original message
    *rsinSize   = (*divSize+7) & ~7;    			// message size on the input to R-S encoder
    *fragSize   = *rsinSize + (*rsinSize/8);// + FEC_HEADER_SIZE; // final size of the encoded fragment
    *fragNumber = fec_chopchop(mSize) + K;		// how many encoded messages we will have 
    
    #ifdef DBG_SHOW_PARAMETERS
    printf("FEC_PARAMETERS: msg of msize = %d will be encoded into %d messages of %d bytes each (with FEC header)\n",
	   mSize, *fragNumber, *fragSize);
    printf("FEC_PARAMETERS: Intermediate params: divSize = % d, sinSize = %d bytes\n",
	   *divSize, *rsinSize);
    #endif
}
/*
 * The function packs FEC information into 8 bytes header 
 * 
 * Inputs:
 * 	fec_header 	- a structure with fec_header information 
 * 
 * Returns:
 * 	FEC Header (8 bytes)
 */
//uint64_t 
const char* create_FEC_header(fec_header_t *fec_header)
{
	    
	std::string header(FEC_HEADER_SIZE, 'x');
	
	
	header[0]= static_cast<char>( fec_header->msgID       & 0xFF);
	header[1]= static_cast<char>((fec_header->fragmentID  & 0xF) << 4  | (fec_header->schemaID & 0xF));
	header[2]= static_cast<char>( fec_header->etherType   & 0xFF);
	header[3]= static_cast<char>((fec_header->msgID >> 8) & 0xFF);  
	header[4]= static_cast<char>( fec_header->originSize  & 0xFF);
	header[5]= static_cast<char>((fec_header->etherType >> 8)   & 0xFF);
	header[6]= static_cast<char>((fec_header->framgentSize >> 3)& 0xFF);
	header[7]= static_cast<char>(((fec_header->originSize >> 8) & 0x1F) | ((fec_header->framgentSize & 0x7 ) << 5));
	
	#ifdef DBG_SHOW_HEADER    
	printf("------------------------------------------------------------\n");
	//printf("      [MSG_%d created header: 0x%llx of the data:]\n",fec_header->fragmentID, header);
	printf("      [MSG_%d created header:]\n",fec_header->fragmentID);
	printf("Encoding FECed message with the FEC Header:\n");
	printf("FEC schema ID                  : 0x%x\n",fec_header->schemaID & 0xF);
	printf("Fragment   ID                  : 0x%x\n",fec_header->fragmentID & 0xF);
	printf("Message    ID                  : 0x%4x\n",fec_header->msgID & 0xFFFF);
	printf("EtherType of the original frame: 0x%4x\n",fec_header->etherType& 0xFFFF);
	printf("Original frame's payload size  : 0x%4x [%d]\n",fec_header->originSize & 0x1FFF,fec_header->originSize & 0x1FFF);
	printf("Encoded  frame's payload size  : 0x%4x [%d]\n",fec_header->framgentSize ,fec_header->framgentSize& 0x1FFF);
	//for (int i = 0; i<8; i++) printf("header[%d] = 0x%2x\n",i, 0xFF & header[i]);
	printf("------------------------------------------------------------\n");
	#endif
	//return header;    
	return reinterpret_cast<const char*>(header.data());
	
}
/*
 * The function takes the message end interprets it's first bytes into the FEC header, reading 
 * the information
 * 
 * Inputs:
 * 	chunk_		- the received message
 * 
 * Outputs:
 * 	fec_header 	- a structure with fec_header information 
 */
void read_FEC_header(fec_header_t *fec_header, const unsigned char* chunk )
{
    std::string eChunk(reinterpret_cast<const char*>(chunk),FEC_HEADER_SIZE); 

			  

    fec_header->schemaID     =   (static_cast<uint8_t> (eChunk[1]))     & 0xF;		//  4 bits
    fec_header->fragmentID   =  ((static_cast<uint8_t> (eChunk[1]))>>4) & 0xF;		//  4 bits
    fec_header->msgID        = (((static_cast<uint8_t> (eChunk[3]))<<8) & 0xFF00) |  	// 16 bits
				((static_cast<uint8_t> (eChunk[0]))     & 0xFF) ;
    fec_header->etherType    = (((static_cast<uint8_t> (eChunk[5]))<<8) & 0xFF00) |	// 16 bits
				((static_cast<uint8_t> (eChunk[2]))     & 0xFF) ;
    fec_header->originSize   = (((static_cast<uint8_t> (eChunk[7]))<<8) & 0x1F00) |	// 13 bits
				((static_cast<uint8_t> (eChunk[4]))     & 0xFF) ;
    fec_header->framgentSize = (((static_cast<uint8_t> (eChunk[6]))<<3) & 0x7F8) |	// 11 bits
			       (((static_cast<uint8_t> (eChunk[7]))>>8) & 0x07) ;

    #ifdef DBG_SHOW_HEADER    
    printf("------------------------------------------------------------\n");
    printf("      [retrieved data from the header ]\n");
    printf("Encoding FECed message with the FEC Header:\n");
    printf("FEC schema ID                  : 0x%x\n",fec_header->schemaID);
    printf("Fragment   ID                  : 0x%x\n",fec_header->fragmentID);
    printf("Message    ID                  : 0x%x\n",fec_header->msgID);
    printf("EtherType of the original frame: 0x%x\n",fec_header->etherType);
    printf("Original frame's payload size  : 0x%x [%d]\n",fec_header->originSize,fec_header->originSize);
    printf("Encoded  frame's payload size  : 0x%x [%d]\n",fec_header->framgentSize,fec_header->framgentSize);
    //for (int i = 0; i<8; i++) printf("eChunk[%d] = 0x%2x\n",i, 0xFF & eChunk[i]);
    printf("------------------------------------------------------------\n");
    #endif    
}
/*
 * This function works like a decoder: 
 * 1. it inputs encoded messages (in other words: payloads of Ethernet Frames)
 * 2. It tries to assemble the original messages (putting together messages with the same msgID)
 * 3. If the received number of fragments is not sufficient yet to recover original message, it 
 *    stores the data in cache and accepts next message (could be of fragment of different original
 *    message
 * 4. As soon as the received number of fragments is sufficient and the original message can be
 *    recovered, the original message is outputted. This is indicated by len being grater then 0
 * 
 * Inputs: 
 * 	chunk		- received encoded fragment of the original message 
 * 	fragSize	- size the encoded message (fragment) inputed in chunk
 * Outputs:
 * 	origMsgSize	- origMsgSize == 0, then not enough number of fragments has been received 
 * 			  yet to recover the original message or the fragment belongs to the 
 * 			  original message which has been already recovered
 * 			- origMsgSize  > 0, then it indicates the length of the original message   
 * 			  which has been recovered, 
 * 			- origMsgSize  < 0, then it indicates that the recovery of the original  
 * 			  message failed and the message has been lost
 * 	origEtherType 	- value of the original message's etherType, valid if len > 0
 * Returns:
 * 	the original message if len > 0
 */
const unsigned char* fec_decode(const unsigned char* chunk, 	 unsigned int   receivedPayloadSize, 
				      int*  origMsgSize, unsigned int* origEtherType)
{
    std::string eChunk(reinterpret_cast<const char*>(chunk),receivedPayloadSize); 
    fec_header_t fec_header;
    unsigned int chunks; //number of 8 byte + 1 parity byte
    unsigned int mSize;
    unsigned int receivedFragSize = receivedPayloadSize - FEC_HEADER_SIZE; //
    
    //parameters calculated from the information about original message size from FEC Header
    unsigned int divSize;
    unsigned int rsinSize;
    unsigned int fragSize;
    unsigned int fragNumber;
    
    *origMsgSize   = 0;
    *origEtherType = 0;
    
    //extract the FEC header from the message
    read_FEC_header(&fec_header,chunk);
    mSize = fec_header.originSize;
    fec_parameters(mSize, &divSize, &rsinSize, &fragSize, &fragNumber);
    
    /***************************************************************************************
     *					Sanity check
     ***************************************************************************************/    
    
    //check decoding sanity:
    if(fec_header.framgentSize 	!= receivedFragSize ||	//the size of received frame different
       fragSize			!= receivedFragSize)	//then declared in the FEC header/calculated
    {							//from the mSize retrieved from header
      *origMsgSize = -1;
      printf("ERROR[fec_decoder]: \n \
	  receivedFragSize 		= %d [receivedPayloadSize = %d]\n \
	  fec_header.framgentSize	= %d \n   \
	  fragSize			= %d \n", receivedFragSize, receivedPayloadSize,
						fec_header.framgentSize, fragSize);
      return 0;
    }
    
    //1. check whether the received fragment belongs to a original message which has been already 
    //recovered
    //2. check whether the fragment is a duplicate
    Map::iterator state = cache.find(fec_header.msgID);
    if (state != cache.end() && state->second.recovered_succesfully == true) 
	return 0; //the original message of this fragment has been already recovered
    else if(state != cache.end() && state->second.recovered_succesfully != true)
    {
	if(! state->second.received[fec_header.fragmentID].empty() )
	{
	    state->second.report.fragments[fec_header.fragmentID].duplicatesReceived++;
	    state->second.report.duplicatesReceived ++;
	    return 0; 						   //if duplicate, quit
	}

	if(state->second.recovered_fragments_received > fragNumber)  //number of received fragments
	{  						   //exceeds expectations, sth is wrong
	    state->second.report.tooManyFragments=true;
	    return 0;
	}

//	for(int i = 0; i< state->second.fragments_received; i++)   //for the number of fragments already received
//	  if(fec_header.fragmentID == state->second.fragmetIDs[i]) //check whether the currently received is not duplicate
//	    return 0; 						   //if duplicate, quit
    }

    // adding new entry in the cache for a new message
    if (state == cache.end()) 		   // the find() function returns pointer to the end if mID 
					   // not found. This means that the received fragment is 
					   // the first of the Control Message with mID
    {
        if (inCache.size() > MAX_output_messages) //if the queue is full, remove the oldest (first) record
        {
            uint32_t kill = inCache.front();      // get the mID of the oldest (first) record
            inCache.pop();			  // get rid of the oldest record from the queue
            cache.erase(cache.find(kill));	  // erase the entry associated with mID 
						  // (of the oldest record) from the cache
        }
        inCache.push(fec_header.msgID);		          // add new record to the queue

        State& newState = cache[fec_header.msgID]; // add it	  // create new record in the cache associative table
        state = cache.find(fec_header.msgID); 		  // get iterator of the added record

	/* fill in the new record in the cache table */
        newState.msize 				= fec_header.originSize;
        newState.recovered_fragments_received 	= 0;
        newState.divsize 			= divSize;
        newState.received.resize(fragNumber);
	
	/* initialize fec report entry for a given original message*/
	for(int i=0;i<8;i++)
	{
	  state->second.report.bitsRecovered		= 0;
	  state->second.report.fragmentsLost		= 0;
	  state->second.report.duplicatesReceived	= 0;
	  state->second.report.fragmentsReceived	= 0;
	  state->second.report.msgRecovered		= false;
	  state->second.report.tooManyFragments		= false;
	  
	  state->second.report.fragments[i].receivedOrderNumber	= 0;
	  state->second.report.fragments[i].sentOrderNumber	= 0;
	  state->second.report.fragments[i].recoveredBitsNumber	= 0;
	  state->second.report.fragments[i].duplicatesReceived	= 0;
	  state->second.report.fragments[i].framgentSize	= 0;
	  state->second.report.fragments[i].fragmentRecovered	= false;
	  state->second.report.fragments[i].fragmentUsed	= false;
	  state->second.report.fragments[i].errorInHeader	= false;  
	}
	
    }
    //fec report
    state->second.report.fragments[fec_header.fragmentID].receivedOrderNumber 	= state->second.report.fragmentsReceived++;
    state->second.report.fragments[fec_header.fragmentID].sentOrderNumber 	= fec_header.fragmentID;
    state->second.report.fragments[fec_header.fragmentID].framgentSize		= fec_header.framgentSize;
    
    /***************************************************************************************
     *					de-Hamming 
     ***************************************************************************************/
    //calculating number of hammed chunks
    chunks = (fec_header.framgentSize)/(CHUNK_SIZE + PARITY_BYTE_PER_CHUNK); 

    printf("^^^^^^^^decoding fragment: %d  [MSG_ID=0x%x] ^^^^^^^^\n",fec_header.fragmentID,fec_header.msgID);
    for (unsigned int i = 0; i < chunks; ++i) 
    {
	unsigned int nByte;
    	unsigned int nBit;
	//it's 8 here because we remove parity byte in the loop, so eChunk gets shorter while looping
    	int decodeResult = hamming_decode(eChunk, (unsigned int)(i*8 + FEC_HEADE_SIZE)     , \
    	                                          (unsigned int)(i*8 + FEC_HEADE_SIZE + 8) , \
								      &nByte,&nBit);
	if(i%8==0) printf("\n");
    	if(decodeResult == 0)
        {
	    printf("%3d [ok],",i);
        }
    	else if(decodeResult == 1)
    	{
		printf("%3d [rc],",i);
		eChunk[i*8+nByte] ^= (char)(0x1 << (nBit));
		//fec report
		state->second.report.fragments[fec_header.fragmentID].recoveredBitsNumber++;
    	}
    	else if(decodeResult == 2)
	{ 
		printf("%3d [x.]\n",i);
		printf("[FAILED] to recover fragment: %d  [MSG_ID=0x%x]\n\n",fec_header.fragmentID,fec_header.msgID);
		*origMsgSize = 0;
		state->second.report.fragmentsLost++;
		return 0; // the fragment is useless
    	}
    	
    	if(decodeResult == 0 || decodeResult == 1)
	{
		//get rid of hamming parity byte
		//CAUTION: we are shortening eChunk while looping through it !!!!
		eChunk.erase((size_t)(i*8 + FEC_HEADE_SIZE + 8), (size_t)1);
	}
	else //something is wrong
	{
		*origMsgSize = -1;
		printf("ERROR[fec_decoder]: decodeResult = %d \n", decodeResult);
    		return 0; // the fragment is useless, we don't add it to the cache
	}
    }
    printf("\n[SUCCEEDED] to recover fragment: %d  [MSG_ID=0x%x]\n",fec_header.fragmentID,fec_header.msgID);


    state->second.report.fragments[fec_header.fragmentID].fragmentRecovered 	= true;

    // Grab the data portion of the buffer (without header, all chunks 
    state->second.received[fec_header.fragmentID] = eChunk.substr((int)FEC_HEADER_SIZE, (int)(receivedFragSize));
    
    unsigned int chopNumber = fec_chopchop(fec_header.originSize);
						      
    //If we don't have enough packets yet (or already decode), stop now
    // if we have any two fragments, we are able to decode
    if (state->second.recovered_fragments_received < chopNumber)
	state->second.report.fragments[fec_header.fragmentID].fragmentUsed = true;
    if (++state->second.recovered_fragments_received != chopNumber) return 0; 

    /***************************************************************************************
     *					de-R-S-ing
     ***************************************************************************************/

    // DECODING TIME!
    std::vector<const unsigned char*> fragments;
    fragments.resize(fragNumber);
   
    for (unsigned int i = 0; i < fragments.size(); ++i)
        if (state->second.received[i].empty())
            fragments[i] = 0;   //fragments set to 0x0 are considered lost
        else
            fragments[i] = reinterpret_cast<const unsigned char*>(state->second.received[i].data());

    // Do the work
    RS_code(divSize, fragments);

    // Reassemble the packet
    result.clear();
    result.reserve(divSize*chopNumber);
    for (unsigned int i = 0; i < chopNumber; ++i)
        for (unsigned int j = 0; j < divSize; ++j)
            result.push_back(fragments[i][j]);

    result.resize(fec_header.originSize); // clip the padding and done

    *origMsgSize   = result.size();
    *origEtherType = fec_header.etherType;
    
    //fec report

    state->second.report.msgRecovered						= true;
    
    return reinterpret_cast<const unsigned char*>(result.data());
}
/*--------------------------------------------------------------------------------------------------
 *  				fec_encoding
 * -----------------------------------------------------------------------------------------------*/
/*
 * 
 * this vector stores the FEC-encoded frames' payloads ready to be sent,
 * this means 2 x original data (with hamming bytes) + 2 x R-S (with hamming bytes)
 * 
 */
static std::vector<std::string> output_messages; 



/*
 * So, this function does all the work: 
 * 1) takes message-to-be-encoded (chunk) of size len
 * 2) splits it into two
 * 3) does R-S and hamming
 * 4) stores the result in the output_masseges vector
 * 
 * Inputs: 
 * 	chunk		- message to be encoded
 * 	len 		- length of the message to be encoded
 * 	origEtherType	- EtherType with which the original message was supposed to be sent
 * Outputs:
 * 	output_messages - this is a global variable, it's not nice, TOBECHANGED, legacy of 
 * 			  the original code
 */

static unsigned int fec_setup(const unsigned char* chunk, unsigned int len, unsigned int  origEtherType)
{
    int i;
    unsigned int mSize = len;
    unsigned int divSize;
    unsigned int rsinSize;
    unsigned int fragSize;
    unsigned int fragNumber;
    fec_header_t fec_header;
    std::string buf(reinterpret_cast<const char*>(chunk), mSize);// buffer for input data
    std::vector<const unsigned char*> fragments;
    
    //calculate FEC params, so sizes of messages at different stages of encoding
    fec_parameters(mSize, &divSize, &rsinSize, &fragSize, &fragNumber);
    
    //assemble header info
    fec_header.schemaID		= DEFAULT_FEC_SCHEMA_ID;
    fec_header.fragmentID	= 0; // will be re-filled later 
    fec_header.msgID		= (++outgoing_msgID) & 0xFFFF;
    fec_header.etherType	= origEtherType;
    fec_header.originSize	= mSize;
    fec_header.framgentSize	= fragSize;
    
    //prepare the vector where the output data will be put (output_messages is a global variable)
    output_messages.clear();
    output_messages.resize(fragNumber); 
    
    
    /***************************************************************************************
    /*					Reed-Solomon
     ***************************************************************************************/
    
    //prepare fragments for R-S
    fragments.reserve(fragNumber);
       
    for (unsigned int i = 0; i < K; ++i)
        fragments.push_back(0);
    for (unsigned int i = 0; i < fec_chopchop(mSize); ++i)
        fragments.push_back(reinterpret_cast<const unsigned char*>(buf.data() + i*divSize));

    // Do the actual RS-encoding
    RS_code(divSize, fragments); //output in the fragments
    
    
    /***************************************************************************************
    /*			Header + Hamming => output fragments
     ***************************************************************************************/    
    
    for (unsigned int i = 0; i < output_messages.size(); ++i)
    {
        
	// msg to be hammed
	//    this >>fragments[(i+2)%4]<< is a bit nasty, but it enables the R-S to work like
	//    in HW (fragments of indecies 0 & 1 are missing) and have the same output of the
	//    FEC module like in HW (first two frames are the original payload, the other two 
	//    are the R-Sed messages)	
        std::string msg(reinterpret_cast<const char*>(fragments[(i+2)%4]), divSize); 
						      
	// msg with hamming parity bits and header
	//std::string msg_hammed(FEC_HEADER_SIZE, 'x'); 
	
						      // here we store the payload with hamming 
						      // the way I (Maciej) implemented it in the HW,
						      // so, store the parity byte after the Hammed
						      // chunk of data (8 bytes), we use this one
	
	unsigned int chunks = rsinSize / 8; //SEC-DED(72,64) is 8 bytes at once
	
	//(1) header
	fec_header.fragmentID = i;
	std::string msg_hammed(create_FEC_header(&fec_header),FEC_HEADER_SIZE) ;
	//create_FEC_header(&fec_header);
	/*
	uint64_t header = create_FEC_header(&fec_header);
	for (int j = 7; j > 0; --j) 
	{
            uint8_t low = header & 0xFF;
            header >>= 8;
	    msg_hammed[j]= static_cast<char>(low); // this will be the output
         }	
         */
	//(2) add padding
        msg.resize(rsinSize); // Pad with 0 to a multiple of 8

	unsigned int j,z;
	unsigned int p_chunk_s; // pointer to the start of a chunk in the msg_hammed table
	//(4) hamming the chunk
	//    in this loop we add Hamming parity bytes
        for (j = 0, p_chunk_s=FEC_HEADER_SIZE; j < chunks; ++j, p_chunk_s=p_chunk_s+9) // skip FEC header !!!!
        {
	  
	    //(4a) hamming the chunk and remembering parity byte
	    std::string parityBits = hamming_encode(msg.substr(j*8,8).c_str());
	    
	    //(4b) [like in HW] store the chunk of data (8 bytes) 		    
	    msg_hammed += std::string(reinterpret_cast<const char*>(msg.substr(j*8,8).c_str()), 8);

	    //(4c) add the parity byte
	    //    [ below is a nasty way of adding the parity byte]
	    std::string tmp(reinterpret_cast<const char*>(parityBits.c_str()), 2);
	    uint8_t pb = parityBits[0] & 0x000000FF;
	    msg_hammed.push_back('x');
	    msg_hammed[p_chunk_s + 8] = static_cast<char>(tmp[0]);
	    
	    #ifdef DBG_SHOW_CHUNKS
	    printf("chunk_%2d: ", j );
	    for (z=0;z<8;z++) printf("0x%2x ",0xFF & msg_hammed[p_chunk_s+z]);
	    printf("[parity :0x%2x]\n",0xFF & msg_hammed[p_chunk_s+z]);
	    #endif

	    parityBits.clear();
        }

	//(5) Fragment done !!!
        output_messages[i] = msg_hammed;

    }
    return fec_header.msgID;
}

/*--------------------------------------------------------------------------------------------------
 * 			functions which should be used by the outside world for encoding
 * -----------------------------------------------------------------------------------------------*/
/*
 * The function takes message-to-be-encoded stored in the origMsg table. The length of the 
 * message-to-be-encoded is provided as origMsgLen input.
 * The outputs are encoded messages (to be send as payloads of Ethernet Frames)
 * 
 * Inputs:
 * 	origMsg[] 	- original message to be encoded
 * 	origMsgLen	- length of the original message
 * 	
 * Outputs:
 * 	encodedMsgs	- a *msg_number* of encoded messages of size *encodedMsgLen*
 * 	encodedMsgLen	- table with lengths of each encoded message
 * 	msg_number	- number of encoded messages (for the time being it's always 4)
 * 	
 * Returns:		
 *			- 1 if successful
 *
*/
int fec_encoder(const unsigned char*  origMsg,     unsigned int  	 origMsgLen, 
		      unsigned int  origEtherType, const unsigned char** encodedMsgs,
		unsigned int* encodedMsgLen,	   unsigned int*         msg_number)  
{
  
    unsigned int msgID = fec_setup(origMsg, origMsgLen, origEtherType);
    *msg_number=(int)output_messages.size();
    for(int index = 0; index < *msg_number; index++)
    {
      encodedMsgLen[index] = output_messages[index].size(); // this include Header
      encodedMsgs[index]   = reinterpret_cast<const unsigned char*>(output_messages[index].data());
    }
    printf("\n-----------------------------  FEC_ENCODER  ----------------------------\n");
    printf("Encoded an original message of %d size into %d msgs of size: \n",
	   origMsgLen, *msg_number);
    for( int i = 0; i < *msg_number; i++) printf("MSG_ID=0x%x, Fragment_ID=%d, size=%dbytes\n", 
						  msgID, i, encodedMsgLen[i]); 
    printf("------------------------------------------------------------------------\n");
    return 1;
}

/*
 * Function by Cesar which provides each encoded message separately). It is just a wrapper
 * for fec_setup() which does all the work.
 * 
 * Inputs:
 * 	chunk	- the message to be encoded 
 * 	index	- the index of the desired encoded message which is returned 
 * Outputs:
 * 	len	- the length of the encoded message
 * Returns:
 * 		- encoded message (fragment number=index)
 */
const unsigned char* fec_encode(const unsigned char* chunk, unsigned int* len, int index)  
{
    if (index == 0) fec_setup(chunk, *len, 0xABCD /*origEtherType*/);
    if (index == (int)output_messages.size()) return 0;

    *len = output_messages[index].size();
    return reinterpret_cast<const unsigned char*>(output_messages[index].data());
}

/*const unsigned char* fec_decode(const unsigned char* chunk)
{*/
  
static std::vector<std::string> erroredMsgs; 
#define MAX_ERROR_NUMBER 15
int erasure_channel(const unsigned char** errorMsgs, 	const unsigned char** encodedMsgs, 
		    unsigned int encodedMsgLen[],	unsigned int msg_number)  
{
     erroredMsgs.clear();
     erroredMsgs.resize(msg_number); 
     unsigned int start = rand() % msg_number;
     unsigned int totalErrorNumber = 0;
     
     for(int i = 0; i < msg_number; i++)
     {
	unsigned int index = (start+i)%4; 
	std::string msg(reinterpret_cast<const char*>(encodedMsgs[index]), encodedMsgLen[index]);
	
	unsigned int errors_number = rand() % MAX_ERROR_NUMBER;
	totalErrorNumber +=errors_number;
	
	for(int j = 0; j< errors_number; j++)
	{
	    //chose fragment
	    unsigned int byte_number     = rand() %  encodedMsgLen[index];
	    unsigned int bit_number	 = rand() % 8;
	    msg[byte_number] 		^= (char)(0x1 <<  bit_number);   
	}
	
	erroredMsgs[i] = msg;
	
	errorMsgs[i]= reinterpret_cast<const unsigned char*>(erroredMsgs[i].data());
     }
     
     printf("\n---------------------------  ERASURE_CHANNEL  --------------------------\n");
     printf("\t\t\ttotal number of bit flips : %d\n",totalErrorNumber);
     printf("------------------------------------------------------------------------\n");
     
     return 0;
}
  
int fec_decoder_report()
{
      printf("\n------------------------------------------------------------------------\n");
      printf("---------------------FEC decoder REPORT---------------------------------\n");
      printf("------------------------------------------------------------------------\n");
      Map::iterator state = cache.begin();
      do
      {
	  printf("MSG ID : .......................... 0x%x\n",state->first);
	  printf("MSG size : ........................ %3d bytes\n",state->second.msize);
	  if(state->second.report.msgRecovered)
	  printf("MSG state: ........................ recovered\n");
	  else
	  printf("MSG state: ........................ lost \n");
	  
	  printf("Fragments received : .............. %2d\n",state->second.report.fragmentsReceived); 
	  printf("Fragments lost: ................... %2d \n",state->second.report.fragmentsLost);
	  printf("Fragment  duplicates: ............. %2d \n",state->second.report.duplicatesReceived);
	  for(int i = 0; i < state->second.report.fragmentsReceived; i++)
	  {
	  printf("Fragment%2d ............................. \n",i);
	  printf("         ID: ...................... %2d \n",state->second.report.fragments[i].sentOrderNumber);
	  printf("         Size: .................... %2d \n",state->second.report.fragments[i].framgentSize);
	  printf("         Received order: .......... %2d \n",state->second.report.fragments[i].receivedOrderNumber);
	  printf("         Recovered bits: .......... %2d \n",state->second.report.fragments[i].recoveredBitsNumber);
	  printf("         Duplicates: .............. %2d \n",state->second.report.fragments[i].duplicatesReceived);
	  if(state->second.report.fragments[i].fragmentRecovered)
	  printf("         Recovered : .............. YES \n");
	  else
	  printf("         Recovered : .............. NO \n");
	  if(state->second.report.fragments[i].fragmentUsed)
	  printf("         Used : ................... YES \n");
	  else
	  printf("         Used : ................... NO \n");
	  }
	  printf("------------------------------------------------------------------------\n");	  
	  
	  state++;
      
      } while(state != cache.end());
      printf("------------------------------------------------------------------------\n");	  
}
  