#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <string.h>
#include <bitset>
#include <iomanip>
#include <cstddef>

using namespace std;

uint32_t bM [12]= {0x7ff,0xee2,0xdc5,0xb8b,0xf16,0xe2d,0xc5b,0x8b7,0x96e,0xadc, 
                   0xdb8, 0xb71};

uint32_t iM[12] = { 0x800, 0x400, 0x200, 0x100, 0x080, 0x040,0x020, 0x010, 0x008, 
                    0x004, 0x002, 0x001 };

uint32_t gM[12] = {};
uint32_t hM[12] = {};

void printMatrix(uint32_t *matrix,size_t lengthM)
{
            for(int i=0;i<lengthM;i++)
            {
                cout << hex << matrix[i] << "\t";
                cout << " vector " << "\t" << i << "\t " << 
                bitset<24>(matrix[i]).to_string() << endl;
            }
}

void printMatrixIndex(uint32_t *matrix, uint32_t length)
{
    for(int i=0;i<12;i++)
    { 
        for(int j=0;j<length;j++)
        {
            if((matrix[i]>>j) & 0x1)
            {  
                 printf("%d  ",j);
            }
            else
            {
                 printf("   ");
            }
        }
        printf("\n");
    }
    printf("\n");
}

void create_hM(uint32_t *matrix)
{

   for(int i=0;i<12;i++)
   {
        hM[i] = (matrix[i] << 12) |  (0x1 << (11-i)) ;  
   }
 
   cout << "Matrix H" << endl;
   printMatrix(hM,12);
 
}

void create_gM(uint32_t *matrix)
{

   for(int i=0;i<12;i++)
   {
       gM[i] = (0x1 << (23-i)) | matrix[i]; 
   }
   
   cout << "Matrix G" << endl;
   printMatrix(gM,12);
 
}

uint32_t encode(uint32_t data)
{
    uint32_t word=0;
    uint32_t parity=0;

    cout << "*****Encoding*****" << endl;


    for(int i=0;i<12;i++)
    {
        word |= (bitset<12>(data & bM[i]).count() &  0x1 ?
                                                     1 : 0)
                                                     << (11-i);
    }   
    word ^= (data << 12);
    return word;
}

void kickInError(uint32_t *data)
{
    uint32_t min,maxB,maxE;
    uint32_t numE,errorP;

    min=1;
    maxB=23; // error in one bit from 0 to 24
    maxE=4;  // number of errors (so far till 3 errors)

    srand(time(NULL));

    cout << "*****Errors Introduced*****" << endl;
    numE = (rand()%(maxE - min) + min);
    cout << "Number of error: " << numE << endl;

    for(uint32_t i=0;i<numE;i++)
    {
        errorP =  rand()%((maxB - min) + min);
        *data ^= (0x1 << errorP);
        cout << " Error " << i+1 <<  " in position " << errorP << endl;
    }

    cout << endl;

}

uint32_t decode(uint32_t data)
{
    uint32_t wordD=0;
    uint32_t syndrome=0;
    uint32_t hammingW=0;
    uint32_t error=0;
    uint32_t syndromexB=0;

    cout << "*****Decoding*****" << endl;
    cout << "****STEP 1****" << endl;

    for(int i=0;i<12;i++)
    {
        syndrome ^=  (bitset<24>(data &gM[i]).count() &  0x1 
                                                      ? 1 : 0)
                                                      << (11-i);

        cout << i <<  " syndrome " << hex <<  syndrome << "\t";        
        cout << bitset<12>(syndrome).to_string() << endl;
   }   

   cout << "----------------------------" << endl;
   cout << "***Step 2****" << endl;
   cout << "Syndrome " << syndrome << " " ;
   cout <<  bitset<24>(syndrome).to_string() << endl;
    
   hammingW = bitset<12>(syndrome).count() - 1;

   cout << "***Step 2****" << endl;
   cout << "Hamming Weight " <<  hammingW << endl;

    if(hammingW<=3)
    {
        cout << "Hamming Weight  <3" << endl;

        wordD = (syndrome << 12) ^ data; // c = (s,0) + r
    }
    else
    {
        cout << "----------------------------" << endl;
        cout << "****STEP 3****" << endl;
        cout << "Hamming Weight >3" << endl;

        for(int j=0;(j<12) && !(hammingW <= 2) ;j++)
        {
            error  = ((syndrome<<12) ^ hM[j]);
            hammingW =  bitset<24>(error).count() - 1;

            cout << j <<  " error "<< hex << error << " ";
            cout << " hamingW " <<  hammingW  << " " 
                 << bitset<24>(error).to_string() <<  endl;
        }

        if(hammingW <=2){
            cout << "HIT 1" << endl;
            wordD = error ^ data;
        }
        else                             // s.B
        {
            cout << "-------------------------" << endl;
            cout << "****STEP 4****" << endl;
            cout << "Hamming Weight >2" << endl;
            cout << "Calculating  s.B" << endl;

            for(int k=0;k<12;k++)
            {
                syndromexB ^= (bitset<12>(syndrome & bM[k]).count() &  0x1 
                                                                    ? 1 : 0)
                                                                    << (11-k);
                cout << k << " error " << syndromexB << endl;
            }
            hammingW = bitset<24>(syndromexB).count();
            cout << "Hamming " << hammingW << endl;

            cout << "--------------------------" << endl;
            cout << "*****STEP 5*****" << endl;

            if(hammingW<=3)
            {
                
                cout << "HIT 2" << endl;
                error = syndromexB;
                wordD = error ^ data;

            }
            else            // (Beta.B+rowi)
            {
                cout << "----------------------------" << endl;
                cout << "Hamming Weight >3" << endl;
                cout << "Calculating  Beta.B+rowi" << endl;

                for(int l=0;(l<12) && !(hammingW <= 2);l++)
                {
                    error  = (syndromexB ^ gM[l]);
                    hammingW =  bitset<24>(error).count()-1;

                    cout << l <<  " error "<< hex << error << "\t ";
                    cout << " hamingW " <<  hammingW  << " " 
                         << bitset<24>(error).to_string() <<  endl;
                }

                if(hammingW<=3)
                {
                    cout << "HIT 3" << endl;
                    wordD = error ^ data;
                }
                else
                {
                    cout << "OOPPPPSSS no decoded" << endl;
                    return 0;
                }

            }

        }

    }


    return wordD;
}
int main(void)
{
    uint32_t data=0x555;
    uint32_t word;
    uint32_t dataD;
    
    cout << "B Matrix" << endl;
    printMatrix(bM,12);
    printMatrixIndex(bM,12);
    
    create_hM(bM);
    printMatrixIndex(hM,24);

    create_gM(bM);
    printMatrixIndex(gM,24);

    word = encode(data);

    cout << "data " << "\t \t \t" << bitset<12>(word).to_string();
    cout << "\t \t"  << word << endl;
    cout << "Encoded Word "<< "\t" << bitset<24>(word).to_string();
    cout <<  "\t" << word << endl;

    kickInError(&word);
    
    //uint32_t errorP=0;

    // word ^= (0x1 << errorP);
    // word ^= (0x1 << errorP+1);
    // word ^= (0x1 << errorP+14);

    cout << "error Word " << "\t" << bitset<24>(word).to_string() ;
    cout << "\t" << word << endl;
 
    dataD = decode(word);
    cout << "decode " << (dataD ) << endl;

    return 0;
}

