/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "Context.hpp"
#include "Base/Utils.hpp"
#include "Base/Logger.hpp"
#include "Base/IGraphicsApp.hpp"
#include "Base/IGraphicsAppManager.hpp"
#include "Base/EngineConstants.hpp"
#include "Base/UI/IVulkanWindow.hpp"
#include "Debuging/ValidationLayer.hpp"
#if __has_include("vulkan/vulkan_metal.h")
#include <vulkan/vulkan_metal.h>
#endif

namespace OpenVulkano::Vulkan
{
	void Context::Init(IGraphicsAppManager* graphicsAppManager, IVulkanWindow* window)
	{
		if (initialized) throw std::runtime_error("The context is already initialized");
		this->graphicsAppManager = graphicsAppManager;
		this->window = window;

		// Get the extensions required to display on the window
		for (const auto& requiredExtension : window->GetRequiredInstanceExtensions()) { RequireExtension(requiredExtension.c_str()); }

		CreateInstance(); // Create the vulkan instance
		surface = window->CreateSurface(instance); // Create the surface from the window
		CreateDevice();


		swapChain.Init(device.get(), surface, window);
		swapChainRenderPass.Init(device.get(), &swapChain, true, true);

		initialized = true;
	}

	void Context::Close()
	{
		if (!initialized) return;
		device->WaitIdle();

		swapChainRenderPass.Close();
		swapChain.Close();
		device = nullptr;
		deviceManager.Close();
		vkDestroySurfaceKHR(static_cast<VkInstance>(instance), surface, nullptr);
		//TODO

		if (enableValidationLayer) Debug::CloseValidationLayers(instance);

		instance.destroy();
		initialized = false;
	}

	void Context::Resize(const uint32_t newWidth, const uint32_t newHeight)
	{
		device->WaitIdle();
		swapChain.Resize(newWidth, newHeight);
	}

	void Context::CreateInstance()
	{
		if (enableValidationLayer) RequireExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
		vk::ApplicationInfo appInfo(graphicsAppManager->GetGraphicsApp()->GetAppName().c_str(), static_cast<uint32_t>(graphicsAppManager->GetGraphicsApp()->GetAppVersion()), ENGINE_NAME, ENGINE_VERSION.intVersion, VK_MAKE_VERSION(1, 1, 0));
		std::vector<const char*> extensions = Utils::toCString(requiredExtensions), layers = Debug::GetValidationLayers();

		vk::InstanceCreateInfo createInfo(vk::InstanceCreateFlags(), &appInfo, enableValidationLayer ? layers.size() : 0,
												layers.data(), extensions.size(), extensions.data());

#ifdef __APPLE__
		VkExportMetalObjectCreateInfoEXT metalExportInfo { VK_STRUCTURE_TYPE_EXPORT_METAL_OBJECT_CREATE_INFO_EXT, nullptr, VK_EXPORT_METAL_OBJECT_TYPE_METAL_DEVICE_BIT_EXT };
		createInfo.pNext = &metalExportInfo;
#endif
		
		instance = vk::createInstance(createInfo);

		if (enableValidationLayer) Debug::SetupValidationLayers(instance, vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning /*| vk::DebugReportFlagBitsEXT::eInformation | vk::DebugReportFlagBitsEXT::eDebug*/);
		dynamicDispatch.init(instance, &vkGetInstanceProcAddr);
	}

	void Context::CreateDevice()
	{
		const std::vector<std::string> neededExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
		deviceManager.Init(instance);
		device = deviceManager.GetCompatibleDevice(neededExtensions);
		device->PrepareDevice(neededExtensions, surface);
		dynamicDispatch.init(instance, &vkGetInstanceProcAddr, device->device, &vkGetDeviceProcAddr);
		Logger::RENDER->info("Found device: {0}", device->GetDeviceName());
	}
}
