use super::song::{Curve, Sound, WavePoint, sample_envelope};
use rodio::{ChannelCount, SampleRate, Source};
use std::{
    num::NonZero,
    sync::{
        Arc,
        atomic::{AtomicBool, AtomicU32, Ordering},
    },
    time::Duration,
};

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

const MASTER_GAIN: f32 = 0.5;

/// Shortest release ramp for a [`Voice::sustained`] note-off, so it never clicks
/// even when the sound has no decay of its own (about 15 ms at 44100 Hz).
const MIN_RELEASE_SAMPLES: usize = 662;

pub(super) fn apply_curve(ratio: f32, curve: Curve) -> f32 {
    curve.apply(ratio)
}

/// A one-point envelope holding `y` for the whole note.
fn steady_point(y: f32) -> WavePoint {
    WavePoint {
        x: 0.0,
        y,
        curve: Curve::LINEAR,
    }
}

/// Bipolar (-1..1) sample of a resolved wave at `phase` radians, linearly
/// interpolated. An empty table is silence.
pub(super) fn table_sample(table: &[f32], phase: f32) -> f32 {
    if table.is_empty() {
        return 0.0;
    }
    let position =
        phase.rem_euclid(std::f32::consts::TAU) / std::f32::consts::TAU * table.len() as f32;
    let index = (position as usize).min(table.len() - 1);
    let fraction = position - index as f32;
    let current = table[index];
    let next = table[(index + 1) % table.len()];
    current + (next - current) * fraction
}

/// A sounding synthesizer voice for one note: sound, frequency, and duration.
///
/// Implements `rodio::Source` (mono, 44100 Hz). The waveform mix, envelope
/// and modulation come from the [`Sound`] definition.
pub struct Voice {
    sample_rate: u32,
    samples_total: usize,
    samples_played: usize,
    phase: f32,
    frequency_env: Vec<WavePoint>,
    volume_env: Vec<WavePoint>,
    sound: Sound,
    tables: Vec<Vec<f32>>,
    vibrato_table: Vec<f32>,
    tremolo_table: Vec<f32>,
    attack_samples: usize,
    decay_samples: usize,
    /// Extra samples ringing out past `samples_total` (the sound's release).
    release_tail_samples: usize,
    noise_state: u32,
    sustaining: bool,
    released: Option<Arc<AtomicBool>>,
    release_start: Option<usize>,
    release_samples: usize,
    live_pitch: Option<Arc<AtomicU32>>,
    live_gain: Option<Arc<AtomicU32>>,
}

/// Controls a held [`Voice`] created with [`Voice::sustained`] while it sounds:
/// note-off plus live pitch and gain. Cheap to clone; releasing more than once
/// is harmless.
#[derive(Clone)]
pub struct VoiceHandle {
    released: Arc<AtomicBool>,
    pitch_bits: Arc<AtomicU32>,
    gain_bits: Arc<AtomicU32>,
}

impl VoiceHandle {
    /// Starts the voice's release: it fades out over the release ramp and ends.
    pub fn release(&self) {
        self.released.store(true, Ordering::Relaxed);
    }

    /// Sets a live frequency multiplier on the sounding voice: 2.0 is one
    /// octave up, 0.5 one down, 1.0 the note's own pitch. The factor feeds the
    /// phase accumulator per sample, so gliding it is click-free and smooth;
    /// vibrato, slide and arpeggio stack on top.
    pub fn set_pitch(&self, factor: f32) {
        self.pitch_bits.store(factor.to_bits(), Ordering::Relaxed);
    }

    /// Sets a live volume multiplier on the sounding voice, applied next to the
    /// sound's own volume. 1.0 is unchanged, 0.0 silent.
    pub fn set_gain(&self, factor: f32) {
        self.gain_bits.store(factor.to_bits(), Ordering::Relaxed);
    }
}

/// Reads a live factor shared with a [`VoiceHandle`]; `None` (a fixed-length
/// voice) is a neutral 1.0.
fn live_factor(bits: Option<&Arc<AtomicU32>>) -> f32 {
    bits.map_or(1.0, |bits| f32::from_bits(bits.load(Ordering::Relaxed)))
}

