#pragma once

#include "GlslShader.h"

#include "GlInclude.h"


namespace lib_gl
{


//! Phongシェーダ
class GlslPhongShader
{
public:
	bool Create(bool bUseMultilightSource=false);
	void Release(void);

	void BeginShader(void) { m_ProgramCore.BeginShader(); }
	void EndShader(void)   { m_ProgramCore.EndShader();   }

	bool   IsInitialized(void) const { return m_ProgramCore.IsInitialized(); }
	GLuint GetID(void)         const { return m_ProgramCore.GetID();         }

	void SetNumLights(int NumLight)
	{
		glUniform1i( glGetUniformLocation( GetID() , "num_lights" ) , NumLight );
	}

	void SetTextureObject(int TexObjIdx)
	{
		glUniform1i( glGetUniformLocation( GetID() , "tex_color" ) , TexObjIdx );
	}

	void SetEnableTexture(bool enable)
	{
		glUniform1i( glGetUniformLocation( GetID() , "enable_texture" ) , enable ? 1 : 0 );
	}

	void SetEnableDoubleSide(bool enable)
	{
		glUniform1i( glGetUniformLocation( GetID() , "enable_doubleside" ) , enable ? 1 : 0 );
	}

	void SetEnableLighting(bool enable)
	{
		glUniform1i( glGetUniformLocation( GetID() , "enable_lighting" ) , enable ? 1 : 0 );
	}

public:
	static const char* GetVertShaderCode(void);
	static const char* GetFragShaderCode(void);
	static const char* GetMultiLightFragShaderCode(void);

protected:
	bool CreateVertexShader(GlslShader& shaer);
	bool CreateFragmentShader(GlslShader& shaer, bool bUseMultilightSource);


protected:
	GlslProgram m_ProgramCore;
};



// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// シェーダ生成.
// bUseMultilightSource=trueで、複数のライトを使用する
inline
bool GlslPhongShader::Create(bool bUseMultilightSource)
{
	Release();

	GlslShader vert;
	if(!CreateVertexShader(vert))
		return false;

	GlslShader frag;
	if(!CreateFragmentShader(frag, bUseMultilightSource))
		return false;

	if( !m_ProgramCore.Create( vert , frag ) )
		return false;

	return true;
}

inline
void GlslPhongShader::Release(void)
{
	m_ProgramCore.Release();
}

inline
bool GlslPhongShader::CreateVertexShader(GlslShader& shaer)
{
	const char* src_vert = GetVertShaderCode();
	size_t len_vert = strlen( src_vert );
	if( !shaer.CreateFromSrc( GL_VERTEX_SHADER , src_vert , len_vert ) )
		return false;

	return true;
}

inline
bool GlslPhongShader::CreateFragmentShader(GlslShader& shaer, bool bUseMultilightSource)
{
	const char* src_frag;
	if( bUseMultilightSource )
		src_frag = GetMultiLightFragShaderCode();
	else
		src_frag = GetFragShaderCode();

	size_t len_frag = strlen( src_frag );
	if( !shaer.CreateFromSrc( GL_FRAGMENT_SHADER , src_frag , len_frag ) )
		return false;

	return true;
}

inline
const char* GlslPhongShader::GetVertShaderCode(void)
{
	static const char* shader_src_vert =
		"varying vec3 v_position;\n"
		"varying vec3 v_normal;\n"
		"\n"
		"void main(void)\n"
		"{\n"
		"	v_position = vec3(gl_ModelViewMatrix * gl_Vertex);\n"
		"	v_normal   = normalize(gl_NormalMatrix * gl_Normal);\n"
		"\n"
		"	gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;\n"
		"	gl_Position = ftransform();\n"
		"\n"
		"	gl_FrontColor = gl_Color;\n"
		"\n"
		"	gl_ClipVertex= gl_ModelViewMatrix * gl_Vertex;\n"
		"}\n"
	;

	return shader_src_vert;
}

// ライト１個限定版
inline
const char* GlslPhongShader::GetFragShaderCode(void)
{
	static const char* shader_src_frag =
		"const int num_lights = 1;\n"
		"\n"
		"uniform bool enable_texture = false;\n"
		"uniform bool enable_doubleside = false;\n"
		"uniform bool enable_lighting = true;\n"
		"\n"
		"uniform sampler2D tex_color;\n"
		"\n"
		"\n"
		"varying vec3 v_position;\n"
		"varying vec3 v_normal;\n"
		"\n"
		"\n"
		"\n"
		"vec4 GetPhongReflection(vec3 N, vec3 L, vec3 V, int light_idx)\n"
		"{\n"
		"	if(enable_doubleside)\n"
		"	{\n"
		"		if(!gl_FrontFacing)\n"
		"			N = -N;\n"
		"	}\n"
		"\n"
		"	float LN = dot(L, N);\n"
		"	if(LN <= 0.0)\n"
		"		return vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	vec4 ref_diffuse = gl_FrontLightProduct[light_idx].diffuse * LN;\n"
		"\n"
		"	vec4 br = ref_diffuse;\n"
		"\n"
		"	vec3 H = normalize(L + V);\n"
		"	float NH = dot(N, H);\n"
		"	if(NH > 0.0)\n"
		"	{\n"
		"		float NHP = pow(NH, gl_FrontMaterial.shininess);\n"
		"\n"
		"		vec4 ref_spec = gl_FrontLightProduct[light_idx].specular * NHP;\n"
		"		br += ref_spec;\n"
		"	}\n"
		"\n"
		"	return br;\n"
		"}\n"
		"\n"
		"vec4 GetPhongReflectionSum(void)\n"
		"{\n"
		"	vec4 ret = vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	vec3 N = normalize(v_normal);\n"
		"\n"
		"	for(int i = 0; i < num_lights; ++i)\n"
		"	{\n"
		"		vec3  light_xyz = gl_LightSource[i].position.xyz;\n"
		"		float light_w   = gl_LightSource[i].position.w;\n"
		"		vec3 L = normalize( light_xyz - v_position * light_w );\n"
		"		vec3 V = normalize( -v_position );\n"
		"\n"
		"		vec4 br = GetPhongReflection(N, L, V, i);\n"
		"\n"
		"		ret += br;\n"
		"	}\n"
		"\n"
		"	vec4 ref_amb = gl_FrontMaterial.ambient * gl_LightModel.ambient;\n"
		"	for(int i = 0; i < num_lights; ++i)\n"
		"	{\n"
		"		ref_amb += gl_FrontLightProduct[i].ambient;\n"
		"	}\n"
		"\n"
		"	ret += ref_amb;\n"
		"\n"
		"	ret += gl_FrontMaterial.emission;\n"
		"\n"
		"	ret.x = min(ret.x, 1.0);\n"
		"	ret.y = min(ret.y, 1.0);\n"
		"	ret.z = min(ret.z, 1.0);\n"
		"\n"
		"	ret.w = gl_FrontMaterial.diffuse.w;\n"
		"\n"
		"	if(enable_texture)\n"
		"	{\n"
		"		vec4 t_color = texture2DProj( tex_color , gl_TexCoord[0] );\n"
		"\n"
		"		ret *= t_color;\n"
		"	}\n"
		"\n"
		"	return ret;\n"
		"}\n"
		"\n"
		"vec4 GetConstantColor(void)\n"
		"{\n"
		"	if(enable_texture)\n"
		"	{\n"
		"		return gl_Color * texture2DProj(tex_color, gl_TexCoord[0]);\n"
		"	}\n"
		"	else\n"
		"	{\n"
		"		return gl_Color;\n"
		"	}\n"
		"}\n"
		"\n"
		"\n"
		"\n"
		"void main (void)\n"
		"{\n"
		"	gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	if(enable_lighting)\n"
		"		gl_FragColor += GetPhongReflectionSum();\n"
		"	else\n"
		"		gl_FragColor += GetConstantColor();\n"
		"}\n"
	;

	return shader_src_frag;
}

// 複数のライトを使用できる版+テクスチャ対応
inline
const char* GlslPhongShader::GetMultiLightFragShaderCode(void)
{
	static const char* shader_src_frag =
		"uniform int num_lights = 1;\n"
		"\n"
		"uniform bool enable_texture = false;\n"
		"uniform bool enable_doubleside = false;\n"
		"uniform bool enable_lighting = true;\n"
		"\n"
		"uniform sampler2D tex_color;\n"
		"\n"
		"\n"
		"varying vec3 v_position;\n"
		"varying vec3 v_normal;\n"
		"\n"
		"\n"
		"\n"
		"vec4 GetPhongReflection(vec3 N, vec3 L, vec3 V, int light_idx)\n"
		"{\n"
		"	if(enable_doubleside)\n"
		"	{\n"
		"		if(!gl_FrontFacing)\n"
		"			N = -N;\n"
		"	}\n"
		"\n"
		"	float LN = dot(L, N);\n"
		"	if(LN <= 0.0)\n"
		"		return vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	vec4 ref_diffuse = gl_FrontLightProduct[light_idx].diffuse * LN;\n"
		"\n"
		"	vec4 br = ref_diffuse;\n"
		"\n"
		"	vec3 H = normalize(L + V);\n"
		"	float NH = dot(N, H);\n"
		"	if(NH > 0.0)\n"
		"	{\n"
		"		float NHP = pow(NH, gl_FrontMaterial.shininess);\n"
		"\n"
		"		vec4 ref_spec = gl_FrontLightProduct[light_idx].specular * NHP;\n"
		"		br += ref_spec;\n"
		"	}\n"
		"\n"
		"	return br;\n"
		"}\n"
		"\n"
		"vec4 GetPhongReflectionSum(void)\n"
		"{\n"
		"	vec4 ret = vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	vec3 N = normalize(v_normal);\n"
		"\n"
		"	for(int i = 0; i < num_lights; ++i)\n"
		"	{\n"
		"		vec3  light_xyz = gl_LightSource[i].position.xyz;\n"
		"		float light_w   = gl_LightSource[i].position.w;\n"
		"		vec3 L = normalize( light_xyz - v_position * light_w );\n"
		"		vec3 V = normalize( -v_position );\n"
		"\n"
		"		vec4 br = GetPhongReflection(N, L, V, i);\n"
		"\n"
		"		ret += br;\n"
		"	}\n"
		"\n"
		"	vec4 ref_amb = gl_FrontMaterial.ambient * gl_LightModel.ambient;\n"
		"	for(int i = 0; i < num_lights; ++i)\n"
		"	{\n"
		"		ref_amb += gl_FrontLightProduct[i].ambient;\n"
		"	}\n"
		"\n"
		"	ret += ref_amb;\n"
		"\n"
		"	ret += gl_FrontMaterial.emission;\n"
		"\n"
		"	ret.x = min(ret.x, 1.0);\n"
		"	ret.y = min(ret.y, 1.0);\n"
		"	ret.z = min(ret.z, 1.0);\n"
		"\n"
		"	ret.w = gl_FrontMaterial.diffuse.w;\n"
		"\n"
		"	if(enable_texture)\n"
		"	{\n"
		"		vec4 t_color = texture2DProj( tex_color , gl_TexCoord[0] );\n"
		"\n"
		"		ret *= t_color;\n"
		"	}\n"
		"\n"
		"	return ret;\n"
		"}\n"
		"\n"
		"vec4 GetConstantColor(void)\n"
		"{\n"
		"	if(enable_texture)\n"
		"	{\n"
		"		return gl_Color * texture2DProj(tex_color, gl_TexCoord[0]);\n"
		"	}\n"
		"	else\n"
		"	{\n"
		"		return gl_Color;\n"
		"	}\n"
		"}\n"
		"\n"
		"\n"
		"\n"
		"void main (void)\n"
		"{\n"
		"	gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n"
		"\n"
		"	if(enable_lighting)\n"
		"		gl_FragColor += GetPhongReflectionSum();\n"
		"	else\n"
		"		gl_FragColor += GetConstantColor();\n"
		"}\n"
	;

	return shader_src_frag;
}


}
