// ::Assets::Constants::Start::

const u32 M3D_FILE = CreateMagicValue('3', 'D', 'M', 'O');
const u32 M3D_PREVIEW = CreateMagicValue('P', 'R', 'V', 'W');
const u32 M3D_HEAD = CreateMagicValue('H', 'E', 'A', 'D');
const u32 M3D_END_CHUNK = CreateMagicValue('O', 'M', 'D', '3');
const u32 M3D_ASSET = CreateMagicValue('A', 'S', 'E', 'T');
const u32 M3D_ACTION = CreateMagicValue('A', 'C', 'T', 'N');
const u32 M3D_COL_MAP = CreateMagicValue('C', 'M', 'A', 'P');
const u32 M3D_TEX_MAP = CreateMagicValue('T', 'M', 'A', 'P');
const u32 M3D_VERT = CreateMagicValue('V', 'R', 'T', 'S');
const u32 M3D_BONE = CreateMagicValue('B', 'O', 'N', 'E');
const u32 M3D_MAT = CreateMagicValue('M', 'T', 'R', 'L');
const u32 M3D_PROCEDURAL = CreateMagicValue('P', 'R', 'O', 'C');
const u32 M3D_MESH = CreateMagicValue('M', 'E', 'S', 'H');
const u32 M3D_VOXEL_TYPE = CreateMagicValue('V', 'O', 'X', 'T');
const u32 M3D_VOXEL_DATA = CreateMagicValue('V', 'O', 'X', 'D');
const u32 M3D_SHAPE = CreateMagicValue('S', 'H', 'P', 'E');
const u32 M3D_LABEL = CreateMagicValue('L', 'B', 'L', 'S');


// ::Assets::Constants::Start::



// ::Assets::Globals::Start::

#ifndef BUILD_PACKER
	u8 ASSET_PACK[] = 
	{	
		#embed "../build/assets.sgp" 
	};
#else
	u8 ASSET_PACK[] = {};
#endif

static FileHeader File_Header = {0};

static AssetFile Asset_Info[ASSET_MAX];
static Asset		 Asset_Data[ASSET_MAX];

static b32 ASSET_HEADER_LOADED = false;


// ::Assets::Global::End::



// ::Assets::Init::Functions::Start::

static void apInit()
{
	MemCpy(&File_Header, ASSET_PACK, sizeof(FileHeader));

	Assert(File_Header.magic_num == CreateMagicValue('s', 't', 'e', 'g'), "Magic value is incorrect");
	Assert(File_Header.version == FILE_VERSION, "Asset file version mismatch");

	MemCpy(Asset_Info, &ASSET_PACK[File_Header.asset_offset], sizeof(AssetFile) * File_Header.asset_counts);

	ASSET_HEADER_LOADED = true;
}

// ::Assets::Init::Functions::End::



// ::Assets::Loading::Functions::Start::

static Asset *
apAssetSearch(u64 hash)
{
	Asset *asset = NULL;

	for (u64 i = 0; i < ASSET_MAX; i++)
	{
		if (asset == NULL && Asset_Data[i].hash == 0)
		{
			asset = Asset_Data + i;
		}

		if (hash == Asset_Data[i].hash)
		{
			asset = Asset_Data + i;
			break;
		}
	}

	return asset;
}

static Asset 
apLoadWithHash(u64 hash)
{
	if (!ASSET_HEADER_LOADED)
	{
		apInit();
	}

	Asset *asset = apAssetSearch(hash);
	AssetFile file_info = {0};

	if (asset->bytes == NULL)
	{
		for (u64 i = 0; i < ASSET_MAX; i++)
		{
			if (hash == Asset_Info[i].hash)
			{
				file_info = Asset_Info[i];
				break;
			}
		}

		if (file_info.hash != 0)
		{
			asset->hash = hash;
			asset->type = file_info.type;

			u8 *bytes = (u8 *)malloc(file_info.len);
			MemCpy(bytes, &ASSET_PACK[file_info.data_offset], file_info.len);

			if (file_info.type == AT_MODEL)
			{
				asset->model = apParseModel(bytes);
				asset->model_meta = file_info.model_meta;
				free(bytes);
			}
			else
			{
				asset->len = file_info.len;
				asset->bytes = bytes;
				asset->texture_meta = file_info.texture_meta;
			}
		}
	}

	return *asset;
}

static Asset
apLoad(c8 *str)
{
	u64 hash = HashFromString(String8CStr(str));
	return apLoadWithHash(hash);
}

static Asset
apLoadS8(String8 str)
{
	return apLoad(str.value);
}

