varying vec3 frontLighting;
varying vec3 backLighting;
varying vec3 frontSpecular;
varying vec3 backSpecular;
varying vec3 eyePos;
varying vec2 texCoord;

uniform int lightCount;
uniform vec3 diffuseColor;
uniform vec3 specularColor;
uniform float hardness;
uniform float diffuseIntensity;

uniform float time;
uniform float strength;
uniform float speed;
uniform float deforming;
uniform float bending;
uniform vec2 direction;
uniform float ground;

uniform mat4 ModelMatrix;
uniform mat4 ViewMatrix;

#define maxLights 16

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

vec2 windDirection = direction; //direction of the wind
float windStrength = strength;  //strength of the wind
float groundLevel = ground;     //everything over ground level will have movement
const int windSamples = 3;      //number of wind sine waves to add

// ########################################Functions for material########################################

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;
}

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;
}

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;
}


void getLighting(vec3 normal, vec3 eyePos, out vec3 fLighting, out vec3 bLighting, out vec3 fSpecular, out vec3 bSpecular)
{
    fLighting = vec3(0, 0, 0);
    bLighting = vec3(0, 0, 0);
    fSpecular = vec3(0, 0, 0);
    bSpecular = vec3(0, 0, 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 fN = normalize(-normal);  //frontface    
        vec3 bN = normalize(normal);  //backface  
        vec3 E = normalize(eyePos);
        /* handle perspective/orthographic */
        E = (gl_ProjectionMatrix[3][3] == 0.0) ? E : vec3(0.0, 0.0, -1.0);
        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 fNdotL = dot(fN, L);
        float bNdotL = dot(bN, L);
        //float NdotH = dot(N, H);
        //float NdotE = dot(N, E);
        float fks;
        float bks;
        float fkd;
        float bkd;
        
        if (lightType[i] == HEMI)
        {
            fNdotL = fNdotL* 0.5 + 0.5;
            bNdotL = bNdotL* 0.5 + 0.5;
            //NdotH = NdotH * 0.5 + 0.5;
            //ks = pow(max(NdotH, 0.0), hardness);
            fks = shade_hemi_spec(fN, H, hardness);
            bks = shade_hemi_spec(bN, H, hardness);
        }
        else
        {
            //Phong specular shading
            fks = shade_phong_spec(fN, H, L, E, hardness);
            bks = shade_phong_spec(bN, H, L, E, hardness);
            
            //CookTorr specular shading
            //fks = shade_cooktorr_spec(H, fN, E);
            //bks = shade_cooktorr_spec(H, bN, E);
            
            //Toon specular shading
            /*
            float size = 0.5;
            float smooth = 0.1;
            fks = shade_toon_spec(fN, H, size, smooth);
            bks = shade_toon_spec(bN, H, size, smooth);
            */
            
            //Wardiso specular shading
            /*
            float slope = 0.1;
            fks = shade_wardiso_spec(fN, H, E, L, slope);
            bks = shade_wardiso_spec(bN, H, E, L, slope);
            */
            
            //Blinn specular shading
            /*
            float refrac = 4.0;
            fks = shade_blinn_spec(fN, H, L, E, refrac, hardness);
            bks = shade_blinn_spec(bN, H, L, E, refrac, hardness);
            */          
        }
        
        fkd = max(fNdotL, 0.0) * attenuation;
        fLighting += fkd * lightColor[i];

        bkd = max(bNdotL, 0.0) * attenuation;
        bLighting += bkd * lightColor[i];

        fks = fks * attenuation;
        fSpecular += fks * specularColor * lightColor[i];
        
        bks = bks * attenuation;
        bSpecular += bks * specularColor * lightColor[i];
    }   
}

// ########################################Functions for vertex deforming########################################

//function to generate the wind
vec2 getWind(vec4 worldPos, float height)
{
    vec2 wind;
    if (height > groundLevel)
    {
        float windTime = time*speed;
        float windDisplacement = cos(0.375*((17.5+worldPos.x)+(17.5+worldPos.y))+(windTime*1.25));

        //worldPos.xy += vec2(1000.0);    //making sure the position is positive for calculations
        
        for (int w = 0; w < windSamples; w++)
        {
            float rAnd = float(w)+1.0-(float(windSamples)/2.0);
            float rCnd = float(w)-1.0+(float(windSamples)/2.0);
            windDisplacement += sin(0.5*((17.5+rAnd+worldPos.x)+(rAnd+worldPos.y))+(windTime*(rAnd*0.1+1.75)));
            windDisplacement -= cos(0.5*((17.5+rCnd+worldPos.x)+(rAnd+worldPos.y))+(windTime*(rCnd*0.1+1.75)));
        }

        wind = windStrength*(height-0.25)*sin((worldPos.xy*normalize(windDirection))+vec2(windTime*0.5))*windDisplacement;
        wind *= ((height - groundLevel) * bending + 1.0 - bending);
    }
    else
    {
        wind = vec2(0.0,0.0);        
    }
    return wind;   
}

mat4 addWorldPos(mat4 modelMat, vec3 pos)
{
   modelMat[3][0] += pos.x;
   modelMat[3][1] += pos.y;
   modelMat[3][2] += pos.z;   
   return modelMat;
}

// ########################################Main-Function########################################

void main()
{
    vec4 vert = gl_Vertex;
    vec4 worldSpacePos = ModelMatrix * (vert*deforming+1.0-deforming);
    
    vec2 wind = getWind(worldSpacePos, vert.z);
    
    mat4 modelViewMatix = ViewMatrix * addWorldPos(ModelMatrix, vec3(wind, 0.0));	//dot(gl_Color.rgb, vec3(0.299, 0.587, 0.114)
    texCoord = gl_MultiTexCoord0.xy;
    vec3 normal = gl_NormalMatrix * gl_Normal;
    eyePos = (modelViewMatix * vert).xyz;
    gl_Position = gl_ProjectionMatrix * modelViewMatix * vert;
    getLighting(normal, eyePos, frontLighting, backLighting, frontSpecular, backSpecular);
    
}