/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "core/material.h"
		#include "core/embedded_resource_handler_interface.h"
		#include "metafile/nml.h"
		#include "log/log.h"

		using namespace GS::Core;
		using namespace GS::NML;


//------------------------------------------------------------------------------
bool				Material::FromMetaTag(Tag &tag)
{
	if	(tag.name != "Material")
		__ERR__(__LOG_E__ << "Could not parse material, incorrect root tag (" << tag.name << ").\n", false);

	Reset();

	/*
		Implementation note.
		These strings are declared here once for the whole function
		to benefit from the hash value-based early exit that the String
		comparison function provide.
	*/
	static	String	_diffuse("Diffuse"), _specular("Specular"), _self("Self"), _ambient("Ambient"),
					_glossiness("Glossiness"), _reflection("Reflection"), _ior("IOR"), _ath("AlphaThreshold"),
					_msa("MaxSmoothingAngle"), _opac("Opacity"), _rmask("RenderMask"), _blendop("BlendOp"), _shadermap("ShaderMap"),
					_shd("Shader"), _stage("TextureStage"), _physic("PhysicMaterial"), _depthbias("DepthBias");

	NMLTagForeach(pt, tag)
	{
				if	(pt->name == _diffuse)		diffuse.FromMetaTag(*pt);
		else	if	(pt->name == _specular)		specular.FromMetaTag(*pt);
		else	if	(pt->name == _self)			self.FromMetaTag(*pt);
		else	if	(pt->name == _ambient)		ambient.FromMetaTag(*pt);

		else	if	(pt->name == _glossiness)	glossiness = Types::Clamp(pt->GetReal(), 0.01f, 16.f);
		else	if	(pt->name == _reflection)	reflection = pt->GetReal();
		else	if	(pt->name == _ior)			irefraction = pt->GetReal();
		else	if	(pt->name == _msa);	// Legacy
		else	if	(pt->name == _opac)			opacity = pt->GetReal();
		else	if	(pt->name == _ath)			athreshold = pt->GetReal();
		else	if	(pt->name == _depthbias)	depth_bias = pt->GetReal();

		else	if	(pt->name == _shd)			shader = pt->GetString();

		// Physic material.
		else	if	(pt->name == _physic)
			PhysicMaterial::FromMetaTag(*pt);

		// Render word.
		else	if	(pt->name == _rmask)
		{
			static	String	_shdless("Shadeless"), _smooth("Smoothing"), _add("Additive"), _dbs("DoubleSided"), _usefbuffer("UseFramebuffer"), _skn("Skinned"),
							_wire("Wireframe"), _unlit("Unlit"), _vcolor("VertexColor"), _soft("SoftBody"), _sub("Subtractive"), _subs("Substractive"),
							_vnrm("ForceVertexNormal"), _nrmtgt("NormalMapTangent"), _toon("Toon"), _alpha("Alpha"), _atest("AlphaTest"),
							_parr("ParralaxDisp"), _nozw("NoZWrite"), _nozt("NoZTest"), _nofog("NoFog"), _fixdfunc("FixedFunction"), _asoftz("AlphaSoftZ"), _asmap("AlphaInShadow");

			NMLTagForeach(mskt, *pt)
			{
						if	(mskt->name == _unlit)		renderword |= Render_Unlit;
				else	if	(mskt->name == _smooth)		renderword |= Render_Smooth;
				else	if	(mskt->name == _nrmtgt)		renderword |= Render_NormalTangent;
				else	if	(mskt->name == _nofog)		renderword |= Render_NoFog;

				else	if	(mskt->name == _dbs)		renderword |= Render_DoubleSided;
				else	if	(mskt->name == _wire)		renderword |= Render_Wire;
				else	if	(mskt->name == _vcolor)		renderword |= Render_VertexColor;
				else	if	(mskt->name == _parr)		renderword |= Render_ParralaxDisp;
				else	if	(mskt->name == _toon)		renderword |= Render_Toon;

				else	if	(mskt->name == _nozw)		renderword |= Render_NoZWrite;
				else	if	(mskt->name == _nozt)		renderword |= Render_NoZTest;

				else	if	(mskt->name == _asoftz)		renderword |= Render_AlphaSoftZ;
				else	if	(mskt->name == _asmap)		renderword |= Render_AlphaInShadow;
				else	if	(mskt->name == _atest)		renderword |= Render_AlphaTest;

				else	if	(mskt->name == _usefbuffer)	renderword |= Render_UseFramebuffer;
				else	if	(mskt->name == _skn)		renderword |= Render_Skinned;
#if		1		// Legacy
				else	if	(mskt->name == _add)		blendop = Blend_Add;
				else	if	(mskt->name == _alpha)		blendop = Blend_Alpha;
				else	if	(mskt->name == _fixdfunc)	blendop = Blend_Alpha;

				else	if	(mskt->name == "Lit")
						;
#endif

				else	__LOG_W__ << "Unknown tag '" << mskt->name << "' in material '" << name << "'.\n";
			}
		}

		// Blend operator.
		else	if	(pt->name == _blendop)
		{
			String op(pt->GetString());

					if	(op == "Add")
						blendop = Blend_Add;
			else	if	(op == "Alpha")
						blendop = Blend_Alpha;
		}

#if	(__PLATFORM_NINTENDO_WII__ == 0)
		// Shader map.
		else	if	(pt->name == _shadermap)
			IEmbeddedResourceHandler::Get()->ExtractEmbeddedShaderTree(shader, *pt, name);
#endif

		// Texture stage.
		else	if	(pt->name == _stage)
		{
			Tag *idx = pt->GetTag("Index");
			if	(idx && (idx->GetType() == Variant::VariantInteger))
			{
				int n = idx->GetInteger();

				if	((n >= 0) && (n < 8))
				{
					TextureStage *lvl = &texstage[n];

					static String _texture("Texture"), _iuv("UV");
					static String _operator("Operator"), _uvsrc("UVSource");

					// Reset level.
					lvl->channel = Channel_None;
					lvl->op = Operator_Default;
					lvl->uv_index = 0;

					// Fetch channel first.
					Tag *tst = pt->GetTag("Channel");
					lvl->channel = tst ? MaterialChannelFromName(tst->GetString()) : Channel_Diffuse;

					// Pool remaining tags.
					NMLTagForeach(tst, *pt)
					{
						if	(tst->name == _texture)
							lvl->t = tst->GetString();

						else	if	(tst->name == _iuv)
							lvl->uv_index = (uchar)tst->GetInteger();

						else	if	(tst->name == _operator)
						{
							String op(tst->GetString());

									if	(op == "Multiply")
									lvl->op = Operator_Multiply;
							else	if	(op == "Add")
									lvl->op = Operator_Add;
						}

						// Texture level UV source.
						else	if	(tst->name == _uvsrc)
						{
							String fnv(tst->GetString());

									if	(fnv == "UVMap")				lvl->uv_mode = UV_UV;
							else	if	(fnv == "LSNormal")				lvl->uv_mode = UV_LSN;
							else	if	(fnv == "FrontMap")				lvl->uv_mode = UV_FrontMap;
							else	if	(fnv == "SphericalEnvironment")	lvl->uv_mode = UV_SphericalEnvironment;

							else	__LOG_W__ << "Unknown UV source for texture stage " << n << " of material '" << name << "'.\n";
						}
					}
					texstage_count++;
				}
				else	__LOG_W__ << "Illegal texture stage index " << n << " in material '" << name << "'.\n";
			}
			else	__LOG_W__ << "Illegal index tag type in material '" << name << "'.\n";
		}
#if		1	// Legacy
		else	if	(pt->name == "Id")
			;
#endif
		else	__LOG_W__ << "Unknown tag '" << pt->name << "' in <Material>.\n";
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag					*Material::AsMetaTag() const
{
	Tag *material = new Tag("Material");
	if	(!material)
		__ERR__(__LOG_E__ << "Could not serialize material '" << name << "'. Failed to create root tag.\n", NULL)

	// Base properties.
	if	(diffuse != Color::White)
		material->AddChild(diffuse.AsMetaTag("Diffuse", true));
	if	(specular != Color::White)
		material->AddChild(specular.AsMetaTag("Specular"));
	if	(self != Color::Black)
		material->AddChild(self.AsMetaTag("Self"));
	if	(ambient != Color::White)
		material->AddChild(ambient.AsMetaTag("Ambient"));

	if	(glossiness != 0.4f)
		material->AddChild("Glossiness", glossiness);
	if	(opacity != 1)
		material->AddChild("Opacity", opacity);
	if	(reflection != 0)
		material->AddChild("Reflection", reflection);
	if	(irefraction != 1)
		material->AddChild("IOR", irefraction);
	if	(athreshold != 0.1)
		material->AddChild("AlphaThreshold", athreshold);
	if	(depth_bias != 0)
		material->AddChild("DepthBias", depth_bias);

	// Physics.
	material->AddChild(PhysicMaterial::AsMetaTag());

	// Render word.
	if	(renderword != Render_None)
	{
		if	(Tag *rword = material->AddChild("RenderMask"))
		{
			if	(renderword & Render_Unlit)				rword->AddChild("Unlit");
			if	(renderword & Render_Smooth)			rword->AddChild("Smoothing");
			if	(renderword & Render_NormalTangent)		rword->AddChild("NormalMapTangent");
			if	(renderword & Render_NoFog)				rword->AddChild("NoFog");

			if	(renderword & Render_DoubleSided)		rword->AddChild("DoubleSided");
			if	(renderword & Render_Wire)				rword->AddChild("Wireframe");
			if	(renderword & Render_VertexColor)		rword->AddChild("VertexColor");
			if	(renderword & Render_ParralaxDisp)		rword->AddChild("ParralaxDisp");
			if	(renderword & Render_Toon)				rword->AddChild("Toon");

			if	(renderword & Render_NoZWrite)			rword->AddChild("NoZWrite");
			if	(renderword & Render_NoZTest)			rword->AddChild("NoZTest");

			if	(renderword & Render_AlphaSoftZ)		rword->AddChild("AlphaSoftZ");
			if	(renderword & Render_AlphaInShadow)		rword->AddChild("AlphaInShadow");
			if	(renderword & Render_AlphaTest)			rword->AddChild("AlphaTest");

			if	(renderword & Render_UseFramebuffer)	rword->AddChild("UseFramebuffer");
			if	(renderword & Render_Skinned)			rword->AddChild("Skinned");
		}
		else	__LOG_W__ << "Could not serialize material '" << name << "' render word.\n";
	}

	// Blend operator.
	if	(blendop != Blend_None)
		switch (blendop)
		{
			case Blend_Add:		material->AddChild("BlendOp", "Add");		break;
			case Blend_Alpha:	material->AddChild("BlendOp", "Alpha");		break;
		}

	// Shader id.
	if	(!shader.IsEmpty())
		material->AddChild("Shader", shader.c_str());

	// Texture stages.
	for	(int n = 0; n < max_texture_stage; n++)
	{
		const TextureStage *lvl = &texstage[n];

		if	(!lvl->t.IsEmpty())
		{
			if	(Tag *ts = new Tag("TextureStage"))
			{
				material->AddChild(ts);

				ts->AddChild("Index", n);
				ts->AddChild("Texture", lvl->t.c_str());
				ts->AddChild("UV", lvl->uv_index);

				if	(lvl->op != Operator_Default)
					switch (lvl->op)
					{
						case Operator_Add:			ts->AddChild("Operator", "Add");			break;
						case Operator_Multiply:		ts->AddChild("Operator", "Multiply");		break;
					}

				if	(lvl->channel != Channel_Diffuse)
				{
					if	(const char *c = MaterialChannelName(lvl->channel))
						ts->AddChild("Channel", c);
					else
						__LOG_W__ << "Bogus channel in texture stage " << n << " material '" << name << "'.\n";
				}

				if	(lvl->uv_mode != UV_UV)
					switch (lvl->uv_mode)
					{
						case UV_UV:						ts->AddChild("UVSource", "UVMap");					break;
						case UV_LSN:					ts->AddChild("UVSource", "LSNormal");				break;
						case UV_FrontMap:				ts->AddChild("UVSource", "FrontMap");				break;
						case UV_SphericalEnvironment:	ts->AddChild("UVSource", "SphericalEnvironment");	break;

						default:
							__LOG_W__ << "Bogus channel in texture stage " << n << " material '" << name << "'.\n";
							break;
					}
			}
			else	__LOG_W__ << "Could not serialize a texture stage in material '" << name << "'.\n";
		}
	}
	return material;
}
//------------------------------------------------------------------------------
