#include "detail/graphics.h"

#include <set>

namespace deerith {
	namespace graphics {
		bool is_device_suitable(VkPhysicalDevice device);
		bool check_device_extension_support(VkPhysicalDevice device);
	} // namespace graphics

	void graphics::create_vulkan_phisical_device() {
		deerith_graphics_trace("Initializing Vulkan phisical device");

		uint32_t deviceCount = 0;
		vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

		if (deviceCount == 0) {
			deerith_graphics_error("failed to find GPUs with Vulkan support!");
			return;
		}

		std::vector<VkPhysicalDevice> devices(deviceCount);
		vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

		for (const auto& device : devices) {
			if (is_device_suitable(device)) {
				physical_device = device;
				break;
			}
		}

		if (physical_device == VK_NULL_HANDLE) {
			deerith_graphics_error("failed to find GPUs");
		}

		queue_family_indices = find_queue_families(physical_device);
	}

	bool graphics::is_device_suitable(VkPhysicalDevice device) {
		VkPhysicalDeviceProperties deviceProperties;
		vkGetPhysicalDeviceProperties(device, &deviceProperties);

		VkPhysicalDeviceFeatures deviceFeatures;
		vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

		QueueFamilyIndices indices = find_queue_families(device);

		SwapChainSupportDetails swap_chainSupport = query_swap_chain_support(device);
		bool swap_chain_adequate = !swap_chainSupport.formats.empty() && !swap_chainSupport.present_modes.empty();

		return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
		       deviceFeatures.geometryShader &&
		       indices.is_complete() &&
		       swap_chain_adequate;
	}

	graphics::QueueFamilyIndices graphics::find_queue_families(VkPhysicalDevice device) {
		graphics::QueueFamilyIndices indices;

		uint32_t queue_familty_count = 0;
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_familty_count, nullptr);

		std::vector<VkQueueFamilyProperties> queueFamilies(queue_familty_count);
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_familty_count, queueFamilies.data());

		int i = 0;
		for (const auto& queueFamily : queueFamilies) {
			if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
				indices.graphics_family = i;
			}

			VkBool32 presentSupport = false;
			vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);

			if (presentSupport) {
				indices.present_family = i;
			}

			if (indices.is_complete()) {
				break;
			}

			i++;
		}

		return indices;
	}

	bool graphics::check_device_extension_support(VkPhysicalDevice device) {
		uint32_t extensionCount;
		vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

		std::vector<VkExtensionProperties> availableExtensions(extensionCount);
		vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

		std::set<std::string> requiredExtensions(device_extensions.begin(), device_extensions.end());

		for (const auto& extension : availableExtensions) {
			requiredExtensions.erase(extension.extensionName);
		}

		return requiredExtensions.empty();
	}
} // namespace deerith