#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

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;

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;
}

struct State 
{
    unsigned int msize;
    unsigned int divsize;
    unsigned int fragments_received;
    std::vector<std::string> received;
};

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

static Map cache;
static Queue inCache;

static std::string result;
const unsigned char* fec_decode(const unsigned char* chunk, unsigned int* len, fec_header_t *fec_header)
{
    unsigned int length = *len - FEC_HEADE_SIZE; // minus FEC header size !!!!
    unsigned int chunks = length / 9;
    unsigned char tmp_chunk;
    if (length % 9 != 0 || length < 18) 
    { 
      printf("quiting decoding because :  (length % 9 != 0 || length < 18)\n");
      return 0;
    }

    std::string eChunk(reinterpret_cast<const char*>(chunk),*len); 

    /*
     * sorting out FEC header staff
     */
    fec_header->schemaID     =   (static_cast<uint8_t> (eChunk[1]))     & 0xF;
    fec_header->fragmentID   =  ((static_cast<uint8_t> (eChunk[1]))>>4) & 0xF;
    fec_header->msgID        = (((static_cast<uint8_t> (eChunk[3]))<<8) & 0xFF00) |  ((static_cast<uint8_t> (eChunk[0]))     & 0xFF) ;
    fec_header->etherType    = (((static_cast<uint8_t> (eChunk[5]))<<8) & 0xFF00) |  ((static_cast<uint8_t> (eChunk[2]))     & 0xFF) ;
    fec_header->originSize   = (((static_cast<uint8_t> (eChunk[7]))<<8) & 0x1F00) |  ((static_cast<uint8_t> (eChunk[4]))     & 0xFF) ;
    fec_header->framgentSize = (((static_cast<uint8_t> (eChunk[6]))<<3) & 0x07F8) | (((static_cast<uint8_t> (eChunk[7]))>>5) & 0x07) ;

    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);
    
    /*
     * dealing with hamming coding
     */
    
    printf("^^^^^^^^ de-Hamming chunks (8 byte of data + 1 byte of parity: ^^^^^^^^ ");
    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(decodeResult == 0)
        {
	    if(i%8==0) printf("\n");
	    printf("%3d [ok], ",i);
	    
        }
    	else if(decodeResult == 1)
    	{
		printf("\n%3d [recovery attempted]\n",i);
    	}
    	else if(decodeResult == 2)
	{ 
		printf("\n%3d [unrecoverable]\n",i);
    		//return 0;
    	}
    	
    	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 
	{
		//we need to use R-S
		//do sth intelligent?
	}
    }
    printf("\n^^^^^^^^^^^^^^^^^^^ de-Hamming finished ^^^^^^^^^^^^^^^^^^^^^^^^^^ \n");


    /*
     * decoding R-S
     */
   
    uint32_t mID         = fec_header->msgID;
    unsigned int msize   = fec_header->originSize;
    unsigned int fragLen = fec_header->framgentSize;
    unsigned int index   = fec_header->fragmentID;         
    
    unsigned int output_messages = fec_chopchop(msize);
    unsigned int divsize = (msize+output_messages-1) / output_messages;
    unsigned int rsinsize = (divsize+7) & ~7;

    if (fragLen != length) return 0;
    if (fragLen != (rsinsize+8)/8*9) return 0;
    if (index > output_messages+K) return 0;

    Map::iterator state = cache.find(mID);
    
   // cout << "state=cache.find(mID): " << state->first <<endl;
    if (state == cache.end())
    {
        if (inCache.size() > MAX_output_messages)
        {
            uint32_t kill = inCache.front();
            inCache.pop();
            cache.erase(cache.find(kill));
        }
        inCache.push(mID);

        State& newState = cache[mID]; // add it
        state = cache.find(mID);

        newState.msize = msize;
        newState.fragments_received = 0;
        newState.divsize = (msize + output_messages-1) / output_messages;
        newState.received.resize(output_messages + K);
    }

    if (state->second.msize != msize) return 0; // Doesn't fit with other packets
    if (!state->second.received[index].empty()) return 0; // Duplicated packet

    // Grab the data portion of the buffer
    state->second.received[index] = eChunk.substr(8, (chunks-1)*8);
    //state->second.received[index] = std::string(reinterpret_cast<const char*>(chunk)+8, (chunks-1)*8);

    //If we don't have enough packets yet (or already decode), stop now
    if (++state->second.fragments_received != output_messages) return 0;

    // DECODING TIME!
    std::vector<const unsigned char*> fragments;
    fragments.resize(state->second.received.size());
    for (unsigned int i = 0; i < fragments.size(); ++i)
        if (state->second.received[i].empty())
            fragments[i] = 0;
        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*output_messages);
    for (unsigned int i = 0; i < output_messages; ++i)
        for (unsigned int j = 0; j < divsize; ++j)
            result.push_back(fragments[i][j]);

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

    *len = result.size();
    return reinterpret_cast<const unsigned char*>(result.data());
}
/*---------------------------------
 *  fec_setup
---------------------------------*/

