/* Copyright (c) 2020-2025, Arm Limited and Contributors
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 the "License";
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "physical_device.h"

namespace vkb
{
PhysicalDevice::PhysicalDevice(vkb::core::InstanceC &instance, VkPhysicalDevice physical_device) :
    instance{instance},
    handle{physical_device}
{
	vkGetPhysicalDeviceFeatures(physical_device, &features);
	vkGetPhysicalDeviceProperties(physical_device, &properties);
	vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);

	LOGI("Found GPU: {}", properties.deviceName);

	uint32_t queue_family_properties_count = 0;
	vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_properties_count, nullptr);
	queue_family_properties = std::vector<VkQueueFamilyProperties>(queue_family_properties_count);
	vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_properties_count, queue_family_properties.data());

	uint32_t device_extension_count;
	VK_CHECK(vkEnumerateDeviceExtensionProperties(get_handle(), nullptr, &device_extension_count, nullptr));
	device_extensions = std::vector<VkExtensionProperties>(device_extension_count);
	VK_CHECK(vkEnumerateDeviceExtensionProperties(get_handle(), nullptr, &device_extension_count, device_extensions.data()));

	// Display supported extensions
	if (device_extensions.size() > 0)
	{
		LOGD("Device supports the following extensions:");
		for (auto &extension : device_extensions)
		{
			LOGD("  \t{}", extension.extensionName);
		}
	}
}

DriverVersion PhysicalDevice::get_driver_version() const
{
	DriverVersion version;

	VkPhysicalDeviceProperties const &properties = get_properties();
	switch (properties.vendorID)
	{
		case 0x10DE:
			// Nvidia
			version.major = (properties.driverVersion >> 22) & 0x3ff;
			version.minor = (properties.driverVersion >> 14) & 0x0ff;
			version.patch = (properties.driverVersion >> 6) & 0x0ff;
			// Ignoring optional tertiary info in lower 6 bits
			break;
		case 0x8086:
			version.major = (properties.driverVersion >> 14) & 0x3ffff;
			version.minor = properties.driverVersion & 0x3ffff;
			break;
		default:
			version.major = VK_VERSION_MAJOR(properties.driverVersion);
			version.minor = VK_VERSION_MINOR(properties.driverVersion);
			version.patch = VK_VERSION_PATCH(properties.driverVersion);
			break;
	}

	return version;
}

vkb::core::InstanceC &PhysicalDevice::get_instance() const
{
	return instance;
}

VkBool32 PhysicalDevice::is_present_supported(VkSurfaceKHR surface, uint32_t queue_family_index) const
{
	VkBool32 present_supported{VK_FALSE};

	if (surface != VK_NULL_HANDLE)
	{
		VK_CHECK(vkGetPhysicalDeviceSurfaceSupportKHR(handle, queue_family_index, surface, &present_supported));
	}

	return present_supported;
}

bool PhysicalDevice::is_extension_supported(const std::string &requested_extension) const
{
	return std::ranges::find_if(device_extensions,
	                            [requested_extension](auto &device_extension) {
		                            return std::strcmp(device_extension.extensionName, requested_extension.c_str()) == 0;
	                            }) != device_extensions.end();
}

const VkFormatProperties PhysicalDevice::get_format_properties(VkFormat format) const
{
	VkFormatProperties format_properties;

	vkGetPhysicalDeviceFormatProperties(handle, format, &format_properties);

	return format_properties;
}

VkPhysicalDevice PhysicalDevice::get_handle() const
{
	return handle;
}

const VkPhysicalDeviceFeatures &PhysicalDevice::get_features() const
{
	return features;
}

const VkPhysicalDeviceProperties &PhysicalDevice::get_properties() const
{
	return properties;
}

const VkPhysicalDeviceMemoryProperties &PhysicalDevice::get_memory_properties() const
{
	return memory_properties;
}

uint32_t PhysicalDevice::get_memory_type(uint32_t bits, VkMemoryPropertyFlags properties, VkBool32 *memory_type_found) const
{
	for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++)
	{
		if ((bits & 1) == 1)
		{
			if ((memory_properties.memoryTypes[i].propertyFlags & properties) == properties)
			{
				if (memory_type_found)
				{
					*memory_type_found = true;
				}
				return i;
			}
		}
		bits >>= 1;
	}

	if (memory_type_found)
	{
		*memory_type_found = false;
		return ~0;
	}
	else
	{
		throw std::runtime_error("Could not find a matching memory type");
	}
}

const std::vector<VkQueueFamilyProperties> &PhysicalDevice::get_queue_family_properties() const
{
	return queue_family_properties;
}

uint32_t PhysicalDevice::get_queue_family_performance_query_passes(
    const VkQueryPoolPerformanceCreateInfoKHR *perf_query_create_info) const
{
	uint32_t passes_needed;
	vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(get_handle(), perf_query_create_info,
	                                                        &passes_needed);
	return passes_needed;
}

void PhysicalDevice::enumerate_queue_family_performance_query_counters(
    uint32_t                            queue_family_index,
    uint32_t                           *count,
    VkPerformanceCounterKHR            *counters,
    VkPerformanceCounterDescriptionKHR *descriptions) const
{
	VK_CHECK(vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(
	    get_handle(), queue_family_index, count, counters, descriptions));
}

const VkPhysicalDeviceFeatures PhysicalDevice::get_requested_features() const
{
	return requested_features;
}

VkPhysicalDeviceFeatures &PhysicalDevice::get_mutable_requested_features()
{
	return requested_features;
}

void *PhysicalDevice::get_extension_feature_chain() const
{
	return last_requested_extension_feature;
}

}        // namespace vkb
