// ::Packer::Includes::CFiles::Start::

#include "packer.h"

#include "xxhash/xxhash.c"
#include "fastlz/fastlz.c"

#include "platform/platform.c"
#include "assets.c"
#include "util.c"
#include "ds.c"
#include "allocators.c"
#include "renderer.c"
#include "game.c"

#ifdef _WIN32
# include <fcntl.h>
#endif

// ::Packer::Includes::CFiles::End::



// ::Packer::Globals::Start::

const FileMapping g_Shader_File_Map[] = {
	{ .file_name = "quad.frag.spv", .ix = QUAD_FRAG_SPIRV_SHADER },
	{ .file_name = "quad.vert.spv", .ix = QUAD_VERT_SPIRV_SHADER },
	{ .file_name = "gui.frag.spv", .ix = GUI_FRAG_SPIRV_SHADER },
	{ .file_name = "gui.vert.spv", .ix = GUI_VERT_SPIRV_SHADER },
};

const FileMapping g_Texture_File_Map[] = {
	{ .file_name = "pattermon.png", .ix = PATTERMON_OBESE },
	{ .file_name = "patamon.png", .ix = PATTERMON_YUKATA }, 
	{ .file_name = "purplemon.png", .ix = PATTERMON_PURPLOID },
	{ .file_name = "hamster.png", .ix = HAMSTER },
	{ .file_name = "ham_smoke.png", .ix = HAMSMOKER },
	{ .file_name = "cheesoid.png", .ix = CHEESOID },
	{ .file_name = "hog.jpg", .ix = HOG },
};

c8 *g_Shader_File_Names[SHADER_ASSET_MAX] = {0};
c8 *g_Texture_File_Names[TEXTURE_ASSET_MAX] = {0};

// ::Packer::Globals::End::



// ::Packer::Files::Functions::Start::

u64 FileLength(FILE *file)
{
	Assert(fseek(file, 0, SEEK_END) == 0, "fseek failure");
	return (u64)ftell(file);
}

u64 WriteData(void *buf, u64 pos, u64 size, FILE *file)
{
	Assert(fseek(file, pos, SEEK_SET) == 0, "WriteData fseek failure");
	u64 written = (u64)FWrite(buf, size, 1, file);
	fflush(file);
	return written;
}

u64 ReadData(void *buf, u64 pos, u64 size, FILE *file)
{
	Assert(fseek(file, pos, SEEK_SET) == 0, "ReadData fseek failure");
	return FRead(buf, size, 1, file);
}

FILE *pOpenFile(c8 *name, c8 *mode)
{
	FILE *file = fopen(name, mode);
	Assert(file != NULL, "pOpenFile: file is null");
	return file;
}

void CloseFile(FILE *file)
{
	fflush(file);
	Assert(fclose(file) != EOF, "Error closing file");
}

// ::Packer::Files::Functions::End::



// ::Packer::Packing::Functions::Start::

void SetArrayLookups()
{
	for (i32 i = 0; i < Len(g_Shader_File_Map); i++)
	{
		FileMapping file_mapping = g_Shader_File_Map[i];
		g_Shader_File_Names[file_mapping.ix] = file_mapping.file_name;
	}

	for (int i = 0; i < SHADER_ASSET_MAX; i++)
	{
		Assert(g_Shader_File_Names[i] != NULL, "Spirv shader file name is null");
	}

	for (i32 i = 0; i < Len(g_Texture_File_Map); i++)
	{
		FileMapping file_mapping = g_Texture_File_Map[i];
		g_Texture_File_Names[file_mapping.ix] = file_mapping.file_name;
	}

	for (int i = 0; i < TEXTURE_ASSET_MAX; i++)
	{
		Assert(g_Texture_File_Names[i] != NULL, "Texture file name is null");
	}
}

i32 WriteHeader(FILE *file, FileHeader *header)
{
	i32 offset = 0;
	
	return offset;
}

void MoveToShaderDir(c8 **return_dir)
{
	if (pDirIsVisible("build"))
	{
		Assert(pDirNavigate("build/shaders/glsl") == 0, "Unable to change to shader directory");
		*return_dir = "../../..";
	}
	else if (pDirIsVisible("shaders"))
	{
		Assert(pDirNavigate("shaders/glsl") == 0 , "Unable to change to shader directory");
		*return_dir = "../..";
	}
	else
		Assert(false, "Unable to find shader directory");
}

void MoveToTextureDir(c8 **return_dir)
{
	if (pDirIsVisible("assets"))
	{
		Assert(pDirNavigate("./assets") == 0, "Unable to change to assets directory");
		*return_dir = "..";
	}
	else if (pDirIsVisible("shaders"))
	{
		Assert(pDirNavigate("../assets") == 0, "Unable to change to assets directory");
		*return_dir = "../build";
	}
	else 
		Assert(false, "Unable to find assets directory");
}

