// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file physical_device.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///


#ifndef FENNEC_RENDERERS_VULKAN_LIB_PHYSICAL_DEVICE_H
#define FENNEC_RENDERERS_VULKAN_LIB_PHYSICAL_DEVICE_H

#include <fennec/renderers/vulkan/lib/forward.h>
#include <fennec/renderers/vulkan/lib/physical_device_properties.h>
#include <fennec/renderers/vulkan/lib/physical_device_features.h>

#include <fennec/containers/dynarray.h>
#include <fennec/core/version.h>

namespace fennec::vk
{





// Queue Families ======================================================================================================

///
/// \brief Struct representing the queue families a device has available.
struct device_queue_families {
// Definitions ---------------------------------------------------------------------------------------------------------
private:

    using properties_t  = dynarray<VkQueueFamilyProperties>;
    using properties2_t = dynarray<VkQueueFamilyProperties2>;


// Constructors & Destructor -------------------------------------------------------------------------------------------
public:

    /// \name Constructors & Destructor
    /// @{

    ///
    /// \brief Queue Families Constructor
    /// \param device The device to obtain queue family information from.
    explicit device_queue_families(VkPhysicalDevice device)
        : _use_deprecated(vkGetPhysicalDeviceQueueFamilyProperties2 == nullptr) {
        uint32_t count = 0;
        if (_use_deprecated) {
            vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr);
            _queues2.resize(count);
            vkGetPhysicalDeviceQueueFamilyProperties(device, &count, _queues.data());
        } else {
            vkGetPhysicalDeviceQueueFamilyProperties2(device, &count, nullptr);
            _queues2.resize(count);
            vkGetPhysicalDeviceQueueFamilyProperties2(device, &count, _queues2.data());
        }

        // TODO: Extensions
    }

    ///
    /// \brief Destructor
    ~device_queue_families() = default;

    /// @}


// Properties ----------------------------------------------------------------------------------------------------------
public:

    /// \name Properties
    /// @{

    ///
    /// \returns The number of queue families associated with the device.
    size_t size() const {
        return _use_deprecated ? _queues.size() : _queues2.size();
    }

    /// @}


// Access --------------------------------------------------------------------------------------------------------------
public:

    /// \name Access
    /// @{

    ///
    /// \brief Queue Properties Access.
    /// \param i The index of the queue family.
    /// \returns A reference to the properties of the specified queue family.
    const VkQueueFamilyProperties& operator[](size_t i) const {
        return _use_deprecated ? _queues[i] : _queues2[i].queueFamilyProperties;
    }

    /// @}


// Private Member Variables --------------------------------------------------------------------------------------------
private:

    bool          _use_deprecated;
    properties_t  _queues;
    properties2_t _queues2;
};


// Physical Device =====================================================================================================

///
/// \brief RAII Wrapper for VkPhysicalDevice.
/// \details Acquires information about the physical device at construction.
struct physical_device {
// Definitions ---------------------------------------------------------------------------------------------------------
public:

    /// \name Definitions
    /// @{

    using handle_t = VkPhysicalDevice; //!< The underlying handle type

    /// @}


// Constructors & Destructor -------------------------------------------------------------------------------------------
public:

    /// \name Constructors & Destructor
    /// @{

    ///
    /// \brief Device Constructor
    /// \param handle The handle to acquire.
    /// \details Acquires device information.
    explicit physical_device(VkPhysicalDevice handle)
        : _handle(handle)
        , _properties(handle)
        , _features(handle, _properties)
        , _queue_families(handle) { }

    ///
    /// \brief Move Constructor
    /// \param device The device to acquire.
    /// \details Moves already gathered information instead polling the driver again.
    physical_device(physical_device&& device) noexcept
        : _handle(device._handle)
        , _properties(move(device._properties))
        , _features(move(device._features))
        , _queue_families(move(device._queue_families)) { }

    /// @}


// Assignment ----------------------------------------------------------------------------------------------------------
public:

    ///
    /// \brief Move Assignment
    /// \param device
    /// \return
    physical_device& operator=(physical_device&& device) noexcept {
        _handle         = device._handle;
        _properties     = move(device._properties);
        _features       = move(device._features);
        _queue_families = move(device._queue_families);
        return *this;
    }


// Properties ----------------------------------------------------------------------------------------------------------
public:

    ///
    /// \returns The API version supported by the device
    version api_version() const {
        return {
            .major = VK_API_VERSION_MAJOR(_properties.core10->apiVersion),
            .minor = VK_API_VERSION_MINOR(_properties.core10->apiVersion),
            .patch = VK_API_VERSION_PATCH(_properties.core10->apiVersion),
            .meta  = VK_API_VERSION_VARIANT(_properties.core10->apiVersion),
        };
    }

    ///
    /// \returns The version of the device's driver
    version driver_version() const {
        return {
            .major = VK_VERSION_MAJOR(_properties.core10->driverVersion),
            .minor = VK_VERSION_MINOR(_properties.core10->driverVersion),
            .patch = VK_VERSION_PATCH(_properties.core10->driverVersion),
        };
    }

    ///
    /// \returns The type of the device.
    /// \see fennec::vk::device_type_
    uint32_t type() const {
        return _properties.core10->deviceType;
    }

    cstring name() const {
        return _properties.core10->deviceName;
    }

    const device_properties& properties() const {
        return _properties;
    }

    const device_features& features() const {
        return _features;
    }

    const device_queue_families& queue_families() const {
        return _queue_families;
    }

    uint32_t score() const {
        uint32_t score = 0;
        version  api   = api_version();
        score          += 1000 * api.major + 100 * api.minor;
        score          += 1000 * _type_score();
        return score;
    }

// Private Member Variables --------------------------------------------------------------------------------------------
private:

    handle_t              _handle;
    device_properties     _properties;
    device_features       _features;
    device_queue_families _queue_families;


// Private Helpers -----------------------------------------------------------------------------------------------------
private:

    uint32_t _type_score() const {
        switch (_properties.core10->deviceType) {
            default:
            case VK_PHYSICAL_DEVICE_TYPE_OTHER: return 1;
            case VK_PHYSICAL_DEVICE_TYPE_CPU: return 2;
            case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 3;
            case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 4;
            case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 5;
        }
    }
};
}

#endif // FENNEC_RENDERERS_VULKAN_LIB_PHYSICAL_DEVICE_H