static void
apUnloadWithHash(u64 hash)
{
	Asset *asset = apAssetSearch(hash);

	if (asset->bytes != NULL)
	{
		if (asset->type == AT_MODEL)
		{
			apFreeModel(asset->model);
		}
		else
		{
			free(asset->bytes);
		}

		asset->bytes = NULL;
		asset->type = AT_NONE;
		asset->len = 0;
		asset->hash = 0;
	}
}

static void 
apUnload(c8 *str)
{
	u64 hash = HashFromString(String8CStr(str));
	apUnloadWithHash(hash);
}

static void
apUnloadS8(String8 str)
{
	apUnload(str.value);
}

static u64
apAssetIndex(c8 *str)
{
	return 0;
}

/*
static Asset apLoadTexture(TextureAsset asset_id)
{
	if (!ASSET_HEADER_LOADED)
	{
		apInit();
	}

	Assert(asset_id < i32(TEXTURE_ASSET_MAX), "LoadTextureAsset failure: asset_id is higher than TEXTURE_ASSET_MAX");

	Asset asset = Texture_Asset_Lookup[asset_id];

	if (asset.bytes == NULL)
	{
		AssetFile *asset_info = Texture_Assets + asset_id;
		u8 *img = malloc(asset_info->len);
		MemCpy(img, &ASSET_PACK[asset_info->data_offset], asset_info->len);

		asset.bytes = img;
		asset.len = asset_info->len;

		Texture_Asset_Lookup[asset_id] = asset;
	}

	return asset;
}

static Asset apLoadShader(ShaderAsset asset_id)
{
	if (!ASSET_HEADER_LOADED)
	{
		apInit();
	}

	Assert(asset_id < SHADER_ASSET_MAX, "LoadShaderAsset failure: asset_id is higher than SHADER_ASSET_MAX");

	Asset asset = Shader_Asset_Lookup[asset_id];
	if (asset.bytes == NULL)
	{
		AssetFile *asset_info = Shader_Assets + asset_id;

		asset.bytes = malloc(asset_info->len);
		MemCpy(asset.bytes, &ASSET_PACK[asset_info->data_offset], asset_info->len);
		asset.len = asset_info->len;
		Shader_Asset_Lookup[asset_id] = asset;
	}

	return asset;
}

static Asset apLoadModel(ModelAsset asset_id)
{
	if (!ASSET_HEADER_LOADED)
	{
		apInit();
	}

	Assert(asset_id < MODEL_ASSET_MAX, "apLoadModel failure: asset_id is higher than MODEL_ASSET_MAX");

	Asset asset = Model_Asset_Lookup[asset_id];
	if (asset.bytes == NULL)
	{
		AssetFile *asset_info = Model_Assets + asset_id;

		u8 *bytes = malloc(asset_info->len);
		MemCpy(bytes, &ASSET_PACK[asset_info->data_offset], asset_info->len);
		asset.model = apParseModel(bytes);

		free(bytes);

		Model_Asset_Lookup[asset_id] = asset;
	}

	return asset;
}

static TexMeta apGetTextureMeta(TextureAsset asset_id)
{
	AssetFile *asset_file = Texture_Assets + asset_id;
	return asset_file->texture_meta;
}

static ModelMeta apGetModelMeta(ModelAsset asset_id)
{
	//AssetFile *asset_file = Model_Assets + asset_id;
	//return asset_file->model_meta;

	ModelMeta meta = {0};
	Asset asset = Model_Asset_Lookup[asset_id];
	if (asset.model != NULL)
	{
		meta.i_count = asset.model->index.count;
	}

	return meta;
}

static void apUnloadTexture(TextureAsset asset_id)
{
	Asset *asset = Texture_Asset_Lookup + asset_id;
	if (asset->bytes != NULL)
	{
		free(asset->bytes);
		asset->bytes = NULL;
		asset->len = 0;
	}
}

static void apUnloadShader(ShaderAsset asset_id)
{
	Asset *asset = Shader_Asset_Lookup + asset_id;
	if (asset->bytes != NULL)
	{
		free(asset->bytes);
		asset->bytes = NULL;
		asset->len = 0;
	}
}

static void apUnloadModel(ModelAsset asset_id)
{
	Asset *asset = Model_Asset_Lookup + asset_id;
	if (asset->bytes != NULL)
	{
		free(asset->bytes);
		asset->bytes = NULL;
		asset->len = 0;
	}
}

*/

// ::Assets::Loading::Functions::End::



// ::Assets::Images::Start::