impl Voice {
    /// Creates a note source with the given sound, fundamental frequency
    /// (Hz), and total duration. Pitch and volume are steady.
    pub fn new(sound: Sound, frequency: f32, duration: Duration) -> Self {
        Self::ramped(
            sound,
            vec![steady_point(frequency)],
            vec![steady_point(1.0)],
            duration,
        )
    }

    /// Creates a note whose frequency (Hz) and volume follow their envelopes
    /// over the duration, each sampled with [`sample_envelope`]. Both should be
    /// non-empty; a single point holds a steady value.
    pub fn ramped(
        sound: Sound,
        frequency_env: Vec<WavePoint>,
        volume_env: Vec<WavePoint>,
        duration: Duration,
    ) -> Self {
        let sample_rate = 44100;
        let samples_total = (sample_rate as f64 * duration.as_secs_f64()) as usize;
        let attack_samples = (sample_rate as f32 * sound.attack.duration) as usize;
        let decay_samples = (sample_rate as f32 * sound.decay.duration) as usize;
        let release_tail_samples = (sample_rate as f32 * sound.release.duration) as usize;
        Self {
            sample_rate,
            samples_total,
            release_tail_samples,
            samples_played: 0,
            phase: 0.0,
            frequency_env,
            volume_env,
            sound,
            tables: Vec::new(),
            vibrato_table: Vec::new(),
            tremolo_table: Vec::new(),
            attack_samples,
            decay_samples,
            noise_state: 0x1234_5678,
            sustaining: false,
            released: None,
            release_start: None,
            release_samples: 0,
            live_pitch: None,
            live_gain: None,
        }
    }

    /// Creates a held note that plays indefinitely until the returned
    /// [`VoiceHandle`] releases it (note-off), then fades over the sound's
    /// release time — falling back to its decay time, with a ~15 ms minimum so
    /// it never clicks. While it sounds, the handle glides its pitch and gain
    /// live ([`VoiceHandle::set_pitch`], [`VoiceHandle::set_gain`]). For live
    /// keyboard or audition playback where the note length is not known up
    /// front.
    pub fn sustained(sound: Sound, frequency: f32) -> (Self, VoiceHandle) {
        let released = Arc::new(AtomicBool::new(false));
        let pitch_bits = Arc::new(AtomicU32::new(1.0f32.to_bits()));
        let gain_bits = Arc::new(AtomicU32::new(1.0f32.to_bits()));
        let mut voice = Self::ramped(
            sound,
            vec![steady_point(frequency)],
            vec![steady_point(1.0)],
            Duration::ZERO,
        );
        voice.sustaining = true;
        voice.samples_total = usize::MAX;
        voice.release_samples = voice
            .release_tail_samples
            .max(voice.decay_samples)
            .max(MIN_RELEASE_SAMPLES);
        voice.released = Some(Arc::clone(&released));
        voice.live_pitch = Some(Arc::clone(&pitch_bits));
        voice.live_gain = Some(Arc::clone(&gain_bits));
        (
            voice,
            VoiceHandle {
                released,
                pitch_bits,
                gain_bits,
            },
        )
    }

    /// Attaches resolved wave data, one entry per harmonic in order. Each
    /// harmonic samples its table; an empty entry is silence.
    pub fn with_tables(mut self, tables: Vec<Vec<f32>>) -> Self {
        self.tables = tables;
        self
    }

    /// Attaches the resolved waves the vibrato and tremolo LFOs oscillate
    /// along. Only needed when the sound has a non-zero modulation depth.
    pub fn with_lfo_tables(mut self, vibrato: Vec<f32>, tremolo: Vec<f32>) -> Self {
        self.vibrato_table = vibrato;
        self.tremolo_table = tremolo;
        self
    }

    fn progress(&self) -> f32 {
        if self.samples_total == 0 {
            0.0
        } else {
            self.samples_played as f32 / self.samples_total as f32
        }
    }

    fn current_volume(&self) -> f32 {
        sample_envelope(&self.volume_env, self.progress())
    }

