use crossbeam_channel::{self, Receiver, Sender};

use super::AudioSource;

const CHANNEL_SIZE: usize = 256;

struct Input {
    source: Box<dyn AudioSource + Send>,
    enabled: bool,
    volume: f32,
}

pub enum Message {
    SetInputEnabled((usize, bool)),
    _SetInputVolume((usize, f32)),
}

pub type Handle = Sender<Message>;

pub struct Mixer {
    inputs: Vec<Input>,
    messages: Receiver<Message>,
}

impl Mixer {
    pub fn new() -> (Mixer, Handle) {
        let (tx, rx) = crossbeam_channel::bounded(CHANNEL_SIZE);
        let mixer = Mixer {
            inputs: Vec::new(),
            messages: rx,
        };
        (mixer, tx)
    }

    pub fn add(
        &mut self,
        source: Box<dyn AudioSource + Send>,
        enabled: bool,
        volume: f32,
    ) -> usize {
        // TODO: Use f32::clamp() once it's stable
        let volume = if volume > 1.0 {
            1.0
        } else if volume < 0.0 {
            0.0
        } else {
            volume
        };
        let input = Input {
            source,
            enabled,
            volume,
        };
        self.inputs.push(input);
        self.inputs.len() - 1
    }
}

impl AudioSource for Mixer {
    fn get_sample(&mut self) -> f32 {
        self.inputs
            .iter_mut()
            .filter(|x| x.enabled)
            .map(|x| x.source.get_sample() * x.volume)
            .sum()
    }

    fn process_messages(&mut self) {
        match self.messages.try_recv() {
            Ok(Message::SetInputEnabled((idx, enabled))) => {
                self.inputs.get_mut(idx).unwrap().enabled = enabled;
            }

            Ok(Message::_SetInputVolume((idx, volume))) => {
                self.inputs.get_mut(idx).unwrap().volume = volume;
            }

            _ => {}
        }

        for input in &mut self.inputs {
            input.source.process_messages();
        }
    }
}
