pub mod amplitude;
pub mod tone;
pub mod utils;

use super::AudioSource;
use crossbeam_channel::{self, Receiver, Sender};
use tone::Tone;

const CHANNEL_SIZE: usize = 256;

pub enum Message {
    NoteHold(usize),
    NoteRelease(usize),
    SetFadePressed(amplitude::Map),
    SetFadeReleased(amplitude::Map),
    MuteAll,
}

pub type Handle = Sender<Message>;

pub struct Synthesizer {
    // State
    notes: Vec<Tone>,
    position: u32,
    // Settings
    sample_rate: u32,
    obertones: Vec<(f32, f32)>,
    normalizer: f32,
    pressed_fade_map: amplitude::Map,
    released_fade_map: amplitude::Map,
    // Message queue
    messages: Receiver<Message>,
}

impl Synthesizer {
    // -- Public methods ------------------------------------------------------
    pub fn new(notes_number: usize, sample_rate: u32) -> (Synthesizer, Handle) {
        let (tx, rx) = crossbeam_channel::bounded(CHANNEL_SIZE);
        let obertones = vec![(1.0, 1.0), (2.0, 0.25), (4.0, 0.125)];
        let normalizer = utils::get_obertone_normalizer(&obertones);
        let notes = (0..notes_number)
            .map(|i| Tone::new(utils::get_note_frequency(i)))
            .collect();

        let synthesizer = Synthesizer {
            notes,
            position: 0,
            sample_rate,
            obertones,
            normalizer,
            pressed_fade_map: amplitude::default_hold(),
            released_fade_map: amplitude::default_release(),
            messages: rx,
        };

        (synthesizer, tx)
    }

    // -- Private methods -----------------------------------------------------

    fn note_hold(&mut self, note_id: usize) {
        self.notes.get_mut(note_id).map(Tone::hold);
    }

    fn note_release(&mut self, note_id: usize) {
        self.notes.get_mut(note_id).map(Tone::release);
    }

    fn mute(&mut self) {
        for note in &mut self.notes {
            note.amplitude_target = 0.0;
        }
    }

    fn advance_sample_state(&mut self) {
        self.position = (self.position + 1) % (self.sample_rate * 60);
        for note in self.notes.iter_mut().filter(|note| note.is_active) {
            let time = note.hold_elapsed_cycles as f32 / self.sample_rate as f32;
            note.amplitude_target = self.pressed_fade_map.value(time);
            if !note.is_held {
                let time = note.release_elapsed_cycles as f32 / self.sample_rate as f32;
                note.amplitude_target *= self.released_fade_map.value(time);
            }
            note.advance();
        }
    }
}

// -- Impl AudioSource trait --------------------------------------------------

impl AudioSource for Synthesizer {
    fn get_sample(&mut self) -> f32 {
        self.advance_sample_state();
        let time = self.position as f32 / self.sample_rate as f32;
        let sample = self
            .notes
            .iter()
            .filter(|note| note.is_active)
            .map(|note| {
                let x = time * note.frequency * 2.0 * std::f32::consts::PI;
                let value = self
                    .obertones
                    .iter()
                    .map(|(mul, den)| (mul * x).sin() * den)
                    .sum::<f32>()
                    * note.amplitude_current
                    / self.normalizer;
                value
            })
            .sum::<f32>();
        sample
    }

    fn process_messages(&mut self) {
        match self.messages.try_recv() {
            Ok(Message::NoteHold(id)) => self.note_hold(id),
            Ok(Message::NoteRelease(id)) => self.note_release(id),
            Ok(Message::SetFadePressed(map)) => self.pressed_fade_map = map,
            Ok(Message::SetFadeReleased(map)) => self.released_fade_map = map,
            Ok(Message::MuteAll) => self.mute(),
            _ => {}
        }
    }
}
