// @Todo:  These should move to draw/core.c
//
internal void G_AssetNameHash(D_Context *draw, Str8 name, U32 id) {
	D_AssetHash *result = M_ArenaPush(draw->arena, D_AssetHash);

	U64 hash  = Str8_Hash(name);
	U64 index = hash & (D_ASSET_HASH_COUNT - 1);

	result->value = name;
	result->hash  = hash;
	result->id    = id;

	SLL_Push(draw->lookup[index], result);
}

void G_ImagesLoad(G_State *game) {
	M_TempScope(0, 0) {
		D_Context *draw = &game->draw;

		Str8 exe_path = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
		Str8 path     = Sf(temp.arena, "%.*s/assets", Sv(exe_path));

		FS_List assets = FS_PathList(temp.arena, path);

		Vk_Buffer *staging = &draw->staging;
		staging->size         = MB(256);
		staging->usage        = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
		staging->host_visible = true;

		Vk_BufferCreate(staging);

		U8 *base   = staging->data;
		U64 offset = 0;

		Vk_CommandBuffer *cmds = Vk_CommandBufferPush();

		// We reserve the first texture for the "white" texture
		draw->n_images = 1;

		for (FS_Entry *it = assets.first; it != 0; it = it->next) {
			if (Str8_EndsWith(it->basename, S("png"))) {
				draw->n_images += 1;
			}
		}

		VkBufferImageCopy copy = { 0 };
		draw->images = M_ArenaPush(game->arena, D_Image, .count = draw->n_images);
		draw->n_images = 1;

		// Upload the white texture
		{
			D_Image *white   = &draw->images[0];
			U32 white_data[] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };

			M_CopySize(base, white_data, sizeof(white_data));

			copy.bufferOffset = offset;
			copy.bufferRowLength = 0;
			copy.bufferImageHeight = 0;

			copy.imageSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
			copy.imageSubresource.mipLevel       = 0;
			copy.imageSubresource.baseArrayLayer = 0;
			copy.imageSubresource.layerCount     = 1;

			copy.imageExtent.width  = 2;
			copy.imageExtent.height = 2;
			copy.imageExtent.depth  = 1;

			base   += sizeof(white_data);
			offset += sizeof(white_data);

			white->name = S("_WHITE");

			// This doesn't _really_ need one because its just zero and always will be zero
			G_AssetNameHash(draw, white->name, 0);

			white->image.width  = 2;
			white->image.height = 2;
			white->image.format = VK_FORMAT_R8G8B8A8_SRGB;
			white->image.usage  = VK_IMAGE_USAGE_SAMPLED_BIT;

			Vk_ImageCreate(&white->image);

			// We could combine all of these 'pre-transfer' and 'post-transfer' layers into one
			// batch, it would simply mean doing three loops over the images and setting them all up
			// and submitting them in one go. It doesn't really matter for now
			//

			VkImageMemoryBarrier2 transfer    = { 0 };
			VkImageMemoryBarrier2 shader_read = { 0 };

			transfer.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
			transfer.srcStageMask  = VK_PIPELINE_STAGE_2_NONE;
			transfer.srcAccessMask = VK_ACCESS_2_NONE;
			transfer.dstStageMask  = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
			transfer.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
			transfer.oldLayout     = VK_IMAGE_LAYOUT_UNDEFINED;
			transfer.newLayout     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
			transfer.image         = white->image.handle;

			transfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			transfer.subresourceRange.layerCount = 1;
			transfer.subresourceRange.levelCount = 1;

			shader_read.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
			shader_read.srcStageMask  = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
			shader_read.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
			shader_read.dstStageMask  = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
			shader_read.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
			shader_read.oldLayout     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
			shader_read.newLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
			shader_read.image         = white->image.handle;

			shader_read.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			shader_read.subresourceRange.layerCount = 1;
			shader_read.subresourceRange.levelCount = 1;

			VkDependencyInfo dep = { 0 };
			dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;

			dep.imageMemoryBarrierCount = 1;
			dep.pImageMemoryBarriers    = &transfer;

			vk.CmdPipelineBarrier2(cmds->handle, &dep);

			vk.CmdCopyBufferToImage(cmds->handle, staging->handle, white->image.handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);

			dep.pImageMemoryBarriers = &shader_read;

			vk.CmdPipelineBarrier2(cmds->handle, &dep);
		}

		// Image upload is sbi_load -> copy to staging -> upload to gpu texture

		for (FS_Entry *it = assets.first; it != 0; it = it->next) {
			if (Str8_EndsWith(it->basename, S("png"))) {
				S32 w, h, c;
				stbi_uc *data = stbi_load((const char *) it->path.data, &w, &h, &c, 4);

				if (data) {
					D_Image *image = &draw->images[draw->n_images];

					U64 image_sz = 4 * w * h;

					M_CopySize(base, data, image_sz);

					copy.bufferOffset      = offset;
					copy.bufferRowLength   = 0;
					copy.bufferImageHeight = 0;

					copy.imageSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
					copy.imageSubresource.mipLevel       = 0;
					copy.imageSubresource.baseArrayLayer = 0;
					copy.imageSubresource.layerCount     = 1;

					copy.imageExtent.width  = w;
					copy.imageExtent.height = h;
					copy.imageExtent.depth  = 1;

					base   += image_sz;
					offset += image_sz;

					Assert(offset <= staging->size);

					image->name = Str8_Copy(game->arena, Str8_RemoveAfterLast(it->basename, '.'));

					G_AssetNameHash(draw, image->name, draw->n_images);

					draw->n_images += 1;
					printf("[Info] :: Loaded %.*s from %.*s\n", Sv(image->name), Sv(it->basename));

					image->image.width  = w;
					image->image.height = h;

					image->image.format = VK_FORMAT_R8G8B8A8_SRGB;
					image->image.usage  = VK_IMAGE_USAGE_SAMPLED_BIT;

					Vk_ImageCreate(&image->image);

					VkImageMemoryBarrier2 transfer = { 0 };
					VkImageMemoryBarrier2 shader_read = { 0 };

					transfer.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
					transfer.srcStageMask  = VK_PIPELINE_STAGE_2_NONE;
					transfer.srcAccessMask = VK_ACCESS_2_NONE;
					transfer.dstStageMask  = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
					transfer.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
					transfer.oldLayout     = VK_IMAGE_LAYOUT_UNDEFINED;
					transfer.newLayout     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
					transfer.image         = image->image.handle;

					transfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
					transfer.subresourceRange.layerCount = 1;
					transfer.subresourceRange.levelCount = 1;

					shader_read.sType         = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
					shader_read.srcStageMask  = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
					shader_read.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
					shader_read.dstStageMask  = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
					shader_read.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
					shader_read.oldLayout     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
					shader_read.newLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
					shader_read.image         = image->image.handle;

					shader_read.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
					shader_read.subresourceRange.layerCount = 1;
					shader_read.subresourceRange.levelCount = 1;

					VkDependencyInfo dep = { 0 };
					dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;

					dep.imageMemoryBarrierCount = 1;
					dep.pImageMemoryBarriers    = &transfer;

					vk.CmdPipelineBarrier2(cmds->handle, &dep);

					vk.CmdCopyBufferToImage(cmds->handle, staging->handle, image->image.handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);

					dep.pImageMemoryBarriers = &shader_read;

					vk.CmdPipelineBarrier2(cmds->handle, &dep);

					stbi_image_free(data);
				}
			}
		}

		Vk_CommandBufferSubmit(cmds, true /* wait */);
	}
}

