#include "VulkanContext.hpp"

#include "VulkanTexture.hpp"
#include "VulkanBuffers.h"
#include "VulkanDebugManager.h"
// #include "VulkanDepthPass.hpp"
#include "VulkanDeviceManager.h"
#include "VulkanFramebuffers.h"
#include "VulkanInstanceManager.hpp"
#include "VulkanPipeline.h"
#include "VulkanRenderPass.h"
#include "VulkanSwapChain.h"

#include <chrono>
#include <cstdint>
#include <memory>
#include <vector>

#include "imgui_impl_glfw.h"
#include "imgui_impl_vulkan.h"
#include "stb_image.h"

#include <GLFW/glfw3.h>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/fwd.hpp>
#include <glm/trigonometric.hpp>
#include <vulkan/vulkan_core.h>

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "utilities/Logger.h"

VulkanContext::VulkanContext() {}

VulkanContext::~VulkanContext() {}

void VulkanContext::Initialize(
	FVulkanConfig&				 InConfig,
	const std::vector<Vertex>&	 InVertices,
	const std::vector<uint16_t>& InIndices
)
{
	Config = InConfig;

	InstanceManager = std::make_unique<VulkanInstanceManager>();
	DebugManager = std::make_unique<VulkanDebugManager>();

	if (Config.bValidationEnabled)
	{
		InstanceManager->CreateInstance(DebugManager.get());
		DebugManager->Initialize(InstanceManager->GetInstance());
	}
	else
	{
		InstanceManager->CreateInstance();
	}

	CreateSurface(InConfig.Window);

	DeviceManager = std::make_unique<VulkanDeviceManager>(
		InstanceManager->GetInstance(),
		Surface,
		InConfig.Window,
		Config.bValidationEnabled
	);
	DeviceManager->PickPhysicalDevice();
	DeviceManager->CreateLogicalDevice();

	VkDevice		 Device = DeviceManager->GetDevice();
	VkPhysicalDevice PhysicalDevice = DeviceManager->GetPhysicalDevice();
	VkQueue			 GraphicsQueue = DeviceManager->GetGraphicsQueue();

	RenderPass = std::make_unique<VulkanRenderPass>();
	RenderPass->Initialize(Device);

	GraphicsPipeline = std::make_unique<VulkanPipeline>();
	GraphicsPipeline->Initialize(Device);

	auto SwapChainSupport = DeviceManager->QuerySwapChainSupport(PhysicalDevice);
	SwapChain = std::make_unique<VulkanSwapChain>(
		Device,
		Surface,
		Config.Window,
		DeviceManager->GetPhysicalQueueFamilies().GraphicsFamily.value(),
		DeviceManager->GetPhysicalQueueFamilies().PresentFamily.value(),
		SwapChainSupport.Capabilities,
		SwapChainSupport.Formats,
		SwapChainSupport.PresentModes
	);
	SwapChain->CreateSwapChain();
	SwapChain->CreateImageViews();

	Buffers = std::make_unique<VulkanBuffers>();

	Textures = std::make_unique<VulkanTexture>(
		Device,
		PhysicalDevice,
		GraphicsQueue,
		*Buffers,
		*SwapChain
	);

	uint32_t imageCount = SwapChain->GetSwapChainImagesCount();
	MAX_FRAMES_IN_FLIGHT = imageCount;

	RenderPass->CreateRenderPass(SwapChain->GetSwapChainImageFormat(), Textures->FindDepthFormat());

	CreateDescriptorSetLayout();

	GraphicsPipeline->CreateGraphicsPipeline(
		SwapChain->GetSwapChainExtent(),
		RenderPass->GetRenderPass(),
		DescriptorSetLayout
	);

	Buffers->Initialize(
		FBufferConfig(Device, RenderPass->GetRenderPass(), PhysicalDevice, GraphicsQueue)
	);
	Buffers->CreateCommandPool(DeviceManager->GetPhysicalQueueFamilies().GraphicsFamily);

	Textures->CreateDepthResources();

	Framebuffers = std::make_unique<VulkanFramebuffers>();
	Framebuffers->Initialize(FFramebufferConfig(
		Device,
		RenderPass->GetRenderPass(),
		SwapChain->GetSwapChainImageViews(),
		SwapChain->GetSwapChainExtent()
	));
	Framebuffers->CreateFramebuffers(Textures->GetDepthImageView());

	Log::Info("textures");
	Textures->LoadFromFile("textures/texture.jpg");
	Textures->CreateImageView();
	Textures->CreateSampler();

	Buffers->CreateVertexBuffer(InVertices);
	Buffers->CreateIndexBuffer(InIndices);
	Buffers->CreateUniformBuffers(MAX_FRAMES_IN_FLIGHT);

	CreateDescriptorPool();
	CreateDescriptorSets();

	Buffers->CreateCommandBuffers(MAX_FRAMES_IN_FLIGHT);

	CreateSyncObjects();

	VkDescriptorPoolSize PoolSizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
										 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
										 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
										 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
										 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
										 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
										 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
										 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
										 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
										 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
										 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } };

	VkDescriptorPoolCreateInfo PoolInfo = {};
	PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
	PoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
	PoolInfo.maxSets = 1000;
	PoolInfo.poolSizeCount = (uint32_t)std::size(PoolSizes);
	PoolInfo.pPoolSizes = PoolSizes;
	vkCreateDescriptorPool(DeviceManager->GetDevice(), &PoolInfo, nullptr, &ImGuiPool);
}

