#pragma once

#include <uf/ext/vulkan/vk.h>
#include <uf/ext/vulkan/enums.h>
#include <uf/ext/vulkan/device.h>
#include <uf/ext/vulkan/swapchain.h>
#include <uf/ext/vulkan/rendermodes/base.h>

#include <uf/engine/scene/scene.h>

#if UF_USE_CPPTRACE
#include <cpptrace/cpptrace.hpp>
#define VK_REGISTER_HANDLE( handle ) if ( ext::vulkan::settings::validation::checkpoints ) {\
	VK_VALIDATION_MESSAGE("Registered handle: {}: {}", TYPE_NAME(decltype(handle)), (void*) handle);\
	uf::stl::string id = ::fmt::format("{}: {}", cpptrace::generate_trace().to_string(), TYPE_NAME(decltype(*this)));\
	ext::vulkan::Resource<std::remove_cvref_t<decltype(handle)>>::add( handle, id ); \
}
#define VK_UNREGISTER_HANDLE( handle ) if ( ext::vulkan::settings::validation::checkpoints ) {\
	VK_VALIDATION_MESSAGE("Unregistered handle: {}: {}", TYPE_NAME(decltype(handle)), (void*) handle);\
	ext::vulkan::Resource<std::remove_cvref_t<decltype(handle)>>::remove( handle );\
}
#else
#define VK_REGISTER_HANDLE( X ) {};
#define VK_UNREGISTER_HANDLE( X ) {};
#endif

namespace ext {
	namespace vulkan {
		template<typename T>
		struct Resource {
		public:
			static uf::stl::vector<Resource<T>> handles;

			static void add( T handle, const uf::stl::string& id ) {
				ext::vulkan::Resource<T>::handles.emplace_back( Resource<T>{
					.handle = handle,
					.id = id
				} );
			}
			static void remove( T handle ) {
				auto& v = ext::vulkan::Resource<T>::handles;
				v.erase( std::remove_if( v.begin(), v.end(), [&]( Resource<T>& resource ){
					return handle == resource.handle;
				} ), v.end() );
			}

			T handle;
			uf::stl::string id;
		};

		VkResult CreateDebugUtilsMessengerEXT(
			VkInstance instance,
			const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
			const VkAllocationCallbacks* pAllocator,
			VkDebugUtilsMessengerEXT* pDebugMessenger
		);

		void DestroyDebugUtilsMessengerEXT(
			VkInstance instance,
			VkDebugUtilsMessengerEXT debugMessenger,
			const VkAllocationCallbacks* pAllocator
		);

		VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
			VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
			VkDebugUtilsMessageTypeFlagsEXT messageType,
			const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
			void* pUserData
		);

		extern UF_API PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR;
		extern UF_API PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
		extern UF_API PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
		extern UF_API PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
		extern UF_API PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
		extern UF_API PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
		extern UF_API PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR;
		extern UF_API PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR;
		extern UF_API PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR;
		extern UF_API PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;
		extern UF_API PFN_vkCmdWriteAccelerationStructuresPropertiesKHR vkCmdWriteAccelerationStructuresPropertiesKHR;
		extern UF_API PFN_vkCmdCopyAccelerationStructureKHR vkCmdCopyAccelerationStructureKHR;
		extern UF_API PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV;
		extern UF_API PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV;

		uf::stl::string UF_API retrieveCheckpoint( VkQueue queue );
		uf::stl::string UF_API errorString( VkResult result );
		VkSampleCountFlagBits UF_API sampleCount( uint8_t );

		typedef VmaAllocator Allocator;

		namespace settings {
			constexpr size_t maxViews = 6;

			extern UF_API float version;
			extern UF_API uint32_t width;
			extern UF_API uint32_t height;
			extern UF_API uint8_t msaa;

			extern UF_API size_t viewCount;
			extern UF_API size_t gpuID;
			extern UF_API size_t scratchBufferAlignment;
			extern UF_API size_t scratchBufferInitialSize;
			extern UF_API size_t defaultTimeout;

			extern UF_API VkFilter swapchainUpscaleFilter;

			namespace requested {
				extern UF_API uf::stl::vector<uf::stl::string> deviceFeatures;
				extern UF_API uf::stl::vector<uf::stl::string> deviceExtensions;
				extern UF_API uf::stl::vector<uf::stl::string> instanceExtensions;
				extern UF_API uf::Serializer featureChain;
			}
			

