use librespot::playback::mixer::{AudioFilter, Mixer, MixerConfig};
use log::error;
use std::error::Error;

pub struct AlsaMixer {
    pub device: String,
    pub mixer: String,
    pub linear_scaling: bool,
}

impl AlsaMixer {
    fn set_volume_with_err(&self, volume: u16) -> Result<(), Box<dyn Error>> {
        let mixer = alsa::mixer::Mixer::new(&self.device, false)?;

        let selem_id = alsa::mixer::SelemId::new(&*self.mixer, 0);
        let elem = mixer.find_selem(&selem_id).ok_or_else(|| {
            format!(
                "Couldn't find selem with name '{}'.",
                selem_id.get_name().unwrap_or("unnamed")
            )
        })?;

        let (min, max) = elem.get_playback_volume_range();

        let volume_steps = (max - min) as f64;
        let normalised_volume = if self.linear_scaling {
            ((f64::from(volume) / f64::from(u16::max_value())) * volume_steps) as i64 + min
        } else {
            (f64::from(volume).log(f64::from(u16::max_value())) * volume_steps).floor() as i64 + min
        };

        elem.set_playback_volume_all(normalised_volume)?;
        Ok(())
    }
}

impl Mixer for AlsaMixer {
    fn open(_: Option<MixerConfig>) -> AlsaMixer {
        AlsaMixer {
            device: "default".to_string(),
            mixer: "Master".to_string(),
            linear_scaling: false,
        }
    }

    fn start(&self) {}

    fn stop(&self) {}

    fn volume(&self) -> u16 {
        let selem_id = alsa::mixer::SelemId::new(&*self.mixer, 0);
        match alsa::mixer::Mixer::new(&self.device, false)
            .ok()
            .as_ref()
            .and_then(|mixer| mixer.find_selem(&selem_id))
            .and_then(|elem| {
                let (min, max) = elem.get_playback_volume_range();
                elem.get_playback_volume(alsa::mixer::SelemChannelId::mono())
                    .ok()
                    .map(|volume| {
                        let volume_steps = max - min + 1;
                        ((volume - min) * (0xFFFF / volume_steps)) as u16
                    })
            }) {
            Some(vol) => vol,
            _ => {
                error!(
                    "Couldn't read volume from alsa device with name \"{}\".",
                    self.device
                );
                0
            }
        }
    }

    fn set_volume(&self, volume: u16) {
        match self.set_volume_with_err(volume) {
            Ok(_) => (),
            Err(e) => error!("Couldn't set volume: {:?}", e),
        }
    }

    fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
        None
    }
}
