// Take a look at the license at the top of the repository in the LICENSE file.

use std::{cmp::Ordering, fmt, marker::PhantomData, str};

use crate::ffi;
use glib::{
    prelude::*,
    translate::{FromGlib, IntoGlib, ToGlibPtr, ToGlibPtrMut, from_glib, from_glib_none},
};

#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
pub enum AudioEndianness {
    Unknown,
    LittleEndian = 1234,
    BigEndian = 4321,
}

impl FromGlib<i32> for AudioEndianness {
    #[allow(unused_unsafe)]
    #[inline]
    unsafe fn from_glib(value: i32) -> Self {
        skip_assert_initialized!();

        match value {
            1234 => Self::LittleEndian,
            4321 => Self::BigEndian,
            _ => Self::Unknown,
        }
    }
}

impl IntoGlib for AudioEndianness {
    type GlibType = i32;

    #[inline]
    fn into_glib(self) -> i32 {
        match self {
            Self::LittleEndian => 1234,
            Self::BigEndian => 4321,
            _ => 0,
        }
    }
}

#[doc(alias = "GstAudioFormatInfo")]
#[derive(Copy, Clone)]
pub struct AudioFormatInfo(&'static ffi::GstAudioFormatInfo);

impl AudioFormatInfo {
    #[inline]
    pub fn from_format(format: crate::AudioFormat) -> Self {
        assert_initialized_main_thread!();

        unsafe {
            let info = ffi::gst_audio_format_get_info(format.into_glib());
            debug_assert!(!info.is_null());

            Self(&*info)
        }
    }

    #[inline]
    pub fn format(&self) -> crate::AudioFormat {
        unsafe { from_glib(self.0.format) }
    }

    #[inline]
    pub fn name<'a>(&self) -> &'a glib::GStr {
        unsafe { glib::GStr::from_ptr(self.0.name) }
    }

    #[inline]
    pub fn description<'a>(&self) -> &'a glib::GStr {
        unsafe { glib::GStr::from_ptr(self.0.description) }
    }

    #[inline]
    pub fn flags(&self) -> crate::AudioFormatFlags {
        unsafe { from_glib(self.0.flags) }
    }

    #[inline]
    pub fn endianness(&self) -> AudioEndianness {
        unsafe { from_glib(self.0.endianness) }
    }

    #[inline]
    pub fn width(&self) -> u32 {
        self.0.width as u32
    }

    #[inline]
    pub fn depth(&self) -> u32 {
        self.0.depth as u32
    }

    #[inline]
    pub fn unpack_format(&self) -> crate::AudioFormat {
        unsafe { from_glib(self.0.unpack_format) }
    }

    #[inline]
    pub fn silence<'a>(&self) -> &'a [u8] {
        &self.0.silence
    }

    pub fn unpack(&self, flags: crate::AudioPackFlags, dest: &mut [u8], src: &[u8]) {
        let unpack_format = Self::from_format(self.unpack_format());
        let unpack_width = unpack_format.width() as usize;

        if unpack_width == 0 || self.0.unpack_func.is_none() {
            panic!("No unpack format for {self:?}");
        }

        let self_width = self.width() as usize;
        if self_width == 0 {
            panic!("No width for {self:?}");
        }

        if !src.len().is_multiple_of(self_width / 8) {
            panic!("Incomplete number of samples in src");
        }

        let nsamples = src.len() / (self_width / 8);

        if dest.len() != nsamples * (unpack_width / 8) {
            panic!("Invalid dest length");
        }

        unsafe {
            (self.0.unpack_func.as_ref().unwrap())(
                self.0,
                flags.into_glib(),
                dest.as_mut_ptr() as *mut _,
                src.as_ptr() as *const _,
                nsamples as i32,
            );
        }
    }

    pub fn pack(&self, flags: crate::AudioPackFlags, dest: &mut [u8], src: &[u8]) {
        let unpack_format = Self::from_format(self.unpack_format());
        let unpack_width = unpack_format.width() as usize;

        if unpack_width == 0 || self.0.pack_func.is_none() {
            panic!("No unpack format for {self:?}");
        }

        let self_width = self.width() as usize;
        if self_width == 0 {
            panic!("No width for {self:?}");
        }

        if !src.len().is_multiple_of(unpack_width / 8) {
            panic!("Incomplete number of samples in src");
        }

        let nsamples = src.len() / (unpack_width / 8);

        if dest.len() != nsamples * (self_width / 8) {
            panic!("Invalid dest length");
        }

        unsafe {
            (self.0.pack_func.as_ref().unwrap())(
                self.0,
                flags.into_glib(),
                src.as_ptr() as *const _,
                dest.as_mut_ptr() as *mut _,
                nsamples as i32,
            );
        }
    }

    #[doc(alias = "gst_audio_format_info_fill_silence")]
    #[doc(alias = "gst_audio_format_fill_silence")]
    pub fn fill_silence(&self, dest: &mut [u8]) {
        let self_width = self.width() as usize;

        if self_width == 0 {
            panic!("Filling with silence unsupported");
        }

        if !dest.len().is_multiple_of(self_width / 8) {
            panic!("Incomplete number of samples in dest");
        }

        unsafe {
            cfg_if::cfg_if! {
                if #[cfg(feature = "v1_20")] {
                    ffi::gst_audio_format_info_fill_silence(self.0, dest.as_mut_ptr() as *mut _, dest.len())
                } else {
                    ffi::gst_audio_format_fill_silence(self.0, dest.as_mut_ptr() as *mut _, dest.len())
                }
            }
        }
    }

    #[inline]
    pub fn is_float(&self) -> bool {
        self.flags().contains(crate::AudioFormatFlags::FLOAT)
    }

    #[inline]
    pub fn is_integer(&self) -> bool {
        self.flags().contains(crate::AudioFormatFlags::INTEGER)
    }

    #[inline]
    pub fn is_signed(&self) -> bool {
        self.flags().contains(crate::AudioFormatFlags::SIGNED)
    }

    #[inline]
    pub fn is_little_endian(&self) -> bool {
        self.endianness() == AudioEndianness::LittleEndian
    }

    #[inline]
    pub fn is_big_endian(&self) -> bool {
        self.endianness() == AudioEndianness::BigEndian
    }
}

