/*
 * Copyright 2025 Rive
 */

#include "rive/renderer/vulkan/vkutil.hpp"
#include "rive_vk_bootstrap/vulkan_instance.hpp"
#include "logging.hpp"
#include "vulkan_debug_callbacks.hpp"
#include "vulkan_library.hpp"

namespace rive_vkb
{
VulkanInstance::VulkanInstance(const Options& opts)
{
    m_library = std::make_unique<VulkanLibrary>();

    // Figure out which version to use
    m_instanceVersion = opts.idealAPIVersion;
    m_apiVersion = opts.idealAPIVersion;
    if (opts.idealAPIVersion > opts.minimumSupportedInstanceVersion)
    {
        if (m_library->canEnumerateInstanceVersion())
        {
            m_library->enumerateInstanceVersion(&m_instanceVersion);
        }
        else
        {
            // If vkEnumerateInstanceVersion doesn't exist then this we need
            // to assume this is Vulkan 1.0 (the function was introduced
            // in 1.1)
            m_instanceVersion = VK_API_VERSION_1_0;
        }
    }

    if (m_instanceVersion < VK_API_VERSION_1_1)
    {
        // The API veresion is intended to be the maximum Vulkan version
        // supported by the given application. However, Vulkan 1.0
        // implementations will fail with VK_ERROR_INCOMPATIBLE_DRIVER for
        // api versions > 1.0, so if we detect that it's not a 1.1 or
        // greater device, we need to force the api version to 1.0
        m_apiVersion = VK_API_VERSION_1_0;
    }

    if (m_instanceVersion < opts.minimumSupportedInstanceVersion)
    {
        LOG_ERROR_LINE(
            "Instance version %d.%d.%d is less than the minimum supported "
            "version of %d.%d.%d",
            VK_VERSION_MAJOR(m_instanceVersion),
            VK_VERSION_MINOR(m_instanceVersion),
            VK_VERSION_PATCH(m_instanceVersion),
            VK_VERSION_MAJOR(opts.minimumSupportedInstanceVersion),
            VK_VERSION_MINOR(opts.minimumSupportedInstanceVersion),
            VK_VERSION_PATCH(opts.minimumSupportedInstanceVersion));
        abort();
    }

    VkApplicationInfo appInfo = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = opts.appName,
        .pEngineName = opts.engineName,
        .apiVersion = m_apiVersion,
    };

    std::vector<VkExtensionProperties> supportedExtensions;
    std::vector<VkLayerProperties> supportedLayers;
    {
        uint32_t count;
        m_library->enumerateInstanceExtensionProperties(nullptr,
                                                        &count,
                                                        nullptr);

        supportedExtensions.resize(count);
        m_library->enumerateInstanceExtensionProperties(
            nullptr,
            &count,
            supportedExtensions.data());

        m_library->enumerateInstanceLayerProperties(&count, nullptr);
        supportedLayers.resize(count);
        m_library->enumerateInstanceLayerProperties(&count,
                                                    supportedLayers.data());

        if (opts.logExtendedCreationInfo)
        {
            LOG_INFO_LINE("Reported Vulkan extensions:");
            for (const auto& ext : supportedExtensions)
            {
                LOG_INFO_LINE("    %s (version %d)",
                              ext.extensionName,
                              ext.specVersion);
            }

            LOG_INFO_LINE("Reported Vulkan layers:");
            for (const auto& layer : supportedLayers)
            {
                LOG_INFO_LINE("    %s (spec: %d, impl: %d)%s%s",
                              layer.layerName,
                              layer.specVersion,
                              layer.implementationVersion,
                              (layer.description[0] != '\0') ? "\n        "
                                                             : "",
                              layer.description);
            }
        }
    }

    bool enableDebugCallbacks = opts.wantDebugCallbacks;
    auto validationType = opts.desiredValidationType;

    std::vector<const char*> extensions;
    std::vector<const char*> layers;

    auto add_extension_if_supported = [&](const char* extName) {
        for (const auto& ext : supportedExtensions)
        {
            if (strcmp(ext.extensionName, extName) == 0)
            {
                extensions.push_back(extName);
                return true;
            }
        }

        return false;
    };

