#version 400 core

#define PI 3.14159265359
const float PI2 = PI * 2.0;

uniform sampler2D colorBuffer;
uniform sampler2D depthBuffer;
uniform sampler2D normalBuffer;
uniform sampler2D pbrBuffer;
uniform sampler2D occlusionBuffer;
uniform bool haveOcclusionBuffer;

uniform vec2 resolution;
uniform mat4 viewMatrix;
uniform mat4 invViewMatrix;
uniform mat4 invProjectionMatrix;
uniform float zNear;
uniform float zFar;

uniform vec4 fogColor;
uniform float fogStart;
uniform float fogEnd;

in vec2 texCoord;

#include <unproject.glsl>
#include <gamma.glsl>
#include <fresnel.glsl>
#include <envMapEquirect.glsl>

uniform vec4 ambientVector;
uniform sampler2D ambientTexture;

vec3 ambientEquirectangularMap(in vec3 wN, in float perceptualRoughness)
{
    ivec2 envMapSize = textureSize(ambientTexture, 0);
    float resolution = float(max(envMapSize.x, envMapSize.y));
    float lod = log2(resolution) * perceptualRoughness;
    return textureLod(ambientTexture, envMapEquirect(wN), lod).rgb;
}

uniform samplerCube ambientTextureCube;
vec3 ambientCubemap(in vec3 wN, in float perceptualRoughness)
{
    ivec2 envMapSize = textureSize(ambientTextureCube, 0);
    float resolution = float(max(envMapSize.x, envMapSize.y));
    float lod = log2(resolution) * perceptualRoughness;
    return textureLod(ambientTextureCube, wN, lod).rgb;
}

uniform int ambientFunc;

uniform float ambientEnergy;

uniform sampler2D ambientBRDF;
uniform bool haveAmbientBRDF;

layout(location = 0) out vec4 fragColor;

void main()
{
    vec4 col = texture(colorBuffer, texCoord);
    if (col.a < 1.0)
        discard;
    
    vec3 albedo = toLinear(col.rgb);
    
    float depth = texture(depthBuffer, texCoord).x;
    vec3 eyePos = unproject(invProjectionMatrix, vec3(texCoord, depth));
    vec3 worldPos = (invViewMatrix * vec4(eyePos, 1.0)).xyz;
    vec3 N = normalize(texture(normalBuffer, texCoord).rgb);
    vec3 E = normalize(-eyePos);
    vec3 R = reflect(E, N);
    float NE = max(dot(N, E), 0.0);
    
    vec3 worldCamPos = (invViewMatrix[3]).xyz;
    vec3 worldE = normalize(worldPos - worldCamPos);
    vec3 worldN = normalize((vec4(N, 0.0) * viewMatrix).xyz);
    vec3 worldR = reflect(worldE, worldN);
    
    vec4 pbr = texture(pbrBuffer, texCoord);
    float roughness = pbr.r;
    float metallic = pbr.g;
    float reflectivity = 1.0;
    
    float occlusion = haveOcclusionBuffer? texture(occlusionBuffer, texCoord).r : 1.0;
    
    vec3 f0 = mix(vec3(0.04), albedo, metallic);

    // Ambient light
    // TODO: support separate irradiance map
    vec3 irradiance, reflection;
    if (ambientFunc == 1)
    {
        irradiance = ambientEquirectangularMap(worldN, 0.99);
        reflection = ambientEquirectangularMap(worldR, sqrt(roughness)) * reflectivity;
    }
    else if (ambientFunc == 2)
    {
        irradiance = ambientCubemap(worldN, 0.99);
        reflection = ambientCubemap(worldR, sqrt(roughness)) * reflectivity;
    }
    else
    {
        irradiance = toLinear(ambientVector.rgb);
        reflection = irradiance * reflectivity;
    }
    
    vec3 F = clamp(fresnelRoughness(NE, f0, roughness), 0.0, 1.0);
    vec3 kD = (1.0 - F) * (1.0 - metallic);
    vec2 brdf = haveAmbientBRDF? texture(ambientBRDF, vec2(NE, 1.0 - roughness)).rg : vec2(1.0, 0.0);
    vec3 diffuse = kD * irradiance * albedo;
    vec3 specular = reflection * clamp(F * brdf.x + brdf.y, 0.0, 1.0);
    vec3 radiance = (diffuse + specular) * occlusion * ambientEnergy;
    
    // Fog
    float linearDepth = abs(eyePos.z);
    float fogFactor = clamp((fogEnd - linearDepth) / (fogEnd - fogStart), 0.0, 1.0);
    radiance = mix(toLinear(fogColor.rgb), radiance, fogFactor);

    fragColor = vec4(radiance, 1.0);
}
