// ::Vulkan::Renderer::Initialization::Functions::Start::

b32 rInit() 
{
	vArenasInit();
	vCustomizePipelines();

	Assert(vInitInstance(), "Unable to initialize instance");
	Assert(vInstanceFunctionsInit(), "Unable to initialize instance functions");
	
	#ifdef BUILD_DEBUG
	{
		vEnableDebug();
		//vRenderDocInit();
	}
	#endif

	Assert(vSurfaceInit(), "Unable to create surface");
	Assert(vDeviceInit(), "Unable to create device");
	Assert(vVmaAllocatorInit(), "Unable to create VMA allocator");
	Assert(vSwapchainInit(), "Unable to initialize swapchain and draw images");
	Assert(vDrawImagesInit(), "Unable to create draw images");
	Assert(vFrameStructuresInit(), "Unable to create frame structures");
	Assert(vImmediateStructuresInit(), "Unable to create immediate structures");
	Assert(vDescriptorsInit(), "Unable to initialize descriptors.");
	Assert(vPipelinesInit(), "Unable to initialize pipelines.");
	Assert(vBuffersInit(), "Unable to initialize buffers.");

	vUploadQueuesInit();
	vLoaderStartThreads();

	ArenaFree(vFrameArena());

	return true;
}

void rDestroy()
{
	VkDevice device 										= v_Renderer.handles.device;
	VkInstance instance 								= v_Renderer.handles.inst;
	vBufferAllocPtrArray *frame_buffers = v_Renderer.buffers.frame_buffers;
	vImmHandles imm											= v_Renderer.imm;
	VmaAllocator vma_alloc 							= v_Renderer.handles.vma_alloc;
	VkSwapchainKHR swapchain 						= v_Renderer.handles.swapchain;
	VkPipeline *pipelines 							= v_Renderer.handles.pipelines;
	VkPipelineLayout pipeline_layout 		= v_Renderer.handles.pipeline_layout;
	VkDescriptorSetLayout *desc_layouts = v_Renderer.handles.desc_layouts;
	VkDescriptorPool desc_pool 					= v_Renderer.handles.desc_pool;
	vAsync async 												= v_Renderer.async;
	VkSampler nearest_sampler 					= v_Renderer.handles.nearest_sampler;

	vkDeviceWaitIdle(device);

	vkDestroySampler(device, nearest_sampler, NULL);

	for (u32 i = R_PIPELINE_CUBE; i < R_PIPELINE_MAX; i++)
		vkDestroyPipeline(device, pipelines[i], NULL);

	vkDestroyPipelineLayout(device, pipeline_layout, NULL);
	
	for (u32 i = vDT_SHARED; i < vDT_MAX; i++)
		vkDestroyDescriptorSetLayout(device, desc_layouts[i], NULL);

	vkDestroyDescriptorPool(device, desc_pool, NULL);

	vDrawImagesDestroy();

	vSwapchainDestroy();

	vkDestroyFence(device, imm.fence, NULL);
	vkFreeCommandBuffers(device, imm.pool, 1, &imm.buffer);
	vkDestroyCommandPool(device, imm.pool, NULL);

	for (u32 i = 0; i < FRAME_OVERLAP; i++)
	{
		vFrameHandles fh = v_Renderer.frame_handles[i];

		vkDestroySemaphore(device, fh.r_sem, NULL);
		vkDestroySemaphore(device, fh.sc_sem, NULL);
		vkDestroyFence(device, fh.r_fence, NULL);
		vkFreeCommandBuffers(device, fh.pool, 1, &fh.buffer);
		vkDestroyCommandPool(device, fh.pool, NULL);

		for (u32 j = 0; j < frame_buffers[i].length; j++)
			vmaDestroyBuffer(vma_alloc, frame_buffers[i].data[j]->buffer, frame_buffers[i].data[j]->alloc);
	}

	vmaUnmapMemory(vma_alloc, v_Renderer.buffers.transfer.alloc.alloc);
	vmaUnmapMemory(vma_alloc, v_Renderer.buffers.gui_vert.alloc.alloc);
	vmaUnmapMemory(vma_alloc, v_Renderer.buffers.gui_idx.alloc.alloc);

	vmaDestroyBuffer(vma_alloc, v_Renderer.buffers.transfer.alloc.buffer, v_Renderer.buffers.transfer.alloc.alloc);
	vmaDestroyBuffer(vma_alloc, v_Renderer.buffers.gui_vert.alloc.buffer, v_Renderer.buffers.gui_vert.alloc.alloc);
	vmaDestroyBuffer(vma_alloc, v_Renderer.buffers.gui_idx.alloc.buffer, v_Renderer.buffers.gui_idx.alloc.alloc);

	vmaDestroyAllocator(v_Renderer.handles.vma_alloc);
	vkDestroyDevice(v_Renderer.handles.device, NULL);
	vkDestroySurfaceKHR(v_Renderer.handles.inst, v_Renderer.handles.surface, NULL);
	
	#ifdef BUILD_DEBUG
	{
		vkDestroyDebugUtilsMessengerEXT(v_Renderer.handles.inst, v_Renderer.handles.debug, NULL);
	}
	#endif

	vkDestroyInstance(v_Renderer.handles.inst, NULL);

	for (u32 i = 0; i < FRAME_OVERLAP; i++)
	{
		ArenaFree(v_Renderer.mem.frame_arenas[i]);
	}

	ArenaFree(v_Renderer.mem.perm_arena);
}

