/*
	VulkanFramework
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Vulkan Surface

#include "VulkanSurface.h"
#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <memory>
#include <array>

namespace Vulkan {

Surface::Surface(Device& device, GLFWwindow* window)
    : m_owner(device)
    , m_surface(nullptr)
{
    Create(window);
}

// virtual
Surface::~Surface()
{
    Destroy();
}

// virtual
void Surface::Create(GLFWwindow* window)
{
    if (!m_surface)
    {
        // Create Window Surface
        m_owner.CheckResult(glfwCreateWindowSurface(m_owner.m_instance, window, m_owner.m_allocator, &m_surface));

        // Check for WSI support
        VkBool32 res;
        vkGetPhysicalDeviceSurfaceSupportKHR(m_owner.m_physicalDevice, m_owner.m_graphicsQueueFamily, m_surface, &res);
        m_owner.CheckResult((res == VK_TRUE) ? VK_SUCCESS : VK_ERROR_UNKNOWN, "Error no WSI support on physical device");
        m_format = SelectFormat();
        m_presentMode = SelectPresentMode();
    }
}

// virtual
void Surface::Destroy()
{
    if (m_surface)
    {
        vkDestroySurfaceKHR(m_owner.m_instance, m_surface, m_owner.m_allocator);
        m_surface = nullptr;
    }
}

// virtual
VkSurfaceFormatKHR Surface::SelectFormat()
{
    uint32_t avail_count;
    vkGetPhysicalDeviceSurfaceFormatsKHR(m_owner.m_physicalDevice, m_surface, &avail_count, nullptr);
    auto avail_formats = std::make_unique<VkSurfaceFormatKHR[]>(avail_count);
    vkGetPhysicalDeviceSurfaceFormatsKHR(m_owner.m_physicalDevice, m_surface, &avail_count, avail_formats.get());

    if (avail_count == 1 && avail_formats[0].format == VK_FORMAT_UNDEFINED) // implies that any format is available
        return VkSurfaceFormatKHR{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLORSPACE_SRGB_NONLINEAR_KHR };

    // Request several formats, the first found will be used
    const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM };
    for (int request_i = 0; request_i < std::size(requestSurfaceImageFormat); ++request_i)
        for (uint32_t avail_i = 0; avail_i < avail_count; ++avail_i)
            if (avail_formats[avail_i].format == requestSurfaceImageFormat[request_i] && avail_formats[avail_i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR)
                return avail_formats[avail_i];

    return avail_formats[0];
}

// virtual
VkPresentModeKHR Surface::SelectPresentMode()
{
    // Unlimited framerate
    VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR };

    // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory
    uint32_t avail_count = 0;
    vkGetPhysicalDeviceSurfacePresentModesKHR(m_owner.m_physicalDevice, m_surface, &avail_count, nullptr);
    auto avail_modes = std::make_unique<VkPresentModeKHR[]>(avail_count);
    vkGetPhysicalDeviceSurfacePresentModesKHR(m_owner.m_physicalDevice, m_surface, &avail_count, avail_modes.get());

    for (int request_i = 0; request_i < std::size(present_modes); request_i++)
        for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++)
            if (present_modes[request_i] == avail_modes[avail_i])
                return present_modes[request_i];

    return VK_PRESENT_MODE_FIFO_KHR; // Always available
}

} // Vulkan namespace