    fn time(&self) -> f32 {
        self.samples_played as f32 / self.sample_rate as f32
    }

    fn next_noise(&mut self) -> f32 {
        self.noise_state = self
            .noise_state
            .wrapping_mul(1103515245)
            .wrapping_add(12345);
        (self.noise_state >> 16) as f32 / 32768.0 - 1.0
    }

    fn current_frequency(&self) -> f32 {
        let base = sample_envelope(&self.frequency_env, self.progress());
        let mut semitones = 0.0;
        let slide = self.sound.slide;
        if slide.time > 0.0 {
            let remaining = (1.0 - self.time() / slide.time).max(0.0);
            semitones += slide.amount * remaining;
        }
        let vibrato = &self.sound.vibrato;
        if vibrato.depth > 0.0 {
            let phase = std::f32::consts::TAU * vibrato.rate * self.time();
            semitones += vibrato.depth * table_sample(&self.vibrato_table, phase);
        }
        let arpeggio = &self.sound.arpeggio;
        if !arpeggio.offsets.is_empty() && arpeggio.rate > 0.0 {
            let step = (self.time() * arpeggio.rate) as usize % arpeggio.offsets.len();
            semitones += arpeggio.offsets[step];
        }
        let base = base * live_factor(self.live_pitch.as_ref());
        if semitones == 0.0 {
            base
        } else {
            base * 2.0_f32.powf(semitones / 12.0)
        }
    }

    fn envelope(&self) -> f32 {
        let position = self.samples_played;
        let attack_gain = if position >= self.attack_samples {
            1.0
        } else {
            let ratio = position as f32 / self.attack_samples.max(1) as f32;
            apply_curve(ratio, self.sound.attack.curve)
        };
        let tail_gain = if self.sustaining {
            // Held at full level until note-off, then a release ramp to zero
            // (shaped by the release curve, falling back to the decay curve).
            match self.release_start {
                None => 1.0,
                Some(start) => {
                    let curve = if self.sound.release.duration > 0.0 {
                        self.sound.release.curve
                    } else {
                        self.sound.decay.curve
                    };
                    let into_release = position.saturating_sub(start) as f32;
                    let ratio = (1.0 - into_release / self.release_samples.max(1) as f32).max(0.0);
                    apply_curve(ratio, curve)
                }
            }
        } else {
            // Anchor the decay to the note end so it always reaches zero there.
            // If it is longer than the note, `decay_start` is negative: the note
            // opens partway down the fade (hearing only its tail) instead of
            // cutting off.
            let decay_start = self.samples_total as f32 - self.decay_samples as f32;
            let into_decay = position as f32 - decay_start;
            let decay_gain = if self.decay_samples == 0 || into_decay <= 0.0 {
                1.0
            } else {
                let ratio = (1.0 - into_decay / self.decay_samples as f32).max(0.0);
                apply_curve(ratio, self.sound.decay.curve)
            };
            // The release rings out past the note end from whatever level the
            // note ended at, so it stacks multiplicatively on the decay.
            let release_gain = if position < self.samples_total || self.release_tail_samples == 0 {
                1.0
            } else {
                let into_release = (position - self.samples_total) as f32;
                let ratio = (1.0 - into_release / self.release_tail_samples as f32).max(0.0);
                apply_curve(ratio, self.sound.release.curve)
            };
            decay_gain * release_gain
        };
        attack_gain.min(tail_gain) * self.sound.sustain
    }

    fn synthesize(&self, noise: f32) -> f32 {
        let mut clean = 0.0;
        for (index, harmonic) in self.sound.harmonics.iter().enumerate() {
            let mut scaled_phase = self.phase * harmonic.multiplier;
            if self.sound.steps > 0 {
                let steps = self.sound.steps as f32;
                let cycle = scaled_phase / std::f32::consts::TAU;
                scaled_phase = (cycle * steps).floor() / steps * std::f32::consts::TAU;
            }
            let table = self.tables.get(index).map_or(&[][..], Vec::as_slice);
            clean += table_sample(table, scaled_phase) * harmonic.weight;
        }
        clean += noise * self.sound.noise;
        if self.sound.distortion > 0.0 {
            (clean * (1.0 + self.sound.distortion)).tanh()
        } else {
            clean
        }
    }

