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

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

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

/// PRNG-based noise generator with amplitude decay.
///
/// Produces random samples that fade out over time.
/// Useful for crash sounds, explosions, and percussive effects.
pub struct Noise {
    remaining: usize,
    amplitude: f32,
    decay: f32,
    state: u32,
}

impl Noise {
    /// Creates a new noise source.
    ///
    /// - `duration`: how long the noise plays
    /// - `amplitude`: starting volume (0.0–1.0)
    /// - `decay`: multiplier applied per sample (e.g. 0.9997 for gradual fade)
    pub fn new(duration: Duration, amplitude: f32, decay: f32) -> Self {
        let sample_rate = 44100;
        Self {
            remaining: (sample_rate as f64 * duration.as_secs_f64()) as usize,
            amplitude,
            decay,
            state: 12345,
        }
    }
}

impl Iterator for Noise {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        if self.remaining == 0 {
            return None;
        }
        self.remaining -= 1;
        self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345);
        let noise = (self.state >> 16) as f32 / 32768.0 - 1.0;
        let sample = noise * self.amplitude;
        self.amplitude *= self.decay;
        Some(sample)
    }
}

impl Source for Noise {
    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
    }
}

/// Sine wave tone with frequency slide and amplitude decay.
///
/// The fundamental building block for sound effects.
/// Can produce pure tones, rising/falling sweeps, and fading notes.
pub struct Tone {
    remaining: usize,
    phase: f32,
    frequency: f32,
    frequency_slide: f32,
    amplitude: f32,
    decay: f32,
    sample_rate: u32,
}

impl Tone {
    /// Creates a new tone source.
    ///
    /// - `duration`: how long the tone plays
    /// - `frequency`: starting frequency in Hz
    /// - `frequency_slide`: Hz change per second (positive = rising, negative = falling)
    /// - `amplitude`: starting volume (0.0–1.0)
    /// - `decay`: multiplier applied per sample (e.g. 0.999 for quick fade)
    pub fn new(
        duration: Duration,
        frequency: f32,
        frequency_slide: f32,
        amplitude: f32,
        decay: f32,
    ) -> Self {
        let sample_rate = 44100;
        Self {
            remaining: (sample_rate as f64 * duration.as_secs_f64()) as usize,
            phase: 0.0,
            frequency,
            frequency_slide,
            amplitude,
            decay,
            sample_rate,
        }
    }
}

impl Iterator for Tone {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        if self.remaining == 0 {
            return None;
        }
        self.remaining -= 1;
        let sample = self.phase.sin() * self.amplitude;
        self.phase += std::f32::consts::TAU * self.frequency / self.sample_rate as f32;
        self.frequency += self.frequency_slide / self.sample_rate as f32;
        self.amplitude *= self.decay;
        Some(sample)
    }
}

impl Source for Tone {
    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
    }
}

/// Triangle wave from a phase value. Warmer than sine at high frequencies.
pub fn triangle(phase: f32) -> f32 {
    let normalized = (phase / std::f32::consts::TAU).fract();
    if normalized < 0.25 {
        normalized * 4.0
    } else if normalized < 0.75 {
        2.0 - normalized * 4.0
    } else {
        normalized * 4.0 - 4.0
    }
}

/// Soft square wave from a phase value. Buzzy but not harsh.
pub fn soft_square(phase: f32) -> f32 {
    let sine = phase.sin();
    sine / (1.0 + sine.abs() * 2.0) * 1.5
}

/// Hard square wave from a phase value. Harsh, full ±1 edges.
pub fn square(phase: f32) -> f32 {
    if (phase / std::f32::consts::TAU).fract() < 0.5 {
        1.0
    } else {
        -1.0
    }
}

/// Sawtooth wave from a phase value. Bright, rising ramp.
pub fn saw(phase: f32) -> f32 {
    (phase / std::f32::consts::TAU).fract() * 2.0 - 1.0
}

/// Hill wave from a phase value: a rectified sine, rounder than triangle.
pub fn hill(phase: f32) -> f32 {
    (phase * 0.5).sin().abs() * 2.0 - 1.0
}
