#version 460

in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec3 tangent;
in vec3 bitangent;
in vec4 FragPosLightSpace;
in float roughness;
flat in int tex_layer;
out vec4 color;
layout (binding = 0) uniform sampler2D shadowMap;
layout (binding = 1) uniform sampler2DArray samp;
layout (binding = 2) uniform sampler2DArray normMap;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 ambientColor;
uniform vec3 sunlightDir;
uniform vec3 cameraPos;
uniform bool shader_on;
uniform int shadowMode;
uniform float specularStrength;
uniform float lightSizeUV; 
uniform float minRadius;
uniform float maxRadius;
uniform bool enablePBR;
uniform bool flipY;

uniform int renderDistance;
uniform vec3 skyColor;

const vec2 poissonDisk32[32] = vec2[](
    vec2(-0.975402, -0.071138),
    vec2(-0.920347, -0.411420),
    vec2(-0.883908,  0.217872),
    vec2(-0.815442, -0.879125),
    vec2(-0.775043,  0.543896),
    vec2(-0.698126, -0.227570),
    vec2(-0.682433,  0.801894),
    vec2(-0.563905,  0.021517),
    vec2(-0.443233, -0.975116),
    vec2(-0.412231,  0.361307),
    vec2(-0.264969, -0.418930),
    vec2(-0.241888,  0.997065),
    vec2(-0.094184, -0.929389),
    vec2(-0.019101,  0.680997),
    vec2( 0.143832, -0.141008),
    vec2( 0.199841,  0.786414),

    vec2( 0.344959,  0.293878),
    vec2( 0.443233, -0.475115),
    vec2( 0.537430, -0.473734),
    vec2( 0.589349,  0.569135),
    vec2( 0.674281, -0.178897),
    vec2( 0.791975,  0.190902),
    vec2( 0.815442,  0.879125),
    vec2( 0.896420, -0.613392),
    vec2( 0.945586, -0.768907),
    vec2( 0.974844,  0.756484),
    vec2(-0.814100,  0.914376),
    vec2(-0.382775,  0.276768),
    vec2(-0.915886,  0.457714),
    vec2( 0.537800,  0.912200),
    vec2(-0.620000, -0.650000),
    vec2( 0.120000, -0.780000)
);

const vec2 poissonDisk16[16] = vec2[](
    vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
    vec2(-0.09418410, -0.92938870), vec2(0.34495938, 0.29387760),
    vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
    vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
    vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
    vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
    vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
    vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
);
const vec2 poissonDisk8[8] = vec2[](
    vec2( 0.1440,  0.7659), vec2(-0.5761,  0.4479),
    vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
    vec2(-0.1276,  0.1657), vec2(-0.0649, -0.0165),
    vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
);
uniform int samples;
float random(vec3 seed) {
    return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}

float FindBlocker(vec2 uv,
                  float zReceiver,
                  vec2 texelSize,
                  float bias,
                  float lightSizeUV)
{
    float avgDepth = 0.0;
    int blockers = 0;

    float searchRadius = lightSizeUV * 0.5;

    for(int i = 0; i < samples; i++)
    {    
        vec2 offset;
        if (samples == 32) {
            offset =
            poissonDisk32[i]
            * searchRadius
            * texelSize;
        } else if (samples == 16) {
            offset =
            poissonDisk16[i]
            * searchRadius
            * texelSize;
        } else if (samples == 8) {
            offset =
            poissonDisk8[i]
            * searchRadius
            * texelSize;
        } else {
            offset =
            poissonDisk32[i]
            * searchRadius
            * texelSize;
        }
        float depth =
            texture(shadowMap, uv + offset).r;

        if(depth < zReceiver - bias)
        {
            avgDepth += depth;
            blockers++;
        }
    }

    if(blockers == 0)
        return -1.0;

    return avgDepth / blockers;
}