void G_PipelinesLoad(G_State *game) {
	D_Context *draw = &game->draw;

	draw->pipelines = M_ArenaPush(game->arena, Vk_Pipeline, .count = 1);

	Vk_Pipeline *basic = &draw->pipelines[0];

	VkShaderModule vshader = 0, fshader = 0;
	M_TempScope(0, 0) {
		Str8 exe_path = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
		Str8 vshader_code = FS_ReadEntireFile(temp.arena, Sf(temp.arena, "%.*s/assets/shaders/basic.vert.spv", Sv(exe_path)));
		Str8 fshader_code = FS_ReadEntireFile(temp.arena, Sf(temp.arena, "%.*s/assets/shaders/basic.frag.spv", Sv(exe_path)));

		VkShaderModuleCreateInfo create_info = { 0 };
		create_info.sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
		create_info.pCode    = (U32 *) vshader_code.data;
		create_info.codeSize = vshader_code.count;

		vk.CreateShaderModule(vk.device, &create_info, 0, &vshader);

		create_info.pCode    = (U32 *) fshader_code.data;
		create_info.codeSize = fshader_code.count;

		vk.CreateShaderModule(vk.device, &create_info, 0, &fshader);
	}

	// Create pipeline layout, its insane what you have to do for this because the ALREADY KNOW
	// all of this information
	{
		VkDescriptorSetLayoutBinding bindings[2] = { 0 };

		bindings[0].binding         = 0;
		bindings[0].descriptorType  = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
		bindings[0].descriptorCount = 1;
		bindings[0].stageFlags      = VK_SHADER_STAGE_VERTEX_BIT;

		bindings[1].binding         = 1;
		bindings[1].descriptorType  = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		bindings[1].descriptorCount = game->draw.n_images + game->draw.n_fonts;
		bindings[1].stageFlags      = VK_SHADER_STAGE_FRAGMENT_BIT;

		VkDescriptorSetLayoutCreateInfo set_info = { 0 };
		set_info.sType        = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
		set_info.bindingCount = ArraySize(bindings);
		set_info.pBindings    = bindings;

		vk.CreateDescriptorSetLayout(vk.device, &set_info, 0, &basic->layout.set);

		VkPushConstantRange push_range = { 0 };
		push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
		push_range.offset     = 0;
		push_range.size       = 128;

		VkPipelineLayoutCreateInfo layout_create = { 0 };
		layout_create.sType                  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
		layout_create.setLayoutCount         = 1;
		layout_create.pSetLayouts            = &basic->layout.set;
		layout_create.pushConstantRangeCount = 1;
		layout_create.pPushConstantRanges    = &push_range;

		vk.CreatePipelineLayout(vk.device, &layout_create, 0, &basic->layout.pipeline);
	}

	basic->targets.items[0] = vk.swapchain.format.format;
	basic->targets.count    = 1;

	basic->shaders.count = 2;

	basic->shaders.items[0].stage  = VK_SHADER_STAGE_VERTEX_BIT;
	basic->shaders.items[0].handle = vshader;

	basic->shaders.items[1].stage  = VK_SHADER_STAGE_FRAGMENT_BIT;
	basic->shaders.items[1].handle = fshader;

	Vk_PipelineCreate(basic);
}

