#pragma once

#if __linux__
# define VK_USE_PLATFORM_XCB_KHR
#elif _WIN32
# define VK_USE_PLATFORM_WIN32_KHR
#endif

#define VK_NO_PROTOTYPES

#include <vulkan/vulkan.h>
#ifdef BUILD_DEBUG
# include "renderdoc/renderdoc_app.h"
#endif

// ::Vulkan::Constants::Header::

static const char *_VK_VALIDATION = "VK_LAYER_KHRONOS_validation";

#if __linux__

static char *vulkan_libs[] = {
	"libvulkan.so.1",
	"libvulkan.so"
};

#define RENDERDOC_LIB "librenderdoc.so"

#endif

#if _WIN32

static char *vulkan_libs[] = {
	"vulkan-1.dll",
};

#define RENDERDOC_LIB "renderdoc.dll"

#endif

#define VERTEX_BUFFER_CAP MB(32)
#define INDEX_BUFFER_CAP MB(8)
#define TRANSFER_BUFFER_CAP MB(64)

// ::Vulkan::Macros::Header::

#define VK_DECLARE(fn) static PFN_##fn fn = NULL
#define STYPE(type) VK_STRUCTURE_TYPE_ ## type

#define INIT_FN(name) do {																					\
	name = (PFN_##name)vkGetInstanceProcAddr(NULL, MakeString(name));	\
	if (!name) return false;																					\
} while (0)

#define INIT_INST_FN(name) do {																					\
	name = (PFN_##name)vkGetInstanceProcAddr(instance, MakeString(name));	\
	if (!name) return false;																							\
} while (0)

#define INIT_DEV_FN(name) do {																			\
	name = (PFN_##name)vkGetDeviceProcAddr(device, MakeString(name));	\
	if (!name) return false;																					\
} while (0)

// ::Vulkan::GlobalFunctions::Header::

VK_DECLARE(vkGetInstanceProcAddr);
VK_DECLARE(vkCreateInstance);
VK_DECLARE(vkEnumerateInstanceLayerProperties);

// ::Vulkan::InstanceFunctions::Header::

