#include "file_tab.h"
#include "threading.h"
#include "config.h"
#include "hashing.h"

#define NAME_CAP(cap) ((cap)*128)
typedef struct {
    rt_file_id *ids;
    unsigned int *name_offsets;
    char *names;
    unsigned int capacity;
    unsigned int name_head;

    rt_mutex *mutex;
} rt_file_tab;

static rt_file_tab _file_tab;

RT_CVAR_I(rt_FileTabCapacity, "Maximum number of filetab entries. Default: 1024", 1024);

rt_result InitFileTab(void) {
    unsigned int max_files = (unsigned int)rt_FileTabCapacity.i;
    _file_tab.ids = calloc(max_files, sizeof(rt_file_id));
    if (!_file_tab.ids)
        return 1;
    _file_tab.name_offsets = calloc(max_files, sizeof(unsigned int));
    if (!_file_tab.name_offsets)
        return 1;
    _file_tab.names = malloc(NAME_CAP(max_files));
    if (!_file_tab.names)
        return 1;

    _file_tab.capacity  = max_files;
    _file_tab.name_head = 0;
    _file_tab.mutex     = rtCreateMutex();
    return RT_SUCCESS;
}

void ShutdownFileTab(void) {
    free(_file_tab.ids);
    free(_file_tab.names);
    free(_file_tab.name_offsets);
    rtDestroyMutex(_file_tab.mutex);
}

RT_DLLEXPORT rt_file_id rtGetFileId(const char *path) {
    rt_text_span span;
    span.start  = path;
    span.length = (unsigned int)strlen(path);
    return rtGetFileIdFromSpan(span);
}

RT_DLLEXPORT rt_file_id rtGetFileIdFromSpan(rt_text_span path) {
    /* Randomly choosen, aka finger smash keyboard */
    rt_file_id fid = (rt_file_id)rtHashBytes(path.start, path.length);
    if (fid == 0)
        fid = ~fid;
    return fid;
}

RT_DLLEXPORT rt_file_id rtAddFileFromSpan(rt_text_span path) {
    rt_file_id fid = rtGetFileIdFromSpan(path);

    if (!rtLockMutex(_file_tab.mutex)) {
        rtReportError("fio", "Failed to lock the guard mutex.");
        return 0;
    }

    /* Hash Insert */
    unsigned int i    = 0;
    unsigned int base = (unsigned int)(fid % _file_tab.capacity);
    while (i < _file_tab.capacity) {
        unsigned int at = (base + i) % _file_tab.capacity;
        if (_file_tab.ids[at] == 0) {
            /* Insert */
            unsigned int slen = (unsigned int)path.length + 1;
            if ((_file_tab.name_head + slen) >= NAME_CAP(_file_tab.capacity)) {
                /* Out of name storage */
                fid = 0;
                break;
            }
            memcpy(_file_tab.names + _file_tab.name_head, path.start, slen);
            _file_tab.names[_file_tab.name_head + slen - 1] = '\0';
            _file_tab.name_offsets[at]                      = _file_tab.name_head;
            _file_tab.ids[at]                               = fid;
            _file_tab.name_head += slen;
            break;
        } else if (_file_tab.ids[at] == fid) {
            break;
        }
        ++i;
    }

    /* Out of space */
    if (i == _file_tab.capacity)
        fid = 0;

    rtUnlockMutex(_file_tab.mutex);
    return fid;
}

RT_DLLEXPORT rt_file_id rtAddFile(const char *path) {
    rt_text_span span;
    span.start  = path;
    span.length = (unsigned int)strlen(path);
    return rtAddFileFromSpan(span);
}

RT_DLLEXPORT const char *rtGetFilePath(rt_file_id fid) {
    /* Hash Lookup */
    if (fid == 0)
        return NULL;

    if (!rtLockMutex(_file_tab.mutex)) {
        rtReportError("fio", "Failed to lock the guard mutex.");
        return 0;
    }
    const char *result = NULL;
    unsigned int i     = 0;
    unsigned int base  = (unsigned int)(fid % _file_tab.capacity);
    while (i < _file_tab.capacity) {
        unsigned int at = (base + i) % _file_tab.capacity;
        if (_file_tab.ids[at] == fid) {
            result = _file_tab.names + _file_tab.name_offsets[at];
            break;
        } else if (_file_tab.ids[at] == 0) {
            break;
        }
        ++i;
    }
    rtUnlockMutex(_file_tab.mutex);
    return result;
}
