/**
 * @file vulkanBase.cp
 * @author Piotr Krygier (everyonecancode@gmail.com)
 * @brief Base configuration for Vulkan API
 * @version 0.1
 * @date 2022-05-13
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "vulkan_base.h"

#include <SDL3/SDL_error.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_vulkan.h>
#include <vma/vk_mem_alloc.h>

#include "src/descriptor_builder.h"
#include "src/graphics_context.h"
#include "utilities/commons.h"
#include "utilities/errors_common.h"
#include "vulkan/vulkan_core.h"

#include "vulkan_commons.h"
#include "vulkan_commands.h"
#include "vulkan_buffers.h"
#include "vulkan_image.h"
#include "vulkan_render_pass.h"
#include "vulkan_swapchain.h"
#include "pipeline_builder.h"

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

#define APPLICATION_NAME "RedScarfEngine PoC"
#define ENGINE_NAME "RedScarf Engine"

/**
 * @brief Calback for debug messenger
 *
 * @param message_severity Message severity
 * @param message_type Type of a message
 * @param callback_data Contains all the callback related data in the VkDebugUtilsMessengerCallbackDataEXT structure
 * @param user_data Data provided by the user
 * @return VkBool32 Should always return VK_FALSE. The VK_TRUE value is reserved for use in layer development
 */
static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
                                             VkDebugUtilsMessageTypeFlagsEXT message_type,
                                             const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data)
{
    (void)message_severity;
    (void)message_type;
    (void)user_data;

    /* There is no guarantee, that the error message will have single severity flag set, so we can't use switch()
     * here. We have to check flags and set severity to most important one */

    if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
        SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage);
    } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
        SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage);
    } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
        SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage);
    } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
        SDL_LogTrace(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage);
    }

    return VK_FALSE;
}

/**
 * @brief Proxy for vkCreateDebugUtilsMessengerEXT
 *
 * @param instance Vulkan instance object
 * @param create_info VkDebugUtilsMessengerCreateInfoEXT structure
 * @param allocator Memory allocator
 * @param debug_messenger Debug messanger handle
 * @return VkResult VK_SUCCESS on success
 */
static VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* create_info,
                                      const VkAllocationCallbacks* allocator,
                                      VkDebugUtilsMessengerEXT* debug_messenger)
{
    PFN_vkCreateDebugUtilsMessengerEXT func = NULL;

    func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    if (func != NULL) {
        return func(instance, create_info, allocator, debug_messenger);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }

    return VK_SUCCESS;
}

/**
 * @brief Setup debugging system for Vulkan API
 *
 * @param context Graphics context
 *
 */
