// Copyright © 2017-2018 Mozilla Foundation
//
// This program is made available under an ISC-style license.  See the
// accompanying file LICENSE for details.

use ffi;
use {ChannelLayout, SampleFormat, StreamParams, StreamPrefs};

///
#[derive(Debug)]
pub struct StreamParamsBuilder(ffi::cubeb_stream_params);

impl Default for StreamParamsBuilder {
    fn default() -> Self {
        StreamParamsBuilder(ffi::cubeb_stream_params {
            format: ffi::CUBEB_SAMPLE_S16NE,
            ..Default::default()
        })
    }
}

impl StreamParamsBuilder {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn format(mut self, format: SampleFormat) -> Self {
        self.0.format = format.into();
        self
    }

    pub fn rate(mut self, rate: u32) -> Self {
        self.0.rate = rate;
        self
    }

    pub fn channels(mut self, channels: u32) -> Self {
        self.0.channels = channels;
        self
    }

    pub fn layout(mut self, layout: ChannelLayout) -> Self {
        self.0.layout = layout.into();
        self
    }

    pub fn prefs(mut self, prefs: StreamPrefs) -> Self {
        self.0.prefs = prefs.bits();
        self
    }

    pub fn take(&self) -> StreamParams {
        StreamParams::from(self.0)
    }
}

#[cfg(test)]
mod tests {
    use SampleFormat;
    use {ffi, StreamParamsBuilder, StreamPrefs};

    #[test]
    fn stream_params_builder_channels() {
        let params = StreamParamsBuilder::new().channels(2).take();
        assert_eq!(params.channels(), 2);
    }

    #[test]
    fn stream_params_builder_format() {
        macro_rules! check(
            ($($real:ident),*) => (
                $(let params = StreamParamsBuilder::new()
                  .format(super::SampleFormat::$real)
                  .take();
                assert_eq!(params.format(), super::SampleFormat::$real);
                )*
            ) );

        check!(S16LE, S16BE, Float32LE, Float32BE);
    }

    #[test]
    fn stream_params_builder_format_native_endian() {
        let params = StreamParamsBuilder::new()
            .format(SampleFormat::S16NE)
            .take();
        assert_eq!(
            params.format(),
            if cfg!(target_endian = "little") {
                super::SampleFormat::S16LE
            } else {
                super::SampleFormat::S16BE
            }
        );

        let params = StreamParamsBuilder::new()
            .format(SampleFormat::Float32NE)
            .take();
        assert_eq!(
            params.format(),
            if cfg!(target_endian = "little") {
                SampleFormat::Float32LE
            } else {
                SampleFormat::Float32BE
            }
        );
    }

    #[test]
    fn stream_params_builder_layout() {
        macro_rules! check(
            ($($real:ident),*) => (
                $(let params = StreamParamsBuilder::new()
                  .layout(super::ChannelLayout::$real)
                  .take();
                assert_eq!(params.layout(), super::ChannelLayout::$real);
                )*
            ) );

        check!(
            UNDEFINED,
            MONO,
            MONO_LFE,
            STEREO,
            STEREO_LFE,
            _3F,
            _3F_LFE,
            _2F1,
            _2F1_LFE,
            _3F1,
            _3F1_LFE,
            _2F2,
            _2F2_LFE,
            QUAD,
            QUAD_LFE,
            _3F2,
            _3F2_LFE,
            _3F2_BACK,
            _3F2_LFE_BACK,
            _3F3R_LFE,
            _3F4_LFE
        );
    }

    #[test]
    fn stream_params_builder_rate() {
        let params = StreamParamsBuilder::new().rate(44100).take();
        assert_eq!(params.rate(), 44100);
    }

    #[test]
    fn stream_params_builder_to_raw_channels() {
        let params = StreamParamsBuilder::new().channels(2).take();
        let raw = unsafe { &*params.as_ptr() };
        assert_eq!(raw.channels, 2);
    }

