varying vec2 texCoord;
varying vec3 vNormal;
varying vec3 eyePos;

uniform float emission;
uniform vec3 ambientColor;
uniform vec3 specularColor;
uniform float hardness;
uniform float diffuseIntensity;

uniform sampler2D diffuse1;
uniform vec2 scale1;

uniform float mistEnable;
uniform float mistStart;
uniform float mistDistance;
uniform float mistIntensity;
uniform float mistType;
uniform vec3 mistColor;

#define maxLights 16

uniform int lightCount;
uniform int lightType[maxLights];
uniform vec3 lightColor[maxLights];
uniform vec3 lightPosition[maxLights];
uniform vec3 lightOrientation[maxLights];
uniform float lightDistance[maxLights];
uniform float lightSpotsize[maxLights];
uniform float lightSpotblend[maxLights];

#define SPOT 0
#define SUN 1
#define POINT 2
#define NORMAL 2
#define HEMI 3
#define M_PI 3.14159265358979323846

// ########################################Functions########################################

float exp_blender(float f)
{
    return pow(2.71828182846, f);
}

float linearrgb_to_srgb(float c)
{
    if(c < 0.0031308)
        return (c < 0.0) ? 0.0: c * 12.92;
    else
        return 1.055 * pow(c, 1.0/2.4) - 0.055;
}

float srgb_to_linearrgb(float c)
{
    if(c < 0.04045)
        return (c < 0.0) ? 0.0: c * (1.0 / 12.92);
    else
        return pow((c + 0.055)*(1.0/1.055), 2.4);
}

vec3 linearrgb_to_srgb(vec3 col_from)
{
    vec3 col_to;
    col_to.r = linearrgb_to_srgb(col_from.r);
    col_to.g = linearrgb_to_srgb(col_from.g);
    col_to.b = linearrgb_to_srgb(col_from.b);

    return col_to;
}

vec3 srgb_to_linearrgb(vec3 col_from)
{
    vec3 col_to;
    col_to.r = srgb_to_linearrgb(col_from.r);
    col_to.g = srgb_to_linearrgb(col_from.g);
    col_to.b = srgb_to_linearrgb(col_from.b);
    
    return col_to;
}

float shade_hemi_spec(vec3 N, vec3 H, float hard)
{
    float NdotH = dot(N, H) * 0.5 + 0.5;
    float specfac = pow(max(NdotH, 0.0), hard);

    return specfac;
}

float shade_phong_spec(vec3 N, vec3 H, vec3 L, vec3 E, float hard)
{
    float rslt = max(dot(H, N), 0.0);

    float specfac = pow(rslt, hard);
    return specfac;
}

float shade_cooktorr_spec(vec3 H, vec3 N, vec3 E)
{
    float specfac;
    float NdotH = dot(N, H);
    if(NdotH < 0.0) 
    {
        specfac = 0.0;
    }
    else
    {
        float maxNdotE = max(dot(N, E), 0.0);
        specfac = pow(NdotH, hardness);
        
        specfac = specfac / (0.1 + maxNdotE);
    }
    return specfac;
}

float shade_blinn_spec(vec3 N, vec3 H, vec3 L, vec3 E, float refrac, float spec_power)
{
    float specfac;
    if(refrac < 1.0) {
        specfac = 0.0;
    }
    else if(spec_power == 0.0) {
        specfac = 0.0;
    }
    else {
        if(spec_power<100.0)
            spec_power= sqrt(1.0/spec_power);
        else
            spec_power= 10.0/spec_power;

        float NdotH = dot(N, H);
        if(NdotH < 0.0) {
            specfac = 0.0;
        }
        else {
            float maxNdotE = max(dot(N, E), 0.01);
            float NdotL = dot(N, L);
            if(NdotL <= 0.01) {
                specfac = 0.0;
            }
            else {
                float maxEdotH = max(dot(E, H), 0.01);

                float a = 1.0;
                float b = (2.0 * NdotH * maxNdotE) / maxEdotH;
                float c = (2.0 * NdotH * NdotL) / maxEdotH;

                float g = 0.0;

                if(a < b && a < c) g = a;
                else if(b < a && b < c) g = b;
                else if(c < a && c < b) g = c;

                float p = sqrt(((refrac * refrac)+(maxEdotH*maxEdotH)-1.0));
                float f = (((p-maxEdotH)*(p-maxEdotH))/((p+maxEdotH)*(p+maxEdotH)))*(1.0+((((maxEdotH*(p+maxEdotH))-1.0)*((maxEdotH*(p+maxEdotH))-1.0))/(((maxEdotH*(p-maxEdotH))+1.0)*((maxEdotH*(p-maxEdotH))+1.0))));
                float ang = acos(NdotH);

                specfac = max(f*g*exp_blender((-(ang*ang)/(2.0*spec_power*spec_power))), 0.0);
            }
        }
    }
    return specfac;
}

float shade_wardiso_spec(vec3 N, vec3 H, vec3 E, vec3 L, float rms)
{
    float maxNdotH = max(dot(N, H), 0.001);
    float maxNdotE = max(dot(N, E), 0.001);
    float maxNdotL = max(dot(N, L), 0.001);
    float angle = tan(acos(maxNdotH));
    float alpha = max(rms, 0.001);

    float specfac= maxNdotL * (1.0/(4.0 * M_PI * alpha * alpha)) * (exp_blender(-(angle * angle)/(alpha * alpha))/(sqrt(maxNdotE * maxNdotL)));
    return specfac;
}