static rse_err_t setup_debug_messenger(VkInstance instance, VkDebugUtilsMessengerEXT* messenger)
{
    VkDebugUtilsMessengerCreateInfoEXT create_info = {0};

    create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    create_info.pNext = NULL;
    create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
                                 VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
                                 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                                 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
    create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                             VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                             VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    create_info.pfnUserCallback = debug_callback;
    create_info.pUserData = NULL;  // Optional
    create_info.flags = 0U;

    if (CreateDebugUtilsMessengerEXT(instance, &create_info, NULL, messenger) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to setup debug messenger");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Proxy for vkDestroyDebugUtilsMessengerEXT
 *
 * @param instance Vulkan instance object
 * @param debug_messenger Debug messanger handle
 * @param allocator Memory allocator
 */
static void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debug_messenger,
                                   const VkAllocationCallbacks* allocator)
{
    PFN_vkDestroyDebugUtilsMessengerEXT func =
        (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != NULL) {
        func(instance, debug_messenger, allocator);
    }
}

/**
 * @brief Create the Vulkan Instance object
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *              VULKAN_ERROR_INSTANCE_INIT_FAILED
 */
static rse_err_t create_instance(VkInstance* instance)
{
    size_t i = 0U;
    size_t j = 0U;
    uint8_t supported = 0U;
    VkResult result = VK_SUCCESS;
    uint32_t extensionCount = 0U;
    uint32_t layersCount = 0U;
    VkApplicationInfo application_info = {0};
    VkInstanceCreateInfo create_info = {0};
    VkLayerProperties* layer_properties;
    VkExtensionProperties* extensions_properties;

    const char* enabled_instance_extensions_names[] = {
        "VK_KHR_surface",
        "VK_KHR_device_group_creation",
#ifdef __linux__
#ifdef WAYLAND
        "VK_KHR_wayland_surface",
#else
        "VK_KHR_xlib_surface",
#endif /* WAYLAND */
#elif defined(_WIN32) || defined(WIN32)
    "VK_KHR_win32_surface",
#endif /* _WIN32 || WIN32 */
        "VK_EXT_debug_utils",
    };
#ifndef NDEBUG
    uint32_t enabled_instance_extensions_count = 4;
    uint32_t enabled_instance_layers_count = 1;
    const char* enabled_instance_layers_names[] = {
        "VK_LAYER_KHRONOS_validation",
    };
#else
    uint32_t enabled_instance_extensions_count = 3;
    uint32_t enabled_instance_layers_count = 0;
    const char* enabled_instance_layers_names[] = {};
#endif /* NDEBUG */

    vkEnumerateInstanceLayerProperties(&layersCount, NULL);
    rse_malloc(layer_properties, sizeof(VkLayerProperties) * layersCount);
    vkEnumerateInstanceLayerProperties(&layersCount, layer_properties);

    vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
    rse_malloc(extensions_properties, sizeof(VkExtensionProperties) * extensionCount);
    vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions_properties);

#ifndef NDEBUG
    SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Supported Layers: ");
    for (i = 0; i < layersCount; i++) {
        SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "\t-%s", layer_properties[i].layerName);
    }

    SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Supported Extensions: ");
    for (size_t i = 0; i < extensionCount; i++) {
        SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "\t-%s", extensions_properties[i].extensionName);
    }
