// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include <carb/settings/ISettings.h>
#include <carb/settings/SettingsUtils.h>

#include <vector>
#include <string>
#include <set>

namespace omni
{
namespace kit
{
namespace xr
{

/**
 * @brief Gets a list of (Vulkan) extensions, which is overridable and has default values
 * 
 * @param path                  a settings path to store an overridable copy of this list
 * @param hardCodedExtensions   pre-loaded list of (vk) extensions
 * @return std::vector<std::string>     list of (vk) extensions to use
 */
inline std::vector<std::string> getExtensions(const char* path, const std::vector<std::string>& hardCodedExtensions)
{
    CARB_PROFILE_ZONE(carb::profiler::kCaptureMaskDefault, "XRContext::getExtensions");
    // This provides a way of other plugins/extensions to list additional extensions to load for
    // an instance or device:
    // This code scans the settings dictionaries under:
    // /renderer/vulkan/deviceExtensions and /renderer/vulkan/instanceExtensions and will check if the items
    // found contain an array, if they do the array is parsed and it's contents is added to the
    // list of extensions to load

    carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
    carb::dictionary::IDictionary* dictionary = carb::getCachedInterface<carb::dictionary::IDictionary>();

    auto type = settings->getItemType(path);

    std::set<std::string> extensions;

    extensions.insert(hardCodedExtensions.begin(), hardCodedExtensions.end());
    if (type == carb::dictionary::ItemType::eDictionary)
    {
        auto item = settings->getSettingsDictionary(path);
        size_t count = dictionary->getItemChildCount(item);
        for (size_t idx = 0; idx < count; idx++)
        {
            auto child = dictionary->getItemChildByIndex(item, idx);
            auto childType = dictionary->getItemType(child);
            if (childType == carb::dictionary::ItemType::eString)
            {
                std::string extension = dictionary->getStringBuffer(child);
                extensions.insert(extension);
            }
            else if (childType == carb::dictionary::ItemType::eDictionary)
            {
                if (dictionary->isAccessibleAsArrayOf(carb::dictionary::ItemType::eString, child))
                {
                    size_t arrLength = dictionary->getArrayLength(child);
                    for (size_t arrIdx = 0; arrIdx < arrLength; ++arrIdx)
                    {
                        std::string ext = dictionary->getStringBufferAt(child, arrIdx);
                        extensions.insert(ext);
                    }
                }
            }
        }
    }

    std::vector<std::string> result;
    result.insert(result.begin(), extensions.begin(), extensions.end());

    return result;
}

/**
 * @brief Ensure compatibility with XR
 * 
 * Various XR plugins (especially SteamVR) require certain renderer options.
 * Ensure those options are set before an XRSystem is initialized.
 */
inline void bootstrapXR()
{
    auto settings = carb::getCachedInterface<carb::settings::ISettings>();
    // If an XR extension is enabled disable the validation layer for now
    if (settings->getAsBool("/xr/debug"))
    {
        // This one breaks openvr
        settings->setBool("/renderer/debug/validation/enabled", false);
    }

#if CARB_PLATFORM_WINDOWS

    std::vector<std::string> instanceExtensions =
        getExtensions("/persistent/xr/vulkanInstanceExtensions",
                        { "VK_NV_external_memory_capabilities", "VK_KHR_external_memory_capabilities",
                        "VK_KHR_get_physical_device_properties2" });

    std::vector<std::string> deviceExtensions = getExtensions(
        "/persistent/xr/vulkanDeviceExtensions", { "VK_NV_dedicated_allocation", "VK_NV_external_memory",
                                                    "VK_NV_external_memory_win32", "VK_NV_win32_keyed_mutex" });

#elif CARB_PLATFORM_LINUX

    std::vector<std::string> instanceExtensions =
        getExtensions("/persistent/xr/vulkanInstanceExtensions",
                        { "VK_KHR_external_memory_capabilities", "VK_KHR_get_physical_device_properties2",
                        "VK_KHR_external_semaphore_capabilities" });

    std::vector<std::string> deviceExtensions = getExtensions(
        "/persistent/xr/vulkanDeviceExtensions",
        { "VK_KHR_external_memory", "VK_KHR_external_semaphore", "VK_KHR_dedicated_allocation",
            "VK_KHR_get_memory_requirements2", "VK_KHR_external_memory_fd", "VK_KHR_external_semaphore_fd" });

#elif CARB_PLATFORM_MACOS
    // FIXME!! do some actual checks here.  MacOS doesn't natively support Vulkan and only
    //         supports it by using MoltenVK.  The set of available extensions may differ
    //         as a result so we don't want to make any assumptions here yet.
    std::vector<std::string> instanceExtensions;
    std::vector<std::string> deviceExtensions;
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif

    carb::settings::setStringArray(
        settings, "/renderer/vulkan/instanceExtensions/omni.kit.xr.plugin", instanceExtensions);
    carb::settings::setStringArray(
        settings, "/renderer/vulkan/deviceExtensions/omni.kit.xr.plugin", deviceExtensions);

}

} // namespace xr
} // namespace kit
} // namespace omni
