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


	#ifndef	__PLATFORM_IOS__
		#include <malloc.h>
	#endif
		#include "gpu/gpu_renderer.h"
		#include "core/renderer_environment_interface.h"
		#include "core/light.h"
		#include "core/camera.h"
		#include "core/object.h"
		#include "core/shader.h"
		#include "container/narray.h"
		#include "platform_config.h"
		#include "platform.h"
		#include "log/file_log.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::GPU;


//------------------------------------------------------------------------------
void				Shader::SetVertexStreamInputs(DisplayList &dls)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryVertexStream].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryVertexStream][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::Position:
				Set(*input->location, 3, Types::ValueHalfFloat, false, dls.stride, (const void *)dls.vertex_offset);
				break;
			case Core::ShaderInput::Normal:
				Set(*input->location, 3, Types::ValueByte, true, dls.stride, (const void *)dls.normal_offset);
				break;
			case Core::ShaderInput::VertexColor:
				Set(*input->location, 4, Types::ValueUByte, true, dls.stride, (const void *)dls.rgb_offset);
				break;

			case Core::ShaderInput::Tangent:
				Set(*input->location, 3, Types::ValueByte, true, dls.stride, (const void *)dls.tangent_offset);
				break;
			case Core::ShaderInput::Bitangent:
				Set(*input->location, 3, Types::ValueByte, true, dls.stride, (const void *)(dls.tangent_offset + 4 * sizeof(char)));
				break;

			case Core::ShaderInput::BoneIndex:
				Set(*input->location, 4, Types::ValueUByte, false, dls.stride, (const void *)dls.skinning_offset);
				break;
			case Core::ShaderInput::BoneWeight:
				Set(*input->location, 4, Types::ValueUByte, true, dls.stride, (const void *)(dls.skinning_offset + 4 * sizeof(char)));
				break;

			case Core::ShaderInput::UV0:
			case Core::ShaderInput::UV1:
			case Core::ShaderInput::UV2:
			{
				int uv_index = (int)input->semantic - (int)Core::ShaderInput::UV0;
				Set(*input->location, 2, Types::ValueHalfFloat, false, dls.stride, (const void *)dls.uv_offset[uv_index]);
			}
			break;
		}
	}
}
void				Shader::SetSkinInputs(DisplayList &dls, Core::Skin &skin)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategorySkin].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategorySkin][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::BoneMatrix:
				if	(float *m = (float *)alloca(4 * 4 * sizeof(float) * dls.bone.GetCount()))
				{
					float *p_m = m;
					for	(uint n = 0; n < dls.bone.GetCount(); ++n)
					{
						Memory::Copy(p_m, skin.bones_mtx[dls.bone[n]].m, 4 * 4 * sizeof(float));
						p_m += 4 * 4;
					}

					Set(*input->location, (Matrix4 *)m, dls.bone.GetCount());
				}
				break;

			case Core::ShaderInput::PreviousBoneMatrix:
				if	(float *m = (float *)alloca(4 * 4 * sizeof(float) * dls.bone.GetCount()))
				{
					float *p_m = m;
					for	(uint n = 0; n < dls.bone.GetCount(); ++n)
					{
						Memory::Copy(p_m, skin.previous_bones_mtx[dls.bone[n]].m, 4 * 4 * sizeof(float));
						p_m += 4 * 4;
					}

					Set(*input->location, (Matrix4 *)m, dls.bone.GetCount());
				}
				break;
		}
	}
}
void				Shader::SetConstantInputs()
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryConstant].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryConstant][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::Constant:
				switch (input->data_type)
				{
					default:
					case Core::ShaderInput::Matrix3:
					case Core::ShaderInput::Matrix4:
						break;

					case Core::ShaderInput::DataTexture2D:
					case Core::ShaderInput::DataTexture3D:
					case Core::ShaderInput::DataTextureCube:
						Set(*input->location, *input->parm_t, input->index);
						break;

					case Core::ShaderInput::Int:
						Set(*input->location, (int *)&input->parm_v.x);
						break;
					case Core::ShaderInput::Float:
						Set(*input->location, &input->parm_v.x);
						break;
					case Core::ShaderInput::Vector2:
						Set(*input->location, &input->parm_v.x, 2);
						break;
					case Core::ShaderInput::Vector3:
						Set(*input->location, &input->parm_v.x, 3);
						break;
					case Core::ShaderInput::Vector4:
						Set(*input->location, &input->parm_v.x, 4);
						break;
				}
				break;
		}
	}
}
void				Shader::SetTextureInputs()
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryTexture].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryTexture][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::Texture2D:
			case Core::ShaderInput::Texture3D:
			case Core::ShaderInput::TextureCube:
				if	(input->parm_t)
					Set(*input->location, *input->parm_t, input->index);
				break;
		}
	}
}
void				Shader::SetRendererInputs(Renderer &r, Material *m)
{
	Color fog_color;
	float fog_near = 0, fog_far = 0;

	bool fog_enabled = r.environment_interface ? r.environment_interface->GetFogConfiguration(fog_color, fog_near, fog_far) : false;

	if	(r.performance_tools.disable_fog)
		fog_enabled = false;
	if	(m && (m->blendop == Core::Material::Blend_Add))
		fog_enabled = false;

	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryRenderer].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryRenderer][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::Clock:
				Set(*input->location, r.frame_clock);
				break;
			case Core::ShaderInput::TimeOfDay:
				Set(*input->location, r.environment_interface->GetTimeOfDay());
				break;

			case Core::ShaderInput::ViewVector:
			{
				Vector4	tmp = r.GetCamera()->GetMatrix().GetRow(2);
				Set(*input->location, &tmp.x, 3);
			}
			break;
			case Core::ShaderInput::ViewPosition:
			{
				Vector4	tmp = r.GetCamera()->GetMatrix().GetRow(3);
				Set(*input->location, &tmp.x, 4);
			}
			break;
			case Core::ShaderInput::Viewport:
			{
				const fRect viewport = r.GetViewport();
				float v[4] = { viewport.sx, viewport.sy, viewport.GetWidth(), viewport.GetHeight() };
				Set(*input->location, v, 4);
			}
			break;

			case Core::ShaderInput::ZNear:
				Set(*input->location, r.GetCamera()->GetNearClippingPlane());
				break;
			case Core::ShaderInput::ZFar:
				Set(*input->location, r.GetCamera()->GetFarClippingPlane());
				break;
			case Core::ShaderInput::ZoomFactor:
				Set(*input->location, r.GetCamera()->zoom_factor);
				break;

			case Core::ShaderInput::DisplayBufferRatio:
			{
				float v[] = { r.GetOutputAspectRatio(), 1.f };
				Set(*input->location, v, 2);
			}
			break;

			case Core::ShaderInput::ViewportRatio:
			{
				float v[] = { r.GetViewport().GetHeight() / r.GetViewport().GetWidth(), 1.f };
				Set(*input->location, v, 2);
			}
			break;

			case Core::ShaderInput::FxScale:
				Set(*input->location, float(r.fx_scale));
				break;

			case Core::ShaderInput::InverseBufferSize:
			{
				tVector2 <uint>	d = r.GetOutputDimensions();
				float v[] = { 1.f / d.x, 1.f / d.y };
				Set(*input->location, v, 2);
			}
			break;

			case Core::ShaderInput::InverseViewportSize:
			{
				float v[] = { 1.f / r.GetViewport().GetWidth(), 1.f / r.GetViewport().GetHeight() };
				Set(*input->location, v, 2);
			}
			break;

			case Core::ShaderInput::ViewDepthOffset:
			{
				float k = 0.f;
				Set(*input->location, &k);
			}
			break;

			case Core::ShaderInput::AmbientColor:
			{
				Color ambient = r.environment_interface->GetAmbientColor();
				Set(*input->location, &ambient.x, 3);
			}
			break;

			case Core::ShaderInput::FogColor:
				Set(*input->location, &fog_color.x, 3);
				break;
			case Core::ShaderInput::FogNear:
				Set(*input->location, fog_near);
				break;
			case Core::ShaderInput::FogFar:
				Set(*input->location, fog_far);
				break;
			case Core::ShaderInput::FogInverseRange:
			{
				bool use_fog = fog_enabled && (fog_far > 0.0);
				if	(m && (m->renderword & Core::Material::Render_NoFog))
					use_fog = false;

				Set(*input->location, use_fog ? 1.f / (fog_far - fog_near) : -1.f);
			}
			break;

			case Core::ShaderInput::DepthBuffer:
				if	(r.render_technique == Renderer::TechniqueDeferred)
						Set(*input->location, *r.t_gbuffer[0], input->index);
				else	Set(*input->location, *r.t_depth, input->index);
				break;

			case Core::ShaderInput::FrameBuffer:
				if	(r.t_fx[0].IsValid())
					Set(*input->location, *r.t_fx[0], input->index);
				break;

			case Core::ShaderInput::GBuffer0:
			case Core::ShaderInput::GBuffer1:
			case Core::ShaderInput::GBuffer2:
			case Core::ShaderInput::GBuffer3:
				Set(*input->location, *r.t_gbuffer[input->semantic - Core::ShaderInput::GBuffer0], input->index);
				break;

			case Core::ShaderInput::NoiseMap:
				if	(r.t_noise.IsValid())
					Set(*input->location, *r.t_noise, input->index);
				break;
		}
	}
}
void				Shader::SetTransformInputs(const Matrix4 &v_pm, const Matrix4 &v_m, const Matrix4 &v_im, const Matrix4 *i_m, const Matrix4 *i_im, uint count)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryTransform].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryTransform][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::NormalMatrix:
				if	(Matrix3 *n_m = (Matrix3 *)alloca(sizeof(Matrix3) * count))
				{
					for	(uint n = 0; n < count; ++n)
						n_m[n] = Matrix3::FromMatrix4(i_m[n]).Normalized();
					Set(*input->location, n_m, count);
				}
				break;
			case Core::ShaderInput::NormalViewMatrix:
				if	(Matrix3 *nv_m = (Matrix3 *)alloca(sizeof(Matrix3) * count))
				{
					Matrix3 vn_m = Matrix3::FromMatrix4(v_m).Normalized().Transposed();
					for	(uint n = 0; n < count; ++n)
						nv_m[n] = vn_m * Matrix3::FromMatrix4(i_m[n]).Normalized();
					Set(*input->location, nv_m, count);
				}
				break;

			case Core::ShaderInput::ModelMatrix:
				Set(*input->location, i_m, count);
				break;
			case Core::ShaderInput::ViewMatrix:
				Set(*input->location, v_im);
				break;
			case Core::ShaderInput::ProjectionMatrix:
				Set(*input->location, v_pm);
				break;
			case Core::ShaderInput::ModelViewMatrix:
				if	(Matrix4 *mv_m = (Matrix4 *)alloca(sizeof(Matrix4) * count))
				{
					for	(uint n = 0; n < count; ++n)
						mv_m[n] = v_im * i_m[n];
					Set(*input->location, mv_m, count);
				}
				break;

			case Core::ShaderInput::ModelViewProjectionMatrix:
				if	(Matrix4 *mvp_m = (Matrix4 *)alloca(sizeof(Matrix4) * count))
				{
					for	(uint n = 0; n < count; ++n)
						mvp_m[n] = v_pm * (v_im * i_m[n]);
					Set(*input->location, mvp_m, count);
				}
				break;

			case Core::ShaderInput::InverseViewProjectionMatrix:
			{
				Matrix4 vpm = v_pm * v_im, ivpm;
				vpm.Inverse(ivpm);
				Set(*input->location, ivpm);
			}
			break;

			case Core::ShaderInput::InverseViewProjectionMatrixAtOrigin:
			{
				Matrix4 v_im_o = v_im;
				v_im_o.SetRow(3, Vector4(0, 0, 0, 1));

				Matrix4 vpm = v_pm * v_im_o, ivpm;
				vpm.Inverse(ivpm);
				Set(*input->location, ivpm);
			}
			break;
		}
	}
}
void				Shader::SetPreviousTransformInputs(const Matrix4 &v_pm, const Matrix4 &v_im, const Matrix4 *i_m, uint count)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryPreviousTransform].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryPreviousTransform][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::PreviousModelViewMatrix:
				if	(Matrix4 *mv_m = (Matrix4 *)alloca(sizeof(Matrix4) * count))
				{
					for	(uint n = 0; n < count; ++n)
						mv_m[n] = v_im * i_m[n];
					Set(*input->location, mv_m, count);
				}
				break;

			case Core::ShaderInput::PreviousModelViewProjectionMatrix:
				if	(Matrix4 *mvp_m = (Matrix4 *)alloca(sizeof(Matrix4) * count))
				{
					for	(uint n = 0; n < count; ++n)
						mvp_m[n] = v_pm * (v_im * i_m[n]);
					Set(*input->location, mvp_m, count);
				}
				break;
		}
	}
}
void				Shader::SetMaterialOpacityInputs(Material &m, float opacity)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryMaterialOpacity].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryMaterialOpacity][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::MaterialOpacity:
				Set(*input->location, m.opacity * opacity);
				break;
		}
	}
}
void				Shader::SetMaterialInputs(Material &m)
{
	for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryMaterial].GetCount(); ++n)
	{
		ShaderInput *input = &input_list[Core::ShaderInput::CategoryMaterial][n];

		switch (input->semantic)
		{
			case Core::ShaderInput::MaterialDiffuse:
				Set(*input->location, &m.diffuse.x, 4);
				break;
			case Core::ShaderInput::MaterialSpecular:
				Set(*input->location, &m.specular.x, 4);
				break;
			case Core::ShaderInput::MaterialAmbient:
				Set(*input->location, &m.ambient.x, 4);
				break;
			case Core::ShaderInput::MaterialSelf:
				Set(*input->location, &m.self.x, 4);
				break;
			case Core::ShaderInput::MaterialGlossiness:
				Set(*input->location, m.glossiness);
				break;
			case Core::ShaderInput::MaterialReflection:
				Set(*input->location, m.reflection);
				break;
			case Core::ShaderInput::MaterialAlphaThreshold:
				Set(*input->location, m.athreshold);
				break;
			case Core::ShaderInput::MaterialDepthBias:
				Set(*input->location, m.depth_bias);
				break;

			case Core::ShaderInput::MaterialTexture0:
			case Core::ShaderInput::MaterialTexture1:
			case Core::ShaderInput::MaterialTexture2:
			case Core::ShaderInput::MaterialTexture3:
			case Core::ShaderInput::MaterialTexture4:
			case Core::ShaderInput::MaterialTexture5:
			case Core::ShaderInput::MaterialTexture6:
			case Core::ShaderInput::MaterialTexture7:
				if	(Render::Texture *t = m.texture_table[input->semantic - Core::ShaderInput::MaterialTexture0])
					Set(*input->location, *t, input->index);
				break;
		}
	}
}
void				Shader::SetLightInputs(Renderer &r, Core::Camera &view_item, Core::Light &l)
{
	float k_clip_fade = 1.f;

	if	(l.range > 0.f)	// [EJ] fade on last 10% of clip range
	{
		float c = l.clip_distance + l.range;
		float d = Vector4::Dist(view_item.GetMatrix().GetRow(3), l.GetMatrix().GetRow(3));

		k_clip_fade = 1.f - GS::Types::Clamp((d - c * 0.9f) / (c * 0.1f));
	}

	if	(Core::Light::RenderData *light_render_data = (Core::Light::RenderData *)l.render_data.c_ptr())
		for	(uint n = 0; n < input_list[Core::ShaderInput::CategoryLight].GetCount(); ++n)
		{
			ShaderInput *input = &input_list[Core::ShaderInput::CategoryLight][n];

			switch (input->semantic)
			{
				case Core::ShaderInput::LightRange:
					Set(*input->location, l.range);
					break;
				case Core::ShaderInput::LightSpotEdge:
					Set(*input->location, Math::Cos(l.edge_angle + l.cone_angle));
					break;
				case Core::ShaderInput::LightSpotCone:
					Set(*input->location, Math::Cos(l.cone_angle));
					break;
				case Core::ShaderInput::LightShadowBias:
					Set(*input->location, l.shadow_bias);
					break;

				case Core::ShaderInput::LightDiffuseColor:
				{
					Color c = l.diffuse_color * l.diffuse_intensity * k_clip_fade;
					Set(*input->location, &c.x, 3);
				}
				break;

				case Core::ShaderInput::LightSpecularColor:
				{
					Color c = l.specular_color * l.specular_intensity * k_clip_fade;
					Set(*input->location, &c.x, 3);
				}
				break;

				case Core::ShaderInput::LightShadowColor:
					Set(*input->location, &l.shadow_color.x, 3);
					break;

				case Core::ShaderInput::LightViewPosition:
				{
					Vector4	p = l.GetMatrix().GetRow(3) * view_item.GetInverseMatrix();
					Set(*input->location, &p.x, 3);
				}
				break;

				case Core::ShaderInput::LightViewDirection:
				{
					Vector4	d = l.GetMatrix().GetRow(2) * Matrix3::FromMatrix4(view_item.GetMatrix()).Normalized().Transposed();
					Set(*input->location, &d.x, 3);
				}
				break;

				case Core::ShaderInput::LightShadowMatrix0:
				case Core::ShaderInput::LightShadowMatrix1:
				case Core::ShaderInput::LightShadowMatrix2:
				case Core::ShaderInput::LightShadowMatrix3:
				case Core::ShaderInput::LightShadowMatrix4:
				case Core::ShaderInput::LightShadowMatrix5:
				{
					uint n = input->semantic - Core::ShaderInput::LightShadowMatrix0;
					if	(n < light_render_data->shadow_data.GetCount())
						Set(*input->location, light_render_data->shadow_data[n].pmatrix * (light_render_data->shadow_data[n].imatrix * view_item.GetMatrix()));
				}
				break;

				case Core::ShaderInput::InverseShadowMapSize:
				{
					float k = r.pcf_radius / r.gpu_config.shadow_size;
					Set(*input->location, k);
				}
				break;

				case Core::ShaderInput::LightShadowMap0:
				case Core::ShaderInput::LightShadowMap1:
				case Core::ShaderInput::LightShadowMap2:
				case Core::ShaderInput::LightShadowMap3:
				case Core::ShaderInput::LightShadowMap4:
				case Core::ShaderInput::LightShadowMap5:
					Set(*input->location, *r.shadow_map, input->index);
					break;

				case Core::ShaderInput::LightPSSMSliceDistance0:
				case Core::ShaderInput::LightPSSMSliceDistance1:
				case Core::ShaderInput::LightPSSMSliceDistance2:
				case Core::ShaderInput::LightPSSMSliceDistance3:
					if	(light_render_data->shadow_data)
						Set(*input->location, light_render_data->shadow_data[input->semantic - Core::ShaderInput::LightPSSMSliceDistance0].slice_distance);
					break;

				case Core::ShaderInput::ViewToLightMatrix:
					Set(*input->location, l.GetInverseMatrix() * view_item.GetMatrix());
					break;

				case Core::ShaderInput::LightProjectionMap:
					if	(light_render_data->projection_texture.IsValid())
						Set(*input->location, *light_render_data->projection_texture, input->index);
					break;
			}
		}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Shader::GetSemanticInputList(Core::ShaderInput::Semantic semantic)
{
	return Core::ShaderInput::semantic_desc[semantic].category;
}
ShaderInput			*Shader::GetInput(Core::ShaderInput::Semantic semantic) const
{
	uint cat = GetSemanticInputList(semantic);
	for	(uint n = 0; n < input_list[cat].GetCount(); ++n)
		if	(input_list[cat][n].semantic == semantic)
			return &input_list[cat][n];
	return NULL;
}
ShaderInput			*Shader::GetInput(const char *n) const
{
	String name(n);
	for (uint l = 0; l < Core::ShaderInput::CategoryLast; ++l)	// need to check all categories here
		for	(uint n = 0; n < input_list[l].GetCount(); ++n)
			if	(!input_list[l][n].name.IsEmpty() && (input_list[l][n].name == name))
				return &input_list[l][n];
	return NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Shader::Create(Render::ResourceFactory &rf, const Core::Shader &shader)
{
	Free();

	__RASSERT_MSG__(renderer.shader_compiler != NULL, String::Format("No shader compiler available for this renderer ('%s').", renderer.GetName()));

	if	(!renderer.shader_compiler->Compile(shader, *this))
		return false;

	// Solve uniforms and attributes.
	Array <AutoPtr <ShaderInputLocation> > locations(shader.input_list.GetCount());

	uint solved_count[Core::ShaderInput::CategoryLast], n = 0;
	Memory::Set(solved_count, 0, sizeof(uint) * Core::ShaderInput::CategoryLast);
	ListForeachPtr(Core::ShaderInput *, input, shader.input_list)
	{
		uint input_index = GetSemanticInputList(input->semantic);

		locations[n] = NewGPUShaderLocation();
		if	(GetLocation(input->name, *locations[n], input->type))
			solved_count[input_index]++;
		else
			locations[n] = NULL;

		++n;
	}

	uint texture_count = 0;
	for	(uint l = 0; l < Core::ShaderInput::CategoryLast; ++l)
	{
		uint n = 0, i = 0;

		if	(input_list[l].Allocate(solved_count[l]))
			ListForeachPtr(Core::ShaderInput *, input, shader.input_list)
			{
				if	((l != GetSemanticInputList(input->semantic)) || locations[i].IsNull())
				{
					++i;
					continue;
				}

				ShaderInput *gpu_input = &input_list[l][n];
				gpu_input->location = locations[i].Detach();
				gpu_input->Set(input);

				// Allocate texture unit index and load render resource.
				if	(input->type == Core::ShaderInput::Uniform)
					if	(input->ConsumesTextureUnit())
					{
						if	(!input->parm_t.IsEmpty())
							gpu_input->parm_t = rf.LoadTexture(input->parm_t);
						gpu_input->index = texture_count++;
					}

				++i; ++n;
			}
	}
	return true;
}
//------------------------------------------------------------------------------
