// 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 std::str;
use util::opt_bytes;

/// The state of a device.
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum DeviceState {
    /// The device has been disabled at the system level.
    Disabled,
    /// The device is enabled, but nothing is plugged into it.
    Unplugged,
    /// The device is enabled.
    Enabled,
}

bitflags! {
    /// Architecture specific sample type.
    pub struct DeviceFormat: ffi::cubeb_device_fmt {
        const S16LE = ffi::CUBEB_DEVICE_FMT_S16LE;
        const S16BE = ffi::CUBEB_DEVICE_FMT_S16BE;
        const F32LE = ffi::CUBEB_DEVICE_FMT_F32LE;
        const F32BE = ffi::CUBEB_DEVICE_FMT_F32BE;
    }
}

bitflags! {
    /// Channel type for a `cubeb_stream`. Depending on the backend and platform
    /// used, this can control inter-stream interruption, ducking, and volume
    /// control.
    pub struct DevicePref: ffi::cubeb_device_pref {
        const NONE = ffi::CUBEB_DEVICE_PREF_NONE;
        const MULTIMEDIA = ffi::CUBEB_DEVICE_PREF_MULTIMEDIA;
        const VOICE = ffi::CUBEB_DEVICE_PREF_VOICE;
        const NOTIFICATION = ffi::CUBEB_DEVICE_PREF_NOTIFICATION;
        const ALL = ffi::CUBEB_DEVICE_PREF_ALL;
    }
}

bitflags! {
    /// Whether a particular device is an input device (e.g. a microphone), or an
    /// output device (e.g. headphones).
    pub struct DeviceType: ffi::cubeb_device_type {
        const UNKNOWN = ffi::CUBEB_DEVICE_TYPE_UNKNOWN as _;
        const INPUT = ffi::CUBEB_DEVICE_TYPE_INPUT as _;
        const OUTPUT = ffi::CUBEB_DEVICE_TYPE_OUTPUT as _;
    }
}

/// An opaque handle used to refer to a particular input or output device
/// across calls.
pub type DeviceId = ffi::cubeb_devid;

ffi_type_heap! {
    /// Audio device description
    type CType = ffi::cubeb_device;
    #[derive(Debug)]
    pub struct Device;
    pub struct DeviceRef;
}

impl DeviceRef {
    fn get_ref(&self) -> &ffi::cubeb_device {
        unsafe { &*self.as_ptr() }
    }

    /// Gets the output device name.
    ///
    /// May return `None` if there is no output device.
    pub fn output_name(&self) -> Option<&str> {
        self.output_name_bytes().map(|b| str::from_utf8(b).unwrap())
    }

    pub fn output_name_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().output_name) }
    }

    /// Gets the input device name.
    ///
    /// May return `None` if there is no input device.
    pub fn input_name(&self) -> Option<&str> {
        self.input_name_bytes().map(|b| str::from_utf8(b).unwrap())
    }

    pub fn input_name_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().input_name) }
    }
}

ffi_type_stack! {
    /// This structure holds the characteristics of an input or output
    /// audio device. It is obtained using `enumerate_devices`, which
    /// returns these structures via `device_collection` and must be
    /// destroyed via `device_collection_destroy`.
    type CType = ffi::cubeb_device_info;
    pub struct DeviceInfo;
    pub struct DeviceInfoRef;
}

impl DeviceInfoRef {
    fn get_ref(&self) -> &ffi::cubeb_device_info {
        unsafe { &*self.as_ptr() }
    }

    /// Device identifier handle.
    pub fn devid(&self) -> DeviceId {
        self.get_ref().devid
    }

    /// Device identifier which might be presented in a UI.
    pub fn device_id(&self) -> Option<&str> {
        self.device_id_bytes().map(|b| str::from_utf8(b).unwrap())
    }

    pub fn device_id_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().device_id) }
    }

    /// Friendly device name which might be presented in a UI.
    pub fn friendly_name(&self) -> Option<&str> {
        self.friendly_name_bytes()
            .map(|b| str::from_utf8(b).unwrap())
    }

    pub fn friendly_name_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().friendly_name) }
    }

    /// Two devices have the same group identifier if they belong to
    /// the same physical device; for example a headset and
    /// microphone.
    pub fn group_id(&self) -> Option<&str> {
        self.group_id_bytes().map(|b| str::from_utf8(b).unwrap())
    }

    pub fn group_id_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().group_id) }
    }

    /// Optional vendor name, may be None.
    pub fn vendor_name(&self) -> Option<&str> {
        self.vendor_name_bytes().map(|b| str::from_utf8(b).unwrap())
    }

    pub fn vendor_name_bytes(&self) -> Option<&[u8]> {
        unsafe { opt_bytes(self.get_ref().vendor_name) }
    }

    /// Type of device (Input/Output).
    pub fn device_type(&self) -> DeviceType {
        DeviceType::from_bits_truncate(self.get_ref().device_type)
    }

    /// State of device disabled/enabled/unplugged.
    pub fn state(&self) -> DeviceState {
        let state = self.get_ref().state;
        macro_rules! check( ($($raw:ident => $real:ident),*) => (
            $(if state == ffi::$raw {
                DeviceState::$real
            }) else *
            else {
                panic!("unknown device state: {}", state)
            }
        ));

        check!(CUBEB_DEVICE_STATE_DISABLED => Disabled,
               CUBEB_DEVICE_STATE_UNPLUGGED => Unplugged,
               CUBEB_DEVICE_STATE_ENABLED => Enabled)
    }

    /// Preferred device.
    pub fn preferred(&self) -> DevicePref {
        DevicePref::from_bits(self.get_ref().preferred).unwrap()
    }

    /// Sample format supported.
    pub fn format(&self) -> DeviceFormat {
        DeviceFormat::from_bits(self.get_ref().format).unwrap()
    }

    /// The default sample format for this device.
    pub fn default_format(&self) -> DeviceFormat {
        DeviceFormat::from_bits(self.get_ref().default_format).unwrap()
    }

    /// Channels.
    pub fn max_channels(&self) -> u32 {
        self.get_ref().max_channels
    }

    /// Default/Preferred sample rate.
    pub fn default_rate(&self) -> u32 {
        self.get_ref().default_rate
    }

    /// Maximum sample rate supported.
    pub fn max_rate(&self) -> u32 {
        self.get_ref().max_rate
    }

    /// Minimum sample rate supported.
    pub fn min_rate(&self) -> u32 {
        self.get_ref().min_rate
    }

    /// Lowest possible latency in frames.
    pub fn latency_lo(&self) -> u32 {
        self.get_ref().latency_lo
    }

    /// Higest possible latency in frames.
    pub fn latency_hi(&self) -> u32 {
        self.get_ref().latency_hi
    }
}

#[cfg(test)]
mod tests {
    use ffi::cubeb_device;
    use Device;

    #[test]
    fn device_device_ref_same_ptr() {
        let ptr: *mut cubeb_device = 0xDEAD_BEEF as *mut _;
        let device = unsafe { Device::from_ptr(ptr) };
        assert_eq!(device.as_ptr(), ptr);
        assert_eq!(device.as_ptr(), device.as_ref().as_ptr());
    }
}
