#include "string_storage.h"

#include <stdlib.h>
#include <string.h>

#define STRING_STORAGE_BUDGET RT_MB(1)
/* Must be greater than sizeof(rt_free_range) */
#define MIN_SPLIT_LENGTH 64

#define HEADER_MAGIC 0x1234567812345678uLL

typedef struct rt_free_range {
    /* Begin is address of this struct */
    size_t length;
    struct rt_free_range *next;
} rt_free_range;

typedef struct {
    size_t alloc_len;
    uint64_t magic;
} rt_string_header;

static char _mem[STRING_STORAGE_BUDGET];
static rt_free_range *_first_free;

_Static_assert(sizeof(rt_free_range) < MIN_SPLIT_LENGTH, "Minimal split length must be greater than the size of a free list node.");

void InitStringStorage(void) {
    _first_free         = (rt_free_range *)& _mem[0];
    _first_free->length = STRING_STORAGE_BUDGET;
    _first_free->next   = NULL;
}

RT_DLLEXPORT char *rtStoreString(const char *sz) {
    size_t len = strlen(sz) + 1;
    size_t alloc_len = len + sizeof(rt_string_header);

    /* Look for a free range that is large enough */
    rt_free_range *free_range = _first_free;
    rt_free_range *prev       = NULL;
    while (free_range && free_range->length < alloc_len) {
        prev       = free_range;
        free_range = free_range->next;
    }
    if (!free_range)
        return NULL;

    /* Should we split? */
    if ((free_range->length - alloc_len) >= MIN_SPLIT_LENGTH) {
        rt_free_range *split = (rt_free_range *)((char *)free_range + alloc_len);
        split->length        = free_range->length - alloc_len;

        /* Enter into the linked list and remove the allocated range */
        split->next          = free_range->next;
        if (prev)
            prev->next = split;
        else
            _first_free = split;
    } else {
        /* Just remove us */
        alloc_len = free_range->length;
        if (prev)
            prev->next = free_range->next;
        else
            _first_free = free_range->next;
    }

    char *ptr = (char *)free_range;
    memcpy(ptr + sizeof(rt_string_header), sz, len);
    rt_string_header *header = (rt_string_header*)ptr;
    header->alloc_len        = alloc_len;
    header->magic            = HEADER_MAGIC;
    return ptr + sizeof(rt_string_header);
}

RT_DLLEXPORT void rtFreeString(char *storeStringRet) {
    if (!storeStringRet)
        return;

    size_t len = strlen(storeStringRet) + 1;
    if (((uintptr_t)storeStringRet < (uintptr_t)&_mem[sizeof(rt_string_header)]) ||
        (((uintptr_t)storeStringRet + len) > (uintptr_t)&_mem[STRING_STORAGE_BUDGET - 1])) {
        rtReportError("STRSTORAGE",
                      "Tried to free a string that is not stored in the string storage.");
        return;
    }
    rt_string_header *header = (rt_string_header *)(storeStringRet - sizeof(rt_string_header));
    if (header->magic != HEADER_MAGIC) {
        rtReportError("STRSTORAGE", "Invalid header in string storage!");
        return;
    }

    size_t alloc_len          = header->alloc_len;
    rt_free_range *free_range = (rt_free_range *)header;
    free_range->length        = alloc_len;

    /* Search the place for inserting it */
    rt_free_range *prev = _first_free;
    if ((uintptr_t)prev < (uintptr_t)free_range) {
        while (prev->next) {
            if ((uintptr_t)prev->next > (uintptr_t)free_range)
                break;
        }
        free_range->next = prev->next;
        prev->next       = free_range;
    } else {
        /* Swap */
        rt_free_range *t = _first_free;
        _first_free      = free_range;
        free_range       = t;
        prev             = _first_free;
    }

    /* Check if we can merge */
    while (((uintptr_t)prev + prev->length) == (uintptr_t)free_range) {
        prev->length += alloc_len;
        prev->next = free_range->next;
        free_range = prev->next;
    }
}