#endif

    /* Check if selected layers are supported*/
    for (i = 0; i < enabled_instance_layers_count; ++i) {
        for(j = 0; i < layersCount; ++j) {
            if(strcmp(layer_properties[j].layerName, enabled_instance_layers_names[i]) == 0) {
                supported = 1;
                break;
            }
        }
        if(supported == 0) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected layer is not supported: %s", layer_properties[j].layerName);
            return RSE_ERROR_INTERNAL_ERROR;
        }
        supported = 0;
    }

    /* Check if selected extensions are supported*/
    for (i = 0; i < enabled_instance_extensions_count; ++i) {
        for(j = 0; j < extensionCount; ++j) {
            if(strcmp(extensions_properties[j].extensionName, enabled_instance_extensions_names[i]) == 0) {
                supported = 1;
                break;
            }
        }
        if(supported == 0) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected extension is not supported: %s", extensions_properties[j].extensionName);
            return RSE_ERROR_INTERNAL_ERROR;
        }
        supported = 0;
    }

    rse_free(layer_properties);
    rse_free(extensions_properties);

    application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    application_info.pNext = NULL;
    application_info.pApplicationName = APPLICATION_NAME;
    application_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
    application_info.pEngineName = ENGINE_NAME;
    application_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
    application_info.apiVersion = VK_API_VERSION_1_4;

    create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    create_info.pNext = NULL;
    create_info.flags = 0U;
    create_info.pApplicationInfo = &application_info;
    create_info.enabledLayerCount = enabled_instance_layers_count;
    create_info.ppEnabledLayerNames = enabled_instance_layers_names;
    create_info.enabledExtensionCount = enabled_instance_extensions_count;
    create_info.ppEnabledExtensionNames = enabled_instance_extensions_names;

    result = vkCreateInstance(&create_info, NULL, instance);
    if (VK_SUCCESS != result) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Vulkan instance initialization failed");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Create a Surface, connection between Vulkan and actual window
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 */
static rse_err_t create_surface(VkInstance instance, SDL_Window* window_handle, VkSurfaceKHR* surface)
{
    if (true != SDL_Vulkan_CreateSurface(window_handle, instance, NULL, surface)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create window surface. Error: %s", SDL_GetError());
        return RSE_ERROR_INTERNAL_ERROR;
    }
    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Choose physical device. Used later for vkDevice
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *              VULKAN_ERROR_NO_PHYSICAL_DEVICE_FOUND
 *              VULKAN_ERROR_NO_SUITABLE_PHYSICAL_DEVICE_FOUND
 */
static rse_err_t pick_physical_device(VkInstance instance, VkPhysicalDevice* physical_device)
{
    uint32_t physical_device_count = 0U;
    VkPhysicalDevice* physical_devices = NULL;
    VkPhysicalDeviceProperties device_properties = {0};;
    VkPhysicalDeviceFeatures device_features = {0};

    /* Get number of existing physical devices */
    vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL);

    if (0 == physical_device_count) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "No physical graphical physical devices found in system");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    rse_malloc(physical_devices, sizeof(VkPhysicalDevice) * physical_device_count);

    vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices);

    /* Check for suitability */
    for (size_t physicalDeviceIdx = 0U; physicalDeviceIdx < physical_device_count; ++physicalDeviceIdx) {
        vkGetPhysicalDeviceProperties(physical_devices[physicalDeviceIdx], &device_properties);
        vkGetPhysicalDeviceFeatures(physical_devices[physicalDeviceIdx], &device_features);

        /* Expect discrete graphics device type */
        if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ||
            device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) {
            /* Application MUST have geometry shader */
            if (device_features.geometryShader) {
                *physical_device = physical_devices[physicalDeviceIdx];
            }
        }
    }

    if (VK_NULL_HANDLE == physical_device) {
        /* No suitable device found */
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "No suitable physical device found");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    rse_free(physical_devices);

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Create vkDevice object
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *               VULKAN_ERROR_QUEUE_NOT_SUPPORTED
 *              VULKAN_ERROR_DEVICE_CREATION_FAILED
 */