float shade_toon_spec(vec3 N, vec3 H, float size, float tsmooth)
{
    float specfac;
    float rslt;
    float NdotH = dot(N, H);
    float ang = acos(NdotH);

    if(ang < size) rslt = 1.0;
    else if(ang >= (size + tsmooth) || tsmooth == 0.0) rslt = 0.0;
    else rslt = 1.0 - ((ang - size)/tsmooth);

    specfac = rslt;
    return specfac;
}

vec3 shade_mist_blend(vec3 co, float enable, float miststa, float mistdist, float misttype, float intensity, vec3 col1, vec3 col2)
{
    float mist = 0.0;
    if(enable == 1.0) 
    {
        float fac, zcor;

        zcor = (gl_ProjectionMatrix[3][3] == 0.0)? length(co): -co[2];
        
        fac = clamp((zcor - miststa) / mistdist, 0.0, 1.0);
        if(misttype == 0.0) fac *= fac;
        else if(misttype == 1.0);
        else fac = sqrt(fac);

        mist = 1.0 - (1.0 - fac) * (1.0 - intensity);
    }
        
    float mixFac = clamp(mist, 0.0, 1.0);
    vec3 result = mix(col1, col2, mixFac);
    
    return result;
}

vec3 getLighting(vec3 normal, vec3 eyePos, vec3 color)
//Calculates lighting
{
    vec3 N = normal;
    vec3 E = normalize(eyePos);
    vec3 diffuse = vec3(0, 0, 0);
    vec3 specular = vec3(0, 0, 0);

    /* handle perspective/orthographic */
    E = (gl_ProjectionMatrix[3][3] == 0.0) ? E : vec3(0.0, 0.0, -1.0);
    
    //workaround for for loop unrolling. Change type from int to float.
    for (float c = 0.0; c < float(lightCount); c++)
    {
        int i = int(c);
        float attenuation = 1.0;
        vec3 lightVec = vec3(0.0, 0.0, 1.0);
        
        if (lightType[i] == SPOT || lightType[i] == POINT)
        {
            lightVec =  eyePos - lightPosition[i];
            float lightDist = length(lightVec);
            // The invsquare attenuation calculation in Blender is not correct
            // I use Blenders attenuation calculation to get the same attenuation
            attenuation = lightDistance[i] / (lightDistance[i] + lightDist * lightDist); //attenuation = visifac in Blender shader code
        }
        else
        {
            lightVec =  lightOrientation[i];
        }

        vec3 L = normalize(lightVec); //L = lv in Blender shader code
        vec3 H = normalize(L+E);
        
        if (lightType[i] == SPOT)
        {   
            float inpr = 0.0;
            vec2 scale = vec2(1.0, 1.0);
            float visifac = attenuation;
            inpr = dot(L, lightOrientation[i]);
            float t = lightSpotsize[i]; //spot size
            
            if(inpr <= t)
            {
                attenuation = 0.0;
            }
            else
            {
                /* soft area */
                if(lightSpotblend[i] != 0.0)
                    inpr *= smoothstep(0.0, 1.0, (inpr - t) / lightSpotblend[i]);

                attenuation = attenuation * inpr;
            }
        }
        
        float NdotL = dot(N, L);
        //float NdotH = dot(N, H);
        //float NdotE = dot(N, E);
        float ks;
        float kd;
        
        if (lightType[i] == HEMI)
        {
            NdotL = NdotL* 0.5 + 0.5;
            //NdotH = NdotH * 0.5 + 0.5;
            //ks = pow(max(NdotH, 0.0), hardness);
            ks = shade_hemi_spec(N, H, hardness);
        }
        else
        {
            //Phong specular shading
            ks = shade_phong_spec(N, H, L, E, hardness);
            
            //CookTorr specular shading
            //ks = shade_cooktorr_spec(H, N, E);
            
            //Toon specular shading
            /*
            float size = 0.5;
            float smooth = 0.1;
            ks = shade_toon_spec(N, H, size, smooth);
            */
            
            //Wardiso specular shading
            /*
            float slope = 0.1;
            ks = shade_wardiso_spec(N, H, E, L, slope);
            */
            
            //Blinn specular shading
            /*
            float refrac = 4.0;
            ks = shade_blinn_spec(N, H, L, E, refrac, hardness);
            */          
        }
        
        kd = max(NdotL, 0.0) * attenuation;
        diffuse += kd * color * lightColor[i];

        ks = ks * attenuation;
        specular += ks * specularColor * lightColor[i];
    }   
    return diffuse + specular;
}

// ########################################Main-Function########################################
void main()
{  
    //vec3 normal = normalize(-normal);  //activated Backface Culling  (or when geometry shader will used)
    vec3 normal = (gl_FrontFacing)? normalize(-vNormal) : normalize(vNormal);  //deactivated Backface Culling  
    
    vec2 ofs = (1.0 - scale1) / 2.0;
    vec4 dif1 = texture2D(diffuse1, texCoord*scale1+ofs);
    dif1.rgb = srgb_to_linearrgb(dif1.rgb);
    
    vec3 lighting = getLighting(normal, eyePos, dif1.rgb);
    vec4 color = vec4((emission * dif1.rgb) + ambientColor + lighting, dif1.a);
    color.rgb = shade_mist_blend(eyePos, mistEnable, mistStart, mistDistance, mistType, mistIntensity, color.rgb, mistColor);
    
    color.rgb = linearrgb_to_srgb(color.rgb);
    gl_FragColor = color;
}