    auto add_layer_if_supported = [&](const char* layerName) {
        for (const auto& layer : supportedLayers)
        {
            if (strcmp(layer.layerName, layerName) == 0)
            {
                layers.push_back(layerName);
                return true;
            }
        }

        return false;
    };

    auto add_extensions_if_supported = [&](const auto& extNames) {
        for (auto* extName : extNames)
        {
            add_extension_if_supported(extName);
        }
    };

    for (auto* extName : opts.requiredExtensions)
    {
        if (!add_extension_if_supported(extName))
        {
            LOG_ERROR_LINE("Required extension '%s' was not supported",
                           extName);
            abort();
        }
    }

    add_extensions_if_supported(opts.optionalExtensions);

    VkInstanceCreateFlags instanceCreateFlags = 0;

#ifdef VK_KHR_portability_enumeration
    if (add_extension_if_supported(
            VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME))
    {
        instanceCreateFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
    }
#endif

    bool useFallbackDebugCallbacks = false;
    if (validationType != VulkanValidationType::none)
    {
        if (!add_layer_if_supported("VK_LAYER_KHRONOS_validation"))
        {
            LOG_ERROR_LINE("WARNING: Validation layers are not supported. "
                           "Creating context without validation layers.\n");
            validationType = VulkanValidationType::none;
        }
        else if (validationType != VulkanValidationType::core)
        {
            // If we're doing any other type of validation, add the settings
            // extension. (RenderDoc does not work when this extension is,
            // enabled, which is why we don't always enable it)
            extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
        }
    }