void G_AudioLoad(G_State *game) {
	(void) game;

	M_TempScope(0, 0) {
		Str8 exe_path = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
		Str8 path     = Sf(temp.arena, "%.*s/assets", Sv(exe_path));

		FS_List assets = FS_PathList(temp.arena, path);

		// @Incomplete: finish this

		for (FS_Entry *it = assets.first; it != 0; it = it->next) {
		}
	}
}

void G_CalculateCamera(G_Camera *camera, F32 aspect) {
	Mat4x4FInv proj = M4x4F_Perspective(camera->fov, aspect, camera->nearp, camera->farp);
	Mat4x4FInv view = M4x4F_CameraView(camera->x, camera->y, camera->z, camera->p);

	camera->proj.fwd = M4x4F_Mul(proj.fwd, view.fwd);
	camera->proj.inv = M4x4F_Mul(view.inv, proj.inv);
}

V3f G_CameraUnprojectAt(G_Camera *camera, V2f clip, F32 z) {
	V3f dir    = V3f_Sub(camera->p, V3f_Scale(camera->z, z));
	V4f z_dist = V4F(dir.x, dir.y, dir.z, 1.0f);
	V4f persp  = M4x4F_VMul4(camera->proj.fwd, z_dist);

	clip = V2f_Scale(clip, persp.w);

	V4f world = M4x4F_VMul4(camera->proj.inv, V4F(clip.x, clip.y, persp.z, persp.w));

	V3f result = world.xyz;
	return result;
}

V3f G_CameraUnproject(G_Camera *camera, V2f clip) {
	V3f result = G_CameraUnprojectAt(camera, clip, camera->p.z);
	return result;
}

R3f G_CameraBounds(G_Camera *camera) {
	R3f result;
	result.min = G_CameraUnproject(camera, V2F(-1, -1));
	result.max = G_CameraUnproject(camera, V2F( 1,  1));

	return result;
}

#include "impl/aabb.c"
#include "impl/nav.c"
#include "impl/player.c"
