import aliases;
import includes;
import std.stdio;
import std.file;
import util;
import assets;
import assets_codegen;
import std.traits;
import std.algorithm.comparison;

void main()
{
	if (isDir("build"))
	{
		chdir("build");
	}


	PackFile();
	TestFile();
}

void
PackFile()
{
	File ap = File("./assets.sgp", "wb");
	scope(exit) 
	{
		ap.flush();
		ap.close();
		chdir("../build");
	}

	chdir("../assets");

	FileHeader h = InitHeader();
	ap.rawWrite([h]);

	u64 offset = FileHeader.sizeof + (AssetInfo.sizeof * h.asset_count);
	AssetInfo[] asset_info;
	foreach(type; [ EnumMembers!AssetType ])
	{
		auto files = GetAssetFiles(type);
		auto names = GetAssetNames(type);
		auto hashes = GetAssetHashes(type);
	
		foreach(i, file; files)
		{
			auto f = File(file, "rb");
			scope(exit) f.close();

			u64 length = cast(u64)f.size();

			AssetInfo info = {
				hash: hashes[i],
				offset: offset,
				length: length,
				type: type,
			};

			auto data = f.rawRead(new u8[length]);
			assert(length == data.length, "rawRead failure: data length returned doesn't match");

			ap.seek(offset);
			ap.rawWrite(data);

			offset += length;

			if (type == AT.Texture)
			{
				info.texture = GetTexMeta(data);
			}
			
			if (type == AT.Model)
			{
				info.model = GetModelMeta(data);
			}

			asset_info ~= info;
		}
	}

	ap.seek(FileHeader.sizeof);
	ap.rawWrite(asset_info);
}

TexMeta
GetTexMeta(u8[] data)
{
	int ch = 4;
	int width, height, has_ch;
	auto img = stbi_load_from_memory(data.ptr, cast(int)data.length, &width, &height, &has_ch, ch);

	assert(img != null, "stbi_load_from_image failure: image is NULL");
	assert(width > 0 && height > 0 && has_ch > 0, "stbi_load_from_image failure: dimensions are invalid");

	stbi_image_free(img);

	return TexMeta(w: width, h: height, ch: has_ch);
}

ModelMeta
GetModelMeta(u8[] data)
{
	m3d_t* model = m3d_load(data.ptr, null, null, null);
	assert(model != null, "m3d_load failure: model is null");

	u64 index_count = cast(u64)(model.numface * 3);

	m3d_free(model);

	return ModelMeta(index_count: index_count);
}

void
TestFile()
{
	File ap = File("assets.sgp", "rb");
	scope(exit)
	{
		ap.flush();
		ap.close();
		chdir("../build");
	}

	FileHeader file_header = ap.rawRead(new FileHeader[1])[0];
	FileHeader test_header = InitHeader();
	assert(file_header == test_header, "TestFile failure: Header is incorrect");

	AssetInfo[] file_info = ap.rawRead(new AssetInfo[file_header.asset_count]);
	assert(file_info.length == file_header.asset_count, "TestFile failure: Incorrect AssetInfo length returned");

	chdir("../assets");

	u64 asset_index = 0;
	foreach(type; [ EnumMembers!AssetType ])
	{
		auto files = GetAssetFiles(type);
		auto names = GetAssetNames(type);
		auto hashes = GetAssetHashes(type);
	
		foreach(i, file; files)
		{
			scope(exit) asset_index += 1;

			AssetInfo* info = file_info.ptr + asset_index;
			File asset = File(file, "rb");

			u8[] data = asset.rawRead(new u8[asset.size()]);
			assert(data.length == info.length, "TestFile failure: File length read is incorrect");

			assert(hashes[i] == info.hash, "TestFile failure: File hash is incorrect");

			ap.seek(info.offset);
			u8[] pack_data = ap.rawRead(new u8[info.length]);
			assert(equal!((a, b) => a == b)(data[], pack_data[]), "TestFile failure: Asset data does not match file data");

			if (type == AT.Texture)
			{
				assert(info.texture == GetTexMeta(pack_data), "TestFile failure: Texture meta is incorrect");
			}

			if (type == AT.Model)
			{
				assert(info.model == GetModelMeta(pack_data), "TestFile failure: Model meta is incorrect");
			}
		}
	}
}

immutable(string[])
GetAssetFiles(AssetType type)
{
	switch (type)
	{
	case AT.Model: return MODEL_FILES;
	case AT.Shader: return SHADER_FILES;
	case AT.Texture: return TEXTURE_FILES;
	default: return [];
	}
}

immutable(string[])
GetAssetNames(AssetType type)
{
	switch (type)
	{
	case AT.Model: return MODEL_NAMES;
	case AT.Shader: return SHADER_NAMES;
	case AT.Texture: return TEXTURE_NAMES;
	default: return [];
	}
}

immutable(u64[])
GetAssetHashes(AssetType type)
{
	switch (type)
	{
	case AT.Model: return MODEL_HASHES;
	case AT.Shader: return SHADER_HASHES;
	case AT.Texture: return TEXTURE_HASHES;
	default: return [];
	}
}

u32
MagicValue(string str)
{
	assert(str.length == 4, "Magic value must 4 characters");
	return cast(u32)(cast(u32)(str[0] << 24) | cast(u32)(str[1] << 16) | cast(u32)(str[2] << 8) | cast(u32)(str[3] << 0));
}

static FileHeader
InitHeader()
{
	FileHeader header = {
		magic: MagicValue("steg"),
		file_version: FILE_VERSION,
		asset_count: cast(u64)(MODEL_FILES.length + SHADER_FILES.length + TEXTURE_FILES.length),
		asset_info_offset: FileHeader.sizeof,
	};

	return header;
}

