#include "GEDrawingContext.h"
#include "GEGraphicsContext.h"
#include <glm/common.hpp>
#include <iostream>
#include <vector>

///////////////////////////////////////////////////////////////////////////////////////////
/////                                                                                 /////
/////                               Métodos públicos                                  /////
/////                                                                                 /////
///////////////////////////////////////////////////////////////////////////////////////////
// 
//
// FUNCIÓN: GEDrawingContext::GEDrawingContext(GEGraphicsContext* gc)
//
// PROPÓSITO: Crea el contexto de dibujo (imágenes y cadena de intercambio)
//
GEDrawingContext::GEDrawingContext(GEGraphicsContext* gc, GEWindowPosition wpos)
{
	createSwapChain(gc, wpos);

	std::cout << "Swapchain created! Image count: " << imageCount << std::endl;
}

//
// FUNCIÓN: GEDrawingContext::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los componentes del contexto de dibujo
//
void GEDrawingContext::destroy(GEGraphicsContext* gc)
{
	vkDestroySwapchainKHR(gc->device, swapChain, nullptr);
}

//
// FUNCIÓN: GEDrawingContext::recreate(GEGraphicsContext* gc,  GEWindowPosition wpos)
//
// PROPÓSITO: Reconstruye los componentes del contexto de dibujo
//
void GEDrawingContext::recreate(GEGraphicsContext* gc, GEWindowPosition wpos)
{
	vkDestroySwapchainKHR(gc->device, swapChain, nullptr);
	createSwapChain(gc, wpos);
}

//
// FUNCIÓN: GEDrawingContext::getFormat()
//
// PROPÓSITO: Obtiene el formato de las imágenes
//
VkFormat GEDrawingContext::getFormat()
{
	return imageFormat;
}

//
// FUNCIÓN: GEDrawingContext::getExtent()
//
// PROPÓSITO: Obtiene el tamaño de las imágenes
//
VkExtent2D GEDrawingContext::getExtent()
{
	return imageExtent;
}

//
// FUNCIÓN: GEDrawingContext::getImageCount()
//
// PROPÓSITO: Obtiene el número de imágenes
//
uint32_t GEDrawingContext::getImageCount()
{
	return imageCount;
}

///////////////////////////////////////////////////////////////////////////////////////////
/////                                                                                 /////
/////                     Métodos de creación de los componentes                      /////
/////                                                                                 /////
///////////////////////////////////////////////////////////////////////////////////////////

//
// FUNCIÓN: GEDrawingContext::createSwapChain(GEGraphicsContext* gc, GEWindowPosition wpos)
//
// PROPÓSITO: Crea los buffers de intercambio de imágenes, el vector de imágenes
//            y sus formatos y tamaños
//
void GEDrawingContext::createSwapChain(GEGraphicsContext* gc, GEWindowPosition wpos)
{
	VkSurfaceCapabilitiesKHR capabilities;
	std::vector<VkSurfaceFormatKHR> formats;
	std::vector<VkPresentModeKHR> presentModes;

	vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gc->physicalDevice, gc->surface, &capabilities);

	uint32_t formatCount;
	vkGetPhysicalDeviceSurfaceFormatsKHR(gc->physicalDevice, gc->surface, &formatCount, nullptr);
	if (formatCount != 0)
	{
		formats.resize(formatCount);
		vkGetPhysicalDeviceSurfaceFormatsKHR(gc->physicalDevice, gc->surface, &formatCount, formats.data());
	}

	VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats);
	VkExtent2D extent = chooseSwapExtent(capabilities, wpos);

	imageCount = 4;
	if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount)
	{
		imageCount = capabilities.maxImageCount;
	}

	VkSwapchainCreateInfoKHR createInfo = {};
	createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
	createInfo.surface = gc->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;

	if (gc->graphicsQueueFamilyIndex != gc->presentQueueFamilyIndex)
	{
		std::vector<uint32_t> queueFamilyIndices;
		queueFamilyIndices.resize(2);
		queueFamilyIndices.push_back(gc->graphicsQueueFamilyIndex);
		queueFamilyIndices.push_back(gc->presentQueueFamilyIndex);

		createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
		createInfo.queueFamilyIndexCount = 2;
		createInfo.pQueueFamilyIndices = queueFamilyIndices.data();
	}
	else
	{
		std::vector<uint32_t> queueFamilyIndices;
		queueFamilyIndices.resize(1);
		queueFamilyIndices.push_back(gc->graphicsQueueFamilyIndex);

		createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
		createInfo.queueFamilyIndexCount = 1;
		createInfo.pQueueFamilyIndices = queueFamilyIndices.data();
	}

	createInfo.preTransform = capabilities.currentTransform;
	createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
	createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
	createInfo.clipped = VK_TRUE;

	if (vkCreateSwapchainKHR(gc->device, &createInfo, nullptr, &swapChain) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to create swap chain!");
	}

	vkGetSwapchainImagesKHR(gc->device, swapChain, &imageCount, nullptr);
	images.resize(imageCount);
	vkGetSwapchainImagesKHR(gc->device, swapChain, &imageCount, images.data());

	imageFormat = surfaceFormat.format;
	imageExtent = extent;
}

///////////////////////////////////////////////////////////////////////////////////////////
/////                                                                                 /////
/////                              Métodos auxiliares                                 /////
/////                                                                                 /////
///////////////////////////////////////////////////////////////////////////////////////////


//
// FUNCIÓN: GEDrawingContext::chooseSwapSurfaceFormat()
//
// PROPÓSITO: Escoge el formato de imagen entre los soportados por la superficie
//
VkSurfaceFormatKHR GEDrawingContext::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats)
{
	for (const VkSurfaceFormatKHR& availableFormat : availableFormats)
	{
		if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
		{
			return availableFormat;
		}
	}

	return availableFormats[0];
}

//
// FUNCIÓN: GEDrawingContext::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GEWindowPosition wpos)
//
// PROPÓSITO: Escoge el tamaño de las imágenes asegurando que puede ser soportado por
//            la superficie
//
VkExtent2D GEDrawingContext::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GEWindowPosition wpos)
{
	if (capabilities.currentExtent.width != UINT32_MAX)
	{
		return capabilities.currentExtent;
	}
	else
	{
		VkExtent2D actualExtent = { };
		if (wpos.fullScreen) 
		{
			actualExtent.width = wpos.screenWidth;
			actualExtent.height = wpos.screenHeight;
		}
		else
		{
			actualExtent.width = wpos.width;
			actualExtent.height = wpos.height;
		}


		actualExtent.width = glm::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
		actualExtent.height = glm::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

		return actualExtent;
	}
}