void VulkanContext::Cleanup()
{
	vkDeviceWaitIdle(DeviceManager->GetDevice());

	if (bImGuiVulkanInitialized)
	{
		ImGui_ImplVulkan_Shutdown();
	}

	if (bImGuiGlfwInitialized)
	{
		ImGui_ImplGlfw_Shutdown();
	}

	Textures->Cleanup();
	Framebuffers->Cleanup();
	SwapChain->Cleanup();

	// vkDestroySampler(DeviceManager->GetDevice(), TextureSampler, nullptr);
	// vkDestroyImageView(DeviceManager->GetDevice(), TextureImageView, nullptr);

	vkDestroyDescriptorPool(DeviceManager->GetDevice(), ImGuiPool, nullptr);
	// vkDestroyImage(DeviceManager->GetDevice(), TextureImage, nullptr);
	// vkFreeMemory(DeviceManager->GetDevice(), TextureImageMemory, nullptr);

	vkDestroyDescriptorPool(DeviceManager->GetDevice(), DescriptorPool, nullptr);
	vkDestroyDescriptorSetLayout(DeviceManager->GetDevice(), DescriptorSetLayout, nullptr);

	Buffers->Cleanup(MAX_FRAMES_IN_FLIGHT);

	GraphicsPipeline->Cleanup();
	RenderPass->Cleanup();

	for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
	{
		vkDestroySemaphore(DeviceManager->GetDevice(), ImageAvailableSemaphores[i], nullptr);
		vkDestroySemaphore(DeviceManager->GetDevice(), RenderFinishedSemaphores[i], nullptr);
		vkDestroyFence(DeviceManager->GetDevice(), InFlightFences[i], nullptr);
	}

	DeviceManager->Cleanup();

	if (Config.bValidationEnabled)
	{
		DebugManager->Cleanup();
	}

	vkDestroySurfaceKHR(InstanceManager->GetInstance(), Surface, nullptr);
	InstanceManager->Cleanup();
}

void VulkanContext::CreateSurface(GLFWwindow* Window)
{
	if (!Window)
	{
		Log::Error("Window not initialized.");
	}

	if (!InstanceManager->GetInstance())
	{
		Log::Error("Instance is null.");
	}

	VkResult result =
		glfwCreateWindowSurface(InstanceManager->GetInstance(), Window, nullptr, &Surface);
	if (result != VK_SUCCESS)
	{
		std::string errorMsg;
		switch (result)
		{
			case VK_ERROR_EXTENSION_NOT_PRESENT:
				errorMsg = "VK_ERROR_EXTENSION_NOT_PRESENT - Required extension not present";
				break;
			case VK_ERROR_INITIALIZATION_FAILED:
				errorMsg = "VK_ERROR_INITIALIZATION_FAILED - Initialization failed";
				break;
			case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
				errorMsg = "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR - Native window already in use";
				break;
			default:
				errorMsg = "Unknown error code: " + std::to_string(result);
				break;
		}

		Log::Error("Failed to create window surface: " + errorMsg);
	}
	else
	{
		Log::Info("Window surface created successfully.");
	}
}