    #[test]
    fn stream_params_builder_to_raw_format() {
        macro_rules! check(
            ($($real:ident => $raw:ident),*) => (
                $(let params = super::StreamParamsBuilder::new()
                  .format(SampleFormat::$real)
                  .take();
                  let raw = unsafe { &*params.as_ptr() };
                  assert_eq!(raw.format, ffi::$raw);
                )*
            ) );

        check!(S16LE => CUBEB_SAMPLE_S16LE,
               S16BE => CUBEB_SAMPLE_S16BE,
               Float32LE => CUBEB_SAMPLE_FLOAT32LE,
               Float32BE => CUBEB_SAMPLE_FLOAT32BE);
    }

    #[test]
    fn stream_params_builder_format_to_raw_native_endian() {
        let params = StreamParamsBuilder::new()
            .format(SampleFormat::S16NE)
            .take();
        let raw = unsafe { &*params.as_ptr() };
        assert_eq!(
            raw.format,
            if cfg!(target_endian = "little") {
                ffi::CUBEB_SAMPLE_S16LE
            } else {
                ffi::CUBEB_SAMPLE_S16BE
            }
        );

        let params = StreamParamsBuilder::new()
            .format(SampleFormat::Float32NE)
            .take();
        let raw = unsafe { &*params.as_ptr() };
        assert_eq!(
            raw.format,
            if cfg!(target_endian = "little") {
                ffi::CUBEB_SAMPLE_FLOAT32LE
            } else {
                ffi::CUBEB_SAMPLE_FLOAT32BE
            }
        );
    }

    #[test]
    fn stream_params_builder_to_raw_layout() {
        macro_rules! check(
            ($($real:ident => $raw:ident),*) => (
                $(let params = super::StreamParamsBuilder::new()
                  .layout(super::ChannelLayout::$real)
                  .take();
                  let raw = unsafe { &*params.as_ptr() };
                  assert_eq!(raw.layout, ffi::$raw);
                )*
            ) );

        check!(UNDEFINED => CUBEB_LAYOUT_UNDEFINED,
               MONO => CUBEB_LAYOUT_MONO,
               MONO_LFE => CUBEB_LAYOUT_MONO_LFE,
               STEREO => CUBEB_LAYOUT_STEREO,
               STEREO_LFE => CUBEB_LAYOUT_STEREO_LFE,
               _3F => CUBEB_LAYOUT_3F,
               _3F_LFE => CUBEB_LAYOUT_3F_LFE,
               _2F1 => CUBEB_LAYOUT_2F1,
               _2F1_LFE=> CUBEB_LAYOUT_2F1_LFE,
               _3F1 => CUBEB_LAYOUT_3F1,
               _3F1_LFE =>  CUBEB_LAYOUT_3F1_LFE,
               _2F2 => CUBEB_LAYOUT_2F2,
               _2F2_LFE => CUBEB_LAYOUT_2F2_LFE,
               QUAD => CUBEB_LAYOUT_QUAD,
               QUAD_LFE => CUBEB_LAYOUT_QUAD_LFE,
               _3F2 => CUBEB_LAYOUT_3F2,
               _3F2_LFE => CUBEB_LAYOUT_3F2_LFE,
               _3F2_BACK => CUBEB_LAYOUT_3F2_BACK,
               _3F2_LFE_BACK => CUBEB_LAYOUT_3F2_LFE_BACK,
               _3F3R_LFE => CUBEB_LAYOUT_3F3R_LFE,
               _3F4_LFE => CUBEB_LAYOUT_3F4_LFE);
    }

    #[test]
    fn stream_params_builder_to_raw_rate() {
        let params = StreamParamsBuilder::new().rate(44100).take();
        let raw = unsafe { &*params.as_ptr() };
        assert_eq!(raw.rate, 44100);
    }

    #[test]
    fn stream_params_builder_prefs_default() {
        let params = StreamParamsBuilder::new().take();
        assert_eq!(params.prefs(), StreamPrefs::NONE);
    }

    #[test]
    fn stream_params_builder_prefs() {
        let params = StreamParamsBuilder::new()
            .prefs(StreamPrefs::LOOPBACK)
            .take();
        assert_eq!(params.prefs(), StreamPrefs::LOOPBACK);
    }
}
