#include <stddef.h>
#include <stdio.h>
#include "primary_library.h"
#include "extended_library.h"



PrimaryLibrary::PrimaryLibrary()
{
    starts = NULL;
    pl = NULL;
    totalNumOfElements = 0;
}

PrimaryLibrary::~PrimaryLibrary()
{
    if(starts)
        delete[] starts;
    if(pl)
        delete[] pl;
}

void PrimaryLibrary::createPL(PartialPrimaryLibrary** ppl, int NumOfWindowsInOneRow, unsigned int NumOfSequences)
{
    if(NumOfWindowsInOneRow == 0)
        return;

    this->seqCount = NumOfSequences;

//    unsigned int totalNumOfStarts = NumOfWindowsInOneRow * NumOfWindowsInOneRow *
//                                    ppl[0]->windowSize * ppl[0]->windowSize + 1;//+1 because we want to know how long the last alignment is
    unsigned int totalNumOfStarts = this->seqCount * this->seqCount + 1;//+1 because we want to know how long the last alignment is

    totalNumOfElements = 0;
    for(int i=0; i< NumOfWindowsInOneRow*NumOfWindowsInOneRow; i++)
        totalNumOfElements += ppl[i]->numberOfElements;

    try
    {
        pl = new unsigned int[totalNumOfElements];
        starts = new unsigned int[totalNumOfStarts];
        printf("Allocation of %.0fMB memory for PL.\n", ((double)(totalNumOfElements*4 + totalNumOfStarts*4))/(1024.0*1024.0));
    }
    catch(...)
    {
        printf("Allocation of %.0fMB memory for PL failed.\n", ((double)(totalNumOfElements*4 + totalNumOfStarts*4))/(1024.0*1024.0));
        throw new IndexOutOfRangeException("Not enough memory for primary library! Terminating.");
    }

    //TODO memcpy itd itp
    //void * memcpy ( void * destination, const void * source, size_t num );

    /*  --   --   --   --
     * |  |>|  |>|  |>|  |
     *  --   --   --   --
     *                 /
     *    ------------
     *   /
     *  --   --   --   --
     * |  |>|  |>|  |>|  |
     *  --   --   --   --
     *  --   --   --   --
     * |  | |  | |  | |  |
     *  --   --   --   --
     *  --   --   --   --
     * |  | |  | |  | |  |
     *  --   --   --   --
     */

    unsigned int WS = ppl[0]->windowSize;

    unsigned int currDest = 0;
    starts[0] = 0;
    unsigned int currStart = 1;// 1 because of starts[0] = 0;
    unsigned int size = 0;
    
    for(int j=0; j<NumOfWindowsInOneRow; j++) //goes through all windows vertically
    {
        for(int y=0; y<WS; y++) //iterate through rows in windows
        {
            if(j*WS + y >= this->seqCount)
                continue;

            for(int i=0; i<NumOfWindowsInOneRow; i++) //goes through all windows horizontally
            {
                PartialPrimaryLibrary* currWin = ppl[j*NumOfWindowsInOneRow + i];
                unsigned int windowsOffset = currWin->starts[y*WS];

                size = currWin->starts[(y+1)*WS] - windowsOffset;// size of the row
                if(size > 0)
                    memcpy (&(pl[currDest]), &(currWin->ppl[windowsOffset]), size * sizeof(int));

                //STARTS
                for (int x=1; x<=WS; x++)
                {
                    if(i*WS + x <= this->seqCount) // we are forgetting about windows at this point
                    {
                        starts[currStart] = currDest + currWin->starts[y*WS + x] - windowsOffset;
                        currStart++;
                    }
                }
                    //printf("%d\n", currDest);

                currDest += size;
            }
        }
    }


    for(int i=0; i<NumOfWindowsInOneRow * NumOfWindowsInOneRow; i++)
    {
        delete ppl[i];
    }
    delete[] ppl;

    
}