// ::Vulkan::Renderer::Initialization::Functions::End::



// ::Vulkan::Renderer::Buffers::Functions::Start::

static rDescHandle rTextureLoad(TextureAsset asset_id)
{
	rDescHandle handle = vDescHandleSearch(vDT_SAMPLED_IMAGE, asset_id);

	if (handle.asset_id == UINT32_MAX)
	{
		vImageView *view = FLMemAlloc(sizeof(vImageView));

		Asset asset = apLoadTexture(asset_id);
		TextureAssetMeta meta = apGetTextureMeta(asset_id);

		// TODO: handle errors instead of failing
		Assert(vImageViewInit(view, meta.w, meta.h, meta.ch), "rTextureLoad failure: vImageViewInit failed");

		handle.asset_id = asset_id;
		handle.desc_index = vDescPushImage(view);

		TicketMutLock(&v_Renderer.upload.mut);

		vTransfer *transfer = vTextureTransferInit(vFrameArena(), asset_id, view->image.image, asset.bytes, &meta);
		u32 job_idx = JobQueueAdd(&v_Renderer.upload.job_queue, 1);
		v_Renderer.upload.transfers[job_idx] = transfer;
		vDescPushImageAndHandle(handle, view);

		TicketMutUnlock(&v_Renderer.upload.mut);

		vLoaderWake();
	}

	return handle;
}

static void rTextureUnload(rDescHandle current_handle)
{
	rDescHandle handle = vDescHandleSearch(vDT_SAMPLED_IMAGE, current_handle.asset_id);

	if (handle.asset_id != UINT32_MAX)
	{
		b8 *queue = vFrameTexDestroyQueue();

		queue[current_handle.asset_id] = true;
	}
}

static void rBufferBindVertex(rRenderBuffer *buffer)
{
	Assert(buffer && buffer->type == rRBT_VERTEX, "rBufferBindVertex: invalid buffer provided");

	VkCommandBuffer cmd = vFrameCmdBuf();
	VkDeviceSize offsets = 0;

	vkCmdBindVertexBuffers(cmd, 0, 1, &buffer->buffer, &offsets);
}

static void rBufferBindIndex(rRenderBuffer *buffer)
{
	Assert(buffer && buffer->type == rRBT_INDEX, "rBufferBindIndex: invalid buffer provided");

	VkCommandBuffer cmd = vFrameCmdBuf();

	vkCmdBindIndexBuffer(cmd, buffer->buffer, 0, VK_INDEX_TYPE_UINT32);
}

static void vBufferQueueWait()
{
	while (!JobQueueCompleted(&v_Renderer.upload.job_queue))
	{
		if (v_Renderer.async.sleeping)
			vLoaderWake();
	}
}

static rawptr rBufferGUIVertMapping()
{
	return v_Renderer.buffers.gui_vert.ptr;
}

static rawptr rBufferGUIIdxMapping()
{
	return v_Renderer.buffers.gui_idx.ptr;
}

static void rBufferBindGUIVertex()
{
	VkCommandBuffer cmd = vFrameCmdBuf();
	VkDeviceSize offsets = 0;
	VkBuffer buffer = v_Renderer.buffers.gui_vert.alloc.buffer;

	vkCmdBindVertexBuffers(cmd, 0, 1, &buffer, &offsets);
}