    fn tremolo_gain(&self) -> f32 {
        let tremolo = &self.sound.tremolo;
        if tremolo.depth <= 0.0 {
            return 1.0;
        }
        let phase = std::f32::consts::TAU * tremolo.rate * self.time();
        let unipolar = table_sample(&self.tremolo_table, phase) * 0.5 + 0.5;
        1.0 - unipolar * tremolo.depth
    }
}

impl Iterator for Voice {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        if self.sustaining {
            if self.release_start.is_none()
                && self
                    .released
                    .as_ref()
                    .is_some_and(|flag| flag.load(Ordering::Relaxed))
            {
                self.release_start = Some(self.samples_played);
            }
            if let Some(start) = self.release_start
                && self.samples_played.saturating_sub(start) >= self.release_samples
            {
                return None;
            }
        } else if self.samples_played >= self.samples_total + self.release_tail_samples {
            return None;
        }
        let envelope = self.envelope();
        let tremolo = self.tremolo_gain();
        let noise = if self.sound.noise > 0.0 {
            self.next_noise()
        } else {
            0.0
        };
        let sample = self.synthesize(noise)
            * envelope
            * tremolo
            * self.sound.volume
            * live_factor(self.live_gain.as_ref())
            * self.current_volume()
            * MASTER_GAIN;
        self.phase += std::f32::consts::TAU * self.current_frequency() / self.sample_rate as f32;
        self.samples_played += 1;
        Some(sample)
    }
}

