#ifndef GAMINGCORE_VULKAN_BASE_HPP
#define GAMINGCORE_VULKAN_BASE_HPP

#include <core/Array.hpp>
#include <core/List.hpp>
#include <core/Types.hpp>
#include <core/Utility.hpp>

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include "core/VulkanUtility.hpp"

namespace Core::Vulkan {
    [[maybe_unused]] constexpr u32 INVALID_QUEUE_FAMILY = 0xFFFF'FFFF;

    struct DeviceQueueData {
        u32 queueFamilyIndex = 0;
        float priority = 0.0f;
    };

    class Base {
        VkInstance instance;
#ifdef DEBUG_VULKAN
        VkDebugUtilsMessengerEXT debugMessenger;
        VkDebugReportCallbackEXT debugReportCallback;
#endif
        VkSurfaceKHR surface;
        VkPhysicalDevice physicalDevice;
        VkDevice device;

    public:
        Base();
        ~Base();
        Base(const Base&) = delete;
        Base(Base&&) = delete;
        Base& operator=(const Base&) = delete;
        Base& operator=(Base&&) = delete;

        bool init();
        void destroy();

        PFN_vkVoidFunction getFunction(const char* name);

        template<typename T>
        using PhysicalDeviceSelector = int (*)(Base&, T&);

        template<typename T>
        bool findPhysicalDevice(T& data, PhysicalDeviceSelector<T> s) {
            Core::Array<VkPhysicalDevice, 32> devices;
            u32 c = devices.getLength();
            VK_CHECK_TRUE(
                vkEnumeratePhysicalDevices(instance, &c, &devices[0]));
            int bestPoints = 0;
            VkPhysicalDevice best = VK_NULL_HANDLE;
            for(u32 i = 0; i < c; i++) {
                physicalDevice = devices[i];
                T userData;
                int points = s(*this, userData);
                if(points > bestPoints) {
                    bestPoints = points;
                    best = physicalDevice;
                    data = userData;
                }
            }
            physicalDevice = best;
            return bestPoints == 0;
        }

        void getPhysicalDeviceProperties(VkPhysicalDeviceProperties& p);
        u32 findQueueFamily(VkQueueFlags flags);
        u32 findSurfaceQueueFamily();
        bool hasExtension(const char* extension);
        bool getSurfaceCapabilities(VkSurfaceCapabilitiesKHR& c);

        using SurfaceFormatSelector = int (*)(const VkSurfaceFormatKHR&);
        bool findSurfaceFormat(
            VkSurfaceFormatKHR& sf, SurfaceFormatSelector sfs);

        using SurfacePresentModeSelector = int (*)(VkPresentModeKHR);
        bool findSurfacePresentMode(
            VkPresentModeKHR& m, SurfacePresentModeSelector spms);

        bool initDevice(
            const List<DeviceQueueData> data,
            const List<const char*>& extensions);

        operator VkDevice() {
            return device;
        }

        operator VkSurfaceKHR() {
            return surface;
        }

    private:
        bool initInstance();
#ifdef DEBUG_VULKAN
        bool initDebugMessenger();
        bool initDebugReportCallback();
        void destroyDebugMessenger();
        void destroyDebugReportCallback();
#endif
        bool initSurface();
    };
}

#endif