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

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

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

use crate::waveform::{soft_square, triangle};

/// Continuous oscillator mixing sine, triangle, and soft-square waveforms.
///
/// Use with a `Player` and call `Player::set_speed()` to modulate pitch
/// and `Player::set_volume()` to control loudness dynamically.
pub struct Oscillator {
    sample_rate: u32,
    phase: f32,
    base_frequency: f32,
    sine_mix: f32,
    triangle_mix: f32,
    square_mix: f32,
    volume: f32,
}

impl Oscillator {
    /// Creates an oscillator with custom waveform mix.
    ///
    /// Each mix value controls how much of that waveform contributes
    /// to the output. Values are relative weights (not required to sum to 1.0).
    pub fn new(base_frequency: f32, sine_mix: f32, triangle_mix: f32, square_mix: f32) -> Self {
        Self {
            sample_rate: 44100,
            phase: 0.0,
            base_frequency,
            sine_mix,
            triangle_mix,
            square_mix,
            volume: 0.04,
        }
    }

    /// Sets the output volume multiplier (default 0.04).
    pub fn with_volume(mut self, volume: f32) -> Self {
        self.volume = volume;
        self
    }

    /// Smooth oscillator: mostly sine with some triangle.
    pub fn smooth(base_frequency: f32) -> Self {
        Self::new(base_frequency, 0.6, 0.3, 0.1)
    }

    /// Rough oscillator: mostly soft-square for a buzzy timbre.
    pub fn rough(base_frequency: f32) -> Self {
        Self::new(base_frequency, 0.2, 0.3, 0.5)
    }

    /// Balanced mix of all three waveforms.
    pub fn medium(base_frequency: f32) -> Self {
        Self::new(base_frequency, 0.4, 0.3, 0.3)
    }
}

impl Iterator for Oscillator {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        let tau = std::f32::consts::TAU;
        self.phase += tau * self.base_frequency / self.sample_rate as f32;

        let sample = self.phase.sin() * self.sine_mix
            + triangle(self.phase) * self.triangle_mix
            + soft_square(self.phase) * self.square_mix;

        Some(sample * self.volume)
    }
}

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