pub mod lode;
pub mod perlin;

use std::f32::consts::PI;

use serde_derive::Deserialize;

use crate::vec3::Vec3;

pub trait NoiseSource: Send + Sync {
    /// value returns noise on the interval [0., 1.).
    fn value(&self, p: Vec3) -> f32;

    fn marble(&self, p: Vec3, period: Vec3, power: f32, size: usize, scale: f32) -> f32 {
        let p = p / scale;
        // TODO(wathiede): can't understand why 255 works for perlin and lode, maybe it's near 360
        // degrees and interacts with the sine function?
        let xyz_value = p.x * period.x / 255.
            + p.y * period.y / 255.
            + p.z * period.z / 255.
            + power * self.turbulence(p, size);
        (xyz_value * PI).sin().abs()
    }

    fn turbulence(&self, p: Vec3, factor: usize) -> f32 {
        let mut value = 0.;
        let initial_factor = factor;
        let mut factor = factor;

        while factor >= 1 {
            value += self.value(p / factor as f32) * factor as f32;
            factor /= 2;
        }
        0.5 * value / initial_factor as f32
    }

    fn scaled(&self, p: Vec3, scale: f32) -> f32 {
        let p = p / scale;
        self.value(p)
    }
}

impl NoiseSource for Box<dyn NoiseSource> {
    fn value(&self, p: Vec3) -> f32 {
        (**self).value(p)
    }
}

#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(tag = "type")]
pub enum NoiseType {
    Scale(f32),
    Turbulence(usize),
    Marble {
        period: Vec3,
        power: f32,
        size: usize,
        scale: f32,
    },
}

impl NoiseType {
    pub fn to_url(&self) -> String {
        match &self {
            NoiseType::Scale(scale) => format!("scale/{}", scale),
            NoiseType::Turbulence(turbulence) => format!("turbulence/{}", turbulence),
            NoiseType::Marble {
                period,
                power,
                size,
                scale,
            } => format!(
                "marble/period/{},{},{}/power/{}/size/{}/scale/{}",
                period.x, period.y, period.z, power, size, scale
            ),
        }
    }

    pub fn parameters(&self) -> Vec<(&str, String)> {
        match &self {
            NoiseType::Scale(scale) => vec![("Scale", scale.to_string())],
            NoiseType::Turbulence(turbulence) => vec![("Turbulence", turbulence.to_string())],
            NoiseType::Marble {
                period,
                power,
                size,
                scale,
            } => vec![
                ("Period", period.to_string()),
                ("Power", power.to_string()),
                ("Size", size.to_string()),
                ("Scale", scale.to_string()),
            ],
        }
    }
}
