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


		#include "core/shader_block.h"
		#include "log/log.h"

		using namespace GS::Core;
		using GS::String;


		// Tools.
		ShaderBlockPin	SwizzleShaderBlock::input_pin[] =
{	{	"Input", "Source input to extract components from.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	SwizzleShaderBlock::output_pin =
{	"Swizzled", "Swizzled input.", ShaderInput::NoData		};

		ShaderBlockPin	BuildShaderBlock::input_pin[] =
{	{	"Input X", "Source component of the built vector.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input Y", "Source component of the built vector.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input Z", "Source component of the built vector.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input W", "Source component of the built vector.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	BuildShaderBlock::output_pin =
{	"Built", "Built vector.", ShaderInput::NoData		};

		ShaderBlockPin	ClampShaderBlock::input_pin[] =
{	{	"Input", "Source to clamp.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Min", "Minimum value.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Max", "Maximum value.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	ClampShaderBlock::output_pin =
{	"Clamped", "Clamped input.", ShaderInput::NoData		};

		ShaderBlockPin	UnpackColorToVectorShaderBlock::input_pin[] =
{	{	"Input", "Vector in the [0;1] range.", ShaderInput::Float | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	UnpackColorToVectorShaderBlock::output_pin =
{	"Unpacked [-1;1]", "Vector in the [-1;1] range.", ShaderInput::NoData	};
		ShaderBlockPin	PackVectorToColorShaderBlock::input_pin[] =
{	{	"Input", "Vector in the [-1;1] range.", ShaderInput::Float | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	PackVectorToColorShaderBlock::output_pin =
{	"Packed [0;1]", "Vector in the [0;1] range.", ShaderInput::NoData	};

		// Operator.
		ShaderBlockPin	MixOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input K", "Source input K.", ShaderInput::Float	}	};
		ShaderBlockPin	MixOperatorShaderBlock::output_pin =
{	"A*K+B*(1-K)", "Input A * Input K + Input B * (1.0 - Input K).", ShaderInput::NoData		};

		ShaderBlockPin	AddOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	AddOperatorShaderBlock::output_pin =
{	"A+B", "Input A + Input B.", ShaderInput::NoData		};
		ShaderBlockPin	SubOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	SubOperatorShaderBlock::output_pin =
{	"A-B", "Input A - Input B.", ShaderInput::NoData		};
		ShaderBlockPin	MulOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4 | ShaderInput::Matrix3 | ShaderInput::Matrix4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4 | ShaderInput::Matrix3 | ShaderInput::Matrix4	}	};
		ShaderBlockPin	MulOperatorShaderBlock::output_pin =
{	"A*B", "Input A * Input B.", ShaderInput::NoData		};
		ShaderBlockPin	DivOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	DivOperatorShaderBlock::output_pin =
{	"A/B", "Input A / Input B.", ShaderInput::NoData		};

		ShaderBlockPin	DotOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	},
	{	"Input B", "Source input B.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	DotOperatorShaderBlock::output_pin =
{	"A.Dot(B)", "The cosinus of the angle between A and B.", ShaderInput::NoData		};
		ShaderBlockPin	CrossOperatorShaderBlock::input_pin[] =
{	{	"Input A", "Source input A.", ShaderInput::Vector3	},
	{	"Input B", "Source input B.", ShaderInput::Vector3	}	};
		ShaderBlockPin	CrossOperatorShaderBlock::output_pin =
{	"A.Cross(B)", "A vector perpendicular to both A and B.", ShaderInput::NoData		};

		ShaderBlockPin	NormalizeOperatorShaderBlock::input_pin[] =
{	{	"Input", "Source input.", ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	NormalizeOperatorShaderBlock::output_pin =
{	"Normalized", "Normalized input.", ShaderInput::NoData		};

		ShaderBlockPin	CosinusShaderBlock::input_pin[] =
{	{	"Input", "Input value in radian.", ShaderInput::Float	}	};
		ShaderBlockPin	CosinusShaderBlock::output_pin =
{	"Cosinus", "Cosinus of input.", ShaderInput::NoData	};
		ShaderBlockPin	SinusShaderBlock::input_pin[] =
{	{	"Input", "Input value in radian.", ShaderInput::Float	}	};
		ShaderBlockPin	SinusShaderBlock::output_pin =
{	"Sinus", "Sinus of input.", ShaderInput::NoData	};

		ShaderBlockPin	PowShaderBlock::input_pin[] =
{	{	"Value", "Input value.", ShaderInput::Float	},
	{	"Power", "Power to raise value to.", ShaderInput::Float	}	};
		ShaderBlockPin	PowShaderBlock::output_pin =
{	"Pow", "Value raised to power.", ShaderInput::NoData	};

		ShaderBlockPin	AbsShaderBlock::input_pin[] =
{	{	"Value", "Input value.", ShaderInput::Float | ShaderInput::Vector2 | ShaderInput::Vector3 | ShaderInput::Vector4	}	};
		ShaderBlockPin	AbsShaderBlock::output_pin =
{	"Abs", "Absolute value of input.", ShaderInput::NoData	};

		// Texture
		ShaderBlockPin	TextureSamplerShaderBlock::input_pin[] =
{	{	"Texture", "Texture input.", ShaderInput::Texture2D	},
	{	"UV Stream", "UV stream input.", ShaderInput::Vector2 | ShaderInput::Vector3	}	};
		ShaderBlockPin	TextureSamplerShaderBlock::output_pin =
{	"Texel", "Texture sample.", ShaderInput::NoData	};

		// No input blocks.
		ShaderBlockPin	GeometryVertexShaderBlock::output_pin =
{	"Vertex", "Vertex stream.", ShaderInput::NoData	};
		ShaderBlockPin	GeometrySkinningShaderBlock::output_pin =
{	"Skinned", "Skinned vector.", ShaderInput::NoData	};
		ShaderBlockPin	GeometryNormalShaderBlock::output_pin =
{	"Normal", "Normal stream.", ShaderInput::NoData	};
		ShaderBlockPin	GeometryUVShaderBlock::output_pin =
{	"UV", "UV stream.", ShaderInput::NoData	};
		ShaderBlockPin	GeometryVertexColorShaderBlock::output_pin =
{	"Color", "Vertex color stream.", ShaderInput::NoData	};
		ShaderBlockPin	GeometryTangentFrameShaderBlock::output_pin =
{	"Tangent", "Tangent frame.", ShaderInput::NoData	};
		ShaderBlockPin	RenderBufferShaderBlock::output_pin =
{	"Buffer", "Texture object.", ShaderInput::NoData	};
		ShaderBlockPin	TextureShaderBlock::output_pin =
{	"Texture", "Texture object.", ShaderInput::NoData	};
		ShaderBlockPin	ConstantShaderBlock::output_pin =
{	"Constant", "Constant value.", ShaderInput::NoData	};
		ShaderBlockPin	ColorShaderBlock::output_pin =
{	"Color", "RGBA Color.", ShaderInput::NoData	};

		ShaderBlockPin	MaterialParamShaderBlock::output_pin =
{	"Parameter", "Material parameter.", ShaderInput::NoData	};
		ShaderBlockPin	MaterialTextureShaderBlock::output_pin =
{	"Slot", "Texture slot.", ShaderInput::NoData	};

		ShaderBlockPin	ClockShaderBlock::output_pin =
{	"Clock (s)", "System clock in second.", ShaderInput::NoData	};

		ShaderBlockPin	ScreenUVShaderBlock::output_pin =
{	"Screen UV", "Screen position.", ShaderInput::NoData	};
		ShaderBlockPin	ViewVectorShaderBlock::output_pin =
{	"World View", "View vector in world space.", ShaderInput::NoData	};
		ShaderBlockPin	ViewportShaderBlock::output_pin =
{	"Viewport", "Viewport origin and dimension in 2d.", ShaderInput::NoData	};

		ShaderBlockPin	NormalViewMatrixShaderBlock::output_pin =
{	"Normal View Matrix", "Normal view matrix.", ShaderInput::NoData	};
		ShaderBlockPin	NormalMatrixShaderBlock::output_pin =
{	"Normal Matrix", "Normal matrix.", ShaderInput::NoData	};
		ShaderBlockPin	ModelViewMatrixShaderBlock::output_pin =
{	"Model View Matrix", "Model view matrix.", ShaderInput::NoData	};
		ShaderBlockPin	ModelMatrixShaderBlock::output_pin =
{	"Model Matrix", "Model matrix.", ShaderInput::NoData	};


//------------------------------------------------------------------------------
String				TextureShaderBlock::GetId() const
{	return String("Texture") + texture;	}

String				ConstantShaderBlock::GetId() const
{
	switch (constant_type)
	{
		case ShaderInput::NoData:
			return String("ConstNone");
		case ShaderInput::Float:
			return String("ConstFloat");
		case ShaderInput::Vector2:
			return String("ConstVec2");
		case ShaderInput::Vector3:
			return String("ConstVec3");
		case ShaderInput::Vector4:
			return String("ConstVec4");
	}
	__ERR__(__LOG_E__ << "Unknown constant.\n", String("ConstUnk"))
}

String				MaterialParamShaderBlock::GetId() const
{	return String::Format("MatParm%d", param);	}

String				MaterialTextureShaderBlock::GetId() const
{	return String::Format("MatTexture%d", slot);	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				ShaderBlock::IsEquivalent(const ShaderBlock *input_block) const
{
	if	(this == input_block)
		return true;

	if	(type == input_block->type)
		switch (type)
		{
			case ShaderBlock::TypeRenderBuffer:
			{
				RenderBufferShaderBlock *a = (RenderBufferShaderBlock *)this, *b = (RenderBufferShaderBlock *)input_block;
				return a->buffer == b->buffer;
			}

			case ShaderBlock::TypeTextureSampler:
			{
				TextureSamplerShaderBlock *a = (TextureSamplerShaderBlock *)this, *b = (TextureSamplerShaderBlock *)input_block;
				return (a->sampler_type == b->sampler_type) && a->input[0] && a->input[1] && b->input[0] && b->input[1] ? a->input[0]->IsEquivalent(b->input[0]) && a->input[1]->IsEquivalent(b->input[1]) : false;
			}

			case ShaderBlock::TypeTexture:
			{
				TextureShaderBlock *a = (TextureShaderBlock *)this, *b = (TextureShaderBlock *)input_block;
				return a->texture == b->texture;
			}

			case ShaderBlock::TypeGeometryUV:
			{
				GeometryUVShaderBlock *a = (GeometryUVShaderBlock *)this, *b = (GeometryUVShaderBlock *)input_block;
				return a->channel == b->channel;
			}

			case ShaderBlock::TypeMaterialParam:
			{
				MaterialParamShaderBlock *a = (MaterialParamShaderBlock *)this, *b = (MaterialParamShaderBlock *)input_block;
				return a->param == b->param;
			}

			case ShaderBlock::TypeClock:
			case ShaderBlock::TypeScreenUV:
			case ShaderBlock::TypeViewVector:
			case ShaderBlock::TypeViewport:
			case ShaderBlock::TypeNormalViewMatrix:
			case ShaderBlock::TypeNormalMatrix:
			case ShaderBlock::TypeModelViewMatrix:
			case ShaderBlock::TypeModelMatrix:
			case ShaderBlock::TypeGeometryVertex:
			case ShaderBlock::TypeGeometrySkinning:
			case ShaderBlock::TypeGeometryNormal:
			case ShaderBlock::TypeGeometryVertexColor:
			case ShaderBlock::TypeGeometryTangentFrame:
				return true;
		}

	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
int					UnpackColorToVectorShaderBlock::GetOutputType() const
{	return !input[0] ? ShaderInput::NoData : input[0]->GetOutputType();	}
int					PackVectorToColorShaderBlock::GetOutputType() const
{	return !input[0] ? ShaderInput::NoData : input[0]->GetOutputType();	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
int					MixOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || !input[2] || (input[0]->GetOutputType() != input[1]->GetOutputType()) || (input[2]->GetOutputType() != ShaderInput::Float)) ? ShaderInput::NoData : input[0]->GetOutputType();	}

int					DotOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || (input[0]->GetOutputType() != input[1]->GetOutputType())) ? ShaderInput::NoData : ShaderInput::Float;	}
int					CrossOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || ((input[0]->GetOutputType() != input[1]->GetOutputType()) && (input[0]->GetOutputType() != ShaderInput::Vector3))) ? ShaderInput::NoData : ShaderInput::Vector3;	}

int					AddOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || (input[0]->GetOutputType() != input[1]->GetOutputType())) ? ShaderInput::NoData : input[0]->GetOutputType();	}
int					SubOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || (input[0]->GetOutputType() != input[1]->GetOutputType())) ? ShaderInput::NoData : input[0]->GetOutputType();	}
int					DivOperatorShaderBlock::GetOutputType() const
{	return (!input[0] || !input[1] || (input[0]->GetOutputType() != input[1]->GetOutputType())) ? ShaderInput::NoData : input[0]->GetOutputType();	}
int					MulOperatorShaderBlock::GetOutputType() const
{
	if	(!input[0] || !input[1])
		return ShaderInput::NoData;

	int	type_0 = input[0]->GetOutputType(),
		type_1 = input[1]->GetOutputType();

	if	(type_0 > type_1)
	{	int	tmp = type_0; type_0 = type_1; type_1 = tmp;	}

	// Switch on largest type.
	switch (type_1)
	{
		case ShaderInput::Matrix3:
			return (type_0 != ShaderInput::Vector3) ? ShaderInput::NoData : ShaderInput::Vector3;
		case ShaderInput::Matrix4:
			return (type_0 != ShaderInput::Vector4) ? ShaderInput::NoData : ShaderInput::Vector4;

		case ShaderInput::Float:
		case ShaderInput::Vector2:
		case ShaderInput::Vector3:
		case ShaderInput::Vector4:
			return (type_0 != type_1) ? ShaderInput::NoData : type_0;
	}
	return ShaderInput::NoData;
}

int					ClampShaderBlock::GetOutputType() const
{
	if	(!input[0] || !input[1] || !input[2])
		return ShaderInput::NoData;

	int	type[3] =
	{
		input[0]->GetOutputType(),
		input[1]->GetOutputType(),
		input[2]->GetOutputType()
	};

	if	(type[1] != type[2])				// min/max must match.
		return ShaderInput::NoData;
	if	(type[0] != type[1])
		return ShaderInput::NoData;		// input and min/max types must match.

	return type[0];
}

int					NormalizeOperatorShaderBlock::GetOutputType() const
{	return !input[0] ? ShaderInput::NoData : input[0]->GetOutputType();	}
//------------------------------------------------------------------------------

//-----------------------------------------------------------
int					SwizzleShaderBlock::GetOutputType() const
//-----------------------------------------------------------
{
	if	(!input[0] || (input[0]->GetOutputType() == ShaderInput::NoData))
		return ShaderInput::NoData;

	int output_count = 0;
	for (int n = 0; n < 4; ++n)
		if	(swizzle[n] != SwizzleNone)
			output_count++;

	switch (output_count)
	{
		case 1:	return ShaderInput::Float;
		case 2:	return ShaderInput::Vector2;
		case 3:	return ShaderInput::Vector3;
		case 4:	return ShaderInput::Vector4;
	}
	return ShaderInput::NoData;
}

//---------------------------------------------------------
int					BuildShaderBlock::GetOutputType() const
//---------------------------------------------------------
{
	int output_count = 0;
	for (int n = 0; n < 4; ++n)
		switch (build[n])
		{
			case BuildZero:
			case BuildOne:
				output_count++;
				break;

			default:
				if	(!input[n])
					n = 4;
				else
				{
					if	(input[n]->GetOutputType() == ShaderInput::NoData)
						return ShaderInput::NoData;
					output_count++;
				}
				break;
		}

	switch (output_count)
	{
		case 1:	return ShaderInput::Float;
		case 2:	return ShaderInput::Vector2;
		case 3:	return ShaderInput::Vector3;
		case 4:	return ShaderInput::Vector4;
	}
	return ShaderInput::NoData;
}

int					CosinusShaderBlock::GetOutputType() const
{	return ShaderInput::Float;	}
int					SinusShaderBlock::GetOutputType() const
{	return ShaderInput::Float;	}
int					PowShaderBlock::GetOutputType() const
{	return ShaderInput::Float;	}

int					AbsShaderBlock::GetOutputType() const
{	return !input[0] ? ShaderInput::NoData : input[0]->GetOutputType();	}

int					GeometryVertexShaderBlock::GetOutputType() const
{	return ShaderInput::Vector4;	}
int					GeometryUVShaderBlock::GetOutputType() const
{	return ShaderInput::Vector2;	}
int					GeometryNormalShaderBlock::GetOutputType() const
{	return ShaderInput::Vector3;	}
int					GeometrySkinningShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix4;	}
int					GeometryVertexColorShaderBlock::GetOutputType() const
{	return ShaderInput::Vector3;	}
int					GeometryTangentFrameShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix3;	}
int					RenderBufferShaderBlock::GetOutputType() const
{	return ShaderInput::Texture2D;	}
int					TextureShaderBlock::GetOutputType() const
{	return ShaderInput::Texture2D;	}
int					TextureSamplerShaderBlock::GetOutputType() const
{	return (input[0] && input[1]) ? ShaderInput::Vector4 : ShaderInput::NoData;	}
int					ConstantShaderBlock::GetOutputType() const
{	return constant_type;	}

int					ColorShaderBlock::GetOutputType() const
{	return ShaderInput::Vector4;	}
int					MaterialParamShaderBlock::GetOutputType() const
{
	switch (param)
	{
		case MaterialGlossiness:
		case MaterialOpacity:
		case MaterialReflection:
			return ShaderInput::Float;
	}
	return ShaderInput::Vector4;
}
int					MaterialTextureShaderBlock::GetOutputType() const
{	return ShaderInput::Texture2D;	}

int					ScreenUVShaderBlock::GetOutputType() const
{	return ShaderInput::Vector2;	}
int					ViewVectorShaderBlock::GetOutputType() const
{	return ShaderInput::Vector3;	}
int					ViewportShaderBlock::GetOutputType() const
{	return ShaderInput::Vector4;	}

int					NormalViewMatrixShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix3;	}
int					NormalMatrixShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix3;	}
int					ModelViewMatrixShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix4;	}
int					ModelMatrixShaderBlock::GetOutputType() const
{	return ShaderInput::Matrix4;	}

int					ClockShaderBlock::GetOutputType() const
{	return ShaderInput::Float;	}