void VulkanContext::CreateDescriptorSetLayout()
{
	VkDescriptorSetLayoutBinding UboLayoutBinding{};
	UboLayoutBinding.binding = 0;
	UboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	UboLayoutBinding.descriptorCount = 1;
	UboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
	UboLayoutBinding.pImmutableSamplers = nullptr;

	VkDescriptorSetLayoutBinding SamplerLayoutBinding{};
	SamplerLayoutBinding.binding = 1;
	SamplerLayoutBinding.descriptorCount = 1;
	SamplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	SamplerLayoutBinding.pImmutableSamplers = nullptr;
	SamplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

	std::array<VkDescriptorSetLayoutBinding, 2> Bindings = { UboLayoutBinding,
															 SamplerLayoutBinding };

	VkDescriptorSetLayoutCreateInfo LayoutInfo{};
	LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	LayoutInfo.bindingCount = static_cast<uint32_t>(Bindings.size());
	LayoutInfo.pBindings = Bindings.data();

	if (vkCreateDescriptorSetLayout(
			DeviceManager->GetDevice(),
			&LayoutInfo,
			nullptr,
			&DescriptorSetLayout
		)
		!= VK_SUCCESS)
	{
		Log::Error("Failed to create descriptor set layout!");
	}
	else
	{
		Log::Info("Successfully created descriptor set layout.");
	}
}

void VulkanContext::CreateDescriptorPool()
{
	std::array<VkDescriptorPoolSize, 2> PoolSizes{};
	PoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	PoolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
	PoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	PoolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

	VkDescriptorPoolCreateInfo PoolInfo{};
	PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
	PoolInfo.poolSizeCount = static_cast<uint32_t>(PoolSizes.size());
	PoolInfo.pPoolSizes = PoolSizes.data();
	PoolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

	if (vkCreateDescriptorPool(DeviceManager->GetDevice(), &PoolInfo, nullptr, &DescriptorPool)
		!= VK_SUCCESS)
	{
		Log::Error("Failed to create descriptor pool!");
	}
	else
	{
		Log::Info("Successfully created descriptor pool.");
	}
}

void VulkanContext::CreateDescriptorSets()
{
	std::vector<VkDescriptorSetLayout> Layouts(MAX_FRAMES_IN_FLIGHT, DescriptorSetLayout);

	VkDescriptorSetAllocateInfo AllocateInfo{};
	AllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
	AllocateInfo.descriptorPool = DescriptorPool;
	AllocateInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
	AllocateInfo.pSetLayouts = Layouts.data();

	DescriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
	if (vkAllocateDescriptorSets(DeviceManager->GetDevice(), &AllocateInfo, DescriptorSets.data())
		!= VK_SUCCESS)
	{
		Log::Error("Failed to allocate descriptor sets!");
	}
	else
	{
		Log::Info("Successfully allocated descriptor sets.");
	}

	for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
	{
		VkDescriptorBufferInfo BufferInfo{};
		BufferInfo.buffer = Buffers->GetUniformBuffers()[i];
		BufferInfo.offset = 0;
		BufferInfo.range = sizeof(UniformBufferObject);

		VkDescriptorImageInfo ImageInfo{};
		ImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
		ImageInfo.imageView = Textures->GetImageView();
		ImageInfo.sampler = Textures->GetSampler();

		std::array<VkWriteDescriptorSet, 2> DescriptorWrites{};
		DescriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		DescriptorWrites[0].dstSet = DescriptorSets[i];
		DescriptorWrites[0].dstBinding = 0;
		DescriptorWrites[0].dstArrayElement = 0;
		DescriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
		DescriptorWrites[0].descriptorCount = 1;
		DescriptorWrites[0].pBufferInfo = &BufferInfo;

		DescriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		DescriptorWrites[1].dstSet = DescriptorSets[i];
		DescriptorWrites[1].dstBinding = 1;
		DescriptorWrites[1].dstArrayElement = 0;
		DescriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		DescriptorWrites[1].descriptorCount = 1;
		DescriptorWrites[1].pImageInfo = &ImageInfo;

		vkUpdateDescriptorSets(
			DeviceManager->GetDevice(),
			static_cast<uint32_t>(DescriptorWrites.size()),
			DescriptorWrites.data(),
			0,
			nullptr
		);
	}
}

