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

namespace blitz {
namespace shader {

static const std::string vertexSource = ShaderProgram::GetShaderHeader() + R"(

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 = normalize(normal);

	pass_textureCoords = textureCoords;
}
)";

static const std::string fragmentSource = ShaderProgram::GetShaderHeader() + R"(

uniform sampler2D textureSampler;

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

in vec2 pass_textureCoords;

out vec4 out_color;

void main(void){

	const float shineDamper = 10.0;
	const float reflectivity = 1.0;

	float lightDistance = length(toLightVector);

	const vec3 attenuation = vec3(0.3, 0.03, 0);
	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 = brightness * texture(textureSampler, pass_textureCoords);
	out_color.w = 1.0;

}
)";

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

bool WorldShader::LoadShader() {
	return 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