void InitHeader(FileHeader *header)
{
	Assert(header != NULL, "File header is null");

	header->magic_num = CreateMagicValue('s', 't', 'e', 'g');
	header->version = FILE_VERSION;

	header->asset_counts[SHADER_ASSET] = SHADER_ASSET_MAX;
	header->tag_counts[SHADER_ASSET] = 0;

	header->asset_counts[TEXTURE_ASSET] = TEXTURE_ASSET_MAX;
	header->tag_counts[TEXTURE_ASSET] = TEXTURE_ASSET_TAG_MAX;

	header->asset_counts[SOUND_ASSET] = SOUND_ASSET_MAX;
	header->tag_counts[SOUND_ASSET] = SOUND_ASSET_TAG_MAX;

	header->asset_counts[MODEL_ASSET] = MODEL_ASSET_MAX;
	header->tag_counts[MODEL_ASSET] = MODEL_ASSET_TAG_MAX;

	u64 offset = sizeof(FileHeader);

	for (u32 i = 0; i < ASSET_TYPE_MAX; i++)
	{
		if (header->tag_counts[i] > 0)
		{
			header->tag_offsets[i] = offset;
			offset += sizeof(AssetTag) * header->tag_counts[i];
		}
		else
			header->tag_offsets[i] = 0;

		if (header->asset_counts[i] > 0)
		{
			header->asset_offsets[i] = offset;
			offset += sizeof(AssetFile) * header->asset_counts[i];
		}
		else
			header->asset_offsets[i] = 0;
	}
}

void PackFiles(Arena *arena, FileHeader *header)
{
	FILE *file = pOpenFile("assets.sgp", "w+");

	u64 file_pos = WriteData(header, 0, sizeof(FileHeader), file);

	u64 data_offset = 0;
	for (u32 i = 0; i < ASSET_TYPE_MAX; i++)
	{
		u64 tag_offset = header->tag_offsets[i] + (header->tag_counts[i] * sizeof(AssetTag));
		if (tag_offset > data_offset)
			data_offset = tag_offset;

		u64 asset_offset = header->asset_offsets[i] + (header->asset_counts[i] * sizeof(AssetFile));
		if (asset_offset > data_offset)
			data_offset = asset_offset;
	}

	c8 *return_dir = ".";
	u32 file_count;
	MoveToShaderDir(&return_dir);

	AssetFile *shader_assets = MakeArray(arena, AssetFile, SHADER_ASSET_MAX);
	for (u32 i = 0; i < SHADER_ASSET_MAX; i++)
	{
		c8 *asset_name = g_Shader_File_Names[i];

		Printfln("Packing file: %s...", asset_name);

		FILE *asset_file = pOpenFile(asset_name, "r");
		u64 file_size = FileLength(asset_file);

		u8 *file_data = MakeArray(arena, u8, file_size);
		ReadData(file_data, 0, file_size, asset_file);

		u64 prev_offset = data_offset;
		data_offset += WriteData(file_data, data_offset, file_size, file);

		Assert((data_offset - prev_offset) == file_size, "File write size invalid");

		shader_assets[i].data_offset = prev_offset;
		shader_assets[i].len = file_size;

		CloseFile(asset_file);
	}

	Assert(pDirNavigate(return_dir) == 0, "Unable to return to previous directory");
	MoveToTextureDir(&return_dir);

	AssetFile *texture_assets = MakeArray(arena, AssetFile, TEXTURE_ASSET_MAX);
	for (u32 i = 0; i < TEXTURE_ASSET_MAX; i++)
	{
		c8 *asset_name = g_Texture_File_Names[i];

		Printfln("Packing file: %s...", asset_name);

		FILE *asset_file = pOpenFile(asset_name, "r");
		u64 file_size = FileLength(asset_file);

		u8 *file_data = MakeArray(arena, u8, file_size);
		ReadData(file_data, 0, file_size, asset_file);

		int ch = 4;
		int w, h, has_ch;
		u8 *image_bytes = stbi_load_from_memory(file_data, file_size, &w, &h, &has_ch, ch);
		Assert(w > 0 && h > 0 && has_ch > 0, "stbi_load_from_memory failure");

		u64 loaded_length = u64(w * h * ch);

		u64 prev_offset = data_offset;
		data_offset += WriteData(image_bytes, data_offset, loaded_length, file);

		Assert((data_offset - prev_offset) == loaded_length, "File write size invalid");

		texture_assets[i].data_offset = prev_offset;
		texture_assets[i].len = loaded_length;
		texture_assets[i].texture_meta.w = u32(w);
		texture_assets[i].texture_meta.h = u32(h);
		texture_assets[i].texture_meta.ch = u32(ch);

		stbi_image_free(image_bytes);

		CloseFile(asset_file);
	}

	WriteData(shader_assets, header->asset_offsets[SHADER_ASSET], sizeof(AssetFile)*SHADER_ASSET_MAX, file);
	WriteData(texture_assets, header->asset_offsets[TEXTURE_ASSET], sizeof(AssetFile)*TEXTURE_ASSET_MAX, file);

	pDirNavigate(return_dir);
}

// ::Packer::Packing::Functions::End::



