#include "Renderer.hpp"

#include <algorithm>
#include <ranges>
#include <vector>

const std::vector<const char*> g_layers{
#ifdef ENGINE_VULKAN_DEBUG
    "VK_LAYER_KHRONOS_validation"
#endif
};

[[nodiscard]] static auto create_instance() -> vk::UniqueInstance
{
    constexpr static vk::ApplicationInfo application_info{};

    const vk::InstanceCreateInfo create_info{
        .pApplicationInfo    = &application_info,
        .enabledLayerCount   = static_cast<uint32_t>(g_layers.size()),
        .ppEnabledLayerNames = g_layers.data(),
    };

    return vk::createInstanceUnique(create_info);
}

[[nodiscard]] static auto choose_physical_device(const vk::Instance t_instance)
    -> vk::PhysicalDevice
{
    const auto physical_devices{ t_instance.enumeratePhysicalDevices() };

    if (std::ranges::empty(physical_devices)) {
        throw std::runtime_error{ "No Vulkan physical device is available." };
    }

    auto ranked_devices_view{
        physical_devices
        | std::views::transform([](vk::PhysicalDevice t_physical_device) {
              switch (t_physical_device.getProperties().deviceType) {
                  case vk::PhysicalDeviceType::eDiscreteGpu:
                      return std::make_pair(t_physical_device, 2);
                  case vk::PhysicalDeviceType::eIntegratedGpu:
                      return std::make_pair(t_physical_device, 1);
                  default: return std::make_pair(t_physical_device, 0);
              }
          })
    };
    std::vector ranked_devices(
        ranked_devices_view.begin(), ranked_devices_view.end()
    );

    std::ranges::sort(
        ranked_devices,
        std::ranges::greater{},
        &std::pair<vk::PhysicalDevice, int>::second
    );

    return ranked_devices.front().first;
}

[[nodiscard]] static auto find_graphics_queue_family(
    const vk::PhysicalDevice t_physical_device
) -> uint32_t
{
    uint32_t index{};
    for (const auto& properties : t_physical_device.getQueueFamilyProperties())
    {
        if (properties.queueFlags & vk::QueueFlagBits::eGraphics) {
            return index;
        }
        index++;
    }
    throw std::runtime_error{ "Could not find graphics queue family" };
}

[[nodiscard]] static auto create_device(const vk::PhysicalDevice t_physical_device)
    -> vk::UniqueDevice
{
    const vk::DeviceQueueCreateInfo queue_create_info{
        .queueFamilyIndex = find_graphics_queue_family(t_physical_device),
        .queueCount       = 1,
    };

    vk::DeviceCreateInfo device_create_info{
        .queueCreateInfoCount = 1,
        .pQueueCreateInfos    = &queue_create_info,
    };

    return t_physical_device.createDeviceUnique(device_create_info);
}

Renderer::Renderer()
    : m_instance{ create_instance() },
      m_physical_device{ choose_physical_device(*m_instance) },
      m_graphics_queue_family{ find_graphics_queue_family(m_physical_device) },
      m_device{ create_device(m_physical_device) },
      m_graphics_queue{ m_device->getQueue(m_graphics_queue_family, 0) }
{}