static std::vector<std::string> output_messages;

static void fec_setup(const unsigned char* chunk, unsigned int len)
{
    unsigned int msize = len;
    output_messages.clear();
    output_messages.resize(fec_chopchop(msize));

    unsigned int divsize = (msize+output_messages.size()-1) / output_messages.size();
    unsigned int rsinsize = (divsize+7) & ~7;

    // Make a -id
    uint32_t msgID = ++outgoing_msgID;

    std::string buf(reinterpret_cast<const char*>(chunk), msize);
    buf.resize(divsize*output_messages.size()); // Pad with 0 to a multiple of output_messages

    std::vector<const unsigned char*> fragments;
    fragments.reserve(output_messages.size()+K);

    for (unsigned int i = 0; i < output_messages.size(); ++i)
        fragments.push_back(reinterpret_cast<const unsigned char*>(buf.data() + i*divsize));
    for (unsigned int i = 0; i < K; ++i)
        fragments.push_back(0);

    // Do the actual RS-encoding
    RS_code(divsize, fragments);

    output_messages.resize(output_messages.size() + K);



    for (unsigned int i = 0; i < output_messages.size(); ++i)
    {
        //std::string msg(8, 'xxxxxxxx');
        std::string msg(8, 'x');

        unsigned int fragLen = (rsinsize + 8)/8*9;
        uint64_t header =
            ((uint64_t)msgID   << 32) |
            ((uint64_t)msize   << 20) |
            ((uint64_t)fragLen <<  8) |
            ((uint64_t)i       <<  0);

        for (int j = 7; j > 0; --j) {
            uint8_t low = header & 0xFF;
            header >>= 8;
            msg[j] = static_cast<char>(low);
         }

        msg += std::string(reinterpret_cast<const char*>(fragments[i]), divsize);
        msg.resize(rsinsize + 8); // Pad with 0 to a multiple of 8


    	//std::string parityBits = hamming_encode(reinterpret_cast<const char*>( msg.c_str()));

       	//msg.push_back(parityBits[0]); // !!! result of hamming code
       	//cout << "Parity HEADER " << parityBits[0] << endl;
       	//cout << "WITH HEADER " << msg << endl;
        //reinterpret_cast<const char*>(fragments[i])

         unsigned int chunks = msg.size() / 8; // SEC-DED(72,64) is 8 bytes at once

#ifdef __DEBUG         
     	 cout << "---------------------CHUNK " << i << "--------------------------" <<  endl;
#endif

        for (unsigned int j = 0; j < chunks; ++j)
        {
            // encode msg[j*chunks] to msg[j*chunks + chunks-1]
        	//std::string parityBits = hamming_encode(reinterpret_cast<const char*>(chunk+j*8));
#ifdef __DEBUG
        cout << "          STRING TO ENCODE " << msg.substr(j*8,8) << endl;
#endif             

       	std::string parityBits = hamming_encode(msg.substr(j*8,8).c_str());
        msg.push_back(parityBits[0]); // !!! result of hamming code

        //cout << "Parity " << parityBits[0] << endl;
        //cout << "NUMBER ENCODED " << j << " "<< msg << endl;
      	//cout << "LENGTH MSG A" << msg.length() << endl;
       	parityBits.clear();
      	//msg.push_back(0);

        }

        output_messages[i] = msg;

#ifdef __DEBUG        
     cout << "-----------------------------------------------------" <<  endl;
   	 cout << "         HAMMING WORD ENCODED " << msg << endl;
   	 cout << "-----------------------------------------------------" <<  endl;
#endif     

    }

#ifdef __DEBUG
     cout << "****END ENCODING CONTROL MESSAGE************************ " << endl;
     cout << "..........TRANSMISSION........................... " << endl;
#endif


}

/*------------------------------------
 *fec_encode
 -----------------------------------*/

const unsigned char* fec_encode(const unsigned char* chunk, unsigned int* len, int index)  
{
	if (index == 0) fec_setup(chunk, *len);
    if (index == (int)output_messages.size()) return 0;

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