// void VulkanContext::CreateImage(
// 	uint32_t			  Width,
// 	uint32_t			  Height,
// 	VkFormat			  Format,
// 	VkImageTiling		  Tiling,
// 	VkImageUsageFlags	  Usage,
// 	VkMemoryPropertyFlags Properties,
// 	VkImage&			  Image,
// 	VkDeviceMemory&		  ImageMemory
// )
// {
//
// 	VkImageCreateInfo ImageInfo{};
// 	ImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
// 	ImageInfo.imageType = VK_IMAGE_TYPE_2D;
// 	ImageInfo.extent.width = Width;
// 	ImageInfo.extent.height = Height;
// 	ImageInfo.extent.depth = 1;
// 	ImageInfo.mipLevels = 1;
// 	ImageInfo.arrayLayers = 1;
// 	ImageInfo.format = Format;
// 	ImageInfo.tiling = Tiling;
// 	ImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
// 	ImageInfo.usage = Usage;
// 	ImageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
// 	ImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
//
// 	if (vkCreateImage(DeviceManager->GetDevice(), &ImageInfo, nullptr, &Image) != VK_SUCCESS)
// 	{
// 		Log::Error("Failed to create image!");
// 	}
// 	else
// 	{
// 		Log::Info("Successfully created image.");
// 	}
//
// 	VkMemoryRequirements MemoryRequirements;
// 	vkGetImageMemoryRequirements(DeviceManager->GetDevice(), Image, &MemoryRequirements);
//
// 	VkMemoryAllocateInfo AllocateInfo{};
// 	AllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
// 	AllocateInfo.allocationSize = MemoryRequirements.size;
// 	AllocateInfo.memoryTypeIndex =
// 		Buffers->FindMemoryType(MemoryRequirements.memoryTypeBits, Properties);
//
// 	if (vkAllocateMemory(DeviceManager->GetDevice(), &AllocateInfo, nullptr, &ImageMemory)
// 		!= VK_SUCCESS)
// 	{
// 		Log::Error("Failed to allocate image memory!");
// 	}
// 	else
// 	{
// 		Log::Info("Successfully allocated imaged memory.");
// 	}
//
// 	vkBindImageMemory(DeviceManager->GetDevice(), Image, ImageMemory, 0);
// }
//
// void VulkanContext::CreateDepthResources()
// {
// 	if (!DepthPass)
// 	{
// 		DepthPass = std::make_unique<VulkanDepthPass>();
// 	}
// 	VkFormat   DepthFormat = DepthPass->FindDepthFormat();
// 	VkExtent2D Extents = SwapChain->GetSwapChainExtent();
//
// 	CreateImage(
// 		Extents.width,
// 		Extents.height,
// 		DepthFormat,
// 		VK_IMAGE_TILING_OPTIMAL,
// 		VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
// 		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
// 		DepthPass->GetDepthImage(),
// 		DepthPass->GetDepthImageMemory()
// 	);
//
// 	DepthPass->SetDepthImageView(SwapChain->CreateImageView(
// 		DepthPass->GetDepthImage(),
// 		DepthFormat,
// 		VK_IMAGE_ASPECT_DEPTH_BIT
// 	));
//
// 	Buffers->TransitionImageLayout(
// 		DepthPass->GetDepthImage(),
// 		DepthFormat,
// 		VK_IMAGE_LAYOUT_UNDEFINED,
// 		VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
// 	);
// }
//
// void VulkanContext::CreateTextureImage(const char* FileName)
// {
// 	int		 TextureWidth, TextureHeight, TextureChannels;
// 	stbi_uc* Pixels =
// 		stbi_load(FileName, &TextureWidth, &TextureHeight, &TextureChannels, STBI_rgb_alpha);
// 	VkDeviceSize ImageSize = TextureWidth * TextureHeight * 4;
//
// 	if (!Pixels)
// 	{
// 		Log::Error("Failed to load texture image!");
// 	}
// 	else
// 	{
// 		Log::Info("Successfully loaded texture image.");
// 	}
//
// 	VkBuffer	   StagingBuffer;
// 	VkDeviceMemory StagingBufferMemory;
//
// 	Buffers->CreateBuffer(
// 		ImageSize,
// 		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
// 		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
// 		StagingBuffer,
// 		StagingBufferMemory
// 	);
//
// 	void* Data;
// 	vkMapMemory(DeviceManager->GetDevice(), StagingBufferMemory, 0, ImageSize, 0, &Data);
// 	memcpy(Data, Pixels, static_cast<size_t>(ImageSize));
// 	vkUnmapMemory(DeviceManager->GetDevice(), StagingBufferMemory);
//
// 	stbi_image_free(Pixels);
//
// 	CreateImage(
// 		TextureWidth,
// 		TextureHeight,
// 		VK_FORMAT_R8G8B8A8_SRGB,
// 		VK_IMAGE_TILING_OPTIMAL,
// 		VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
// 		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
// 		TextureImage,
// 		TextureImageMemory
// 	);
//
// 	Buffers->TransitionImageLayout(
// 		TextureImage,
// 		VK_FORMAT_R8G8B8A8_SRGB,
// 		VK_IMAGE_LAYOUT_UNDEFINED,
// 		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
// 	);
//
// 	Buffers->CopyBufferToImage(
// 		StagingBuffer,
// 		TextureImage,
// 		static_cast<uint32_t>(TextureWidth),
// 		static_cast<uint32_t>(TextureHeight)
// 	);
//
// 	Buffers->TransitionImageLayout(
// 		TextureImage,
// 		VK_FORMAT_R8G8B8A8_SRGB,
// 		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
// 		VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
// 	);
//
// 	vkDestroyBuffer(DeviceManager->GetDevice(), StagingBuffer, nullptr);
// 	vkFreeMemory(DeviceManager->GetDevice(), StagingBufferMemory, nullptr);
// }
//
// void VulkanContext::CreateTextureImageView()
// {
// 	TextureImageView = SwapChain->CreateImageView(
// 		TextureImage,
// 		VK_FORMAT_R8G8B8A8_SRGB,
// 		VK_IMAGE_ASPECT_COLOR_BIT
// 	);
// }

