#version 330 core

struct Light
{
    vec3 direction;
    vec3 color;
};

struct ColorMap
{
    bool has_tex;
    vec4 color;
};

uniform ColorMap map_albedo;    uniform sampler2D map_albedo_tex;
uniform ColorMap map_diffuse;   uniform sampler2D map_diffuse_tex;
uniform ColorMap map_specular;  uniform sampler2D map_specular_tex;
uniform ColorMap map_normals;   uniform sampler2D map_normals_tex;
uniform ColorMap map_roughness; uniform sampler2D map_roughness_tex;
uniform ColorMap map_metallic;  uniform sampler2D map_metallic_tex;
uniform ColorMap map_ao;        uniform sampler2D map_ao_tex;
uniform ColorMap map_ambient;   uniform sampler2D map_ambient_tex;
uniform ColorMap map_emissive;  uniform sampler2D map_emissive_tex;

#define sample_colormap(ColorMap_, uv_) \
        (ColorMap_.has_tex ? texture( ColorMap_##_tex, uv_ ) : ColorMap_.color)


in vec3 out_normal;
in vec3 out_tangent;
in vec3 out_binormal;
in vec2 out_texcoord;
in vec3 out_worldpos;
in vec3 out_to_camera;


uniform float specular_shininess;

uniform float skysphere_rotation;
uniform float skysphere_mip_count;
uniform float exposure;

uniform vec3 camera_position;
uniform Light lights[3];

uniform vec4 global_ambient;

uniform bool has_tex_skysphere;
uniform bool has_tex_skyenv;

uniform sampler2D tex_skysphere;
uniform sampler2D tex_skyenv;

out vec4 frag_color;

const float PI = 3.1415926536;

vec2 sphere_to_polar( vec3 normal )
{
    normal = normalize( normal );
    return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, acos( normal.y ) / PI );
}

vec3 sample_irradiance_fast( vec3 normal )
{
    // Sample the irradiance map if it exists, otherwise fall back to blurred reflection map.
    if ( has_tex_skyenv )
    {
        vec2 polar = sphere_to_polar( normal );
        // HACK: Sample a smaller mip here to avoid high frequency color variations on detailed normal
        //       mapped areas.
        float miplevel = 5.5; // tweaked for a 360x180 irradiance texture
        return textureLod( tex_skyenv, polar, miplevel ).rgb * exposure;
    }
    else
    {
        vec2 polar = sphere_to_polar( normal );
        return textureLod( tex_skysphere, polar, 0.80 * skysphere_mip_count ).rgb * exposure;
    }
}

float calculate_specular( vec3 normal, vec3 light_direction )
{
    vec3 V = normalize( out_to_camera );
    vec3 L = -normalize( light_direction );
    vec3 H = normalize( V + L );
    float rdotv = clamp( dot( normal, H ), 0.0, 1.0 );
    float total_specular = pow( rdotv, specular_shininess );

    return total_specular;
}

void main(void)
{
    vec4 specular;
    if( map_specular.has_tex ) {
        specular = sample_colormap( map_specular, out_texcoord );
    } else {
        float roughness = 1.0;
        float metallic = 0.0;

        if( map_metallic.has_tex && map_roughness.has_tex ) {
            metallic = sample_colormap( map_metallic, out_texcoord ).x;
            roughness = sample_colormap( map_roughness, out_texcoord ).x;
        }
        else if( map_roughness.has_tex ) {
            //< @r-lyeh, metalness B, roughness G, (@todo: self-shadowing occlusion R; for now, any of R/B are metallic)
            metallic = sample_colormap( map_roughness, out_texcoord ).b + sample_colormap( map_roughness, out_texcoord ).r;
            roughness = sample_colormap( map_roughness, out_texcoord ).g;
        }

        float gloss = metallic * (1.0 - roughness);
        specular = vec4(gloss);
    }

    vec4 baseColor_alpha;
    if ( map_albedo.has_tex )
        baseColor_alpha = sample_colormap( map_albedo, out_texcoord );
    else
        baseColor_alpha = sample_colormap( map_diffuse, out_texcoord );

    vec3 diffuse = baseColor_alpha.xyz;
    float alpha = baseColor_alpha.w;
    vec3 ambient = sample_colormap( map_ambient, out_texcoord ).xyz;
    vec3 normals = normalize(texture( map_normals_tex, out_texcoord ).xyz * vec3(2.0) - vec3(1.0));

    vec3 normal = out_normal;
    if ( map_normals.has_tex )
    {
        // Mikkelsen's tangent space normal map decoding. See http://mikktspace.com/ for rationale.
        vec3 bi = cross( out_normal, out_tangent );
        vec3 nmap = normals.xyz;
        normal = nmap.x * out_tangent + nmap.y * bi + nmap.z * out_normal;
    }

    normal = normalize( normal );

    vec3 irradiance = sample_irradiance_fast( normal );
    ambient *= irradiance;

    vec3 color = ambient * global_ambient.rgb;
    for ( int i = 0; i < lights.length(); i++ )
    {
        float ndotl = clamp( dot( normal, -normalize( lights[ i ].direction ) ), 0.0, 1.0 );

        vec3 specular = specular.rgb * calculate_specular( normal, lights[ i ].direction ) * specular.a;
        color += (diffuse + specular) * ndotl * lights[ i ].color;
    }

    color += sample_colormap( map_emissive, out_texcoord ).rgb;

    frag_color = vec4( pow( color * exposure, vec3(1. / 2.2) ), alpha );
}