unsafe impl Sync for AudioFormatInfo {}
unsafe impl Send for AudioFormatInfo {}

impl PartialEq for AudioFormatInfo {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.format() == other.format()
    }
}

impl Eq for AudioFormatInfo {}

impl PartialOrd for AudioFormatInfo {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for AudioFormatInfo {
    // See GST_AUDIO_FORMATS_ALL for the sorting algorithm
    fn cmp(&self, other: &Self) -> Ordering {
        self.depth()
            .cmp(&other.depth())
            .then_with(|| self.width().cmp(&other.width()))
            .then_with(|| {
                match (
                    self.flags().contains(crate::AudioFormatFlags::FLOAT),
                    other.flags().contains(crate::AudioFormatFlags::FLOAT),
                ) {
                    (true, false) => Ordering::Greater,
                    (false, true) => Ordering::Less,
                    _ => Ordering::Equal,
                }
            })
            .then_with(|| {
                match (
                    self.flags().contains(crate::AudioFormatFlags::SIGNED),
                    other.flags().contains(crate::AudioFormatFlags::SIGNED),
                ) {
                    (true, false) => Ordering::Greater,
                    (false, true) => Ordering::Less,
                    _ => Ordering::Equal,
                }
            })
            .then_with(|| match (self.endianness(), other.endianness()) {
                (crate::AudioEndianness::LittleEndian, crate::AudioEndianness::BigEndian) => {
                    #[cfg(target_endian = "little")]
                    {
                        Ordering::Greater
                    }
                    #[cfg(target_endian = "big")]
                    {
                        Ordering::Less
                    }
                }
                (crate::AudioEndianness::BigEndian, crate::AudioEndianness::LittleEndian) => {
                    #[cfg(target_endian = "little")]
                    {
                        Ordering::Less
                    }
                    #[cfg(target_endian = "big")]
                    {
                        Ordering::Greater
                    }
                }
                _ => Ordering::Equal,
            })
    }
}

impl fmt::Debug for AudioFormatInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("AudioFormatInfo")
            .field("format", &self.format())
            .field("name", &self.name())
            .field("description", &self.description())
            .field("flags", &self.flags())
            .field("endianness", &self.endianness())
            .field("width", &self.width())
            .field("depth", &self.depth())
            .field("silence", &self.silence())
            .finish()
    }
}

impl fmt::Display for AudioFormatInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.name())
    }
}

impl str::FromStr for crate::AudioFormatInfo {
    type Err = glib::BoolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        skip_assert_initialized!();
        let format = s.parse()?;
        Ok(Self::from_format(format))
    }
}

impl From<crate::AudioFormat> for AudioFormatInfo {
    #[inline]
    fn from(f: crate::AudioFormat) -> Self {
        skip_assert_initialized!();
        Self::from_format(f)
    }
}