static void rBufferBindGUIIndex()
{
	VkCommandBuffer cmd = vFrameCmdBuf();
	VkBuffer buffer = v_Renderer.buffers.gui_idx.alloc.buffer;

	vkCmdBindIndexBuffer(cmd, buffer, 0, VK_INDEX_TYPE_UINT32);
}

// ::Vulkan::Renderer::Buffers::Functions::End::



// ::Vulkan::Renderer::Uniforms::Functions::Start:: 
// ::Vulkan::Renderer::PushConstants::Functions::Start::

static void rViewportSize(Vec2 *size)
{
	size->x = (f32)v_Renderer.state.swapchain.extent.width;
	size->y = (f32)v_Renderer.state.swapchain.extent.height;
}

static void rGlobalUniformSet(rShaderGlobals *globals)
{
	
}

static void rPushConstantsSet(rPushConst *pc)
{
	VkCommandBuffer cmd = vFrameCmdBuf();

	vkCmdPushConstants(cmd, 
										 v_Renderer.handles.pipeline_layout, 
										 VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT|VK_SHADER_STAGE_COMPUTE_BIT, 
										 0, 
										 sizeof(rPushConst), 
										 pc);
}

// ::Vulkan::Renderer::Uniforms::Functions::End:: 
// ::Vulkan::Renderer::PushConstants::Functions::End::



// ::Vulkan::Renderer::Config::Functions::Start::

static void rResolutionSet(u32 x, u32 y)
{
	v_Renderer.state.renderer.width = x;
	v_Renderer.state.renderer.height = y;
}

// ::Vulkan::Renderer::Config::Functions::End::



// ::Vulkan::Renderer::Rendering::Functions::Start::

b32 rFrameBegin()
{
	b32 success = true;
	VkResult result;


	VkDevice device = v_Renderer.handles.device;
	VkSwapchainKHR swapchain = v_Renderer.handles.swapchain;
	VkCommandBuffer cmd = vFrameCmdBuf();
	VkFence fence = vFrameRenderFence();
	VkSemaphore sc_sem = vFrameSwapSem();
	VkSemaphore rndr_sem = vFrameRenderSem();

#ifdef RDOC_ENABLED
	if (!v_rdoc_captured)
		v_rdoc_api->StartFrameCapture(device, pWindowGet());
#endif

	// TODO(MA): make this work with VK_PRESENT_MODE_MAILBOX_KHR and remove assignment of present mode to FIFO
	result = vkWaitForFences(device, 1, &fence, VK_TRUE, 1000000000);
	if (result != VK_SUCCESS)
	{
		Printf("vkWaitForFences failure: %s", vVkResultStr(result));
		success = false;
	}

	if (success)
	{
		result = vkResetFences(device, 1, &fence);
		if (result != VK_SUCCESS)
		{
			Printf("vkResetFences failure: %s", vVkResultStr(result));
			success = false;
		}
	}

	if (success)
	{
		result = vkAcquireNextImageKHR(device, swapchain, 1000000000, sc_sem, 0, &v_Renderer.state.vk.image_idx);
		if (result == VK_ERROR_OUT_OF_DATE_KHR)
			vSwapchainResize();
		else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
		{
			Printf("vkAcquireNextImageKHR failure: %s", vVkResultStr(result));
			success = false;
		}
	}

	if (success)
	{
		result = vkResetCommandBuffer(cmd, 0);
		if (result != VK_SUCCESS)
		{
			Printf("vkResetCommandBuffer failure: %s", vVkResultStr(result));
			success = false;
		}
	}


	if (success)
	{
		VkCommandBufferBeginInfo cmd_info = {
			.sType = STYPE(COMMAND_BUFFER_BEGIN_INFO),
			.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
		};

		result = vkBeginCommandBuffer(cmd, &cmd_info);
		if (result != VK_SUCCESS)
		{
			Printf("vkBeginCommandBuffer failure: %s", vVkResultStr(result));
			success = false;
		}

		if (success)
			vRenderingBegin();
	}

	return success;
}

