#include "list.h"

#include <iostream>
#include <vector>
#include <stdio.h>

static int FindPointerPosition(const std::vector<ListNode*>& pList, ListNode* pNode)
{
    for (int i = 0; i < static_cast<int>(pList.size()); ++i)
    {
        if (pList[i] == pNode)
            return i;
    }

    return -1;
}

void List::Serialize(FILE* file)
{
    // file data: [items n] n:[rand,rand ... n] n:[size, data ... n]
    // rand = id of ListNode

    if (file == nullptr)
    {
        std::cout << __FUNCTION__ << " fopen returns null ptr; saving stop" << std::endl;

        return;
    }

    if (count == 0)
    {
        std::cout << __FUNCTION__ << " nothing to save, stop" << std::endl;

        return;
    }

    std::vector<ListNode*> vctItemsVector;
    vctItemsVector.reserve(count);

    vctItemsVector.push_back(head);

    for (int i = 1; i < count; ++i)
    {
        ListNode* const pNextNode = vctItemsVector[i-1]->next;

        if (pNextNode)
            vctItemsVector.push_back(pNextNode);

        // list nodes pointers adding to vector
    }

    fwrite(reinterpret_cast<const char*>(&count), sizeof(char), sizeof(count), file);

    int iPosition = -1;
    const int iNoPosition = -1;

    for (auto* pListNode : vctItemsVector)
    {
        if (pListNode->rand)
        {
            iPosition = FindPointerPosition(vctItemsVector, pListNode->rand);

            fwrite(reinterpret_cast<const char*>(&iPosition), sizeof(char), sizeof(int), file);
        }
        else
        {
            fwrite(reinterpret_cast<const char*>(&iNoPosition), sizeof(char), sizeof(int), file);
        }

        // saving position index of rand data field (if exists, if not = -1) at vector
    }

    ListNode* pCurrentItem = head;

    while (pCurrentItem)
    {
        const int iStringDataSize = pCurrentItem->data.size();

        fwrite(reinterpret_cast<const char*>(&iStringDataSize), sizeof(char), sizeof(int), file);

        if (pCurrentItem->data.size())
            fwrite(pCurrentItem->data.c_str(), sizeof(char), pCurrentItem->data.size(), file);

        pCurrentItem = pCurrentItem->next;

        // every node saving: [data size][data]
    }
}

static int GetBytesCount(FILE* file)
{
    fseek(file, 0L, SEEK_END);
    const int iSize = ftell(file);
    rewind(file);

    return iSize;
}

void List::Deserialize(FILE* file)
{
    ClearNodes();

    if (file == nullptr)
    {
        std::cout << __FUNCTION__ << " file not found, load stop" << std::endl;

        return;
    }

    const int iBytesCountInFile = GetBytesCount(file);

    if (iBytesCountInFile == 0)
    {
        std::cout << "File with data is empty, load stop" << std::endl;

        return;
    }

    std::string strLoadData;
    strLoadData.reserve(iBytesCountInFile);

    fread(&strLoadData[0], sizeof(char), iBytesCountInFile, file);

    // file data: [items n] n:[rand,rand ... n] n:[size, data ... n]

    const char* pBufferPointer = strLoadData.data();
    const int iElementsCount = *reinterpret_cast<const int*>(pBufferPointer);
    pBufferPointer += sizeof(int); // move to point after data count

    std::vector<ListNode*> vctItemsVector;
    vctItemsVector.reserve(iElementsCount);

    std::vector<int> vctRandData;
    vctRandData.reserve(iElementsCount);

    for (int i = 0; i < iElementsCount; ++i)
    {
        vctRandData.push_back(*reinterpret_cast<const int*>(pBufferPointer));
        pBufferPointer += sizeof(int); // move after data with link info

        // filling vector with position index (if exists, if not = -1 value)
    }

    for (int i = 0; i < iElementsCount; ++i)
    {
        vctItemsVector.push_back(new ListNode);

        const int iStringSize = *reinterpret_cast<const int*>(pBufferPointer);
        pBufferPointer += sizeof(int); // move after string size info

        if (iStringSize)
        {
            vctItemsVector[i]->data.assign((char*)pBufferPointer, iStringSize);
            pBufferPointer += iStringSize; // move after string data size
        }

        // creating data field
    }

    vctItemsVector[0]->prev = nullptr;
    if (vctRandData[0] != -1)
        vctItemsVector[0]->rand = vctItemsVector[vctRandData[0]];
    else
        vctItemsVector[0]->rand = nullptr;

    if (vctItemsVector.size() > 1)
        vctItemsVector[0]->next = vctItemsVector[1];
    else
        vctItemsVector[0]->next = nullptr;

    for (int i = 1; i < iElementsCount; ++i)
    {
        vctItemsVector[i]->prev = vctItemsVector[i-1];

        if (vctRandData[i] != -1)
            vctItemsVector[i]->rand = vctItemsVector[vctRandData[i]];
        else
            vctItemsVector[i]->rand = nullptr;

        vctItemsVector[i]->next = ((i + 1) < iElementsCount) ? vctItemsVector[i + 1] : nullptr;

        // restoring data order and setting rand field
    }

    head  = vctItemsVector[0];
    tail  = vctItemsVector[iElementsCount - 1];
    count = vctItemsVector.size();
}

void List::ClearNodes()
{
    ListNode* pCurrentNode  = head;
    ListNode* pSaveNextNode = nullptr;

    while (pCurrentNode)
    {
        pSaveNextNode = pCurrentNode->next;

        delete pCurrentNode;
        pCurrentNode = pSaveNextNode;
    }

    head = nullptr;
    tail = nullptr;
}

void List::ShowData() const
{
    if (count == 0)
    {
        std::cout << "List is empty" << std::endl;

        return;
    }

    std::cout << "have nodes: " << count << std::endl;

    ListNode* pListNode = head;
    int iNodeNumber = 0;

    while (pListNode)
    {
        std::cout << "current node " << iNodeNumber;

        std::cout << "; data: " << pListNode->data;

        if (pListNode->rand == nullptr)
            std::cout << "; rand = nullptr" << std::endl;
        else
            std::cout << "; rand set on item with string data: " << pListNode->rand->data << std::endl;

        std::cout << "------------------- item end -------------------" << std::endl;

        pListNode = pListNode->next;
        ++iNodeNumber;
    }
}

List::~List()
{
    ClearNodes();
}

void List::SetNodes(ListNode* pHead, ListNode* pTail, int iCount)
{
    head  = pHead;
    tail  = pTail;
    count = iCount;
}