static Image
apLoadImage(u8 *data, u64 len)
{
	Image image = {0};
	image.w = -1;
	image.h = -1;
	image.ch = -1;

	int ch = 4;
	int w, h, has_ch;
	u8 *bytes = stbi_load_from_memory(data, len, &w, &h, &has_ch, ch);
	if (w <= 0 || h <= 0 || has_ch <= 0)
	{
		Printfln("apLoadImage failure: %s", stbi_failure_reason());
	}
	else
	{
		image.data = bytes;
		image.w = w;
		image.h = h;
		image.ch = has_ch;
	}

	return image;
}

static void
apFreeImage(Image *image)
{
	if (image->data != NULL)
	{
		stbi_image_free(image->data);
	}
}

// ::Assets::Images::End::



// ::Assets::Models::Functions::Start::

static inline b32 apChkMag(u32 *magic, u32 expected)
{
	return *magic == expected;
}

// TODO: parse this without the SDK
static Model *
apParseModel(u8 *data)
{
	m3d_t *m3d = m3d_load(data, NULL, NULL, NULL);
	Assert(m3d != NULL, "model returned is NULL");

	Printfln("model: %s", m3d->name);
	Printfln("numcmap: %llu", m3d->numcmap);
	Printfln("numtmap: %llu", m3d->numtmap);
	Printfln("numtexture: %llu", m3d->numtexture);
	Printfln("numbone: %llu", m3d->numbone);
	Printfln("numvertex: %llu", m3d->numvertex);
	Printfln("numskin: %llu", m3d->numskin);
	Printfln("nummaterial: %llu", m3d->nummaterial);
	Printfln("numface: %llu", m3d->numface);
	Printfln("numvoxtype: %llu", m3d->numvoxtype);
	Printfln("numvoxel: %llu", m3d->numvoxel);
	Printfln("numshape: %llu", m3d->numshape);
	Printfln("numlabel: %llu", m3d->numlabel);
	Printfln("numaction: %llu", m3d->numaction);
	Printfln("numinlined: %llu", m3d->numinlined);
	Printfln("numextra: %llu", m3d->numextra);

	Model *model = (Model *)malloc(sizeof(Model));
	
	model->vertex.count = u64(m3d->numvertex);
	model->vertex.data = (Vertex *)malloc(sizeof(Vertex) * model->vertex.count);

	model->index.count = u64(m3d->numface * 3);
	model->index.data = (u32 *)malloc(sizeof(u32) * model->index.count);

	model->material.count = u64(m3d->nummaterial);
	model->material.data = (Material *)malloc(sizeof(Material) * m3d->nummaterial);

	for (u64 i = 0; i < m3d->nummaterial; i += 1)
	{
		for (u64 j = 0; j < m3d->material[i].numprop; j += 1)
		{
			switch (m3d->material[i].prop[j].type)
			{
				case m3dp_Kd:
					{
						U32ColToVec4(&model->material.data[i].color, m3d->material[i].prop[j].value.color);
					} break;
				case m3dp_Ka:
					{
						U32ColToVec4(&model->material.data[i].ambient, m3d->material[i].prop[j].value.color);
					} break;
				case m3dp_Ks: 
					{
						U32ColToVec4(&model->material.data[i].specular, m3d->material[i].prop[j].value.color);
					} break;
				case m3dp_Ke:
					{
						U32ColToVec4(&model->material.data[i].emission, m3d->material[i].prop[j].value.color);
					} break;
				case m3dp_Ns:
					{
						model->material.data[i].shininess = m3d->material[i].prop[j].value.fnum;
					} break;
			}
		}
	}

	for (u64 i = 0; i < m3d->numinlined; i += 1)
	{
		Image texture = apLoadImage(m3d->inlined[i].data, m3d->inlined[i].length);
		Assert(texture.data != NULL, "Image is null");
	}

	/*
	TODO: replace
	for (u64 i = 0; i < m3d->numvertex; i += 1)
	{
		model->vertex.data[i].pos.x = m3d->vertex[i].x;
		model->vertex.data[i].pos.y = m3d->vertex[i].y;
		model->vertex.data[i].pos.z = m3d->vertex[i].z;
		model->vertex.data[i].pos.w = m3d->vertex[i].w;
		
		model->vertex.data[i].col.packed = m3d->vertex[i].color;
	}

	for (u64 i = 0, j = 0; i < m3d->numface; i += 1, j += 3)
	{
		model->index.data[j] 	= m3d->face[i].vertex[0];
		model->index.data[j+1] = m3d->face[i].vertex[1];
		model->index.data[j+2] = m3d->face[i].vertex[2];
	}
	*/

	m3d_free(m3d);

	return model;
}

static void apFreeModel(Model *model)
{
	free(model->vertex.data);
	free(model->index.data);
	free(model);
}

// ::Assets::Models::Functions::End::

