#include "command_list.h"
#include "backend_api.h"

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

RT_CVAR_SZ(r_PerFrameCommandListMemory, "Amount of memory to allocate for single frame command list management."
    " The total amount of memory will be this times the maximum number of frames in flight. (Default: 16 MB)",
    RT_MB(16));

static rt_arena _list_arenas[3];
static rt_arena *_current_arena = &_list_arenas[0];
static unsigned int _current_arena_idx = 0;
static rt_mutex *_mutex;

rt_result InitCommandLists(void) {
    _mutex = rtCreateMutex();
    if (!_mutex)
        return RT_UNKNOWN_ERROR;

    for (unsigned int i = 0; i < RT_ARRAY_COUNT(_list_arenas); ++i) {
        rt_create_arena_result arena_res = rtCreateArena(NULL, r_PerFrameCommandListMemory.sz);
        if (!arena_res.ok) {
            rtDestroyMutex(_mutex);
            return RT_OUT_OF_MEMORY;
        }
        _list_arenas[i] = arena_res.arena;
    }
    return RT_SUCCESS;
}

void ShutdownCommandLists(void) {
    for (unsigned int i = 0; i < RT_ARRAY_COUNT(_list_arenas); ++i) {
        rtReleaseArena(&_list_arenas[i]);
    }
    rtDestroyMutex(_mutex);
}

void CommandListsOnBeginFrame(void) {
    _current_arena_idx = (_current_arena_idx + 1) % RT_ARRAY_COUNT(_list_arenas);
    _current_arena = &_list_arenas[_current_arena_idx];
    rtArenaClear(_current_arena);
}

#define COMMAND_LIST_CAPACITY RT_KB(512)
#define AVERAGE_COMMAND_DATA_SIZE sizeof(rt_draw_indirect_data)
#define COMMAND_LIST_MAX_LENGTH (COMMAND_LIST_CAPACITY / AVERAGE_COMMAND_DATA_SIZE)

/* Get a new render command list. */
RT_DLLEXPORT rt_begin_render_command_list_result rtBeginRenderCommandList(void) {
    size_t mem_required = COMMAND_LIST_MAX_LENGTH * sizeof(rt_render_command_header) + COMMAND_LIST_CAPACITY;
    rtLockMutex(_mutex);
    void *mem = rtArenaPush(_current_arena, mem_required);
    rtUnlockMutex(_mutex);
    if (!mem) {
        rtReportError("RENDERER", "Ran out of memory for command lists.");
        return (rt_begin_render_command_list_result){.result = RT_OUT_OF_MEMORY};
    }
    rt_render_command_list list = {
        .headers = mem,
        .data = (void *)((rt_render_command_header *)mem + COMMAND_LIST_MAX_LENGTH),
        .length = 0u,
        .data_capacity =  COMMAND_LIST_CAPACITY,
        .data_end = 0u
    };
    return (rt_begin_render_command_list_result){
        .result = RT_SUCCESS,
        .list = list
    };
}

/* Lowlevel function that writes the data to the queue. */
RT_DLLEXPORT rt_result rtEncodeRenderCommand(rt_render_command_list *list, rt_render_command_type type, rt_render_queue queue, const void *data) {
    size_t data_size = 0u;
    switch (type) {
    case RT_RENDER_COMMAND_DRAW_INDIRECT:
        data_size = sizeof(rt_draw_indirect_data);
        break;
    default:
        rtReportError("RENDERER", "Invalid render command type %u", type);
        return RT_INVALID_VALUE;
    }

    if (list->length == COMMAND_LIST_MAX_LENGTH || (list->data_end + data_size) > list->data_capacity) {
        rtReportError("RENDERER", "Reached maximum lenght or capacity of command list.");
        return RT_OUT_OF_MEMORY;
    }

    list->headers[list->length].type = type;
    list->headers[list->length].target_queue = queue;
    ++list->length;
    char *dest = (char *)list->data + list->data_end;
    memcpy(dest, data, data_size);
    list->data_end += data_size;
    return RT_SUCCESS;
}

/* Submit a finished command list to the graphics device. The list will not be usable after this function is done. */
RT_DLLEXPORT rt_result rtSubmitCommandList(rt_render_command_list *list) {
    return g_device_i.SubmitCommandList(g_device_i.o, list);
}
