#include "VulkanSwapChain.h"

#include <vector>
#include <cstdint>	 // Necessary for uint32_t
#include <limits>	 // Necessary for std::numeric_limits
#include <algorithm> // Necessary for std::clamp
#include <vulkan/vulkan_core.h>

#include "utilities/Logger.h"

VulkanSwapChain::VulkanSwapChain(
	VkDevice							   Device,
	VkSurfaceKHR						   Surface,
	GLFWwindow*							   Window,
	uint32_t							   GraphicsFamily,
	uint32_t							   PresentFamily,
	const VkSurfaceCapabilitiesKHR&		   Capabilities,
	const std::vector<VkSurfaceFormatKHR>& Formats,
	const std::vector<VkPresentModeKHR>&   PresentModes
)
	: Device(Device)
	, Surface(Surface)
	, Window(Window)
	, GraphicsFamily(GraphicsFamily)
	, PresentFamily(PresentFamily)
	, Capabilities(Capabilities)
	, Formats(Formats)
	, PresentModes(PresentModes)
{
}

VulkanSwapChain::~VulkanSwapChain()
{
	// Cleanup();
}

// void VulkanSwapChain::Initialize(FIn
// {
// 	= In
// }

void VulkanSwapChain::Cleanup()
{
	for (auto ImageView : SwapChainImageViews)
	{
		vkDestroyImageView(Device, ImageView, nullptr);
	}
	vkDestroySwapchainKHR(Device, SwapChain, nullptr);
}

VkSurfaceFormatKHR
VulkanSwapChain::ChooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& AvailableFormats)
{
	for (const auto& AvailableFormat : AvailableFormats)
	{
		if (AvailableFormat.format == VK_FORMAT_B8G8R8A8_SRGB
			&& AvailableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
		{
			return AvailableFormat;
		}
	}
	return AvailableFormats[0];
}

VkPresentModeKHR
VulkanSwapChain::ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& AvailablePresentModes)
{
	for (const auto& AvailablePresentMode : AvailablePresentModes)
	{
		if (AvailablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR)
		{
			return AvailablePresentMode;
		}
	}
	return VK_PRESENT_MODE_FIFO_KHR;
}

VkExtent2D VulkanSwapChain::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& Capabilities)
{
	if (Capabilities.currentExtent.width != (std::numeric_limits<uint32_t>::max)())
	{
		return Capabilities.currentExtent;
	}
	else
	{
		if (Window == nullptr)
		{
			Log::Error("GLFW window is null in CreateSwapChain!");
		}
		int Width, Height;
		glfwGetFramebufferSize(Window, &Width, &Height);

		VkExtent2D ActualExtent = { static_cast<uint32_t>(Width), static_cast<uint32_t>(Height) };

		ActualExtent.width = std::clamp(
			ActualExtent.width,
			Capabilities.minImageExtent.width,
			Capabilities.maxImageExtent.width
		);
		ActualExtent.height = std::clamp(
			ActualExtent.height,
			Capabilities.minImageExtent.height,
			Capabilities.maxImageExtent.height
		);

		return ActualExtent;
	}
}

void VulkanSwapChain::CreateSwapChain()
{
	VkSurfaceFormatKHR SurfaceFormat = ChooseSwapSurfaceFormat(Formats);
	VkPresentModeKHR   PresentMode = ChooseSwapPresentMode(PresentModes);
	VkExtent2D		   Extent = ChooseSwapExtent(Capabilities);

	uint32_t ImageCount = Capabilities.minImageCount + 1;

	if (Capabilities.maxImageCount > 0 && ImageCount > Capabilities.maxImageCount)
	{
		ImageCount = Capabilities.maxImageCount;
	}

	VkSwapchainCreateInfoKHR CreateInfo{};
	CreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
	CreateInfo.surface = Surface;

	CreateInfo.minImageCount = ImageCount;
	CreateInfo.imageFormat = SurfaceFormat.format;
	CreateInfo.imageColorSpace = SurfaceFormat.colorSpace;
	CreateInfo.imageExtent = Extent;
	CreateInfo.imageArrayLayers = 1;
	CreateInfo.imageUsage =
		VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // may need VK_IMAGE_USAGE_TRANSFER_DST_BIT for post
											 // processing
											 // https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain#:~:text=VK%5FIMAGE%5FUSAGE%5FTRANSFER%5FDST%5FBIT

	uint32_t QueueFamilyIndices[] = { GraphicsFamily, PresentFamily };

	if (GraphicsFamily != PresentFamily)
	{
		CreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
		CreateInfo.queueFamilyIndexCount = 2;
		CreateInfo.pQueueFamilyIndices = QueueFamilyIndices;
	}
	else
	{
		CreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
		CreateInfo.queueFamilyIndexCount = 0;
		CreateInfo.pQueueFamilyIndices = nullptr;
	}

	CreateInfo.preTransform = Capabilities.currentTransform;
	CreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
	CreateInfo.presentMode = PresentMode;
	CreateInfo.clipped = VK_TRUE;
	CreateInfo.oldSwapchain = VK_NULL_HANDLE;

	if (vkCreateSwapchainKHR(Device, &CreateInfo, nullptr, &SwapChain) != VK_SUCCESS)
	{
		Log::Error("Failed to create swap chain.");
	}
	else
	{
		Log::Info("Successfully created swap chain.");
	}

	vkGetSwapchainImagesKHR(Device, SwapChain, &ImageCount, nullptr);
	SwapChainImages.resize(ImageCount);
	vkGetSwapchainImagesKHR(Device, SwapChain, &ImageCount, SwapChainImages.data());

	SwapChainImageFormat = SurfaceFormat.format;
	SwapChainExtent = Extent;
}

VkImageView
VulkanSwapChain::CreateImageView(VkImage Image, VkFormat Format, VkImageAspectFlags AspectFlags)
{
	VkImageViewCreateInfo ViewInfo{};
	ViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	ViewInfo.image = Image;

	ViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
	ViewInfo.format = Format;
	ViewInfo.subresourceRange.aspectMask = AspectFlags;
	ViewInfo.subresourceRange.baseMipLevel = 0;
	ViewInfo.subresourceRange.levelCount = 1;
	ViewInfo.subresourceRange.baseArrayLayer = 0;
	ViewInfo.subresourceRange.layerCount = 1;

	VkImageView ImageView;
	if (vkCreateImageView(Device, &ViewInfo, nullptr, &ImageView) != VK_SUCCESS)
	{
		Log::Error("Failed to create image view!");
	}
	else
	{
		Log::Info("Successfully created image view.");
	}

	return ImageView;
}

void VulkanSwapChain::CreateImageViews()
{
	SwapChainImageViews.resize(SwapChainImages.size());

	for (size_t i = 0; i < SwapChainImages.size(); i++)
	{
		SwapChainImageViews[i] =
			CreateImageView(SwapChainImages[i], SwapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
	}
}
