#include <vector>

#include <Vk/Instance/Instance.hpp>
#include <Version/Version.hpp>
#include <Log/Log.hpp>
#include <Settings/Settings.hpp>

#define DECL_EXT_FUNC(name) PFN_##name Vk::Instance::name
#include <Vk/Instance/InstanceExtFuncList.hpp>

const std::vector<const char*> Vk::Instance::validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData
)
{
    std::string messageTypeName;
    switch (messageType)
    {
    case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
        messageTypeName = "General";
        break;
    case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
        messageTypeName = "Validation";
        break;
    case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
        messageTypeName = "Performance";
        break;
    default:
        messageTypeName = "???";
        break;
    }

    std::string line = formatString("[Vulkan] [$+] $+", messageTypeName, pCallbackData->pMessage);
    switch (messageSeverity)
    {
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
        Log::debug(line);
        break;
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
        crash(line);
        break;
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
        Log::warn(line);
        break;
    default:
        Log::info(line);
        break;
    }

    return VK_FALSE;
}

PFN_vkVoidFunction Vk::Instance::loadExtFunc(const char *name)
{
    PFN_vkVoidFunction func = vkGetInstanceProcAddr(m_instance, name);
    if (func == nullptr)
    {
        Log::warn(formatString("Failed to load extension function '$+'!", name));
    }

    return func;
}

void Vk::Instance::loadExtFuncs()
{
    #define DECL_EXT_FUNC(name) name = (PFN_##name) loadExtFunc(#name)
    #include <Vk/Instance/InstanceExtFuncList.hpp>
}

Vk::Instance::Instance(SDL_Window *window)
{
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Voxelverse";
    appInfo.applicationVersion = VK_MAKE_VERSION(
        Version::ofThisBuild.major,
        Version::ofThisBuild.minor,
        Version::ofThisBuild.patchLevel
    );
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;

    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;

    uint32_t extensionCount;
    const char* const* ext = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
    std::vector<const char*> extensions(ext, ext + extensionCount);

    if (settings.validationLayers)
    {
        Log::info("Enabling Vulkan validation layers.");

        createInfo.ppEnabledLayerNames = validationLayers.data();
        createInfo.enabledLayerCount = validationLayers.size();
    }

    extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);

    createInfo.enabledExtensionCount = extensions.size();
    createInfo.ppEnabledExtensionNames = extensions.data();

    if (vkCreateInstance(&createInfo, nullptr, &m_instance) != VK_SUCCESS)
    {
        crash("Failed to create the Vulkan instance!");
    }

    loadExtFuncs();

    VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo{};
    messengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    messengerCreateInfo.messageSeverity =
        VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
        VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
        VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
        VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
    messengerCreateInfo.messageType =
        VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
        VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
        VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    messengerCreateInfo.pfnUserCallback = debugCallback;
    messengerCreateInfo.pUserData = nullptr;

    if (vkCreateDebugUtilsMessengerEXT(m_instance, &messengerCreateInfo, nullptr, &m_debugMessenger) != VK_SUCCESS)
    {
        crash("Failed to create the debug messenger!");
    }
}

Vk::Instance::~Instance()
{
    if (settings.validationLayers)
    {
        vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugMessenger, nullptr);
    }

    vkDestroyInstance(m_instance, nullptr);
}

VkInstance Vk::Instance::inner()
{
    return m_instance;
}

std::vector<VkPhysicalDevice> Vk::Instance::getPhysicalDevices()
{
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr);

    if (deviceCount == 0)
    {
        return std::vector<VkPhysicalDevice>();
    }

    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());

    return devices;
}