impl Source for Voice {
    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> {
        if self.sustaining {
            return None;
        }
        Some(Duration::from_secs_f64(
            (self.samples_total + self.release_tail_samples) as f64 / self.sample_rate as f64,
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::{Voice, apply_curve, steady_point};
    use crate::track::{Curve, Envelope, Sound};
    use std::time::Duration;

    #[test]
    fn release_rings_past_the_note_end() {
        let sound = Sound {
            release: Envelope {
                duration: 0.1,
                curve: Curve::LINEAR,
            },
            ..Sound::default()
        };
        let make = || {
            Voice::ramped(
                sound.clone(),
                vec![steady_point(440.0)],
                vec![steady_point(1.0)],
                Duration::from_millis(100),
            )
        };
        // The voice plays the note plus the release tail.
        assert_eq!(make().count(), 4410 + 4410);
        // Full level at the note end, half way down mid-tail, silent at the end.
        let mut probe = make();
        probe.samples_played = 4410;
        assert!((probe.envelope() - 1.0).abs() < 1e-3);
        probe.samples_played = 4410 + 2205;
        assert!((probe.envelope() - 0.5).abs() < 1e-3);
        probe.samples_played = 4410 + 4409;
        assert!(probe.envelope() < 1e-3);
    }

    const EASE_IN: Curve = Curve {
        ease_in: true,
        ease_out: false,
    };
    const EASE_OUT: Curve = Curve {
        ease_in: false,
        ease_out: true,
    };
    const EASE_BOTH: Curve = Curve {
        ease_in: true,
        ease_out: true,
    };

    fn approx(a: f32, b: f32) -> bool {
        (a - b).abs() < 1e-5
    }

    #[test]
    fn curves_pin_endpoints() {
        for curve in [Curve::LINEAR, EASE_IN, EASE_OUT, EASE_BOTH] {
            assert!(approx(apply_curve(0.0, curve), 0.0));
            assert!(approx(apply_curve(1.0, curve), 1.0));
        }
    }

    #[test]
    fn linear_is_identity() {
        assert!(approx(apply_curve(0.5, Curve::LINEAR), 0.5));
    }

    #[test]
    fn ease_out_starts_fast_ease_in_starts_slow() {
        // At the midpoint, ease-out is already past halfway, ease-in behind.
        assert!(apply_curve(0.5, EASE_OUT) > 0.5);
        assert!(apply_curve(0.5, EASE_IN) < 0.5);
        // The S-curve is symmetric: exactly 0.5 at the midpoint.
        assert!(approx(apply_curve(0.5, EASE_BOTH), 0.5));
    }

    #[test]
    fn short_note_overlaps_attack_and_decay() {
        use super::Voice;
        use crate::track::{Envelope, Sound};
        use std::time::Duration;

        let ramp = Envelope {
            duration: 0.04,
            curve: Curve::LINEAR,
        };
        let sound = Sound {
            attack: ramp,
            decay: ramp,
            ..Sound::default()
        };
        // Note (50 ms) is shorter than attack + decay (80 ms): they overlap.
        let mut voice = Voice::new(sound, 440.0, Duration::from_millis(50));
        let total = voice.samples_total;
        let mut peak = 0.0f32;
        let mut envelope = Vec::with_capacity(total);
        for position in 0..total {
            voice.samples_played = position;
            let value = voice.envelope();
            peak = peak.max(value);
            envelope.push(value);
        }
        // Attack never fully opens before decay pulls it back → capped triangle.
        assert!(peak < 1.0);
        // Silent at both ends: no click.
        assert!(envelope[0] < 0.05);
        assert!(envelope[total - 1] < 0.1);
    }

    #[test]
    fn decay_longer_than_note_finishes_at_zero() {
        use super::Voice;
        use crate::track::{Envelope, Sound};
        use std::time::Duration;

        let sound = Sound {
            decay: Envelope {
                duration: 2.0,
                curve: Curve::LINEAR,
            },
            ..Sound::default()
        };
        // Note is 1 s, decay is 2 s: only the fade's second half plays.
        let mut voice = Voice::new(sound, 440.0, Duration::from_secs(1));
        let total = voice.samples_total;
        voice.samples_played = 0;
        // Opens partway down the fade (halfway, since the note is half the decay).
        assert!((voice.envelope() - 0.5).abs() < 1e-3);
        // Reaches zero at the end instead of cutting off at 0.5.
        voice.samples_played = total - 1;
        assert!(voice.envelope() < 0.01);
    }

    #[test]
    fn live_pitch_scales_the_phase_advance() {
        use super::Voice;
        use crate::track::Sound;

        let advance = |factor: f32| {
            let (mut voice, handle) = Voice::sustained(Sound::default(), 440.0);
            handle.set_pitch(factor);
            for _ in 0..100 {
                voice.next();
            }
            voice.phase
        };
        let single = advance(1.0);
        assert!(single > 0.0);
        assert!((advance(2.0) / single - 2.0).abs() < 1e-3);
        assert!((advance(0.5) / single - 0.5).abs() < 1e-3);
    }

    #[test]
    fn live_gain_scales_the_output() {
        use super::Voice;
        use crate::track::Sound;

        let sound = Sound {
            harmonics: vec![crate::track::Harmonic {
                multiplier: 1.0,
                weight: 1.0,
                duty: 0.0,
                wave: String::new(),
            }],
            ..Sound::default()
        };
        let samples = move |gain: f32| {
            let (voice, handle) = Voice::sustained(sound.clone(), 440.0);
            handle.set_gain(gain);
            voice
                .with_tables(vec![
                    (0..64)
                        .map(|step| (step as f32 / 64.0 * std::f32::consts::TAU).sin())
                        .collect(),
                ])
                .take(1000)
                .collect::<Vec<f32>>()
        };
        let full = samples(1.0);
        let half = samples(0.5);
        for (full, half) in full.iter().zip(&half) {
            assert!((full * 0.5 - half).abs() < 1e-6);
        }
        assert!(full.iter().any(|sample| sample.abs() > 0.01));
    }

    #[test]
    fn sustained_voice_holds_until_released() {
        use super::Voice;
        use crate::track::Sound;

        let (mut voice, handle) = Voice::sustained(Sound::default(), 440.0);
        // Plays on well past any fixed note length while the key is held.
        for _ in 0..50_000 {
            assert!(voice.next().is_some());
        }
        handle.release();
        // After note-off it ends within the release ramp.
        let mut tail = 0;
        while voice.next().is_some() {
            tail += 1;
            assert!(tail < 5000);
        }
        assert!(tail > 0);
    }
}
