#ifndef VULKAN_SWAPCHAIN_HPP_
#define VULKAN_SWAPCHAIN_HPP_

#include <memory>
#include <vector>
#include <set>
#include <optional>

#include <Utilities/Extern.hpp>
#include <Vk/Instance/Instance.hpp>
#include <Vk/Surface/Surface.hpp>
#include <Vk/Device/Device.hpp>
#include <Vk/Semaphore/Semaphore.hpp>
#include <Vk/Fence/Fence.hpp>

namespace Vk
{
    /**
     * @brief Represents a Vulkan swapchain.
     */
    class Swapchain
    {
    private:
        VkSwapchainKHR m_swapchain;
        std::shared_ptr<Vk::Device> m_device;
        std::shared_ptr<Vk::Surface> m_surface;
        VkFormat m_imageFormat;
        VkExtent2D m_extent;
        std::vector<VkImage> m_images;

        VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
        VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
        VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, int width, int height);
    
    public:
        /**
         * @brief Construct a swapchain.
         * 
         * @param device The device on which to create the swapchain.
         * @param fbWidth The framebuffer width.
         * @param fbHeight The framebuffer height.
         */
        Swapchain(std::shared_ptr<Vk::Device> device, std::shared_ptr<Vk::Surface>, int fbWidth, int fbHeight);
        Swapchain(const Swapchain &) = delete;
        ~Swapchain();

        /**
         * @brief Get the wrapped swapchain.
         */
        VkSwapchainKHR inner();

        /**
         * @brief Get the extent of the images in this swapchain.
         */
        VkExtent2D getExtent();

        /**
         * @brief Get the image format.
         */
        VkFormat getImageFormat();

        /**
         * @brief Get the reference to the images.
         */
        std::vector<VkImage>& images();

        /**
         * @brief Try to acquire the next swpachain image.
         * 
         * @param timeout The timeout in nanoseconds (infinite by default).
         * @param semaphore The semaphore to signal when the image is available.
         * @param fence The fence to signal when the image is available.
         * @return The image index if successful, no value on error.
         */
        std::optional<uint32_t> acquireNextImage(
            uint64_t timeout = UINT64_MAX,
            Vk::Semaphore *semaphore = nullptr,
            Vk::Fence *fence = nullptr
        );
    };
}

#endif