#version 330 core

precision highp float;
precision highp sampler2D;

in vec4 v_normal;
in vec4 color;
in vec3 light_direction;
in vec4 v_light_space_position;

uniform int render_normals_bool;
uniform int render_shadows_bool;

uniform float shadow_max_bias;
uniform float shadow_min_bias;
uniform float shadow_fac_bias;
uniform int shadow_step_size;
uniform int shadow_step_count;

uniform highp sampler2D shadow_map;

out vec4 FragColor;

const float ambient_strenght = 0.3;
const float diffuse_strenght = 0.7;

/**
 * Calculates the shadow factor (0.0 for shadow, 1.0 for lit).
 */
float calculate_shadow() {
        float cosTheta = dot(normalize(v_normal.xyz), normalize(light_direction));
        float bias = shadow_fac_bias * tan(acos(cosTheta));
        bias = clamp(bias, shadow_min_bias, shadow_max_bias);
        // float bias = max(shadow_max_bias * (1.0 - dot_product), shadow_min_bias);

        // 1. Perspective divide to get Normalized Device Coordinates (NDC)
        vec3 proj_coords = v_light_space_position.xyz / v_light_space_position.w;
        proj_coords = proj_coords * 0.5 + 0.5;

        float current_depth = proj_coords.z;

        // Deal with coords outside texture or infinite distance (no objects found)
        if (proj_coords.x > 1.0 || proj_coords.x < 0.0 || proj_coords.y > 1.0 || proj_coords.y < 0.0 || current_depth > 1.0) {
                return 1.0;
        }

        vec2 texelSize = 1.0 / textureSize(shadow_map, 0);

        float proj_depth = texture(shadow_map, proj_coords.xy).r;
        // int show_shadow = current_depth - bias > proj_depth ? 0 : 1;
        // if (show_shadow == 1) {
        //         return 1.0;
        // }
        float shadow = 0.0;

        int range = (shadow_step_count - 1) / 2;
        for (int x = -range; x <= range; x++)
        {
                for (int y = -range; y <= range; y++)
                {
                        float pcfDepth = texture(shadow_map, proj_coords.xy + (vec2(float(x), float(y)) * texelSize) * shadow_step_size).r;
                        shadow += current_depth - bias > pcfDepth ? 0.0 : 1.0;
                }
        }
        shadow /= float(shadow_step_count * shadow_step_count);

        return shadow;
}

void main() {
        float diff = max(dot(normalize(vec3(v_normal.x, v_normal.y, v_normal.z)), normalize(light_direction)), 0) * diffuse_strenght;
        vec4 app_color;
        if (render_normals_bool == 1) {
                app_color = normalize(abs(v_normal));
        }
        else {
                app_color = color / 256.0;
        }

        if (render_shadows_bool == 1) {
                float shadow_factor = calculate_shadow();

                float lighting = ambient_strenght + diff * shadow_factor;

                FragColor = app_color * lighting;
        }
        else {
                float lighting = ambient_strenght + diff;
                FragColor = app_color * lighting;
        }
}
