#include "brdf/fresnel.glsl"
#include "brdf/diffuse/hammon.glsl"

#include "ambient/hbao.glsl"

#include "/lib/shared/surface/sphericalHarmonics.glsl"

vec3 waterExtinctionCoefficient;
vec3 waterScatteringCoefficient;
vec3 waterAttenuationCoefficient;

void waterCoefficients() {
    waterExtinctionCoefficient = vec3(0.40, 0.15, 0.07);
    waterScatteringCoefficient = vec3(0.1);
    waterAttenuationCoefficient = waterExtinctionCoefficient + waterScatteringCoefficient;
    if(isEyeInWater == 1) {
        waterScatteringCoefficient *= 0.55;
        waterAttenuationCoefficient *= 0.55;
    }
}

float getCloudShadows(vec3 position) {
    position     = mat3(shadowModelView) * position;
    position.xy /= 200.0;
    position.xy /= 1.0 + length(position.xy);
    position.xy  = position.xy * 0.5 + 0.5;
    position.xy *= 256.0 * viewPixelSize;

    return texture(colortex6, position.xy).a;
}

float getCaustics(vec3 scenePosition, in float depth, in bool volume) {
    vec2 offset[2] = {        
        vec2(0.0, 0.0),
        vec2(0.0, 0.5)
    };

    int res[2] = {
        480,
        480/2
    };

    vec3 position[2];

    for(int i = 0; i < 2; ++i) {
        position[i]     = mat3(shadowModelView) * scenePosition;
        position[i].xy /= 12.0;
        position[i].xy /= 1.0 + length(position[i].xy);
        position[i].xy  = position[i].xy * 0.5 + 0.5;
        position[i].xy *= res[i] * viewPixelSize;
        position[i].xy  = position[i].xy+offset[i];
    }

    float causticsD1  = 1.0;
    float causticsD2 = 1.0;
    if(volume) {
        causticsD1  = texture(colortex6, position[0].xy).r;
        causticsD2 = texture(colortex6, position[1].xy).r;
    } else {
        causticsD1  = texture(colortex6, position[0].xy).r;
        causticsD2 = texture(colortex6, position[1].xy).r;
    }

    float depthD1 = exp(-(depth-0.2)/2.0);
    float depthD2 = exp(-(depth-10.0)/16.0);

    return mix(causticsD2, mix(causticsD1, 1.0, saturate(depthD1)), saturate(depthD2));
}

bool waterShadow(vec3 position, inout float waterDepth, inout float waterFraction) {
    bool result = false;

    position = shadowDistortion(position);

    vec4 shadowDepth = texture(shadowtex0, position.xy * 0.5 + 0.5) * 2.0 - 1.0;
    vec4 shadowTranslucent = texture(shadowtex1, position.xy * 0.5 + 0.5);

    waterDepth = shadowDepth.r - position.z;
    waterDepth = clamp(-2.0 * SHADOW_DEPTH_RADIUS * waterDepth, 0.0, 1e35);

    waterFraction = texture(shadowcolor1, position.xy * 0.5 + 0.5).a;
    waterFraction *= shadowTranslucent.r - shadowDepth.r * shadowTranslucent.r;

    float waterCast = texture(shadowcolor1, position.xy * 0.5 + 0.5).a;

    if(waterCast > 0.9) result = true;

    return result;
}

vec2 calculateDitherPattern(in vec2 coordinate) {
    coordinate *= viewSize;
    coordinate = mod(coordinate, vec2(64.0));
    coordinate /= noiseTextureResolution;

    return texture(noisetex, coordinate).rg;
}

float calculateRTAO() {
    float ao = 1.0;
    const int rays = 1;
    for(int i = 0; i < rays; ++i) {
        vec3 direction = genCosineVector(sd.viewNormals, randNext2F());

        vec3 hitPosition;
        vec3 startPosition = pd.screenPosition[0];
        bool hit = raytraceIntersection(depthtex0, startPosition, direction, hitPosition, 6u, 25.0, true);

        if(hit) {
            ao -= 1.0 / rays;
        } else {
            break;
        }
    }

    return max(ao, 0.0);
}

