#include "client/render/shader/WorldShader.h"

namespace blitz {
namespace shader {

#ifdef __ANDROID__
static const char vertexSource[] =
	R"(#version 300 es

precision mediump float;

layout(location = 0) in vec3 position;
layout(location = 1) in vec2 textureCoords;
layout(location = 2) in vec3 normal;

uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

out vec3 toLightVector;
out vec3 toCameraVector;
out vec3 surfaceNormal;

out vec2 pass_textureCoords;

void main(void){
	vec3 lightPosition = vec3(0, 15, 0);

   	gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);

	vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;

	toLightVector = vec3(0, 15, 0) - position;
	toCameraVector = camPos - position;

	surfaceNormal = normal;

	pass_textureCoords = textureCoords;
}
)";

static const char fragmentSource[] =
	R"(#version 300 es

precision mediump float;

uniform sampler2D textureSampler;

in vec3 toLightVector;
in vec3 toCameraVector;
in vec3 surfaceNormal;

in vec2 pass_textureCoords;

out vec4 out_color;

void main(void){

	float shineDamper = 10.0;
	float reflectivity = 1.0;

    vec3 unitNormal = normalize(surfaceNormal);
	vec3 unitLightVector = normalize(toLightVector);
	vec3 unitCamVector = normalize(toCameraVector);

	vec3 lightDirection = -unitLightVector;
	vec3 reflectedLightDirection = reflect(lightDirection, unitNormal);

	float diffuse = max(0.2, dot(unitNormal, unitLightVector));

	float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector));
	float specular = pow(specularFactor, shineDamper) * reflectivity;

	float brightness = diffuse + specular;

	out_color = vec4(1.0, 1.0, 1.0, 1.0) * brightness * texture(textureSampler, pass_textureCoords);

}
)";
#else
static const char vertexSource[] = R"(
#version 330

layout(location = 0) in vec3 position;
layout(location = 1) in vec2 textureCoords;
layout(location = 2) in vec3 normal;

uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

uniform vec3 lightPosition;

out vec3 toLightVector;
out vec3 toCameraVector;
out vec3 surfaceNormal;

out vec2 pass_textureCoords;

void main(void){
	gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);

	vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;

	toLightVector = lightPosition - position;
	toCameraVector = camPos - position;

	surfaceNormal = normal;

	pass_textureCoords = textureCoords;
}
)";

static const char fragmentSource[] = R"(
#version 330

uniform float shineDamper = 10.0;
uniform float reflectivity = 1.0;

uniform sampler2D textureSampler;

in vec3 toLightVector;
in vec3 toCameraVector;
in vec3 surfaceNormal;

in vec2 pass_textureCoords;

out vec4 out_color;

void main(void){

	float lightDistance = length(toLightVector);

	const vec3 attenuation = vec3(0.4, 0.1, 0.01);
	float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance;

	vec3 unitNormal = normalize(surfaceNormal);
	vec3 unitLightVector = normalize(toLightVector);
	vec3 unitCamVector = normalize(toCameraVector);

	vec3 lightDirection = -unitLightVector;
	vec3 reflectedLightDirection = reflect(lightDirection, unitNormal);

	float diffuse = max(0.2, dot(unitNormal, unitLightVector));

	float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector));
	float specular = pow(specularFactor, shineDamper) * reflectivity;

	float brightness = (diffuse + specular) / attenuationFactor;

	out_color = vec4(1.0, 1.0, 1.0, 1.0) * brightness * texture(textureSampler, pass_textureCoords);

}
)";
#endif

WorldShader::WorldShader() : ShaderProgram() {}

void WorldShader::LoadShader() {
	ShaderProgram::LoadProgram(vertexSource, fragmentSource);
}

void WorldShader::GetAllUniformLocation() {
	m_LocationProjection = static_cast<unsigned int>(GetUniformLocation("projectionMatrix"));
	m_LocationLight = static_cast<unsigned int>(GetUniformLocation("lightPosition"));
	m_LocationView = static_cast<unsigned int>(GetUniformLocation("viewMatrix"));
}

void WorldShader::SetProjectionMatrix(const Mat4f& proj) const {
	LoadMat4(m_LocationProjection, proj);
}

void WorldShader::SetViewMatrix(const Mat4f& view) const {
	LoadMat4(m_LocationView, view);
}

void WorldShader::SetLightPosition(const Vec3f& lightPos) const {
	LoadVector(m_LocationLight, lightPos);
}

}  // namespace shader
}  // namespace blitz