#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>

#include "file_data/spv.c"

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

static const char *_VK_VALIDATION = "VK_LAYER_KHRONOS_validation";

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

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

#if __unix__ && !__linux__
#error Not yet implemented
#endif 

// ::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);
#elif __APPLE__ || __MACH__
#error Not yet implemented
#elif __unix__ && !__linux__
#error Not yet implemented
#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);

#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 (RENDER_BUFFER_TYPE_UNIFORM | RENDER_BUFFER_TYPE_STAGING)

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

typedef enum DescType_e
{
	DESC_TYPE_SHARED,
	DESC_TYPE_COMBINED_SAMPLER,
	DESC_TYPE_STORAGE_IMAGE,
	DESC_TYPE_UNIFORM,

	DESC_TYPE_MAX,
} DescType;

typedef struct ShaderGlobals
{
	Vec2 res;
} ShaderGlobals;

typedef struct RenderBuffer
{
	RenderBufferType  type;
	VkBuffer          buffer;
	VmaAllocation     alloc;
	VmaAllocationInfo info;
	u32               size;
	i32               mem_index; // TODO(MA): use this
} RenderBuffer;

typedef struct MeshBuffer
{
	RenderBuffer index_buf, vertex_buf;
	u32          index_count;
} MeshBuffer;

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

typedef struct DescAssetInfo
{
	DescHandle handle;
	DescType  type;
	union
	{
		u64					 asset_id;
		TextureAsset texture_id;
	};
} DescAssetInfo;

typedef struct DescBindings
{
	u16        *free;
	u16        free_count;
	u16        *used;
	u16        used_count;
	HashTable  lookup_table;
} DescBindings;

typedef struct PipelineStructures
{
	VkPipelineLayout      pipeline_layout;
	VkDescriptorPool      pool;
	VkDescriptorSetLayout layouts[DESC_TYPE_MAX];
	VkDescriptorSet       sets[DESC_TYPE_MAX];
	DescBindings          *bindings;
	u16                   bindings_count;
	VkPipeline            pipelines[PIPELINE_MAX];
} PipelineStructures;

typedef struct PushConst
{
	Vec2 res;
	Vec2 img_res;
} PushConst;

typedef struct FrameStructures
{
	VkCommandPool   *pools;
	VkCommandBuffer *buffers;
	VkSemaphore     *swapchain_sems;
	VkSemaphore     *render_sems;
	VkFence         *render_fences;
	RenderBuffer    **buffer_destroy_queues;
	u32             *buffer_counts;
} FrameStructures;

typedef struct ImmediateStructures
{
	VkCommandPool   *pools;
	VkCommandBuffer *cmds;
	VkFence         *fences;
	RenderBuffer 		**queued_buffers;
	rawptr 					*data;
	i32 volatile 		job_count;
	i32 volatile 		remaining_count;
	TicketMut				mut;
} ImmediateStructures;

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

typedef struct Image
{
	VkImage       img;
	VkImageView   view;
	VmaAllocation alloc;
	VkFormat      fmt;
	VkImageLayout curr_layout;
} Image;

typedef struct SwapchainStructures
{
	VkFormat         format;
	VkColorSpaceKHR  color_space;
	VkPresentModeKHR present_mode;
	VkExtent3D       extent;
	VkImage          *imgs;
	VkImageView      *views;
	u32              img_count;
	Image            draw_img;
	Image            depth_img;
} SwapchainStructures;

typedef struct FrameState
{
	u32            img_ix;
	u64            frame_cnt;
	u64            prev_frame;
	b8             begin_rendering;
	PipelineHandle last_pipeline;
	RenderBuffer   *prev_buffers;
	u32            prev_buffer_count;
} FrameState;