    if (enableDebugCallbacks)
    {
        if (validationType != VulkanValidationType::none)
        {
            // If we are enabling validation layers, VK_EXT_debug_utils willl
            // come along with that, even though it currently isn't in the
            // reported list of extensions, so just assume that it will work.
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        }
        else if (!add_extension_if_supported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
        {
            if (add_extension_if_supported(VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
            {
                // Some devices only have the older debug report extension, so
                // use that where we need to.
                useFallbackDebugCallbacks = true;
            }
            else
            {
                LOG_ERROR_LINE("WARNING: Debug callbacks are not supported. "
                               "Creating context without debug callbacks.\n");
                enableDebugCallbacks = false;
            }
        }
    }

    bool enabledKHRDeviceProperties2 = false;
    if (m_instanceVersion < VK_API_VERSION_1_1)
    {
        enabledKHRDeviceProperties2 = add_extension_if_supported(
            VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
    }

    VkInstanceCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .flags = instanceCreateFlags,
        .pApplicationInfo = &appInfo,
        .enabledLayerCount = uint32_t(layers.size()),
        .ppEnabledLayerNames = layers.data(),
        .enabledExtensionCount = uint32_t(extensions.size()),
        .ppEnabledExtensionNames = extensions.data(),
    };

    // Options to switch to synchronization validation vs. core validation
    constexpr VkBool32 vkFalseBool = VK_FALSE;
    constexpr VkBool32 vkTrueBool = VK_TRUE;
    const VkLayerSettingEXT settingsForSyncValidate[] = {
        {
            .pLayerName = "VK_LAYER_KHRONOS_validation",
            .pSettingName = "validate_core",
            .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT,
            .valueCount = 1,
            .pValues = &vkFalseBool,
        },
        {
            .pLayerName = "VK_LAYER_KHRONOS_validation",
            .pSettingName = "validate_sync",
            .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT,
            .valueCount = 1,
            .pValues = &vkTrueBool,
        },
        {
            .pLayerName = "VK_LAYER_KHRONOS_validation",
            .pSettingName = "thread_safety",
            .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT,
            .valueCount = 1,
            .pValues = &vkTrueBool,
        },
    };

    VkLayerSettingsCreateInfoEXT validationLayerSettingsForSyncValidation = {
        .sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT,
        .settingCount = std::size(settingsForSyncValidate),
        .pSettings = settingsForSyncValidate,
    };

    if (validationType == VulkanValidationType::synchronization)
    {
        validationLayerSettingsForSyncValidation.pNext = createInfo.pNext;
        createInfo.pNext = &validationLayerSettingsForSyncValidation;
    }

    VK_CHECK(
        m_library->createInstance(&createInfo, VK_NULL_HANDLE, &m_instance));

    m_enabledExtensions = std::move(extensions);
    m_enabledLayers = std::move(layers);

    LOAD_REQUIRED_MEMBER_INSTANCE_FUNC(vkDestroyInstance, *this);

    // This should always exist on Vulkan 1.1 but there are some devices where
    // the reported instance version is > 1.0 that actually only
    // have 1.0-enabled devices.
    LOAD_MEMBER_INSTANCE_FUNC(vkGetPhysicalDeviceFeatures2, *this);
    if (m_vkGetPhysicalDeviceFeatures2 == nullptr &&
        enabledKHRDeviceProperties2)
    {
        LOAD_MEMBER_INSTANCE_FUNC(vkGetPhysicalDeviceFeatures2KHR, *this);
    }

    if (enableDebugCallbacks)
    {
        if (useFallbackDebugCallbacks)
        {
            LOG_INFO_LINE("Note: " VK_EXT_DEBUG_UTILS_EXTENSION_NAME
                          " was not supported, falling back "
                          "to " VK_EXT_DEBUG_REPORT_EXTENSION_NAME ".");
            DEFINE_AND_LOAD_INSTANCE_FUNC(vkCreateDebugReportCallbackEXT,
                                          *this);

            VkDebugReportCallbackCreateInfoEXT debugCreateInfo = {};
            debugCreateInfo.sType =
                VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
            debugCreateInfo.flags =
                VK_DEBUG_REPORT_WARNING_BIT_EXT |
                VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
                VK_DEBUG_REPORT_ERROR_BIT_EXT;
            debugCreateInfo.pfnCallback = &defaultDebugReportCallback;
            vkCreateDebugReportCallbackEXT(m_instance,
                                           &debugCreateInfo,
                                           nullptr,
                                           &m_debugReportCallback);
        }
        else
        {
            DEFINE_AND_LOAD_INSTANCE_FUNC(vkCreateDebugUtilsMessengerEXT,
                                          *this);

            VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {
                .sType =
                    VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
                .messageSeverity =
                    VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                    VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
                .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,
                .pfnUserCallback = &defaultDebugUtilCallback,
            };

            if (auto result =
                    vkCreateDebugUtilsMessengerEXT(m_instance,
                                                   &messengerCreateInfo,
                                                   nullptr,
                                                   &m_debugUtilsMessenger);
                result != VK_SUCCESS)
            {
                LOG_ERROR_LINE(
                    "Failed to create debug messenger. Error code: %d",
                    uint32_t(result));
            }
        }
    }
}

VulkanInstance::~VulkanInstance()
{
    if (m_debugUtilsMessenger != VK_NULL_HANDLE)
    {
        DEFINE_AND_LOAD_INSTANCE_FUNC(vkDestroyDebugUtilsMessengerEXT, *this);
        vkDestroyDebugUtilsMessengerEXT(m_instance,
                                        m_debugUtilsMessenger,
                                        nullptr);
    }

    if (m_debugReportCallback != VK_NULL_HANDLE)
    {
        DEFINE_AND_LOAD_INSTANCE_FUNC(vkDestroyDebugReportCallbackEXT, *this);
        vkDestroyDebugReportCallbackEXT(m_instance,
                                        m_debugReportCallback,
                                        nullptr);
    }

    m_vkDestroyInstance(m_instance, nullptr);
}

PFN_vkVoidFunction VulkanInstance::loadInstanceFunc(const char* name) const
{
    return m_library->getInstanceProcAddr(m_instance, name);
}

bool VulkanInstance::tryGetPhysicalDeviceFeatures2(
    VkPhysicalDevice device,
    VkPhysicalDeviceFeatures2* inoutFeatures)
{
    if (m_vkGetPhysicalDeviceFeatures2 != nullptr)
    {
        m_vkGetPhysicalDeviceFeatures2(device, inoutFeatures);
        return true;
    }
    else if (m_vkGetPhysicalDeviceFeatures2KHR != nullptr)
    {
        m_vkGetPhysicalDeviceFeatures2KHR(device, inoutFeatures);
        return true;
    }

    return false;
}

PFN_vkGetInstanceProcAddr VulkanInstance::getVkGetInstanceProcAddrPtr() const
{
    return m_library->getVkGetInstanceProcAddrPtr();
}

} // namespace rive_vkb