static rse_err_t create_device(struct graphics_context_t* context)
{
    bool graphics_family_found = false;
    bool presentation_familiy_found = false;
    float* graphics_familiy_queue_priorities = NULL;
    float* presentation_familiy_queue_priorities = NULL;
    uint32_t device_queues_count = 0;
    int64_t graphics_family_idx = 0;
    uint32_t presentation_familiy_idx = 0;
    uint32_t queue_families_property_count = 0U;
    uint32_t enabled_device_extensions_count = 2;
    VkBool32 bindless_supported = VK_FALSE;
    VkDeviceCreateInfo device_create_info = {0};
    VkQueueFamilyProperties* queue_family_properties = NULL;
    VkDeviceQueueCreateInfo device_queue_createinfos[2] = {0};
    VkPhysicalDeviceFeatures2 physical_features2 = {0};
    VkPhysicalDeviceDescriptorIndexingFeatures indexing_features = {0};
    const char* enabled_device_extensions_names[] = {
        "VK_KHR_swapchain",
        "VK_EXT_descriptor_indexing",
    };

    /* Enable descriptori indexing for bindless textures */
    indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;

    physical_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
    physical_features2.pNext = &indexing_features;

    vkGetPhysicalDeviceFeatures2(context->vulkan_handles.physical_device, &physical_features2);

    bindless_supported = indexing_features.descriptorBindingPartiallyBound && indexing_features.runtimeDescriptorArray;
    if (bindless_supported == VK_FALSE) {
        SDL_LogError(SDL_LOG_CATEGORY_GPU, "Required features for bindless design not supported");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    /* Get information about supported queue families */
    vkGetPhysicalDeviceQueueFamilyProperties(context->vulkan_handles.physical_device, &queue_families_property_count, NULL);
    rse_malloc(queue_family_properties, sizeof(VkQueueFamilyProperties) * queue_families_property_count);
    vkGetPhysicalDeviceQueueFamilyProperties(context->vulkan_handles.physical_device, &queue_families_property_count,
                                             queue_family_properties);

    /* Get queue families that support graphics operations AND have most
     * queues. */
    {
        size_t numberOfQueues = 0;
        for (uint32_t familyIdx = 0; familyIdx < queue_families_property_count; ++familyIdx) {
            SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Checking queue: %d", familyIdx);
            if (queue_family_properties[familyIdx].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                if (queue_family_properties[familyIdx].queueCount > numberOfQueues) {
                    graphics_family_idx = familyIdx;
                    graphics_family_found = true;
                    SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Setting graphics family idx to %d", familyIdx);
                    numberOfQueues = queue_family_properties[familyIdx].queueCount;
                    device_queues_count++;
                }
            }

            /* Check for family with surface support */
            VkBool32 presentSupport = VK_FALSE;
            vkGetPhysicalDeviceSurfaceSupportKHR(context->vulkan_handles.physical_device, familyIdx, context->vulkan_handles.surface, &presentSupport);
            if (presentSupport) {
                presentation_familiy_idx = familyIdx;
                presentation_familiy_found = true;
                /* If this is the same queue family as in graphics family, don't increase queue count */
                if (presentation_familiy_idx != graphics_family_idx) {
                    device_queues_count++;
                }
            }

            if (graphics_family_found && presentation_familiy_found) {
                break;
            }
        }
    }

    /* This is not magic number ;P. We want to support exact number of required queues flags */
    if (graphics_family_idx < 0) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected physical device does not support required queue capabilities");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    context->queue_family_indices[QUEUE_FAMILY_INDEX_GRAPHICS] = graphics_family_idx;
    context->queue_family_indices[QUEUE_FAMILY_INDEX_PRESENTATION] = presentation_familiy_idx;

    rse_malloc(graphics_familiy_queue_priorities, sizeof(float) * queue_family_properties[graphics_family_idx].queueCount);

    for (size_t i = 0; i < queue_family_properties[graphics_family_idx].queueCount; i++) {
        graphics_familiy_queue_priorities[i] = 1.0f;
    }

    rse_malloc(presentation_familiy_queue_priorities, sizeof(float) * queue_family_properties[presentation_familiy_idx].queueCount);
    for (size_t i = 0; i < queue_family_properties[presentation_familiy_idx].queueCount; i++) {
        presentation_familiy_queue_priorities[i] =
            1.0f;
    }

    device_queue_createinfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    device_queue_createinfos[0].pNext = NULL;
    device_queue_createinfos[0].flags = 0;
    device_queue_createinfos[0].queueFamilyIndex = graphics_family_idx;
    device_queue_createinfos[0].queueCount = queue_family_properties[graphics_family_idx].queueCount;
    device_queue_createinfos[0].pQueuePriorities = graphics_familiy_queue_priorities;

    device_queue_createinfos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    device_queue_createinfos[1].pNext = NULL;
    device_queue_createinfos[1].flags = 0;
    device_queue_createinfos[1].queueFamilyIndex = presentation_familiy_idx;
    device_queue_createinfos[1].queueCount = queue_family_properties[presentation_familiy_idx].queueCount;
    device_queue_createinfos[1].pQueuePriorities = presentation_familiy_queue_priorities;

    device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    device_create_info.pNext = &physical_features2;
    device_create_info.flags = 0U;
    device_create_info.queueCreateInfoCount = device_queues_count;
    device_create_info.pQueueCreateInfos = device_queue_createinfos;
    device_create_info.enabledLayerCount = 0;
    device_create_info.ppEnabledLayerNames = NULL;
    device_create_info.enabledExtensionCount = enabled_device_extensions_count;
    device_create_info.ppEnabledExtensionNames = enabled_device_extensions_names;
    device_create_info.pEnabledFeatures = NULL;

    if (VK_SUCCESS != vkCreateDevice(context->vulkan_handles.physical_device, &device_create_info, NULL, &context->vulkan_handles.device)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create logical device");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    vkGetDeviceQueue(context->vulkan_handles.device, graphics_family_idx, 0, &context->vulkan_handles.graphics_queue);
    vkGetDeviceQueue(context->vulkan_handles.device, presentation_familiy_idx, 0, &context->vulkan_handles.present_queue);

    rse_free(presentation_familiy_queue_priorities);
    rse_free(graphics_familiy_queue_priorities);
    rse_free(queue_family_properties);

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Create a Memory Allocator object
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *              VULKAN_ERROR_ALLOCATOR_CREATION_FAILED
 */
static rse_err_t create_memory_allocator(struct graphics_context_t* context)
{
    VmaVulkanFunctions vulkan_functions = {0};
    vulkan_functions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
    vulkan_functions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;

    VmaAllocatorCreateInfo allocator_create_info = {0};
    allocator_create_info.vulkanApiVersion = VK_API_VERSION_1_3;
    allocator_create_info.physicalDevice = context->vulkan_handles.physical_device;
    allocator_create_info.device = context->vulkan_handles.device;
    allocator_create_info.instance = context->vulkan_handles.instance;
    allocator_create_info.pVulkanFunctions = &vulkan_functions;

    if (VK_SUCCESS != vmaCreateAllocator(&allocator_create_info, &context->vulkan_handles.allocator)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create Vulkan Memory Allocator");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return RSE_ERROR_NO_ERROR;
}


/**
 * @brief Create a Sync Objects
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *             VULKAN_ERROR_SYNCOBJCTS_CREATION_FAILED
 */
static rse_err_t create_sync_objects(struct graphics_context_t* context)
{
    VkSemaphoreCreateInfo semaphore_info = {0};
    semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

    VkFenceCreateInfo fenceInfo = {0};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

    for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) {
        if (VK_SUCCESS != vkCreateSemaphore(context->vulkan_handles.device, &semaphore_info, NULL, &context->vulkan_handles.image_available_semaphores[i]) ||
            VK_SUCCESS != vkCreateSemaphore(context->vulkan_handles.device, &semaphore_info, NULL, &context->vulkan_handles.render_finished_semaphores[i]) ||
            VK_SUCCESS != vkCreateFence(context->vulkan_handles.device, &fenceInfo, NULL, &context->vulkan_handles.in_flight_fences[i])) {
                SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create sync objects.");
                return RSE_ERROR_INTERNAL_ERROR;
        }
    }

    return RSE_ERROR_NO_ERROR;
}


rse_err_t vulkan_init(struct graphics_context_t* context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;

    STATUS_CHECK(create_instance(&context->vulkan_handles.instance));
    STATUS_CHECK(setup_debug_messenger(context->vulkan_handles.instance, &context->vulkan_handles.debug_messenger));
    STATUS_CHECK(create_surface(context->vulkan_handles.instance, context->window_handle, &context->vulkan_handles.surface));
    STATUS_CHECK(pick_physical_device(context->vulkan_handles.instance, &context->vulkan_handles.physical_device));
    STATUS_CHECK(create_device(context));
    STATUS_CHECK(create_memory_allocator(context));
    STATUS_CHECK(create_render_pass(context));
    STATUS_CHECK(init_commands(context));
    STATUS_CHECK(create_buffers(context));
    STATUS_CHECK(init_vulkan_images(context));
    STATUS_CHECK(create_swapchain_and_framebuffers(context));

    return status;
}

rse_err_t vulkan_run(struct graphics_context_t* context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;

    STATUS_CHECK(create_sync_objects(context));

    init_time();

    return status;
}

rse_err_t draw_frame(struct graphics_context_t* context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;
    uint32_t image_index = 0U;
    VkResult result = VK_FALSE;
    VkSubmitInfo submit_info = {0};
    VkSemaphore wait_semaphores[1];
    VkPipelineStageFlags wait_stages[1];
    VkSemaphore signal_semaphores[1];
    VkPresentInfoKHR present_info = {0};
    VkSwapchainKHR swap_chains[1];

    vkWaitForFences(context->vulkan_handles.device, 1, &context->vulkan_handles.in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX);

    STATUS_CHECK(buffers_update(context));

    update_time();

    result = vkAcquireNextImageKHR(context->vulkan_handles.device, context->swapchain_data.swapchain, UINT64_MAX, context->vulkan_handles.image_available_semaphores[context->current_frame],
                                            VK_NULL_HANDLE, &image_index);

    if (result == VK_ERROR_OUT_OF_DATE_KHR) {
        recreate_swapchain(context);
        return RSE_ERROR_NO_ERROR;
    } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to acquire swapchain image");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    // update_uniform_buffers(context);

    /* Only reset the fence if we are submitting work */
    vkResetFences(context->vulkan_handles.device, 1, &context->vulkan_handles.in_flight_fences[context->current_frame]);
    reset_command_buffer(context);

    record_command_buffer(context, image_index);

    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

    wait_semaphores[0] = context->vulkan_handles.image_available_semaphores[context->current_frame];
    wait_stages[0] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    submit_info.waitSemaphoreCount = 1;
    submit_info.pWaitSemaphores = wait_semaphores;
    submit_info.pWaitDstStageMask = wait_stages;

    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &context->vulkan_handles.command_buffers[context->current_frame];

    signal_semaphores[0] = context->vulkan_handles.render_finished_semaphores[context->current_frame];
    submit_info.signalSemaphoreCount = 1;
    submit_info.pSignalSemaphores = signal_semaphores;

    if (vkQueueSubmit(context->vulkan_handles.graphics_queue, 1, &submit_info, context->vulkan_handles.in_flight_fences[context->current_frame]) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to submit draw command buffer!");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

    present_info.waitSemaphoreCount = 1;
    present_info.pWaitSemaphores = &context->vulkan_handles.render_finished_semaphores[context->current_frame];

    swap_chains[0] = context->swapchain_data.swapchain;
    present_info.swapchainCount = 1;
    present_info.pSwapchains = swap_chains;

    present_info.pImageIndices = &image_index;

    result = vkQueuePresentKHR(context->vulkan_handles.present_queue, &present_info);

    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || context->is_framebuffer_resized) {
        context->is_framebuffer_resized = false;
        recreate_swapchain(context);
    } else if (result != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to submit draw command buffer!");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    context->current_frame = (context->current_frame + 1) % SWAP_BUFFER_COUNT;

    return status;
}

void vulkan_deinit(struct graphics_context_t* context)
{
    destroy_buffers(context);
    for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) {
        vkDestroySemaphore(context->vulkan_handles.device, context->vulkan_handles.image_available_semaphores[i], NULL);
        vkDestroySemaphore(context->vulkan_handles.device, context->vulkan_handles.render_finished_semaphores[i], NULL);
        vkDestroyFence(context->vulkan_handles.device, context->vulkan_handles.in_flight_fences[i], NULL);
    }

    destroy_textures(context);

    destroy_commands(context);

    destroy_render_pass(context);
    cleanup_swapchain(context);
    destroy_pipelines(context);
    destroy_descriptors(context);

    vmaDestroyAllocator(context->vulkan_handles.allocator);
    vkDestroyDevice(context->vulkan_handles.device, NULL);
    DestroyDebugUtilsMessengerEXT(context->vulkan_handles.instance, context->vulkan_handles.debug_messenger, NULL);
    vkDestroySurfaceKHR(context->vulkan_handles.instance, context->vulkan_handles.surface, NULL);
    vkDestroyInstance(context->vulkan_handles.instance, NULL);
}