// ::Packer::Tests::Functions::Start::

static inline void TestAssetIsCorrect(Arena *arena, c8 *file_name, AssetFile *file_info, AssetType type, FILE *file)
{
	FILE *asset_file = pOpenFile(file_name, "r");
	u64 size = FileLength(asset_file);


	u8 *file_data = MakeArray(arena, u8, size);
	u64 write_count = ReadData(file_data, 0, size, asset_file);
	Assert(write_count == size, "Incorrect asset size retrieved");

	u8 *packed_asset = MakeArray(arena, u8, file_info->len);
	ReadData(packed_asset, file_info->data_offset, file_info->len, file);

	u8 *image_bytes;
	u64 image_length;
	if (type == TEXTURE_ASSET)
	{
		int ch = 4;
		int w, h, has_ch;
		image_bytes = stbi_load_from_memory(file_data, size, &w, &h, &has_ch, ch);
		image_length = u64(w * h * ch);

		Assert(file_info->len == image_length, "file length incorrect");

		for (u64 i = 0; i < image_length; i++)
		{
			Assert(image_bytes[i] == packed_asset[i], "Asset file data is invalid");
		}

		stbi_image_free(image_bytes);
	}
	else 
	{
		Assert(file_info->len == size, "file length incorrect");

		for (u64 i = 0; i < size; i++)
		{
			Assert(file_data[i] == packed_asset[i], "Asset file data is invalid");
		}
	}

	CloseFile(asset_file);
}

void TestAssetPack(Arena *arena)
{
	FILE *file = pOpenFile("assets.sgp", "r");

	FileHeader header;
	i64 offset = FRead(&header, sizeof(FileHeader), 1, file);

	Assert(header.magic_num == CreateMagicValue('s', 't', 'e', 'g'), "Magic number is incorrect");
	Assert(header.version == FILE_VERSION, "File version is incorrect");

	Assert(header.tag_counts[SHADER_ASSET] == 0, "Shader tag count incorrect");
	Assert(header.asset_counts[SHADER_ASSET] == SHADER_ASSET_MAX, "Shader count incorrect");

	Assert(header.tag_counts[TEXTURE_ASSET] == TEXTURE_ASSET_TAG_MAX, "Texture tag count incorrect");
	Assert(header.asset_counts[TEXTURE_ASSET] == TEXTURE_ASSET_MAX, "Texture count incorrect");

	Assert(header.tag_counts[SOUND_ASSET] == SOUND_ASSET_TAG_MAX, "Sound tag count incorrect");
	Assert(header.asset_counts[SOUND_ASSET] == SOUND_ASSET_MAX, "Sound count incorrect");
	
	Assert(header.tag_counts[MODEL_ASSET] == MODEL_ASSET_TAG_MAX, "Model tag count incorrect");
	Assert(header.asset_counts[MODEL_ASSET] == MODEL_ASSET_MAX, "Model count incorrect");

	AssetTag *tags[ASSET_TYPE_MAX];
	AssetFile *files[ASSET_TYPE_MAX];

	for (u32 i = 0; i < ASSET_TYPE_MAX; i++)
	{
		if (header.tag_counts[i] > 0)
		{
			tags[i] = MakeArray(arena, AssetTag, header.tag_counts[i]);
			ReadData(tags[i], header.tag_offsets[i], sizeof(AssetTag)*header.tag_counts[i], file);
		}

		if (header.asset_counts[i] > 0)
		{
			files[i] = MakeArray(arena, AssetFile, header.asset_counts[i]);
			ReadData(files[i], header.asset_offsets[i], sizeof(AssetFile)*header.asset_counts[i], file);
		}
	}

	c8 *return_dir = ".";
	MoveToShaderDir(&return_dir);
	
	for (u32 i = 0; i < SHADER_ASSET_MAX; i++)
	{
		TestAssetIsCorrect(arena, g_Shader_File_Names[i], &files[SHADER_ASSET][i], SHADER_ASSET, file);
	}

	pDirNavigate(return_dir);
	MoveToTextureDir(&return_dir);

	for (u32 i = 0; i < TEXTURE_ASSET_MAX; i++)
	{
		TestAssetIsCorrect(arena, g_Texture_File_Names[i], &files[TEXTURE_ASSET][i], TEXTURE_ASSET, file);
	}
}

// ::Packer::Tests::Functions::End::



// ::Packer::Main::Functions::Start::

int main(int argc, c8 **argv)
{
	#ifdef _WIN32
	{
		_set_fmode(_O_BINARY);
	}
	#endif

	SetArrayLookups();

	void *mem = pMemAllocZeroed(GB(1));
	Arena *arena = ArenaInitDebug(mem, GB(1), __LINE__);

	FILE *file = fopen("assets.sgp", "w+");
	Assert(file != NULL, "File is null");

	FileHeader header = {0};
	InitHeader(&header);

	PackFiles(arena, &header);
	
	CloseFile(file);

	TestAssetPack(arena);
}

// ::Packer::Main::Functions::End::