void PrimaryLibrary::saveLibraryToFile(const char* filename, void* extLib, Sequences* seqs, int K)
{
    ExtendedLibrary* EL = (ExtendedLibrary*)extLib;

    FILE* file = fopen(filename, "w");

    int sequenceNumber = seqs->getSequenceNumber();

    char* seqBuffer = new char[seqs->getMaxSeqLen() + 1];

    fprintf(file, "! TC_LIB_FORMAT_01\n");
    fprintf(file, "%d\n", sequenceNumber); // number of sequences
    for (int i = 0; i < sequenceNumber; i++)
    {
        // retrieving original input sequences
        for (int a = 0; a < (seqs->getLengths()[i]); a++)
        {
            seqBuffer[a] = seqs->getSubtitutionMatrix()->getRevConv()
                          [ seqs->getSequences()
                          [ seqs->getStarts()[i] + seqs->getLengths()[i] - a - 1] ]
                          - 'A' + 'a'; // to convert to small letters
        }
        seqBuffer[seqs->getLengths()[i]] = 0;

        // TODO: nazwy sekwencji
        fprintf(file, "%s %d %s\n", seqs->getSeqName(i), seqs->getLengths()[i],
                seqBuffer);
    }
    delete[] seqBuffer;

    // primary library weights
    char* plBuffer = new char[seqs->getMaxSeqLen() * 2 * (1 + K) * 18]; //+18 because of carret+=18
    unsigned int carret = 0;
    int stop;
    unsigned int val;
    int charCount;
    for (int xx = 0; xx < sequenceNumber; xx++) // X
        for (int yy = 0; yy < sequenceNumber; yy++) // Y
        {
            if (xx < yy)
            {
                carret = 0;
                fprintf(file, "#%d %d\n", xx + 1, yy + 1);
                stop = this->starts[yy * sequenceNumber + xx + 1];
                //int l = this->starts[xx * sequenceNumber + yy];
                for (int k = this->starts[yy * sequenceNumber + xx]; k < stop; k++)
                {
                    if (this->pl[k] == 0xFFFFFFFF)
                        break;


                    val = (this->pl[k] >> 20) + 1; // X
                    if (val == 0)
                    {
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = '0';
                    } 
                    else
                    {
                        charCount = 4;
                        while (val > 0)
                        {
                            plBuffer[carret + charCount] = (val % 10) + '0';
                            val /= 10;
                            charCount--;
                        }
                        while (charCount >= 0)
                        {
                            plBuffer[carret + charCount] = ' ';
                            charCount--;
                        }
                        carret += 5;
                    }

                    val = ((this->pl[k] >> 8)&0xFFF) + 1; // Y

                    if (val == 0)
                    {
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = '0';
                    } 
                    else
                    {
                        charCount = 5;
                        while (val > 0)
                        {
                            plBuffer[carret + charCount] = (val % 10) + '0';
                            val /= 10;
                            charCount--;
                        }
                        while (charCount >= 0)
                        {
                            plBuffer[carret + charCount] = ' ';
                            charCount--;
                        }
                        carret += 6;
                    }


                    if(EL)
                    {
                        val = EL->el[k]; // extended library weight
                    }
                    else
                    {
                        val = this->pl[k]&0xFF; // primary library weight
                    }

                    if (val == 0)
                    {
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = ' ';
                        plBuffer[carret++] = '0';
                    }
                    else
                    {
                        charCount = 5;
                        while (val > 0)
                        {
                            plBuffer[carret + charCount] = (val % 10) + '0';
                            val /= 10;
                            charCount--;
                        }
                        while (charCount >= 0)
                        {
                            plBuffer[carret + charCount] = ' ';
                            charCount--;
                        }
                        carret += 6;
                    }

                    plBuffer[carret++] = '\n';

                    //                        sprintf(&(plBuffer[carret     ]), "%5d ",   this->pl->pl[k] >> 20);
                    //                        sprintf(&(plBuffer[carret +  6]), "%5d ",  (this->pl->pl[k] >> 8)&0xFFF);
                    //                        sprintf(&(plBuffer[carret + 12]), "%5d\n",  this->pl->pl[k]&0xFF);
                    //                        carret += 18;
                }
                plBuffer[carret] = 0;
                //fprintf(file, "%s", plBuffer);
                fwrite(plBuffer, 1, carret, file);
            }
        }

    fprintf(file, "! CPU 90\n! SEQ_1_TO_N");

    fclose(file);
    delete[] plBuffer;
}


int PrimaryLibrary::getResiduePosIn1Seq(int seq1No, int seq2No, int element)
{
    if (pl == NULL)
        return -1;
    unsigned int start = starts[seq2No + seq1No * seqCount];
    unsigned int residue = pl[start + element];
    residue >>= 20;
    return (int) residue;
}

int PrimaryLibrary::getResiduePosIn2Seq(int seq1No, int seq2No, int element)
{
    if (pl == NULL)
        return -1;
    unsigned int start = starts[seq2No + seq1No * seqCount];
    unsigned int residue = pl[start + element];
    residue >>= 8;
    residue &= 0xFFF;
    return (int) residue;
}

int PrimaryLibrary::getWeight(int seq1No, int seq2No, int element)
{
    if (pl == NULL)
        return -1;
    unsigned int start = starts[seq2No + seq1No * seqCount];
    unsigned int weight = pl[start + element];
    weight &= 0xFF;
    return (int) weight;
}