vec3 calculateLighting() {
    vec3 albedo = sd.albedo;
    //albedo = vec3(1.0);
    vec3 torchColor = blackbody(2000.0) * 2e3;

    float nDotV = dot(sd.viewNormals, -fNormalize(pd.viewPosition[0]));
    float nDotL = saturate(dot(sd.viewNormals, shadowLightVectorView));
    float lDotV = dot(shadowLightVectorView, -fNormalize(pd.viewPosition[0]));
    float nDotH = dot(sd.viewNormals, fNormalize(-fNormalize(pd.viewPosition[0]) + shadowLightVectorView));

    vec3 diffuse = hammonDiffuse(albedo, sd.n, nDotV, nDotL, nDotH, lDotV, sd.roughnessSquared);

    bool subsurface = false;

    if(sd.id == 18.0) {
        subsurface = true;
        diffuse = albedo * max(0.4, nDotL) / pi;
    }

    if(sd.particle) {
        diffuse = albedo / pi;
    }

    vec3 shadows = vec3(0.0);
    if(dot(sd.viewFlatNormals, shadowLightVectorView) < 0.0 && !subsurface) {
        diffuse = vec3(0.0);
        shadows = vec3(0.0);
    } else {
        shadows = calculateSoftShadows(pd.shadowClipPosition, subsurface);

        float waterDepth;
        float waterFraction;
        bool castWater = waterShadow(pd.shadowClipPosition, waterDepth, waterFraction);
        if(castWater && waterDepth > 0.2) {
            vec3 waterTransmittance = vec3(0.0);
            waterDepth = waterDepth - 0.2;
            for(int n = 0; n < 3; ++n) {
                waterTransmittance += exp(-(waterAttenuationCoefficient * waterDepth) * pow(0.50, n)) * pow(0.50, n);
            }
            shadows *= waterTransmittance/2.0;
            //shadows *= calculateCausticsSEUS(pd.scenePosition[0] + cameraPosition);
            shadows *= getCaustics(pd.scenePosition[0], waterDepth, false);
        }

        shadows *= getCloudShadows(pd.scenePosition[0]);
    }

    float ao = sd.vanillaAO;
        #ifdef HBAO
            ao = calculateHBAO();
        #endif
        #if defined RTAO && !defined HBAO
            ao = calculateRTAO();
        #endif

    vec3 ibl = vec3(0.0);
    #ifdef SPHERICAL_HARMONICS
        ibl = albedo * EvaluateSHIrradiance(sd.normals, lc.skySh);
    #else
        ibl = albedo * (lc.indirect * rcp(pi));
    #endif

    if(sd.particle) {
        ibl = albedo * (lc.indirect * rcp(1.6));
        ao = 1.0;
    }

    vec3 shading = shadows * diffuse * lc.direct;
        shading = ao * ibl * sd.lightmap.y + shading;
        shading = albedo * ao * torchColor * sd.lightmap.x + shading;
        shading = albedo * square(ao) * vec3(0.02) + shading;

    vec3 emission = vec3(1.0);
    if(sd.id == 11.0) emission *= sqrt(dot(albedo, albedo)) * 15.0;
    if(sd.id == 12.0) emission *= pow(max(dot(albedo, albedo) * 0.7 - 0.7, 0.0), 1.0) * 25.0;
    emission *= torchColor * sd.lightmap.x * albedo;

    /*
        if(sd.id == 11.0) {
            vec2 noiseCoordinate = (pd.scenePosition + cameraPosition).xz / 4.0;
            float noise  = textureBicubic(noisetex, noiseCoordinate + vec2(-frameTimeCounter * 0.05, frameTimeCounter * 0.05)).r;
                noise *= textureBicubic(noisetex, (noiseCoordinate/2.0) + vec2(frameTimeCounter * 0.05, -frameTimeCounter * 0.05)).r * 0.7;
                noise *= textureBicubic(noisetex, (noiseCoordinate*0.7) + (frameTimeCounter * 0.05)).r * 0.5;
            texColor = blackbody(1500.0) * noise;
        }
    */

    return (shading + emission);
}