#define WATER_NOISE_BUFFER waterNoise

float sharp_sine(vec2 Coords, float Amp, float Speed, vec2 FlowDir) {
    float x = dot(Coords, FlowDir) + (frameTimeCounter * Speed);
    return Amp * sin(x);
}

vec2 sharp_sine_d(vec2 Coords, float Amp, float Speed, vec2 FlowDir) {
    float x = dot(Coords, FlowDir) + (frameTimeCounter * Speed);
    return Amp * FlowDir * cos(x);
}

float get_water_height(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz + WorldPos.y;
    Coords *= 1.5;
    const float WAVE_ITER = 32;
    vec2 FlowDir = vec2(1, 1);
    float Amp = 1, Speed = 1.25, Sum = 0, AmpSum = 0;
    for (int i = 1; i <= WAVE_ITER; i++) {
        vec2 PrevWave = sharp_sine_d(Coords, Amp, Speed, FlowDir);

        Coords *= 1 + 2.0 / WAVE_ITER;
        FlowDir = rotate(FlowDir, 2.224);
        Amp *= 1 - 0.7 / WAVE_ITER;
        Speed *= 1 + 1.75 / WAVE_ITER;

        float Wave = sharp_sine(Coords + PrevWave, Amp, Speed, FlowDir);

        Sum += Wave;
        AmpSum += Amp;
    }  
    return (Sum / AmpSum) * 0.15;
}

vec2 get_water_height_d(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz; 
    Coords *= 1.5;
    const float WAVE_ITER = 32;
    vec2 Sum = vec2(0), FlowDir = vec2(1, 1);
    float Amp = 1, Speed = 1.25, AmpSum = 0;
    vec2 PrevWave = vec2(0); // Used for domain warping
    for (int i = 1; i <= WAVE_ITER; i++) {
        Coords *= 1 + 2.0 / WAVE_ITER;
        FlowDir = rotate(FlowDir, 2.224);
        Amp *= 1 - 0.7 / WAVE_ITER;
        Speed *= 1 + 1.75 / WAVE_ITER;

        vec2 Wave = sharp_sine_d(Coords + PrevWave, Amp, Speed, FlowDir);

        Sum += Wave;
        PrevWave = Wave;
        AmpSum += Amp;
    }
    float AmpTotal = texture(cloudNoise, (WorldPos.xz) / 64).r;
    return Sum / AmpSum * 0.1;
}

vec3 change_flow_dir(vec3 Coords, vec3 WorldNormal) {
    // Fix normals for water falling vertically
    if(abs(WorldNormal.y) < 0.01) {
        if(abs(WorldNormal.x) < 0.5) {
            Coords.xz = Coords.xy * sign(-WorldNormal.z);
        } else {
            Coords.xz = Coords.yz * sign(-WorldNormal.x);
        }
    }
    // Increase flow speed in the direction the water flows
    Coords.xz -= frameTimeCounter * normalize(WorldNormal.xz) * 8 * (1 - pow4(WorldNormal.y));
    return Coords;
}

vec3 get_water_normal(vec3 Coords, vec3 WorldNormal) {
    Coords = change_flow_dir(Coords, WorldNormal);
    vec2 H = get_water_height_d(Coords); // * pow(max(1+Dist/128, 0), 4);

    return normalize(vec3(H.x, H.y, 1 - (H.x * H.x + H.y * H.y)));
}

float get_water_caustics(vec3 PlayerPos) {
    vec3 WorldPos = PlayerPos + cameraPosition;
    vec3 slpP = view_player(sLightPosN, false);
    vec3 PlayerPosS = WorldPos - slpP / slpP.y * WorldPos.y;
    float WaterHeight = get_water_height(PlayerPosS);
    float CausticsColor = 10 * exp(-abs(WaterHeight) * 20) + 0.25;
    return CausticsColor;
}