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


		#include "core/shader_tree.h"
		#include "core/shader_block.h"
		#include "math/vector_nml.h"
		#include "log/log.h"

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

		/// Id of the NOOP shader block.
		static String __ShaderBlockNoneTagId("None");


//------------------------------------------------------------------------------
Array <pShaderBlock> *ShaderBlock::BlockMapFromMetaTag(Tag &tag)
{
	if	(tag.name != "Map")
		return NULL;

	// Allocate block map.
	Array <pShaderBlock> *block_map = new Array <pShaderBlock> (tag.GetChildCount());
	if	(!block_map)
		__ERR__(__LOG_E__ << "Failed to allocate shader block map.\n", NULL)

	for (uint n = 0; n < block_map->GetCount(); ++n)
		(*block_map)[n] = NULL;

	// Fill the block map.
	uint block_count = 0;

	NMLTagForeach(entry_tag, tag)
		if	(entry_tag->name == "Entry")
		{
			ShaderBlock *_block = NULL;
			Tag *type_tag = entry_tag->GetTag("Type"),
					 *parm_tag = entry_tag->GetTag("Param");

			//----------------------------------------------------
			#define	__InstanciateNewShaderBlock(__VAR__, __TYPE__)\
			__TYPE__ *__VAR__ = new __TYPE__;\
			_block = __VAR__;

			#define	__ValidateShaderBlockParam\
			if	(!parm_tag)\
				break;
			//----------------------------------------------------

			if	(type_tag)
			{
				int type;
				String type_string(type_tag->GetString());

				// Legacy block type support.
				if	((type_string == "Sampler 2D") || (type_string == "Sampler Cube"))
					type_string = "Texture Sampler";
				if	(type_string == "Normal Matrix")
					type_string = "Normal View Matrix";

				// Locate block.
				for (type = ShaderBlock::TypeNone; type < ShaderBlock::TypeInvalid; ++type)
					if	(type_string == ShaderBlock::BlockTypeToString((BlockType)type))
						break;

				switch (type)
				{
					case TypeGeometryUV:
					{
						__InstanciateNewShaderBlock(block, GeometryUVShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Channel;", Variant::VariantInteger))
							block->channel = tag->GetInteger();
					}
					break;

					case TypeTextureSampler:
					{
						__InstanciateNewShaderBlock(block, TextureSamplerShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Type;", Variant::VariantString))
						{
							String sampler_type(tag->GetString());

									if	(sampler_type == "3D")		block->sampler_type = TextureSamplerShaderBlock::Sampler3D;
							else	if	(sampler_type == "Cube")	block->sampler_type = TextureSamplerShaderBlock::SamplerCube;
							else									block->sampler_type = TextureSamplerShaderBlock::Sampler2D;
						}
					}
					break;

					case TypeRenderBuffer:
					{
						__InstanciateNewShaderBlock(block, RenderBufferShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Buffer;", Variant::VariantInteger))
							block->buffer = (RenderBufferShaderBlock::RenderBuffer)tag->GetInteger();
					}
					break;

					case TypeTexture:
					{
						__InstanciateNewShaderBlock(block, TextureShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Texture;", Variant::VariantString))
							block->texture = tag->GetString();

						if	(Tag *tag = parm_tag->GetTypedTag("Type;", Variant::VariantString))
						{
							String texture_type(tag->GetString());

									if	(texture_type == "3D")		block->texture_type = TextureShaderBlock::Texture3D;
							else	if	(texture_type == "Cube")	block->texture_type = TextureShaderBlock::TextureCube;
							else									block->texture_type = TextureShaderBlock::Texture2D;
						}
					}
					break;

					case TypeConstant:
					{
						__InstanciateNewShaderBlock(block, ConstantShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Type;", Variant::VariantInteger))
							block->constant_type = (ShaderInput::DataType)tag->GetInteger();

						Vector4 v(0, 0, 0);
						if	(Tag *tag = parm_tag->GetTag("Value;"))
							v.FromMetaTag(*tag);

						block->constant[0] = v.x;
						block->constant[1] = v.y;
						block->constant[2] = v.z;
						block->constant[3] = v.w;
					}
					break;

					case TypeColor:
					{
						__InstanciateNewShaderBlock(block, ColorShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTag("Color;"))
							block->color.FromMetaTag(*tag);
					}
					break;

					case TypeMaterialParam:
					{
						__InstanciateNewShaderBlock(block, MaterialParamShaderBlock)
						__ValidateShaderBlockParam
						
						if	(Tag *tag = parm_tag->GetTypedTag("Param;", Variant::VariantInteger))
							block->param = (MaterialParamShaderBlock::MaterialParam)tag->GetInteger();
					}
					break;

					case TypeMaterialTexture:
					{
						__InstanciateNewShaderBlock(block, MaterialTextureShaderBlock)
						__ValidateShaderBlockParam

						if	(Tag *tag = parm_tag->GetTypedTag("Slot;", Variant::VariantInteger))
							block->slot = tag->GetInteger();

						if	(Tag *tag = parm_tag->GetTypedTag("Type;", Variant::VariantString))
						{
							String texture_type(tag->GetString());

									if	(texture_type == "3D")		block->texture_type = MaterialTextureShaderBlock::Texture3D;
							else	if	(texture_type == "Cube")	block->texture_type = MaterialTextureShaderBlock::TextureCube;
							else									block->texture_type = MaterialTextureShaderBlock::Texture2D;
						}
					}
					break;

					case TypeSwizzle:
					{
						__InstanciateNewShaderBlock(block, SwizzleShaderBlock)
						__ValidateShaderBlockParam

						const char *swizzle = "nnnn";
						if	(Tag *tag = parm_tag->GetTag("Swizzle;"))
							swizzle = tag->GetString();

						for (int n = 0; n < 4; ++n)
								 if	(swizzle[n] == 'n')	block->swizzle[n] = SwizzleShaderBlock::SwizzleNone;
							else if	(swizzle[n] == 'x')	block->swizzle[n] = SwizzleShaderBlock::SwizzleX;
							else if	(swizzle[n] == 'y')	block->swizzle[n] = SwizzleShaderBlock::SwizzleY;
							else if	(swizzle[n] == 'z')	block->swizzle[n] = SwizzleShaderBlock::SwizzleZ;
							else if	(swizzle[n] == 'w')	block->swizzle[n] = SwizzleShaderBlock::SwizzleW;
							else
							{
								__LOG_E__ << "Unexpected end of swizzle mask.\n";
								break;
							}
					}
					break;

					case TypeBuild:
					{
						__InstanciateNewShaderBlock(block, BuildShaderBlock)
						__ValidateShaderBlockParam

						const char *build = "0000";
						if	(Tag *tag = parm_tag->GetTag("Build;"))
							build = tag->GetString();

						for (int n = 0; n < 4; ++n)
								 if	(build[n] == '0')	block->build[n] = BuildShaderBlock::BuildZero;
							else if	(build[n] == '1')	block->build[n] = BuildShaderBlock::BuildOne;
							else if	(build[n] == 'x')	block->build[n] = BuildShaderBlock::BuildX;
							else if	(build[n] == 'y')	block->build[n] = BuildShaderBlock::BuildY;
							else if	(build[n] == 'z')	block->build[n] = BuildShaderBlock::BuildZ;
							else if	(build[n] == 'w')	block->build[n] = BuildShaderBlock::BuildW;
							else
							{
								__LOG_E__ << "Unexpected end of build mask.\n";
								break;
							}
					}
					break;

					case TypeGeometryVertex:		_block = new GeometryVertexShaderBlock;			break;
					case TypeGeometrySkinning:		_block = new GeometrySkinningShaderBlock;		break;
					case TypeGeometryNormal:		_block = new GeometryNormalShaderBlock;			break;
					case TypeGeometryVertexColor:	_block = new GeometryVertexColorShaderBlock;	break;
					case TypeGeometryTangentFrame:	_block = new GeometryTangentFrameShaderBlock;	break;
					case TypeScreenUV:				_block = new ScreenUVShaderBlock;				break;
					case TypeViewVector:			_block = new ViewVectorShaderBlock;				break;
					case TypeViewport:				_block = new ViewportShaderBlock;				break;
					case TypeNormalViewMatrix:		_block = new NormalViewMatrixShaderBlock;		break;
					case TypeNormalMatrix:			_block = new NormalMatrixShaderBlock;			break;
					case TypeModelViewMatrix:		_block = new ModelViewMatrixShaderBlock;		break;
					case TypeModelMatrix:			_block = new ModelMatrixShaderBlock;			break;
						 
					case TypeMix:					_block = new MixOperatorShaderBlock;			break;
					case TypeAdd:					_block = new AddOperatorShaderBlock;			break;
					case TypeMul:					_block = new MulOperatorShaderBlock;			break;
					case TypeSub:					_block = new SubOperatorShaderBlock;			break;
					case TypeDiv:					_block = new DivOperatorShaderBlock;			break;
					case TypeDot:					_block = new DotOperatorShaderBlock;			break;
					case TypeCross:					_block = new CrossOperatorShaderBlock;			break;
					case TypeClamp:					_block = new ClampShaderBlock;					break;
					case TypeNormalize:				_block = new NormalizeOperatorShaderBlock;		break;
					case TypeSin:					_block = new SinusShaderBlock;					break;
					case TypeCos:					_block = new CosinusShaderBlock;				break;
					case TypePow:					_block = new PowShaderBlock;					break;
					case TypeAbs:					_block = new AbsShaderBlock;					break;
					case TypeClock:					_block = new ClockShaderBlock;					break;
						 
					case TypeUnpackColorToVector:	_block = new UnpackColorToVectorShaderBlock;	break;
					case TypePackVectorToColor:		_block = new PackVectorToColorShaderBlock;		break;

					default:
						__LOG_E__ << "Invalid block type '" << type_string << "'.\n";
						break;
				}
			}

			if	(_block)
				if	(Tag *pos_tag = entry_tag->GetTag("Pos"))
					tVectorFromMetaTag(_block->pos, *pos_tag);

			// Add block to the map.
			if	(block_map->GetCount() > block_count)
				(*block_map)[block_count++] = _block;
			else
				__LOG_E__ << "Block map full, unexpected error.\n";
		}
		else __LOG_W__ << "Unexpected tag '" << entry_tag->name << "' in shader map.\n";

	return block_map;
}
ShaderBlock		*ShaderBlock::BranchFromMetaTag(Tag &tag, Array <pShaderBlock> *block_map)
{
	// Load block.
	Tag *index_tag = tag.GetTypedTag("Index;", Variant::VariantInteger);
	if	(!index_tag)
		__ERR__(__LOG_E__ << "No block index found.\n", NULL)

	// Load map if none provided.
	bool drop_map = false;

	if	(!block_map)
	{
		Tag *map_tag = tag.GetTag("Map;");
		if	(!map_tag)
			__ERR__(__LOG_E__ << "No map to build shader branch.\n", NULL)

		if	((block_map = BlockMapFromMetaTag(*map_tag)) == NULL)
			return NULL;

		drop_map = true;
	}

	// Link block to its inputs.
	ShaderBlock *block = (*block_map)[index_tag->GetUnsigned()];
	if	(block && block->GetInputCount())
		if	(Tag *inputs_tag = tag.GetTag("Input;"))
		{
			int	input_count = 0;
			NMLTagForeach(input_tag, *inputs_tag)
				if	(input_tag && (input_tag->name != __ShaderBlockNoneTagId))
					block->SetInput(input_count++, ShaderBlock::BranchFromMetaTag(*input_tag, block_map));
		}

	if	(drop_map)
		_safe_delete(block_map);
	return block;
}
bool				ShaderTree::FromMetaTag(Tag &tag)
{
	if	(tag.name != "ShaderMap")
		return false;

	// Load import map.
	Tag *map_tag = tag.GetTag("Map;");
	Array <pShaderBlock> *block_map = map_tag ? ShaderBlock::BlockMapFromMetaTag(*map_tag) : NULL;

	// Sink block position.
	if	(Tag *pos_tag = tag.GetTag("Pos"))
		tVectorFromMetaTag(pos, *pos_tag);

	// Read in sinks.
	Tag *sink_tag;

	if	((sink_tag = tag.GetTag("Vertex:Block;")) != NULL)
		sink[SinkVertex] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Normal:Block;")) != NULL)
		sink[SinkNormal] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Diffuse:Block;")) != NULL)
		sink[SinkDiffuse] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Modulate:Block;")) != NULL)
		sink[SinkModulate] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Specular:Block;")) != NULL)
		sink[SinkSpecular] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Glossiness:Block;")) != NULL)
		sink[SinkGlossiness] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Constant:Block;")) != NULL)
		sink[SinkConstant] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Opacity:Block;")) != NULL)
		sink[SinkOpacity] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);
	if	((sink_tag = tag.GetTag("Reflection:Block;")) != NULL)
		sink[SinkReflection] = ShaderBlock::BranchFromMetaTag(*sink_tag, block_map);

	_safe_delete(block_map);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag			*ShaderBlock::ParamAsMetaTag() const
{
	Tag *parm = new Tag("Param");

	switch (type)
	{
		case TypeGeometryUV:
		{
			GeometryUVShaderBlock *block = (GeometryUVShaderBlock *)this;
			parm->AddChild("Channel", block->channel);
		}
		break;

		case TypeTextureSampler:
		{
			TextureSamplerShaderBlock *block = (TextureSamplerShaderBlock *)this;

			switch (block->sampler_type)
			{
				default:
				case TextureSamplerShaderBlock::Sampler2D:		parm->AddChild("Type", "2D");		break;
				case TextureSamplerShaderBlock::Sampler3D:		parm->AddChild("Type", "3D");		break;
				case TextureSamplerShaderBlock::SamplerCube:	parm->AddChild("Type", "Cube");		break;
			}
		}
		break;

		case TypeRenderBuffer:
		{
			RenderBufferShaderBlock *block = (RenderBufferShaderBlock *)this;
			parm->AddChild("Buffer", (int)block->buffer);
		}
		break;

		case TypeTexture:
		{
			TextureShaderBlock *block = (TextureShaderBlock *)this;
			if	(!block->texture.IsEmpty())
				parm->AddChild("Texture", block->texture.c_str());

			switch (block->texture_type)
			{
				default:
				case TextureShaderBlock::Texture2D:		parm->AddChild("Type", "2D");		break;
				case TextureShaderBlock::Texture3D:		parm->AddChild("Type", "3D");		break;
				case TextureShaderBlock::TextureCube:	parm->AddChild("Type", "Cube");		break;
			}
		}
		break;

		case TypeConstant:
		{
			ConstantShaderBlock *block = (ConstantShaderBlock *)this;
			parm->AddChild("Type", (int)block->constant_type);
			parm->AddChild(Vector4(block->constant[0], block->constant[1], block->constant[2], block->constant[3]).AsMetaTag("Value", true));
		}
		break;

		case TypeColor:
		{
			ColorShaderBlock *block = (ColorShaderBlock *)this;
			parm->AddChild(block->color.AsMetaTag("Color", true));
		}
		break;

		case TypeMaterialParam:
		{
			MaterialParamShaderBlock *block = (MaterialParamShaderBlock *)this;
			parm->AddChild("Param", block->param);
		}
		break;

		case TypeMaterialTexture:
		{
			MaterialTextureShaderBlock *block = (MaterialTextureShaderBlock *)this;
			parm->AddChild(new Tag("Slot", block->slot));

			switch (block->texture_type)
			{
				default:
				case MaterialTextureShaderBlock::Texture2D:		parm->AddChild("Type", "2D");	break;
				case MaterialTextureShaderBlock::Texture3D:		parm->AddChild("Type", "3D");	break;
				case MaterialTextureShaderBlock::TextureCube:	parm->AddChild("Type", "Cube");	break;
			}
		}
		break;

		case	TypeSwizzle:
		{
			SwizzleShaderBlock *block = (SwizzleShaderBlock *)this;

			char swizzle[5];
			for (int n = 0; n < 4; ++n)
				switch (block->swizzle[n])
				{
					case SwizzleShaderBlock::SwizzleNone:	swizzle[n] = 'n';	break;
					case SwizzleShaderBlock::SwizzleX:		swizzle[n] = 'x';	break;
					case SwizzleShaderBlock::SwizzleY:		swizzle[n] = 'y';	break;
					case SwizzleShaderBlock::SwizzleZ:		swizzle[n] = 'z';	break;
					case SwizzleShaderBlock::SwizzleW:		swizzle[n] = 'w';	break;
				}
			swizzle[4] = 0;

			parm->AddChild("Swizzle", (const char *)swizzle);
		}
		break;

		case	TypeBuild:
		{
			BuildShaderBlock *block = (BuildShaderBlock *)this;

			char build[5];
			for (int n = 0; n < 4; ++n)
				switch (block->build[n])
				{
					case BuildShaderBlock::BuildZero:	build[n] = '0';	break;
					case BuildShaderBlock::BuildOne:	build[n] = '1';	break;
					case BuildShaderBlock::BuildX:		build[n] = 'x';	break;
					case BuildShaderBlock::BuildY:		build[n] = 'y';	break;
					case BuildShaderBlock::BuildZ:		build[n] = 'z';	break;
					case BuildShaderBlock::BuildW:		build[n] = 'w';	break;
				}
			build[4] = 0;

			parm->AddChild("Build", (const char *)build);
		}
		break;

		default:
			break;
	}
	return parm;
}
void				ShaderBlock::GatherChildren(List <ShaderBlock *> &children)
{
	if	(!children.Find(this))
		children.Add(this);

	for (uint n = 0; n < GetInputCount(); ++n)
		if	(GetInput(n))
			GetInput(n)->GatherChildren(children);
}
Tag					*ShaderBlock::BranchAsMetaTag(List <ShaderBlock *> &block_map)
{
	// Locate block in map.
	uint index = 0;
	ListForeachPtr(ShaderBlock *, block, block_map)
	{
		if	(block == this)
			break;
		index++;
	}
	if	(index == block_map.GetCount())
		return NULL;

	// Export block and inputs.
	Tag *block = new Tag("Block");
	block->AddChild("Index", index);

	if	(GetInputCount())
	{
		Tag *input = block->AddChild("Input");
		for (uint n = 0; n < GetInputCount(); ++n)
			input->AddChild(GetInput(n) ? GetInput(n)->BranchAsMetaTag(block_map) : new Tag(__ShaderBlockNoneTagId.c_str()));
	}
	return block;
}
Tag					*ShaderBlock::BlockMapAsMetaTag(const List <ShaderBlock *> &block_map)
{
	Tag *map = new Tag("Map");
	if	(!map)
		__ERR__(__LOG_E__ << "Failed to allocate root tag.\n", NULL);

	ListForeachPtr(ShaderBlock *, block, block_map)
		if (block)
			if	(Tag *entry = map->AddChild("Entry"))
			{
				entry->AddChild("Type", ShaderBlock::BlockTypeToString(block->type));
				entry->AddChild(tVectorAsMetaTag(block->pos, "Pos"));
				entry->AddChild(block->ParamAsMetaTag());
			}

	return map;
}
Tag					*ShaderTree::AsMetaTag() const
{
	Tag *map = new Tag("ShaderMap");
	if	(!map)
		__ERR__(__LOG_E__ << "Could not serialize shader map. Failed to create root tag.\n", NULL);

	// Sink block position.
	map->AddChild(tVectorAsMetaTag(pos, "Pos"));

	// Build block export map.
	List <ShaderBlock *> block_map;
	for (int n = 0; n < SinkInvalid; ++n)
		if	(sink[n])
			sink[n]->GatherChildren(block_map);

	// Serialize map.
	map->AddChild(ShaderBlock::BlockMapAsMetaTag(block_map));

	// Serialize all sinks.
	#define	__SerializeShaderMapSink(__SINK__, __LABEL__)\
	if	(__SINK__)\
	{\
		Tag *sinktag = map->AddChild(__LABEL__);\
		sinktag->AddChild((__SINK__)->BranchAsMetaTag(block_map));\
	}

	// Serialize shader map.
	__SerializeShaderMapSink(sink[SinkVertex], "Vertex")
	__SerializeShaderMapSink(sink[SinkNormal], "Normal")
	__SerializeShaderMapSink(sink[SinkDiffuse], "Diffuse")
	__SerializeShaderMapSink(sink[SinkModulate], "Modulate")
	__SerializeShaderMapSink(sink[SinkSpecular], "Specular")
	__SerializeShaderMapSink(sink[SinkGlossiness], "Glossiness")
	__SerializeShaderMapSink(sink[SinkConstant], "Constant")
	__SerializeShaderMapSink(sink[SinkOpacity], "Opacity")
	__SerializeShaderMapSink(sink[SinkReflection], "Reflection")

	return map;
}
//------------------------------------------------------------------------------
