#include <runtime/config.h>
#include <runtime/threading.h>
#include <runtime/runtime.h>
#include <stdlib.h>
#include <string.h>


#include "virtual_resource_registry.h"
#include "render_resource.h"

RT_CVAR_SZ(r_MaxRenderResources, "Maximum number of render resources that can exist simultaneously. (Default: 16384)", 16384);

typedef struct {
    rt_render_resource_handle *handles;
    struct {
        union {
            /* For management */
            uint32_t next_free;
            
            rt_render_buffer_desc buffer;
            rt_render_texture2d_desc texture2d;
        };
    } *descs;
    uint32_t first_free;
    rt_rwlock lock;
} rt_description_table;

static rt_description_table _description_tab;

rt_result InitVirtualResourceRegistry(void) {
    _description_tab.handles = calloc(r_MaxRenderResources.sz, sizeof(rt_render_resource_handle));
    if (!_description_tab.handles)
        return RT_OUT_OF_MEMORY;
    _description_tab.descs = calloc(r_MaxRenderResources.sz, sizeof(*_description_tab.descs));
    if (!_description_tab.descs) {
        free(_description_tab.handles);
        return RT_OUT_OF_MEMORY;
    }
    rt_create_rwlock_result lock_res = rtCreateRWLock();
    if (!lock_res.ok) {
        free(_description_tab.handles);
        free(_description_tab.descs);
        return RT_UNKNOWN_ERROR;
    }
    _description_tab.lock = lock_res.lock;
    _description_tab.first_free = 0;
    for (uint32_t i = 0; i < r_MaxRenderResources.sz; ++i) {
        _description_tab.descs[i].next_free = i + 1;
    }
    return RT_SUCCESS;
}

void ShutdownVirtualResourceRegistry(void) {
    free(_description_tab.handles);
    free(_description_tab.descs);
    rtDestroyRWLock(&_description_tab.lock);
}

/* Returns true if the handle refers to an existing resource. */
bool rtIsRenderResourceKnown(rt_render_resource_handle h) {
    uint32_t idx = rtGetRenderResourceHandleIndex(h);
    if (idx >= r_MaxRenderResources.sz)
        return false;
    rtLockRead(&_description_tab.lock);
    bool is_known = _description_tab.handles[idx].value == h.value;
    rtUnlockRead(&_description_tab.lock);
    return is_known;
}

static const void *GetResourceDescription(rt_render_resource_handle h) {
    rt_render_resource_type type = rtGetRenderResourceHandleType(h);    
    uint32_t idx = rtGetRenderResourceHandleIndex(h);
    if (idx >= r_MaxRenderResources.sz)
        return false;
    const void *desc = NULL;
    rtLockRead(&_description_tab.lock);
    if (_description_tab.handles[idx].value == h.value) {
        switch (type) {
        case RT_RENDER_RESOURCE_TYPE_BUFFER:
            desc = &_description_tab.descs[idx].buffer;
            break;
        case RT_RENDER_RESOURCE_TYPE_TEXTURE2D:
            desc = &_description_tab.descs[idx].texture2d;
            break;
        default:
            desc = NULL;
            break;
        }
    }
    rtUnlockRead(&_description_tab.lock);
    return desc;
}

/* Returns a pointer to the resource description or NULL, if the handle does not refer to a valid buffer */
const rt_render_buffer_desc *rtGetRenderBufferDescription(rt_render_resource_handle h) {
    return GetResourceDescription(h);
}

/* Returns a pointer to the resource description or NULL, if the handle does not refer to a valid texture2d */
const rt_render_texture2d_desc *rtGetRenderTexture2DDescription(rt_render_resource_handle h) {    
    return GetResourceDescription(h);
}

static rt_render_resource_handle AllocSlot(rt_render_resource_type type, const void *desc) {
    rt_render_resource_handle h = rtMakeRenderResourceHandle(RT_RENDER_RESOURCE_TYPE_INVALID, 0, 0);
    rtLockWrite(&_description_tab.lock);
    uint32_t slot = _description_tab.first_free;
    if (slot < r_MaxRenderResources.sz) {
        _description_tab.first_free = _description_tab.descs[slot].next_free;
        uint32_t current_version = rtGetRenderResourceHandleVersion(_description_tab.handles[slot]);
        uint32_t next_version = (current_version + 1) & RT_RENDER_RESOURCE_MAX_VERSION;
        h = rtMakeRenderResourceHandle(type, next_version, slot);
        _description_tab.handles[slot] = h;
        switch (type) {
        case RT_RENDER_RESOURCE_TYPE_BUFFER:
            memcpy(&_description_tab.descs[slot].buffer, desc, sizeof(rt_render_buffer_desc));
            break;
        case RT_RENDER_RESOURCE_TYPE_TEXTURE2D:
            memcpy(&_description_tab.descs[slot].texture2d, desc, sizeof(rt_render_texture2d_desc));
            break;
        default:
            rtReportError("RENDERER", "Tried to create a resource with invalid type %u", type);
            break;
        }
    }
    rtUnlockWrite(&_description_tab.lock);
    return h;
}

/* Stores the render buffer description and returns a handle to the new resource. */
rt_render_resource_handle  rtCreateRenderBuffer(const rt_render_buffer_desc *desc) {
    return AllocSlot(RT_RENDER_RESOURCE_TYPE_BUFFER, desc);
}

/* Stores the texture2d description and returns a handle to the new resource. */
rt_render_resource_handle rtCreateRenderTexture2D(const rt_render_texture2d_desc *desc) {
    return AllocSlot(RT_RENDER_RESOURCE_TYPE_TEXTURE2D, desc);
}