float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
{

    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

    projCoords = projCoords * 0.5 + 0.5;
    if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
        projCoords.y < 0.0 || projCoords.y > 1.0 ||
        projCoords.z < 0.0 || projCoords.z > 1.0) {
        return 0.0;
    }
    float currentDepth = projCoords.z;
    vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
    float shadow = 0.0;

    float bias =
    clamp(
        0.001 * (1.0 - dot(norm, lightDir)),
        0.0003,
        0.003
    );

    if (shadowMode == 0) {
        vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
        float angle = random(seed) * 6.2831853;; // 2*PI
        float s = sin(angle), c = cos(angle);
        mat2 rot = mat2(c, -s, s, c);
        //float radius = 0.7;
        float radius = mix(1.0, 4.0, currentDepth);
        
        for (int i = 0; i < samples; ++i) {
            vec2 offset;
            if (samples == 32) {
                offset = rot * poissonDisk32[i] * radius * texelSize;
            } else if (samples == 16) {
                offset = rot * poissonDisk16[i] * radius * texelSize;
            } else if (samples == 8) {
                offset = rot * poissonDisk8[i] * radius * texelSize;
            } else {
               offset = rot * poissonDisk32[i] * radius * texelSize;
            }
            float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
            shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
        }
        shadow /= float(samples);
    } else if (shadowMode == 1) {
        for (int x = -1; x <= 1; ++x) {
            for (int y = -1; y <= 1; ++y) {
                vec2 offset = vec2(x, y) * texelSize;
                float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
                shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
            }
        }
        shadow /= 9.0;
    } else if (shadowMode == 2) {
        // pcf off
        float pcfDepth =
        texture(shadowMap, projCoords.xy).r;

        shadow =
        currentDepth - bias > pcfDepth
            ? 1.0
            : 0.0;
    } else if (shadowMode == 3) {
        float avgBlockerDepth =
            FindBlocker(
                projCoords.xy,
                currentDepth,
                texelSize,
                bias,
                lightSizeUV
            );

        if(avgBlockerDepth < 0.0)
        {
            return 0.0;
        }

        vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
        float angle = random(seed) * 6.2831853;; // 2*PI
        float s = sin(angle), c = cos(angle);
        mat2 rot = mat2(c, -s, s, c);
        /*
        float penumbraRatio = (currentDepth - avgBlockerDepth);
        float radius = clamp(
            penumbraRatio * lightSizeUV,
            minRadius,
            maxRadius
        );
        */
        float radius =
            mix(
                minRadius,
                maxRadius,
                smoothstep(
                    0.0,
                    0.05,
                    currentDepth - avgBlockerDepth
                )
            );
        for (int i = 0; i < samples; ++i) {
            vec2 offset;
            if (samples == 32) {
                offset = rot * poissonDisk32[i] * radius * texelSize;
            } else if (samples == 16) {
                offset = rot * poissonDisk16[i] * radius * texelSize;
            } else if (samples == 8) {
                offset = rot * poissonDisk8[i] * radius * texelSize;
            } else {
               offset = rot * poissonDisk32[i] * radius * texelSize;
            }
            float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
            shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
        }
        shadow /= float(samples);

    } else {
        float pcfDepth =
        texture(shadowMap, projCoords.xy).r;

        shadow =
        currentDepth - bias > pcfDepth
            ? 1.0
            : 0.0;
    }

   
    return shadow;
}

vec3 calcNewNormal() {
    mat3 TBN = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
    vec3 retrievedNormal = texture(normMap, vec3(tc, tex_layer)).xyz;       
    retrievedNormal = retrievedNormal * 2.0 - 1.0;
    if (flipY) {
        retrievedNormal.y = -retrievedNormal.y;
    }
    vec3 newNormal = TBN * retrievedNormal;
    return normalize(newNormal);
}

void main(void) {
    vec4 objectColor = texture(samp, vec3(tc, tex_layer));

    if (objectColor.a < 0.8) {
        discard;
    }
    if (!shader_on) {
        color = objectColor;
        return;
    }
   

    vec3 lightDir = normalize(-sunlightDir);
    vec3 norm; 
    if (enablePBR) {
        norm = calcNewNormal();
    } else {
        norm = normalize(normal);
    }


    vec3 V =
        normalize(cameraPos - vert_pos);

    vec3 H = 
        normalize(lightDir + V);

    vec3 ambient = ambientStrength * ambientColor;
    
    float diff = max(dot(norm, lightDir), 0.0);
    
    vec3 diffuse = diff * sunlightColor;
    
    float r =
        clamp(roughness, 0.0, 1.0);

    float shininess =
        mix(
            512.0,
            4.0,
            r
        );
    float ks =
        mix(
            0.8,
            0.02,
            r
        );

    float spec = 0.0;

    if(diff > 0.0)
    {
        spec =
            ks *
            pow(
                max(dot(norm,H),0.0),
                shininess
            );
    }



    
    vec3 specular = spec * sunlightColor * specularStrength;

    float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);  
    
    // fog
    float dist = length(cameraPos - vert_pos);
    vec4 fogColor = vec4(skyColor, 1.0);
    float fogStart = renderDistance * 16 * 0.9;
    float fogEnd   = renderDistance * 16;

    float fogFactor = smoothstep(fogEnd, fogStart, dist);
    color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb + (1.0-shadow) * specular * objectColor.rgb, objectColor.a);
    
    color = mix(fogColor, color, fogFactor);
    //color = vec4(normal * 0.5 + 0.5, 1.0);
    //color = vec4(tangent * 0.5 + 0.5, 1.0);;
    //color = vec4(norm * 0.5 + 0.5, 1.0);
    //color = vec4(calcNewNormal(), 1.0);
}
