#include "vulkan_swapchain.h"

#include <SDL3/SDL_events.h>
#include <stdlib.h>
#include <string.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_image.h"

/**
 * @brief Create a Swapchain
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 *             VULKAN_ERROR_SWAPCHAIN_CREATION_FAILED
 */
static rse_err_t create_swapchain(struct graphics_context_t* context)
{
    VkSwapchainCreateInfoKHR create_info;
    VkSurfaceCapabilitiesKHR physical_device_surface_capabilities;
    VkBool32 surfaceSupported;

    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context->vulkan_handles.physical_device,
                                              context->vulkan_handles.surface,
                                              &physical_device_surface_capabilities);

    /* Store extent in global variable, for later use */
    context->swapchain_data.swapchain_extent = physical_device_surface_capabilities.currentExtent;

    /* Check if device supports surface for presentation */
    vkGetPhysicalDeviceSurfaceSupportKHR(context->vulkan_handles.physical_device,
                                         context->queue_family_indices[0],
                                         context->vulkan_handles.surface,
                                         &surfaceSupported);

    create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    create_info.pNext = NULL;
    create_info.flags = 0U;
    create_info.surface = context->vulkan_handles.surface;
    create_info.minImageCount = SWAP_BUFFER_COUNT;
    create_info.imageFormat = IMAGE_FORMAT;
    create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
    create_info.imageExtent = context->swapchain_data.swapchain_extent;
    create_info.imageArrayLayers = 1U; /* For non-stereoscopic-3D applications, this value is 1. */
    create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; /* Using only one queue family, so this is ok
                                                               */
    create_info.queueFamilyIndexCount = 1U;
    create_info.pQueueFamilyIndices = context->queue_family_indices;
    create_info.preTransform = physical_device_surface_capabilities.currentTransform;
    create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
    create_info.clipped = VK_TRUE;
    create_info.oldSwapchain = VK_NULL_HANDLE;

    if (VK_SUCCESS !=
        vkCreateSwapchainKHR(context->vulkan_handles.device, &create_info, NULL, &context->swapchain_data.swapchain)) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create swapchain");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    /* Obtain swapchain images */
    vkGetSwapchainImagesKHR(context->vulkan_handles.device,
                            context->swapchain_data.swapchain,
                            &context->swapchain_images_count,
                            NULL);
    vkGetSwapchainImagesKHR(context->vulkan_handles.device,
                            context->swapchain_data.swapchain,
                            &context->swapchain_images_count,
                            context->swapchain_data.swapchain_images);

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Create a Swapchain Image Views
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 */
static rse_err_t create_swapchain_image_views(struct graphics_context_t* context)
{
    for (size_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) {
        VkImageViewCreateInfo create_info;

        create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        create_info.pNext = NULL;
        create_info.flags = 0U;
        create_info.image = context->swapchain_data.swapchain_images[i];
        create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; /* 2D Texture */
        create_info.format = VK_FORMAT_B8G8R8A8_SRGB;
        create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
        create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
        create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
        create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
        create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        create_info.subresourceRange.baseMipLevel = 0U;
        create_info.subresourceRange.levelCount = 1U;
        create_info.subresourceRange.baseArrayLayer = 0U;
        create_info.subresourceRange.layerCount = 1U;

        if (VK_SUCCESS != vkCreateImageView(context->vulkan_handles.device,
                                            &create_info,
                                            NULL,
                                            &context->swapchain_data.swapchain_image_views[i])) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create swapchain image view");
            return RSE_ERROR_INTERNAL_ERROR;
        }
    }

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Create Frambuffers
 *
 * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors:
 */
static rse_err_t create_framebuffers(struct graphics_context_t* context)
{
    for (size_t i = 0; i < context->swapchain_images_count; i++) {
        VkImageView attachments[] = {context->color_image.image_view,
                                     context->depth_image.image_view,
                                     context->swapchain_data.swapchain_image_views[i]};

        VkFramebufferCreateInfo framebufferInfo = {0};
        framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
        framebufferInfo.renderPass = context->vulkan_handles.render_pass;
        framebufferInfo.attachmentCount = 3;
        framebufferInfo.pAttachments = attachments;
        framebufferInfo.width = context->swapchain_data.swapchain_extent.width;
        framebufferInfo.height = context->swapchain_data.swapchain_extent.height;
        framebufferInfo.layers = 1;

        if (vkCreateFramebuffer(context->vulkan_handles.device,
                                &framebufferInfo,
                                NULL,
                                &context->swapchain_data.swapchain_framebuffers[i]) != VK_SUCCESS) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create framebuffer");
            return RSE_ERROR_INTERNAL_ERROR;
        }
    }

    return RSE_ERROR_NO_ERROR;
}

/**
 * @brief Recreate swapchain
 *
 */
void recreate_swapchain(struct graphics_context_t* context)
{
    SDL_Event event;
    int width = 0;
    int height = 0;

    SDL_GetWindowSizeInPixels(context->window_handle, &width, &height);
    while (width == 0 || height == 0) {
        SDL_GetWindowSizeInPixels(context->window_handle, &width, &height);
        SDL_WaitEvent(&event);
    }

    vkDeviceWaitIdle(context->vulkan_handles.device);

    cleanup_swapchain(context);

    create_swapchain(context);
    create_swapchain_image_views(context);
    create_color_resource(context);
    create_depth_resources(context);
    create_framebuffers(context);
}

/**
 * @brief Cleanup swapchain
 *
 */
void cleanup_swapchain(struct graphics_context_t* context)
{
    size_t i;

    destroy_color_resource(context);
    destroy_depth_resource(context);
    for (i = 0; i < context->swapchain_images_count; i++) {
        vkDestroyFramebuffer(context->vulkan_handles.device, context->swapchain_data.swapchain_framebuffers[i], NULL);
    }

    for (i = 0; i < context->swapchain_images_count; i++) {
        vkDestroyImageView(context->vulkan_handles.device, context->swapchain_data.swapchain_image_views[i], NULL);
    }

    vkDestroySwapchainKHR(context->vulkan_handles.device, context->swapchain_data.swapchain, NULL);
}

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

    STATUS_CHECK(create_swapchain(context));
    STATUS_CHECK(create_swapchain_image_views(context));
    STATUS_CHECK(create_framebuffers(context));

    return status;
}