typedef struct Vulkan
{
	Library                  lib;
	VkInstance               inst;
	VkSurfaceKHR             surface;
	VkDevice                 device;
	VkSwapchainKHR           swapchain;
	VkPhysicalDevice         phys_device;
	DeviceQueues             queues;
	VmaAllocator             alloc;
	FrameStructures          frame;
	ImmediateStructures      imm;
	SwapchainStructures      sc;
	PipelineStructures       pipe;
#ifdef BUILD_DEBUG
	VkDebugUtilsMessengerEXT debug;
#endif
} Vulkan;

typedef struct PendingUpdates
{
	u16 render_width;
	u16 render_height;
	b8  resized;
} PendingUpdates;

typedef struct VulkanConfig
{
	u8        avail_threads;
#ifdef __linux__
	pthread_t *threads;
#elif _WIN32
	#error not yet implemented
#endif
} VulkanConfig;

typedef struct Renderer
{
	Vulkan       	 vk;
	VulkanConfig   vk_conf;
	FrameState     frame_state;
	PendingUpdates pending;
	Arena          *arena;
	Arena          *perm_arena;
} Renderer;

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

static b32 VLayersSupported();
static VKAPI_ATTR VkBool32 DebugCallback(
	VkDebugUtilsMessageSeverityFlagBitsEXT			message_severity,
	VkDebugUtilsMessageTypeFlagsEXT							message_types,
	const VkDebugUtilsMessengerCallbackDataEXT	*pCallbackData,
	void																				*pUserData
);
const char *VkResultStr(VkResult result);

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

static b32 LoadVulkanLib();
static b32 InitVkInstanceFunctions();
static b32 InitVkGlobalFunctions();
static b32 CreateSurface();
static b32 CreateDevice();
static b32 CheckQueueSurfaceSupport(i32 index, VkPhysicalDevice device, VkSurfaceKHR surface);
static DeviceQueues CheckDeviceQueueSupport(VkPhysicalDevice device, VkSurfaceKHR surface);
static b32 CheckDevicePropertiesSupport(VkPhysicalDevice device, VkSurfaceKHR surface, b32 *discrete);
static b32 CheckDeviceFeatureSupport(VkPhysicalDevice device);
static b32 InitVkDeviceFunctions();
static b32 CreateVmaAllocator();
static b32 CreateFrameStructures();
static b32 CreateImmediateStructures();
static b32 CreateSwapchain();
static b32 CreateDrawImages();
static VkFormat GetImageFormat();
static b32 CreateDescriptors();
static b32 CreatePipelines();
static b32 CreateShaderModule(u8 *bytes, u32 len, VkShaderModule *module);
static void StartVkLoaderThreads();

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

static inline VkCommandBuffer GetFrameCmdBuf();
static inline VkFence *GetFrameRenderFence();
static inline VkSemaphore GetFrameRenderSem();
static inline VkSemaphore GetFrameSwapSem();
static inline u32 GetFrameIndex();
static inline VkImage GetFrameSwapImage();
static inline u32 *GetFrameBufferCount();
static inline RenderBuffer *GetFrameRenderBuffers();
static inline void TransitionImage(VkCommandBuffer cmd, Image *img, VkImageLayout new);
static inline void TransitionImageLayout(VkCommandBuffer cmd, VkImage img, VkImageLayout curr, VkImageLayout new);
static inline void CopyImageToImage(VkCommandBuffer cmd, VkImage src, VkImage dst, VkExtent2D src_ext, VkExtent2D dst_ext);

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

#ifdef __linux__
void *VkLoaderStart(void *thread_data);
#elif _WIN32
#error not yet implemented
#endif

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

static b32 BeginImmSubmit(VkDevice device, VkFence *fence, VkCommandBuffer cmd);
static b32 FinishImmSubmit(VkDevice device, VkFence *fence, VkCommandBuffer cmd, VkQueue queue);

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

static void BeginRendering();

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

static void ResizeSwapchain();

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

static DescAssetInfo *DescriptorTableSearch(DescType type, u64 asset_id);

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

static void DestroySwapchain();
static void DestroyDrawImages();

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

static void VkInfo(const char *str);
static void VkWarn(const char *str);
static void VkError(const char *str);

#include "vulkan_config.c"


