use super::components::Water;
use nalgebra_glm::{Vec2, Vec3};

const ITER_GEOMETRY: i32 = 3;

fn hash(p: Vec2) -> f32 {
    let mut p3 = Vec3::new(p.x, p.y, p.x) * 0.1031;
    p3 = Vec3::new(p3.x.fract(), p3.y.fract(), p3.z.fract());
    let dot_val = p3.dot(&Vec3::new(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
    p3 += Vec3::new(dot_val, dot_val, dot_val);
    ((p3.x + p3.y) * p3.z).fract()
}

fn noise(p: Vec2) -> f32 {
    let i = Vec2::new(p.x.floor(), p.y.floor());
    let f = Vec2::new(p.x.fract(), p.y.fract());
    let u = Vec2::new(f.x * f.x * (3.0 - 2.0 * f.x), f.y * f.y * (3.0 - 2.0 * f.y));

    let h00 = hash(i + Vec2::new(0.0, 0.0));
    let h10 = hash(i + Vec2::new(1.0, 0.0));
    let h01 = hash(i + Vec2::new(0.0, 1.0));
    let h11 = hash(i + Vec2::new(1.0, 1.0));

    let mix_x0 = h00 + (h10 - h00) * u.x;
    let mix_x1 = h01 + (h11 - h01) * u.x;
    let mix_y = mix_x0 + (mix_x1 - mix_x0) * u.y;

    -1.0 + 2.0 * mix_y
}

fn sea_octave(uv_in: Vec2, choppy: f32) -> f32 {
    let noise_val = noise(uv_in);
    let uv = uv_in + Vec2::new(noise_val, noise_val);
    let mut wv = Vec2::new(1.0 - uv.x.sin().abs(), 1.0 - uv.y.sin().abs());
    let swv = Vec2::new(uv.x.cos().abs(), uv.y.cos().abs());
    wv = Vec2::new(wv.x + (swv.x - wv.x) * wv.x, wv.y + (swv.y - wv.y) * wv.y);
    (1.0 - (wv.x * wv.y).powf(0.65)).powf(choppy)
}

fn map(water: &Water, p: Vec3, time: f32, iter: i32) -> f32 {
    let mut freq = water.frequency;
    let mut amp = water.wave_height;
    let mut choppy = water.choppy;
    let mut uv = Vec2::new(p.x * 0.75, p.z);

    let sea_time = 1.0 + time * water.speed;

    let mut h = 0.0;
    for _ in 0..iter {
        let d1 = sea_octave((uv + Vec2::new(sea_time, sea_time)) * freq, choppy);
        let d2 = sea_octave((uv - Vec2::new(sea_time, sea_time)) * freq, choppy);
        let d = d1 + d2;
        h += d * amp;

        let new_uv_x = uv.x * 1.6 + uv.y * 1.2;
        let new_uv_y = uv.x * -1.2 + uv.y * 1.6;
        uv = Vec2::new(new_uv_x, new_uv_y);

        freq *= 1.9;
        amp *= 0.22;
        choppy = choppy + (1.0 - choppy) * 0.2;
    }

    p.y - (water.base_height + h)
}

pub fn sample_wave_height(water: &Water, x: f32, z: f32, time: f32) -> f32 {
    let mut low = water.base_height - 5.0;
    let mut high = water.base_height + water.wave_height * 4.0;

    for _ in 0..8 {
        let mid = (low + high) * 0.5;
        let val = map(water, Vec3::new(x, mid, z), time, ITER_GEOMETRY);
        if val < 0.0 {
            low = mid;
        } else {
            high = mid;
        }
    }

    (low + high) * 0.5
}

pub fn sample_wave_normal(water: &Water, x: f32, z: f32, time: f32) -> Vec3 {
    let epsilon = 0.5;
    let h_x_neg = sample_wave_height(water, x - epsilon, z, time);
    let h_x_pos = sample_wave_height(water, x + epsilon, z, time);
    let h_z_neg = sample_wave_height(water, x, z - epsilon, time);
    let h_z_pos = sample_wave_height(water, x, z + epsilon, time);

    let dx = (h_x_pos - h_x_neg) / (epsilon * 2.0);
    let dz = (h_z_pos - h_z_neg) / (epsilon * 2.0);

    nalgebra_glm::normalize(&Vec3::new(-dx, 1.0, -dz))
}