			namespace experimental {
				extern UF_API bool dedicatedThread;
				extern UF_API bool rebuildOnTickBegin;
				extern UF_API bool batchQueueSubmissions;
				extern UF_API bool enableMultiGPU;
				extern UF_API bool memoryBudgetBit;
				extern UF_API bool registerRenderMode;
				extern UF_API bool skipRenderOnRebuild;
			}

			namespace validation {
				extern UF_API bool enabled;
				extern UF_API bool messages;
				extern UF_API bool checkpoints;
				extern UF_API uf::stl::vector<uf::stl::string> filters;
			}

			namespace invariant {
				extern UF_API bool deviceAddressing;

				extern UF_API bool waitOnRenderEnd;
				extern UF_API bool individualPipelines;
				extern UF_API bool multithreadedRecording;

				extern UF_API uf::stl::string deferredMode;
				extern UF_API bool multiview;
			}

			namespace pipelines {
				extern UF_API bool deferred;
				extern UF_API bool vsync;
				extern UF_API bool hdr;
				extern UF_API bool vxgi;
				extern UF_API bool culling;
				extern UF_API bool bloom;
				extern UF_API bool dof;
				extern UF_API bool rt;
				extern UF_API bool postProcess;
				extern UF_API bool fsr;

				namespace names {
					extern UF_API uf::stl::string deferred;
					extern UF_API uf::stl::string vsync;
					extern UF_API uf::stl::string hdr;
					extern UF_API uf::stl::string vxgi;
					extern UF_API uf::stl::string culling;
					extern UF_API uf::stl::string bloom;
					extern UF_API uf::stl::string dof;
					extern UF_API uf::stl::string rt;
					extern UF_API uf::stl::string postProcess;
					extern UF_API uf::stl::string fsr;
				}
			}

			namespace formats {
				extern UF_API VkColorSpaceKHR colorSpace;
				extern UF_API ext::vulkan::enums::Format::type_t color;
				extern UF_API ext::vulkan::enums::Format::type_t depth;
			}
		}
		namespace states {
			extern UF_API bool initialized;
			extern UF_API bool rebuild;
			extern UF_API bool resized;
			extern UF_API uint32_t currentBuffer;
			
			extern UF_API uint32_t frameAccumulate;
			extern UF_API bool frameAccumulateReset;
			extern UF_API uint32_t frameSkip;
		}

		extern UF_API Device device;

		extern UF_API Allocator allocator;
		extern UF_API Swapchain swapchain;
		extern UF_API std::mutex mutex;

	//	extern UF_API RenderMode* currentRenderMode;
		extern UF_API uf::stl::vector<RenderMode*> renderModes;
		extern UF_API uf::stl::unordered_map<uf::stl::string, RenderMode*> renderModesMap;
		extern UF_API uf::ThreadUnique<RenderMode*> currentRenderMode;

		extern UF_API Buffer scratchBuffer;

		bool UF_API hasRenderMode( const uf::stl::string&, bool = true );
		RenderMode& UF_API addRenderMode( RenderMode*, const uf::stl::string& = "" );
		RenderMode& UF_API getRenderMode( const uf::stl::string&, bool = true );
		uf::stl::vector<RenderMode*> UF_API getRenderModes( const uf::stl::string&, bool = true );
		uf::stl::vector<RenderMode*> UF_API getRenderModes( const uf::stl::vector<uf::stl::string>&, bool = true );
		void UF_API removeRenderMode( RenderMode*, bool = true );
		RenderMode* UF_API getCurrentRenderMode();
		RenderMode* UF_API getCurrentRenderMode( std::thread::id );
		void UF_API setCurrentRenderMode( RenderMode* renderMode );
		void UF_API setCurrentRenderMode( RenderMode* renderMode,  std::thread::id );

		void UF_API initialize( bool = false );
		void UF_API tick();
		void UF_API render();
		void UF_API destroy( bool = false );
		
		void UF_API synchronize( uint8_t = 0b11 );
		void UF_API flushCommandBuffers();

		uf::stl::string UF_API allocatorStats();
		ext::vulkan::enums::Format::type_t UF_API formatFromString( const uf::stl::string& );
	}
}

template<typename T> uf::stl::vector<ext::vulkan::Resource<T>> ext::vulkan::Resource<T>::handles;