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

AssetType[string] Lookup = [
	".m3d": AT.ModelM3D,
	".png": AT.Texture,
	".jpg": AT.Texture,
	".spv": AT.Shader,
];

u64 ASSET_COUNT = 0;
string[] FILE_NAMES = [];

/**************************************************
 ****** UPDATE FILE_VERSION AFTER CHANGES !! ******
 **************************************************/

void main()
{
	Log("running");
	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");

	foreach(string file; dirEntries(".", SpanMode.depth))
	{
		if (isDir(file)) continue;

		FILE_NAMES ~= file;
		ASSET_COUNT += 1;
	}

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

	u64 offset = FileHeader.sizeof + (AssetInfo.sizeof * ASSET_COUNT);
	AssetInfo[] asset_info;
	foreach(file; FILE_NAMES)
	{
		AssetType type = AT.None;
		foreach(extension, t; Lookup)
		{
			if (file.endsWith(extension))
			{
				type = t;
				break;
			}
		}

		assert(type != AT.None, "Asset Type is none, offending file " ~ file);

		auto f = File(file, "rb");
		scope(exit) f.close();

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

		string base_name = chompPrefix(file, "./");
		AssetInfo info = {
			hash: Hash(base_name),
			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;

		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(ASSET_COUNT);
	assert(file_header == test_header, "TestFile failure: Header is incorrect");

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

	chdir("../assets");

	u64 asset_index = 0;
	foreach(i, file; FILE_NAMES)
	{
		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");

		string base_name = chompPrefix(file, "./");
		assert(Hash(base_name) == 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");
	}
}

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(u64 asset_count)
{
	FileHeader header = {
		magic: MagicValue("steg"),
		file_version: FILE_VERSION,
		asset_count: asset_count,
		asset_info_offset: FileHeader.sizeof,
	};

	return header;
}