b32 rFrameFinish()
{
	b32 success = true;
	VkResult result;

	VkCommandBuffer cmd = vFrameCmdBuf();
	VkSemaphore sc_sem = vFrameSwapSem();
	VkSemaphore rndr_sem = vFrameRenderSem();
	VkFence fence = vFrameRenderFence();
	VkImage curr_img = vFrameImage();

	vkCmdEndRendering(cmd);

	vImageTransition(cmd, &v_Renderer.images.draw.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

	VkExtent2D extent = {
		.width = v_Renderer.state.swapchain.extent.width,
		.height = v_Renderer.state.swapchain.extent.height,
	};

	vImageCopyToImage(cmd, v_Renderer.images.draw.image.image, curr_img, extent, extent);

	vImageTransitionLayout(cmd, curr_img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);

	result = vkEndCommandBuffer(cmd);
	if (result != VK_SUCCESS)
	{
		Printf("vkEndCommandBuffer failure: %s", vVkResultStr(result));
		success = false;
	}

	if (success)
	{
		VkCommandBufferSubmitInfo cmd_info = {
			.sType = STYPE(COMMAND_BUFFER_SUBMIT_INFO),
			.commandBuffer = cmd,
		};

		VkSemaphoreSubmitInfo wait_info = {
			.sType = STYPE(SEMAPHORE_SUBMIT_INFO),
			.semaphore = sc_sem,
			.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
			.value = 1,
		};

		VkSemaphoreSubmitInfo signal_info = {
			.sType = STYPE(SEMAPHORE_SUBMIT_INFO),
			.semaphore = rndr_sem,
			.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
			.value = 1,
		};

		VkSubmitInfo2 submit_info = {
			.sType = STYPE(SUBMIT_INFO_2),
			.waitSemaphoreInfoCount = 1,
			.pWaitSemaphoreInfos = &wait_info,
			.signalSemaphoreInfoCount = 1,
			.pSignalSemaphoreInfos = &signal_info,
			.commandBufferInfoCount = 1,
			.pCommandBufferInfos = &cmd_info,
		};

		result = vkQueueSubmit2(v_Renderer.handles.gfx_queue, 1, &submit_info, fence);
		if (result != VK_SUCCESS)
		{
			Printf("vkQueueSubmit2 failure: %s", vVkResultStr(result));
			success = false;
		}
	}

	if (success)
	{
		VkPresentInfoKHR present_info = {
			.sType = STYPE(PRESENT_INFO_KHR),
			.pSwapchains = &v_Renderer.handles.swapchain,
			.swapchainCount = 1,
			.pWaitSemaphores = &rndr_sem,
			.waitSemaphoreCount = 1,
			.pImageIndices = &v_Renderer.state.vk.image_idx,
		};

		result = vkQueuePresentKHR(v_Renderer.handles.gfx_queue, &present_info);
		if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
			vSwapchainResize();
		else if (result != VK_SUCCESS)
		{
			Printf("vkQueuePresentKHR failure: %s", vVkResultStr(result));
			success = false;
		}
	}

	v_Renderer.state.renderer.frame_count += 1;

#ifdef RDOC_ENABLED
	if (!v_rdoc_captured)
	{
		v_rdoc_api->EndFrameCapture(v_Renderer.handles.device, pWindowGet());
	}
#endif

	return success;
}

static void rDrawIndexed(u32 index_count, u32 instance_count)
{
	VkCommandBuffer cmd = vFrameCmdBuf();

	vkCmdDrawIndexed(cmd, index_count, instance_count, 0, 0, 0);
}

static void rPipelineBind(rPipelineHandle handle, rPipelineType type)
{
	VkCommandBuffer cmd = vFrameCmdBuf();

	vkCmdBindPipeline(cmd, (VkPipelineBindPoint)type, v_Renderer.handles.pipelines[handle]);

	VkViewport viewport = {
		.width = (f32)v_Renderer.state.swapchain.extent.width,
		.height = (f32)v_Renderer.state.swapchain.extent.height,
		.maxDepth = 1.0,
	};

	vkCmdSetViewport(cmd, 0, 1, &viewport);

	VkRect2D scissor = {
		.extent = {
			.width = v_Renderer.state.swapchain.extent.width,
			.height = v_Renderer.state.swapchain.extent.height,
		},
	};

	vkCmdSetScissor(cmd, 0, 1, &scissor);
}

// ::Vulkan::Renderer::Rendering::Functions::End::
