#version 330 core
out vec4 FragColor;

//in vec2 TexCoords;

uniform sampler2D dsbPositionTex;
uniform sampler2D dsbNormalTex;
uniform sampler2D dsbAlbedoSpecTex;

struct Light {
    int  type;  // 0 ambient, 1 directional, 2 point, 3 spot, 4 point_atn, 5 spot_atn
    vec3 direction;
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
	
    vec3 atn;
    //float atn_cnst;
    //float atn_lin;
    //float atn_quad;
    
    vec4 cutOffFOV;
    //float cutoffInner;
    //float cutOffSt;
    //float cutOff;
    //float FOV;

    vec2 farClip;
    //float farClip;
    //float farClipSq;
};

uniform int lightNmb;
uniform Light light[1];
uniform bool isBlinnPhong;
uniform mat4 view_proj_light[2];  // projection * view, light source perspective

uniform sampler2D shadowMap;
uniform samplerCube shadowMapCube[4];
uniform samplerCube shadowMapCubeColors;
uniform samplerCube shadowMapCubeColorsMove;
uniform sampler2DArray shadowMapPara;
uniform int ptShdwMethod;

uniform vec3 viewPos;
uniform int dsLgtOut;

uniform vec2 screenSizePix;

void calc_light_dir_and_atn( out vec3 lightDir, out float atn_amb, out float atn_ds, vec3 frag_pos, Light light ){
    lightDir = vec3( 0.0, -1.0, 0.0 );
    atn_amb  = 1.0f;
    atn_ds   = 1.0f;

    float amb_intensity  = 1.0f;
    float spot_intensity = 1.0f;

    if( light.type == 0 ){
        atn_ds   = 0.0f;
    } else if( light.type == 1 ){
        lightDir = light.direction;  // lightDir and light.direction only equal because the directional light has no distance component
        lightDir = normalize( lightDir );
    } else {
        lightDir = frag_pos - light.position;
        float distSq = lightDir.x * lightDir.x + lightDir.y * lightDir.y + lightDir.z * lightDir.z;
        // (farClip.y = farClipSq)
        if( distSq < light.farClip.y ){
            float distance = sqrt( distSq );
            vec3 lightDir_norm = lightDir / distance;
            if( light.type > 3 ){
                // distance based attenuation (atn.x = atn_cst, atn.y = atn_lin, atn.z = atn_quad)
                atn_amb        = 1.0 / ( light.atn.x + ( light.atn.y + ( light.atn.z * distance ) ) * distance );
                atn_ds         = atn_amb;
            }

            if( light.type == 3 || light.type == 5 ){
                // spotlight (soft edges)
                // The lightDir_norm is the position relative to the vsPosWrld while the light[lgt_spt_adj].direction is the pointing direction
                // (cutOffFOV.y = cutOffSt, cutOffFOV.z = cutOff, cutOffFOV.w = FOV)
                float theta    = dot( -lightDir_norm, normalize( -light.direction ) );
                spot_intensity = clamp( ( theta - light.cutOffFOV.z ) / ( light.cutOffFOV.y - light.cutOffFOV.z ), 0.0, 1.0 );
                amb_intensity  = clamp( ( theta - light.cutOffFOV.w ) / ( light.cutOffFOV.y - light.cutOffFOV.w ), 0.0, 1.0 );
                atn_ds  *= spot_intensity;
                atn_amb *= amb_intensity;
            }
            lightDir = lightDir_norm;
        } else {
            atn_amb = 0.0;
            atn_ds  = 0.0;
        }
    }
}

void calc_ambient( out vec3 ambient_out, vec3 light, vec3 material ){
    ambient_out = light * material;
}

void calc_diffuse( out vec3 diffuse_out, vec3 normal, vec3 pixel_pos, vec3 light_dir, vec3 light, vec3 material ){
    float diff  = max( dot( normal, -light_dir ), 0.0 );
    diffuse_out = light * diff * material;
}

void calc_specular( out vec3 specular_out, vec3 normal, vec3 view_dir, vec3 light_dir, vec3 light, vec3 material, float shiny ){
    float viewedReflect;
    if( isBlinnPhong ){
        vec3 halfwayDir = normalize( view_dir - light_dir );  
        viewedReflect   = max( dot( normal, halfwayDir ), 0.0 );
    } else {
        vec3 reflectDir = reflect( light_dir, normal );
        viewedReflect   = max( dot( view_dir, reflectDir ), 0.0 );
    }
    float spec          = pow( viewedReflect, shiny );
    specular_out        = light * spec * material;
}

float ShadowCalculation( vec4 posSrcLgtVP, int shdwNmb ){
    vec3 projCoords = posSrcLgtVP.xyz / posSrcLgtVP.w; // perform perspective divide
    projCoords      = projCoords * 0.5 + 0.5; // transform to [0,1] range
    float shadow    = 0.0;  // keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
    if( projCoords.z <= 1.0 ){
        // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
        float closestDepth = texture(shadowMap, projCoords.xy)[shdwNmb]; 
        float currentDepth = projCoords.z; // get depth of current fragment from light's perspective
        shadow             = (currentDepth - 0.0001) > closestDepth  ? 1.0 : 0.0;
    }
    return shadow;
}

