#include "vulkan_commands.h"
#include <SDL3/SDL_log.h>
#include <vulkan/vulkan_core.h>

#include "src/graphics_context.h"
#include "utilities/commons.h"
#include "vulkan_commons.h"
#include "utilities/errors_common.h"

/**
 * @brief Create Command Pool
 *
 * @param context Graphics context handle
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *              VULKAN_ERROR_COMMAND_POOL_CREATION_FAILED
 */
static rse_err_t create_command_pool(struct graphics_context_t* context)
{
    VkCommandPoolCreateInfo create_info;

    create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    create_info.pNext = NULL;
    create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    create_info.queueFamilyIndex = context->queue_family_indices[QUEUE_FAMILY_INDEX_GRAPHICS];

    if (VK_SUCCESS != vkCreateCommandPool(context->vulkan_handles.device, &create_info, NULL, &context->vulkan_handles.command_pool)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create command pool");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Allocate Command Buffers
 *
 * @param context Graphics context handle
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *             VULKAN_ERROR_COMMAND_BUFFER_ALLOCATION_FAILED
 */
static rse_err_t allocate_command_buffers(struct graphics_context_t* context)
{
    VkCommandBufferAllocateInfo allocate_info;

    /* Allocate memory for command buffers */
    allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocate_info.pNext = NULL;
    allocate_info.commandPool = context->vulkan_handles.command_pool;
    allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocate_info.commandBufferCount = SWAP_BUFFER_COUNT;

    if (VK_SUCCESS != vkAllocateCommandBuffers(context->vulkan_handles.device,
                                               &allocate_info,
                                               context->vulkan_handles.command_buffers)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to allocate command buffers\n");
        return RSE_ERROR_INTERNAL_ERROR;
    }
    return RSE_ERROR_NO_ERROR;
}

rse_err_t begin_single_time_command(struct graphics_context_t* context, VkCommandBuffer* command_buffer)
{
    VkCommandBufferBeginInfo begin_info = {0};
    VkCommandBufferAllocateInfo alloc_info = {0};

    alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    alloc_info.pNext = NULL;
    alloc_info.commandPool = context->vulkan_handles.command_pool;
    alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    alloc_info.commandBufferCount = 1;

    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    begin_info.pNext = NULL;
    begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    begin_info.pInheritanceInfo = NULL;

    if (vkAllocateCommandBuffers(context->vulkan_handles.device, &alloc_info, command_buffer) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to allocate new command buffer");
        return RSE_ERROR_INTERNAL_ERROR;
    }
    if (vkBeginCommandBuffer(*command_buffer, &begin_info) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to begin command buffer");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return RSE_ERROR_NO_ERROR;
}

rse_err_t end_single_time_comands(struct graphics_context_t* context, VkCommandBuffer command_buffer)
{
    VkSubmitInfo submit_info = {0};

    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submit_info.pNext = NULL;
    submit_info.waitSemaphoreCount = 0;
    submit_info.pWaitSemaphores = NULL;
    submit_info.pWaitDstStageMask = NULL;
    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &command_buffer;
    submit_info.signalSemaphoreCount = 0;
    submit_info.pSignalSemaphores = NULL;

    if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to end command buffer");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    if (vkQueueSubmit(context->vulkan_handles.graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed submit queue");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    if (vkQueueWaitIdle(context->vulkan_handles.graphics_queue) != VK_SUCCESS) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to wait for graphics queue");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    vkFreeCommandBuffers(context->vulkan_handles.device, context->vulkan_handles.command_pool, 1, &command_buffer);

    return RSE_ERROR_NO_ERROR;
}

rse_err_t init_commands(struct graphics_context_t* context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;
    STATUS_CHECK(create_command_pool(context));
    STATUS_CHECK(allocate_command_buffers(context));

    return status;
}

void destroy_commands(struct graphics_context_t* context)
{
    vkDestroyCommandPool(context->vulkan_handles.device, context->vulkan_handles.command_pool, NULL);
}
