#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 * exp(sin(x) - 1.0);
}

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

float get_water_height(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz * 0.8;
    const int WAVE_ITER = 8;
    vec2 FlowDir = normalize(vec2(1.0, 0.7));
    float Amp = 0.08, Speed = 1.2, Sum = 0.0;
    
    for (int i = 0; i < WAVE_ITER; i++) {
        float wave = sharp_sine(Coords, Amp, Speed, FlowDir);
        Sum += wave;

        Coords += FlowDir * wave * 0.3; // Trochoidal displacement approximation
        FlowDir = rotate(FlowDir, 1.15);
        Amp *= 0.62;
        Speed *= 1.12;
        Coords *= 1.5;
    }  
    return Sum;
}

vec2 get_water_height_d(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz * 0.8; 
    const int WAVE_ITER = 8;
    vec2 Sum = vec2(0.0), FlowDir = normalize(vec2(1.0, 0.7));
    float Amp = 0.08, Speed = 1.2;
    
    for (int i = 0; i < WAVE_ITER; i++) {
        vec2 dWave = sharp_sine_d(Coords, Amp, Speed, FlowDir);
        Sum += dWave;

        float wave = sharp_sine(Coords, Amp, Speed, FlowDir);
        Coords += FlowDir * wave * 0.3;
        FlowDir = rotate(FlowDir, 1.15);
        Amp *= 0.62;
        Speed *= 1.12;
        Coords *= 1.5;
    }
    return Sum;
}

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) * 9.2 * (1 - pow4(WorldNormal.y)); 
    return Coords;
}

vec3 get_water_normal(vec3 Coords, vec3 WorldNormal, float Dist) {
    Coords = change_flow_dir(Coords, WorldNormal);
    vec2 H = get_water_height_d(Coords);
    
    // Detailed micro-ripples using noise texture
    vec2 noiseScroll = vec2(frameTimeCounter * 0.05);
    vec2 noise = texture(waterNoise, Coords.xz * 0.5 + noiseScroll).rg;
    noise += texture(waterNoise, Coords.xz * 1.2 - noiseScroll * 1.4).rg;
    H += (noise - 1.0) * 0.04;

    return normalize(vec3(H.x, H.y, 1.0));
}

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