impl glib::types::StaticType for AudioFormatInfo {
    #[inline]
    fn static_type() -> glib::types::Type {
        unsafe { glib::translate::from_glib(ffi::gst_audio_format_info_get_type()) }
    }
}

impl glib::value::ValueType for AudioFormatInfo {
    type Type = Self;
}

#[doc(hidden)]
unsafe impl<'a> glib::value::FromValue<'a> for AudioFormatInfo {
    type Checker = glib::value::GenericValueTypeOrNoneChecker<Self>;

    unsafe fn from_value(value: &'a glib::Value) -> Self {
        unsafe {
            skip_assert_initialized!();
            from_glib_none(glib::gobject_ffi::g_value_get_boxed(value.to_glib_none().0)
                as *mut ffi::GstAudioFormatInfo)
        }
    }
}

#[doc(hidden)]
impl glib::value::ToValue for AudioFormatInfo {
    fn to_value(&self) -> glib::Value {
        let mut value = glib::Value::for_value_type::<Self>();
        unsafe {
            glib::gobject_ffi::g_value_set_boxed(
                value.to_glib_none_mut().0,
                self.to_glib_none().0 as *mut _,
            )
        }
        value
    }

    fn value_type(&self) -> glib::Type {
        Self::static_type()
    }
}

#[doc(hidden)]
impl glib::value::ToValueOptional for AudioFormatInfo {
    fn to_value_optional(s: Option<&Self>) -> glib::Value {
        skip_assert_initialized!();
        let mut value = glib::Value::for_value_type::<Self>();
        unsafe {
            glib::gobject_ffi::g_value_set_boxed(
                value.to_glib_none_mut().0,
                s.to_glib_none().0 as *mut _,
            )
        }
        value
    }
}

#[doc(hidden)]
impl From<AudioFormatInfo> for glib::Value {
    fn from(v: AudioFormatInfo) -> glib::Value {
        skip_assert_initialized!();
        glib::value::ToValue::to_value(&v)
    }
}

#[doc(hidden)]
impl glib::translate::GlibPtrDefault for AudioFormatInfo {
    type GlibType = *mut ffi::GstAudioFormatInfo;
}

#[doc(hidden)]
unsafe impl glib::translate::TransparentPtrType for AudioFormatInfo {}

#[doc(hidden)]
impl<'a> glib::translate::ToGlibPtr<'a, *const ffi::GstAudioFormatInfo> for AudioFormatInfo {
    type Storage = PhantomData<&'a Self>;

    #[inline]
    fn to_glib_none(&'a self) -> glib::translate::Stash<'a, *const ffi::GstAudioFormatInfo, Self> {
        glib::translate::Stash(self.0, PhantomData)
    }

    fn to_glib_full(&self) -> *const ffi::GstAudioFormatInfo {
        unimplemented!()
    }
}

#[doc(hidden)]
impl glib::translate::FromGlibPtrNone<*mut ffi::GstAudioFormatInfo> for AudioFormatInfo {
    #[inline]
    unsafe fn from_glib_none(ptr: *mut ffi::GstAudioFormatInfo) -> Self {
        unsafe { Self(&*ptr) }
    }
}

#[doc(hidden)]
impl glib::translate::FromGlibPtrNone<*const ffi::GstAudioFormatInfo> for AudioFormatInfo {
    #[inline]
    unsafe fn from_glib_none(ptr: *const ffi::GstAudioFormatInfo) -> Self {
        unsafe { Self(&*ptr) }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get() {
        gst::init().unwrap();

        let info = AudioFormatInfo::from_format(crate::AudioFormat::S16le);
        assert_eq!(info.name(), "S16LE");

        let other_info = "S16LE".parse().unwrap();
        assert_eq!(info, other_info);
    }

    #[test]
    fn pack_unpack() {
        use byte_slice_cast::*;

        gst::init().unwrap();

        let info = AudioFormatInfo::from_format(crate::AudioFormat::S16le);
        let unpack_info = AudioFormatInfo::from_format(info.unpack_format());

        assert!(unpack_info.width() > 0);

        let input = [0i16, i16::MAX, i16::MIN, 0i16];
        let mut unpacked = [0i32; 4];
        let mut output = [0i16; 4];

        info.unpack(
            crate::AudioPackFlags::empty(),
            unpacked.as_mut_byte_slice(),
            input.as_byte_slice(),
        );
        info.pack(
            crate::AudioPackFlags::empty(),
            output.as_mut_byte_slice(),
            unpacked.as_byte_slice(),
        );

        assert_eq!(input, output);
    }
}
