#include "common.h"
#include "PatchHeap.h"

PatchHeap::PatchHeap()
    : _freeBlocks(nullptr)
{
    _unusedBlockPool = &_blocks[0];
    for (u32 i = 0; i < _blocks.size() - 1; i++)
        _blocks[i].next = &_blocks[i + 1];
    _blocks[_blocks.size() - 1].next = nullptr;
}

void PatchHeap::AddFreeSpace(void* block, u32 size)
{
    PatchHeapBlock* cur = _freeBlocks;

    bool added = false;
    while (cur)
    {
        if ((u8*)cur->block + cur->size == block)
        {
            cur->size += size;
            added = true;
            break;
        }
        else if ((u8*)block + size == cur->block)
        {
            cur->block = block;
            cur->size += size;
            added = true;
            break;
        }

        cur = cur->next;
    }

    if (added)
        return;

    PatchHeapBlock* heapBlock = _unusedBlockPool;
    _unusedBlockPool = _unusedBlockPool->next;

    heapBlock->block = block;
    heapBlock->size = size;
    heapBlock->next = _freeBlocks;
    _freeBlocks = heapBlock;
}

void* PatchHeap::Alloc(u32 size)
{
    PatchHeapBlock* prev = nullptr;
    PatchHeapBlock* cur = _freeBlocks;

    PatchHeapBlock* bestBlock = nullptr;
    PatchHeapBlock* bestBlockPrev = nullptr;

    while (cur)
    {
        if (cur->size >= size && (!bestBlock || cur->size < bestBlock->size))
        {
            bestBlock = cur;
            bestBlockPrev = prev;
            if (bestBlock->size == size)
                break;
        }

        prev = cur;
        cur = cur->next;
    }

    if (!bestBlock)
    {
        LOG_FATAL("No space found to put patch of size 0x%x\n", size);
        while (1);
        return nullptr;
    }

    void* result = bestBlock->block;

    if (bestBlock->size == size)
    {
        if (bestBlockPrev)
            bestBlockPrev->next = bestBlock->next;

        if (_freeBlocks == bestBlock)
            _freeBlocks = bestBlock->next;

        bestBlock->next = _unusedBlockPool;
        _unusedBlockPool = bestBlock;
    }
    else
    {
        bestBlock->block = (u8*)bestBlock->block + size;
        bestBlock->size -= size;
    }

    LOG_DEBUG("Allocated 0x%p, size = 0x%X\n", result, size);

    return result;
}