float ShadowCalculationCubemap( vec3 posWrld, Light light, int lgt ){
    vec3 fragToLight   = posWrld - light.position; // get vector between fragment position and light position
    float currentDepth = length( fragToLight ); // now get current linear depth as the length between the fragment and light position
    //float closestDepth = texture( shadowMapCubeStatic, fragToLight )[lgt];
    float closestDepth;
    if( ptShdwMethod == 0 ){
        closestDepth = texture( shadowMapCube[lgt], fragToLight ).r;
    } else if( ptShdwMethod == 1 ){
        closestDepth = texture( shadowMapCubeColorsMove, fragToLight )[lgt];
    } else {
        // calculate the front paraboloid map texture coordinates
        vec3 Pos = fragToLight.xyz;
        Pos     /= currentDepth;

        if( fragToLight.z >= 0.0 )
        {
            //generate texture coords for the front hemisphere
            Pos.z   = 1.0 + Pos.z;
            Pos.xy /= Pos.z;
            Pos.x   = -Pos.x;
            Pos.xy *= -0.5;
            Pos.xy += 0.5;
            closestDepth = texture( shadowMapPara, vec3( Pos.xy, 0 ) )[lgt];
        }
        else
        {
            //generate texture coords for the rear hemisphere
            Pos.z   = 1.0 - Pos.z;
            Pos.xy /= Pos.z;
            Pos.xy *= -0.5;
            Pos.xy += 0.5;
            closestDepth = texture( shadowMapPara, vec3( Pos.xy, 1 ) )[lgt];
        }
    }
    closestDepth      *= light.farClip.x; // closestDepth is currently in linear range between [0,1]. Re-transform back to original value (farClip.x = farClip)
    return (currentDepth - 0.01) > closestDepth ? 1.0 : 0.0;
}

void main(){        
    vec2 TexCoords = gl_FragCoord.xy / screenSizePix;
    // retrieve data from gbuffer
    vec3 dsbPosWrld   = texture(dsbPositionTex,   TexCoords).rgb;
    vec3 dsbNormal    = texture(dsbNormalTex,     TexCoords).rgb;
    vec3 dsbDiffuse   = texture(dsbAlbedoSpecTex, TexCoords).rgb;
    vec3 dsbSpecular  = vec3(texture(dsbAlbedoSpecTex, TexCoords).a);
    
    if( dsLgtOut == 0 ){
        // then calculate lighting as usual
        vec3 result  = vec3(0.0);
        vec3 viewDir = normalize(viewPos - dsbPosWrld);
        vec3 lightDir, ambient, diffuse, specular;
        float atn_amb, atn_ds;

        if( light[0].type == 1 ){
            calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, dsbPosWrld, light[0] );
            calc_ambient( ambient, light[0].ambient, vec3(0.1) );
            calc_diffuse(  diffuse,  dsbNormal, dsbPosWrld, lightDir, light[0].diffuse,  dsbDiffuse );
            calc_specular( specular, dsbNormal, viewDir,    lightDir, light[0].specular, dsbSpecular, 16.0 );
            FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
        } else if( light[0].type == 4 ){
            calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, dsbPosWrld, light[0] );
            float shadow = ShadowCalculationCubemap( dsbPosWrld, light[0], lightNmb );
            atn_ds *= ( 1.0 - shadow );

            calc_ambient( ambient, light[0].ambient, vec3(0.1) );
            if( atn_ds > 0.0 ){
                calc_diffuse(  diffuse,  dsbNormal, dsbPosWrld, lightDir, light[0].diffuse,  dsbDiffuse );
                calc_specular( specular, dsbNormal, viewDir,    lightDir, light[0].specular, dsbSpecular, 16.0 );
            }
            FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
        } else if( light[0].type == 5 ){
            calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, dsbPosWrld, light[0] );
            float shadow = ShadowCalculation( view_proj_light[lightNmb] * vec4( dsbPosWrld, 1.0 ), lightNmb );
            atn_ds *= ( 1.0 - shadow );

            calc_ambient( ambient, light[0].ambient, vec3(0.1) );
            if( atn_ds > 0.0 ){
                calc_diffuse(  diffuse,  dsbNormal, dsbPosWrld, lightDir, light[0].diffuse,  dsbDiffuse );
                calc_specular( specular, dsbNormal, viewDir,    lightDir, light[0].specular, dsbSpecular, 16.0 );
            }
            FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
        }
    } else if( dsLgtOut == 1 ){
        FragColor   = vec4( dsbPosWrld, 1.0 );
    } else if( dsLgtOut == 2 ){
        FragColor   = vec4( dsbNormal, 1.0 );
    } else if( dsLgtOut == 3 ){
        FragColor   = vec4( dsbDiffuse, 1.0 );
    } else {
        FragColor   = vec4( dsbSpecular, 1.0 );
    }
}