// void VulkanContext::CreateTextureSampler()
// {
// 	VkSamplerCreateInfo SamplerInfo{};
// 	SamplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
// 	SamplerInfo.magFilter = VK_FILTER_LINEAR;
// 	SamplerInfo.minFilter = VK_FILTER_LINEAR;
// 	SamplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
// 	SamplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
// 	SamplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
// 	SamplerInfo.anisotropyEnable = VK_TRUE;
//
// 	VkPhysicalDeviceProperties Properties{};
// 	vkGetPhysicalDeviceProperties(DeviceManager->GetPhysicalDevice(), &Properties);
//
// 	SamplerInfo.maxAnisotropy = Properties.limits.maxSamplerAnisotropy;
// 	SamplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
// 	SamplerInfo.unnormalizedCoordinates = VK_FALSE;
// 	SamplerInfo.compareEnable = VK_FALSE;
// 	SamplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
// 	SamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
// 	SamplerInfo.mipLodBias = 0.0f;
// 	SamplerInfo.minLod = 0.0f;
// 	SamplerInfo.maxLod = 0.0f;
//
// 	if (vkCreateSampler(DeviceManager->GetDevice(), &SamplerInfo, nullptr, &TextureSampler)
// 		!= VK_SUCCESS)
// 	{
// 		Log::Error("Failed to create texture sampler!");
// 	}
// 	else
// 	{
// 		Log::Info("Successfully created texture sampler.");
// 	}
// }

void VulkanContext::CreateSyncObjects()
{
	ImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
	RenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
	InFlightFences.resize(MAX_FRAMES_IN_FLIGHT);

	VkSemaphoreCreateInfo SemaphoreInfo{};
	SemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

	VkFenceCreateInfo FenceInfo{};
	FenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
	FenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

	for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
	{
		if (vkCreateSemaphore(
				DeviceManager->GetDevice(),
				&SemaphoreInfo,
				nullptr,
				&ImageAvailableSemaphores[i]
			) != VK_SUCCESS
			|| vkCreateSemaphore(
				   DeviceManager->GetDevice(),
				   &SemaphoreInfo,
				   nullptr,
				   &RenderFinishedSemaphores[i]
			   ) != VK_SUCCESS
			|| vkCreateFence(DeviceManager->GetDevice(), &FenceInfo, nullptr, &InFlightFences[i])
				!= VK_SUCCESS)
		{
			Log::Error("Failed to create semaphores!");
		}
		else
		{
			Log::Info("Successfully created semaphores");
		}
	}
}

