use std::{num::NonZero, time::Duration};

use rodio::{ChannelCount, SampleRate, Source};

const RATE: SampleRate = NonZero::new(44100).unwrap();

/// Continuous ambient sound texture with layered noise and tonal drone.
///
/// Combines three noise layers (deep, mid, bright) with a tonal hum
/// to create rich atmospheric backgrounds like wind, rain, or lava.
///
/// # Example
/// ```
/// use game_audio::Texture;
///
/// let wind = Texture::wind();
/// let rain = Texture::rain();
/// let lava = Texture::lava();
/// ```
pub struct Texture {
    sample_rate: u32,
    sample_position: usize,
    layers: [TextureLayer; 3],
    drone_frequency: f32,
    drone_volume: f32,
    drone_phase: f32,
    drone_modulation_speed: f32,
    volume: f32,
}

struct TextureLayer {
    smoothing: usize,
    volume: f32,
    modulation_speed: f32,
    modulation_depth: f32,
    modulation_offset: f32,
    state: u32,
    accumulator: f32,
    accumulator_count: usize,
    current_value: f32,
}

impl TextureLayer {
    fn new(
        smoothing: usize,
        volume: f32,
        modulation_speed: f32,
        modulation_depth: f32,
        seed: u32,
        modulation_offset: f32,
    ) -> Self {
        Self {
            smoothing: smoothing.max(1),
            volume,
            modulation_speed,
            modulation_depth,
            modulation_offset,
            state: seed,
            accumulator: 0.0,
            accumulator_count: 0,
            current_value: 0.0,
        }
    }

    fn next_sample(&mut self, time: f32) -> f32 {
        self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345);
        let noise = (self.state >> 16) as f32 / 32768.0 - 1.0;

        self.accumulator += noise;
        self.accumulator_count += 1;

        if self.accumulator_count >= self.smoothing {
            self.current_value = self.accumulator / self.accumulator_count as f32;
            self.accumulator = 0.0;
            self.accumulator_count = 0;
        }

        let phase = (time * self.modulation_speed + self.modulation_offset) * std::f32::consts::TAU;
        let modulation = 1.0 - self.modulation_depth * 0.5 * (1.0 + phase.sin());

        self.current_value * self.volume * modulation
    }
}

impl Texture {
    /// Creates a wind ambient (slow, deep, sweeping).
    pub fn wind() -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(40, 0.06, 0.12, 0.8, 12345, 0.0),
                TextureLayer::new(15, 0.03, 0.25, 0.6, 67890, 0.3),
                TextureLayer::new(5, 0.015, 0.4, 0.9, 11111, 0.7),
            ],
            drone_frequency: 55.0,
            drone_volume: 0.01,
            drone_phase: 0.0,
            drone_modulation_speed: 0.08,
            volume: 1.0,
        }
    }

    /// Creates a rain ambient (bright, varied, patterned).
    pub fn rain() -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(8, 0.03, 0.5, 0.4, 22222, 0.0),
                TextureLayer::new(3, 0.04, 0.8, 0.5, 33333, 0.4),
                TextureLayer::new(1, 0.02, 1.2, 0.6, 44444, 0.8),
            ],
            drone_frequency: 80.0,
            drone_volume: 0.005,
            drone_phase: 0.0,
            drone_modulation_speed: 0.3,
            volume: 1.0,
        }
    }

    /// Creates a lava ambient (deep rumble, slow bubbling).
    pub fn lava() -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(50, 0.08, 0.06, 0.9, 55555, 0.0),
                TextureLayer::new(25, 0.04, 0.15, 0.7, 66666, 0.5),
                TextureLayer::new(10, 0.02, 0.3, 0.8, 77777, 0.2),
            ],
            drone_frequency: 40.0,
            drone_volume: 0.02,
            drone_phase: 0.0,
            drone_modulation_speed: 0.05,
            volume: 1.0,
        }
    }

    /// Creates a cave ambient (echoey dripping atmosphere).
    pub fn cave() -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(30, 0.03, 0.1, 0.7, 88888, 0.0),
                TextureLayer::new(12, 0.02, 0.2, 0.5, 99999, 0.6),
                TextureLayer::new(4, 0.01, 0.5, 0.8, 10101, 0.3),
            ],
            drone_frequency: 65.0,
            drone_volume: 0.015,
            drone_phase: 0.0,
            drone_modulation_speed: 0.04,
            volume: 1.0,
        }
    }

    /// Creates an electric/buzzing ambient (for speed tracks).
    pub fn electric() -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(6, 0.02, 0.7, 0.3, 20202, 0.0),
                TextureLayer::new(2, 0.03, 1.5, 0.5, 30303, 0.2),
                TextureLayer::new(1, 0.01, 3.0, 0.7, 40404, 0.5),
            ],
            drone_frequency: 120.0,
            drone_volume: 0.01,
            drone_phase: 0.0,
            drone_modulation_speed: 0.5,
            volume: 1.0,
        }
    }

    /// Creates a custom texture with full control.
    ///
    /// - `volume`: overall volume multiplier
    /// - `drone_frequency`: base tone frequency in Hz (0.0 to disable)
    /// - `drone_volume`: volume of the tonal drone
    #[allow(clippy::doc_markdown)]
    /// - `layers`: three layers as (smoothing, volume, modulation_speed, modulation_depth)
    pub fn custom(
        volume: f32,
        drone_frequency: f32,
        drone_volume: f32,
        layers: [(usize, f32, f32, f32); 3],
    ) -> Self {
        Self {
            sample_rate: 44100,
            sample_position: 0,
            layers: [
                TextureLayer::new(
                    layers[0].0,
                    layers[0].1,
                    layers[0].2,
                    layers[0].3,
                    12345,
                    0.0,
                ),
                TextureLayer::new(
                    layers[1].0,
                    layers[1].1,
                    layers[1].2,
                    layers[1].3,
                    67890,
                    0.35,
                ),
                TextureLayer::new(
                    layers[2].0,
                    layers[2].1,
                    layers[2].2,
                    layers[2].3,
                    11111,
                    0.7,
                ),
            ],
            drone_frequency,
            drone_volume,
            drone_phase: 0.0,
            drone_modulation_speed: 0.1,
            volume,
        }
    }

    /// Sets the overall volume multiplier.
    pub fn set_volume(&mut self, volume: f32) {
        self.volume = volume;
    }
}

impl Iterator for Texture {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        let time = self.sample_position as f32 / self.sample_rate as f32;

        let noise_sum: f32 = self
            .layers
            .iter_mut()
            .map(|layer| layer.next_sample(time))
            .sum();

        let drone = if self.drone_frequency > 0.0 {
            self.drone_phase +=
                std::f32::consts::TAU * self.drone_frequency / self.sample_rate as f32;
            let drone_modulation =
                0.7 + 0.3 * (time * self.drone_modulation_speed * std::f32::consts::TAU).sin();
            self.drone_phase.sin() * self.drone_volume * drone_modulation
        } else {
            0.0
        };

        self.sample_position = self.sample_position.wrapping_add(1);
        Some((noise_sum + drone) * self.volume)
    }
}

impl Source for Texture {
    fn current_span_len(&self) -> Option<usize> {
        None
    }
    fn channels(&self) -> ChannelCount {
        ChannelCount::MIN
    }
    fn sample_rate(&self) -> SampleRate {
        RATE
    }
    fn total_duration(&self) -> Option<Duration> {
        None
    }
}