VK_DECLARE(vkEnumeratePhysicalDevices);
VK_DECLARE(vkCreateDevice);
VK_DECLARE(vkGetPhysicalDeviceQueueFamilyProperties);
VK_DECLARE(vkGetPhysicalDeviceSurfaceSupportKHR);
VK_DECLARE(vkGetPhysicalDeviceProperties);
VK_DECLARE(vkGetPhysicalDeviceFeatures2);
VK_DECLARE(vkEnumerateDeviceExtensionProperties);
VK_DECLARE(vkGetPhysicalDeviceSurfacePresentModesKHR);
VK_DECLARE(vkGetPhysicalDeviceSurfaceFormatsKHR);
VK_DECLARE(vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
VK_DECLARE(vkGetPhysicalDeviceImageFormatProperties);

// ::Vulkan::PlatformFunctions::Header::

#ifdef __linux__
VK_DECLARE(vkCreateXcbSurfaceKHR);
#elif _WIN32
VK_DECLARE(vkCreateWin32SurfaceKHR);
#endif


#ifdef BUILD_DEBUG
VK_DECLARE(vkCreateDebugUtilsMessengerEXT);
VK_DECLARE(vkDestroyDebugUtilsMessengerEXT);
#endif

// ::Vulkan::DeviceFunctions::Header::

VK_DECLARE(vkGetDeviceProcAddr);
VK_DECLARE(vkCreateSwapchainKHR);
VK_DECLARE(vkCreateImage);
VK_DECLARE(vkCreateImageView);
VK_DECLARE(vkGetSwapchainImagesKHR);
VK_DECLARE(vkGetDeviceQueue);
VK_DECLARE(vkCreateSemaphore);
VK_DECLARE(vkAllocateCommandBuffers);
VK_DECLARE(vkCreateCommandPool);
VK_DECLARE(vkCreateFence);
VK_DECLARE(vkCreateDescriptorPool);
VK_DECLARE(vkCreateDescriptorSetLayout);
VK_DECLARE(vkAllocateDescriptorSets);
VK_DECLARE(vkCreatePipelineLayout);
VK_DECLARE(vkResetDescriptorPool);
VK_DECLARE(vkCreateShaderModule);
VK_DECLARE(vkCreateGraphicsPipelines);
VK_DECLARE(vkCreateComputePipelines);
VK_DECLARE(vkUpdateDescriptorSets);
VK_DECLARE(vkDestroyDevice);
VK_DECLARE(vkDestroyInstance);
VK_DECLARE(vkDestroySurfaceKHR);
VK_DECLARE(vkDestroyDescriptorPool);
VK_DECLARE(vkDestroySwapchainKHR);
VK_DECLARE(vkDestroyImage);
VK_DECLARE(vkDestroyImageView);
VK_DECLARE(vkDestroyCommandPool);
VK_DECLARE(vkDestroySemaphore);
VK_DECLARE(vkDestroyFence);
VK_DECLARE(vkDestroyPipelineLayout);
VK_DECLARE(vkDestroyPipeline);
VK_DECLARE(vkWaitForFences);
VK_DECLARE(vkBeginCommandBuffer);
VK_DECLARE(vkEndCommandBuffer);
VK_DECLARE(vkAcquireNextImageKHR);
VK_DECLARE(vkCmdBindPipeline);
VK_DECLARE(vkCmdBindDescriptorSets);
VK_DECLARE(vkCmdDispatch);
VK_DECLARE(vkCmdBeginRendering);
VK_DECLARE(vkCmdEndRendering);
VK_DECLARE(vkCmdSetViewport);
VK_DECLARE(vkCmdSetScissor);
VK_DECLARE(vkCmdPushConstants);
VK_DECLARE(vkCmdBindIndexBuffer);
VK_DECLARE(vkCmdBindVertexBuffers);
VK_DECLARE(vkCmdDrawIndexed);
VK_DECLARE(vkCmdBlitImage2);
VK_DECLARE(vkCmdPipelineBarrier2);
VK_DECLARE(vkCmdCopyBufferToImage);
VK_DECLARE(vkCmdCopyBuffer);
VK_DECLARE(vkQueueSubmit2);
VK_DECLARE(vkResetFences);
VK_DECLARE(vkResetCommandBuffer);
VK_DECLARE(vkFreeCommandBuffers);
VK_DECLARE(vkDestroyDescriptorSetLayout);
VK_DECLARE(vkDestroyShaderModule);
VK_DECLARE(vkQueuePresentKHR);
VK_DECLARE(vkCmdDraw);
VK_DECLARE(vkDeviceWaitIdle);
VK_DECLARE(vkCmdClearColorImage);
VK_DECLARE(vkCreateSampler);
VK_DECLARE(vkDestroySampler);

#include "vma/vk_mem_alloc.h"

// ::Vulkan::Defines::Header::

#define FRAME_OVERLAP 2
#define DESC_MAX_BINDINGS 256
#define BUFFER_QUEUE_LEN 32
#define HOST_VISIBLE_BUFFERS (rRBT_UNIFORM | rRBT_STAGING)

// ::Vulkan::Types::Header::

typedef enum DescType_e
{
	vDT_SHARED,
	vDT_SAMPLED_IMAGE, // DO NOT MOVE FROM POSITION 1 !!
	vDT_MATERIAL,

	vDT_MAX,
} vDescType;

typedef struct rDescHandle
{
	u32 asset_id;
	u32 desc_index;
} rDescHandle;

typedef struct vAssetInfo
{
	rDescHandle handle;
	vDescType  	type;
	union
	{
		u64					 asset_id;
		TextureAsset texture_id;
	};
} vAssetInfo;

typedef struct vDeviceQueues
{
	i32     graphics, transfer; 
	VkQueue graphics_queue, transfer_queue;
	b8 			single_queue;
} vDeviceQueues;

typedef struct vDescBindings
{
	u32        *free;
	u32        free_count;
	HashTable  lookup_table;
} vDescBindings;

typedef struct vImage
{
	VkImage       image;
	VmaAllocation alloc;
	VkFormat      format;
	VkImageLayout layout;
} vImage;

typedef struct vImageView
{
	vImage 			image;
	VkImageView view;
} vImageView;

PtrArrayType(vImageView);
ArrayType(vImageView);
PtrArrayType(b8);

typedef struct vSampler
{
	vImage 			image;
	VkImageView view;
	VkSampler 	sampler;
} vSampler;

typedef struct vBufferAlloc
{
	VkBuffer 			buffer;
	VmaAllocation alloc;
} vBufferAlloc;

PtrArrayType(vBufferAlloc);
ArrayType(vBufferAlloc);

typedef struct vSwapchainState
{
	VkFormat 				 format;
	VkColorSpaceKHR  color_space;
	VkPresentModeKHR present_mode;
	VkExtent3D			 extent;
} vSwapchainState;

typedef struct vVkState
{
	u32 gfx_queue_idx;
	u32 tfer_queue_idx;
	u32 image_idx;
	b8 	single_queue;
} vVkState;

typedef struct vRendererState
{
	u64 						frame_count;
	rPipelineHandle pipeline;
	u32							width;
	u32							height;
} vRendererState;

typedef struct vState
{
	vSwapchainState	swapchain;
	vRendererState 	renderer;
	vVkState				vk;
} vState;

typedef struct vImmHandles
{
	VkCommandPool 	pool;
	VkCommandBuffer buffer;
	VkFence					fence;
} vImmHandles;

ArrayType(vImmHandles);

typedef struct vFrameHandles
{
	VkCommandPool 	pool;
	VkCommandBuffer buffer;
	VkSemaphore 		sc_sem;
	VkSemaphore 		r_sem;
	VkFence 				r_fence;
} vFrameHandles;

typedef struct vAsync
{
	u32			thread_idx;
	b32			sleeping;
	pThread thread;
} vAsync;

typedef struct vRHandles
{
	pLibrary 							lib;
	VkInstance 						inst;
	VkPhysicalDevice 			phys_device;
	VkDevice 							device;
	VkSurfaceKHR 					surface;
	VkSwapchainKHR 				swapchain;
	VmaAllocator 					vma_alloc;
	VkDescriptorPool 			desc_pool;
	VkPipeline 						pipelines[R_PIPELINE_MAX];
	VkPipelineLayout      pipeline_layout;
	VkDescriptorSetLayout desc_layouts[vDT_MAX];
	VkDescriptorSet				desc_sets[vDT_MAX];
	VkQueue 							gfx_queue;
	VkQueue 							tfer_queue;
	VkSampler							nearest_sampler;
#ifdef BUILD_DEBUG
	VkDebugUtilsMessengerEXT debug;
#endif
} vRHandles;

typedef struct vMemory
{
	Arena *perm_arena;
	Arena *frame_arenas[FRAME_OVERLAP];
} vMemory;

typedef struct vRImages
{
	vImageView 			draw;
	vImageView 			depth;
	vImageViewArray sc;
} vRImages;

typedef struct vMappedBuffer
{
	vBufferAlloc alloc;
	rawptr			 ptr;
	u64					 cap;
} vMappedBuffer;

typedef struct vRBuffers
{
	HashTable						 buffers;
	vBufferAllocPtrArray frame_buffers[FRAME_OVERLAP];
	
	HashTable 	 				 images;
	vImageViewPtrArray	 frame_images[FRAME_OVERLAP];
	b8PtrArray					 tex_destroy_queue;

	vMappedBuffer				 transfer;
	vMappedBuffer				 gui_vert;
	vMappedBuffer				 gui_idx;
} vRBuffers;

typedef enum vTransferType : u32
{
	vTT_NONE,
	vTT_BUFFER,
	vTT_IMAGE,
} vTransferType;

typedef struct vTransfer
{
	vTransferType type;
	rawptr 				data;
	u32						asset_id;
	union
	{
		u64 size;
		struct 
		{
			u32 w;
			u32 h;
			u8 	ch;
		};
	};
	union
	{
		VkBuffer 		buffer;
		VkImage  		image;
	};
	union
	{
		VkBufferCopy buffer_copy;
		VkBufferImageCopy image_copy;
	};
} vTransfer;

typedef struct vUploadQueue
{
	vTransfer **transfers;
	TicketMut 	mut;
	JobQueue		job_queue;
} vUploadQueue;

typedef struct vRenderer
{
	vRHandles 				handles;
	vFrameHandles			frame_handles[FRAME_OVERLAP];
	vState						state;
	vRBuffers 				buffers;
	vImmHandles				imm;
	vDescBindings			desc_bindings[vDT_MAX];
	vAsync						async;
	vMemory						mem;
	vRImages					images;
	vUploadQueue			upload;
} vRenderer;

// ::Vulkan::FrontEndTypes::Header::

typedef struct rShaderGlobals
{
	Vec2 res;
} rShaderGlobals;

typedef struct rTextureBuffer
{
	rTextureBufferType type;
	vImage						 image;
	u32								 width;
	u32 							 height;
	u32 							 channels;
	u32								 index;
} rTextureBuffer;

typedef struct rRenderBuffer
{
	rRenderBufferType type;
	VkBuffer          buffer;
	VmaAllocation     alloc;
	VmaAllocationInfo info;
	u32               size;
	u32               index; // TODO(MA): use this
} rRenderBuffer;

typedef struct rPushConst
{
	Vec2 res;
	f32  time;
} rPushConst;

typedef struct rGlobalUniforms
{
	f32 res[2];
} rGlobalUniforms;

// ::Vulkan::Debug::Functions::Header::

static b32 vValidationSupported();
static VKAPI_ATTR VkBool32 vDebugCallback(
	VkDebugUtilsMessageSeverityFlagBitsEXT			message_severity,
	VkDebugUtilsMessageTypeFlagsEXT							message_types,
	const VkDebugUtilsMessengerCallbackDataEXT	*pCallbackData,
	void																				*pUserData
);
const char *vVkResultStr(VkResult result);

// ::Vulkan::Init::Functions::Header::

static b32 vInitInstance();
static void vEnableDebug();
static void vArenasInit();
static b32 vLibraryLoad();
static b32 vInstanceFunctionsInit();
static b32 vGlobalFunctionsInit();
static b32 vDeviceFunctionsInit();
static b32 vSurfaceInit();
static b32 vDeviceInit();
static b32 vQueueCheckSurfaceSupport(i32 index, VkPhysicalDevice device, VkSurfaceKHR surface);
static vDeviceQueues vQueueCheckSupport(VkPhysicalDevice device, VkSurfaceKHR surface);
static b32 vDeviceCheckPropertiesSupport(VkPhysicalDevice device, VkSurfaceKHR surface, b32 *discrete);
static b32 vDeviceCheckFeatureSupport(VkPhysicalDevice device);
static b32 vVmaAllocatorInit();
static b32 vFrameStructuresInit();
static b32 vImmediateStructuresInit();
static b32 vSwapchainInit();
static b32 vDrawImagesInit();
static VkFormat vImageFormatGet();
static b32 vDescriptorsInit();
static b32 vPipelinesInit();
static b32 vShaderModuleInit(u8 *bytes, u32 len, VkShaderModule *module);
static void vUploadQueuesInit();
static void vLoaderStartThreads();
static b32 vBuffersInit();
static b32 vRenderDocInit();

// ::Vulkan::Util::Functions::Header::

static inline VkCommandBuffer vFrameCmdBuf();
static inline VkFence vFrameRenderFence();
static inline VkImage vFrameImage();
static inline Arena *vFrameArena();
static inline VkSemaphore vFrameRenderSem();
static inline VkSemaphore vFrameSwapSem();
static inline u32 vFrameIndex();
static inline VkImage vFrameSwapImage();
static inline b8 *vFrameTexDestroyQueue();
static inline b8 *vFrameNextTexDestroyQueue();
static inline vBufferAllocPtrArray *vFrameBuffers();
static inline void vImageTransition(VkCommandBuffer cmd, vImage *img, VkImageLayout new);
static inline void vImageTransitionLayout(VkCommandBuffer cmd, VkImage img, VkImageLayout curr, VkImageLayout new);
static inline void vImageCopyToImage(VkCommandBuffer cmd, VkImage src, VkImage dst, VkExtent2D src_ext, VkExtent2D dst_ext);

// ::Vulkan::Async::Functions::Header::

static void vBufferQueueWait();
static void vLoaderWake();
static void vLoader();

#ifdef __linux__
	void *vLoaderStart(void *thread_data);
#elif _WIN32
	DWORD WINAPI vLoaderStart(LPVOID thread_data);
#endif

// ::Vulkan::ImmediateSubmit::Functions::Header::

static b32 vImmSubmitBegin(VkDevice device, VkFence fence, VkCommandBuffer cmd);
static b32 vImmSubmitFinish(VkDevice device, VkFence fence, VkCommandBuffer cmd, VkQueue queue);

// ::Vulkan::Rendering::Functions::Header::

static void vRenderingBegin();

// ::Vulkan::Swapchain::Functions::Header::

static void vSwapchainResize();

// ::Vulkan::Images::Functions::Header::

static vTransfer *vTextureTransferInit(Arena *arena, u32 asset_id, VkImage image, u8 *bytes, TextureAssetMeta *meta);
static vImageView *vImageViewCreate(TextureAssetMeta meta);
static b32 vImageViewInit(vImageView *view, u32 width, u32 height, u32 channels);
static void vTextureCleanUp();
static void vImagePush(TextureAsset asset_id, vImageView *view);
static vImageView *vImagePop(TextureAsset asset_id);
static vImageView *vImageSearch(TextureAsset asset_id);

// ::Vulkan::Descriptors::Functions::Header::

static void vDescPushImageAndHandle(rDescHandle handle, vImageView *view);
static void vDescIndexPush(vDescType type, u32 index);
static u32 vDescIndexPop(vDescType type);
static rDescHandle vDescHandleSearch(vDescType type, u32 asset_id);
static void vDescHandlePush(vDescType type, rDescHandle handle);
static rDescHandle vDescHandlePop(vDescType type, u32 asset_id);
static void vDescHandleDelete(vDescType type, u32 asset_id);
static u32 vDescPushImage(vImageView *view);

// ::Vulkan::Buffers::Functions::Header::

static VkResult vBufferCreate(vBufferAlloc* buf, rRenderBufferType type, u64 size);
static rawptr vMapBuffer(VmaAllocation alloc);

// ::Vulkan::CleanUp::Functions::Header::

static void vSwapchainDestroy();
static void vDrawImagesDestroy();

// ::Vulkan::Logging::Functions::Header::

static void vInfo(const char *str);
static void vWarn(const char *str);
static void vError(const char *str);

#include "vulkan_config.c"