void VulkanContext::DrawFrame(bool bDrawImGui, uint32_t InVerticesSize, uint32_t InIndexSize)
{
	vkWaitForFences(
		DeviceManager->GetDevice(),
		1,
		&InFlightFences[CurrentFrame],
		VK_TRUE,
		UINT64_MAX
	);

	uint32_t ImageIndex;
	VkResult result = vkAcquireNextImageKHR(
		DeviceManager->GetDevice(),
		SwapChain->GetSwapChain(),
		UINT64_MAX,
		ImageAvailableSemaphores[CurrentFrame],
		VK_NULL_HANDLE,
		&ImageIndex
	);

	if (result == VK_ERROR_OUT_OF_DATE_KHR)
	{
		RecreateSwapChain();
		return;
	}
	else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
	{
		Log::Error("Failed to acquire swap chain images!");
	}

	vkResetFences(DeviceManager->GetDevice(), 1, &InFlightFences[CurrentFrame]);

	vkResetCommandBuffer(Buffers->GetCommandBuffer(CurrentFrame), 0);

	FRecordBuffersParams Params{
		Buffers->GetCommandBuffer(CurrentFrame),
		ImageIndex,
		Buffers->GetVertexBuffer(),
		InVerticesSize,
		Buffers->GetIndexBuffer(),
		InIndexSize,
		RenderPass->GetRenderPass(),
		SwapChain->GetSwapChainExtent(),
		GraphicsPipeline->GetGraphicsPipeline(),
		Framebuffers->GetSwapChainFrameBuffers(),
		DescriptorSets[CurrentFrame],
		GraphicsPipeline->GetPipelineLayout(),
		bDrawImGui
		// DrawData
	};

	Buffers->RecordCommandBuffer(Params);

	UpdateUniformBuffer(CurrentFrame);

	VkSubmitInfo SubmitInfo{};
	SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

	VkSemaphore			 WaitSemaphores[] = { ImageAvailableSemaphores[CurrentFrame] };
	VkPipelineStageFlags WaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
	SubmitInfo.waitSemaphoreCount = 1;
	SubmitInfo.pWaitSemaphores = WaitSemaphores;
	SubmitInfo.pWaitDstStageMask = WaitStages;

	SubmitInfo.commandBufferCount = 1;
	VkCommandBuffer CommandBuffer = Buffers->GetCommandBuffer(CurrentFrame);
	SubmitInfo.pCommandBuffers = &CommandBuffer;

	VkSemaphore SignalSemaphores[] = { RenderFinishedSemaphores[CurrentFrame] };
	SubmitInfo.signalSemaphoreCount = 1;
	SubmitInfo.pSignalSemaphores = SignalSemaphores;

	if (vkQueueSubmit(
			DeviceManager->GetGraphicsQueue(),
			1,
			&SubmitInfo,
			InFlightFences[CurrentFrame]
		)
		!= VK_SUCCESS)
	{
		Log::Error("Failed to submit draw command buffer!");
	}

	VkPresentInfoKHR PresentInfo{};
	PresentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
	PresentInfo.waitSemaphoreCount = 1;
	PresentInfo.pWaitSemaphores = SignalSemaphores;

	VkSwapchainKHR SwapChains[] = { SwapChain->GetSwapChain() };

	PresentInfo.swapchainCount = 1;
	PresentInfo.pSwapchains = SwapChains;
	PresentInfo.pImageIndices = &ImageIndex;
	PresentInfo.pResults = nullptr;

	result = vkQueuePresentKHR(DeviceManager->GetPresentQueue(), &PresentInfo);

	if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || bFramebufferResized)
	{
		bFramebufferResized = false;
		RecreateSwapChain();
	}
	else if (result != VK_SUCCESS)
	{
		Log::Error("Failed to present swap chain image!");
	}

	CurrentFrame = (CurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

void VulkanContext::UpdateUniformBuffer(uint32_t CurrentImage)
{
	static auto StartTime = std::chrono::high_resolution_clock::now();

	auto CurrentTime = std::chrono::high_resolution_clock::now();

	float Time =
		std::chrono::duration<float, std::chrono::seconds::period>(CurrentTime - StartTime).count();

	UniformBufferObject Ubo{};
	Ubo.Model =
		glm::rotate(glm::mat4(1.0f), Time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
	Ubo.View = glm::lookAt(
		glm::vec3(2.0f, 2.0f, 2.0f),
		glm::vec3(0.0f, 0.0f, 0.0f),
		glm::vec3(0.0f, 0.0f, 1.0f)
	);
	Ubo.Projection = glm::perspective(
		glm::radians(45.0f),
		SwapChain->GetSwapChainExtent().width / (float)SwapChain->GetSwapChainExtent().height,
		0.1f,
		10.0f
	);
	Ubo.Projection[1][1] *= -1;

	memcpy(Buffers->GetUniformBuffersMapped()[CurrentImage], &Ubo, sizeof(Ubo));
}

void VulkanContext::RecreateSwapChain()
{
	int Width = 0, Height = 0;
	glfwGetFramebufferSize(Config.Window, &Width, &Height);
	while (Width == 0 || Height == 0)
	{
		glfwGetFramebufferSize(Config.Window, &Width, &Height);
		glfwWaitEvents();
	}

	Log::Info("Recreating SwapChain...");
	vkDeviceWaitIdle(DeviceManager->GetDevice());

	CleanupSwapChain();

	auto SwapChainSupport =
		DeviceManager->QuerySwapChainSupport(DeviceManager->GetPhysicalDevice());

	SwapChain->CreateSwapChain();
	SwapChain->CreateImageViews();
	Textures->CreateDepthResources();

	Framebuffers->Initialize(FFramebufferConfig(
		DeviceManager->GetDevice(),
		RenderPass->GetRenderPass(),
		SwapChain->GetSwapChainImageViews(),
		SwapChain->GetSwapChainExtent()
	));
	Framebuffers->CreateFramebuffers(Textures->GetDepthImageView());

	InitImGui();
}

void VulkanContext::InitImGui()
{
	if (!bImGuiGlfwInitialized)
	{
		if (!ImGui_ImplGlfw_InitForVulkan(Config.Window, true))
		{
			Log::Error("Failed to initialize ImGui GLFW backend!");
			return;
		}
		bImGuiGlfwInitialized = true;
	}

	if (bImGuiVulkanInitialized)
	{
		ImGui_ImplVulkan_Shutdown();
		bImGuiVulkanInitialized = false;
	}

	ImGui_ImplVulkan_InitInfo ImGuiInitInfo = {};
	ImGuiInitInfo.Instance = InstanceManager->GetInstance();
	ImGuiInitInfo.PhysicalDevice = DeviceManager->GetPhysicalDevice();
	ImGuiInitInfo.Device = DeviceManager->GetDevice();
	ImGuiInitInfo.QueueFamily =
		DeviceManager->GetPhysicalQueueFamilies().GraphicsFamily.value_or(0);
	ImGuiInitInfo.Queue = DeviceManager->GetGraphicsQueue();
	ImGuiInitInfo.PipelineCache = VK_NULL_HANDLE;
	ImGuiInitInfo.DescriptorPool = ImGuiPool;
	ImGuiInitInfo.MinImageCount = MAX_FRAMES_IN_FLIGHT;
	ImGuiInitInfo.ImageCount = SwapChain->GetSwapChainImageViews().size();
	ImGuiInitInfo.Allocator = nullptr;
	ImGuiInitInfo.PipelineInfoMain.RenderPass = RenderPass->GetRenderPass();
	ImGuiInitInfo.PipelineInfoMain.Subpass = 0;
	ImGuiInitInfo.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
	ImGuiInitInfo.CheckVkResultFn = nullptr;

	if (!ImGui_ImplVulkan_Init(&ImGuiInitInfo))
	{
		Log::Error("Failed to reinitialize ImGui Vulkan backend!");
		return;
	}

	bImGuiVulkanInitialized = true;
}

void VulkanContext::CleanupSwapChain()
{
	Textures->CleanupDepth();
	Framebuffers->Cleanup();
	SwapChain->Cleanup();
}
