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

use std::{mem, slice};

use crate::ffi;
use glib::{Type, prelude::*, translate::*, value::FromValue};

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
#[non_exhaustive]
#[doc(alias = "GstAudioChannelPosition")]
#[repr(i32)]
pub enum AudioChannelPosition {
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_NONE")]
    None = ffi::GST_AUDIO_CHANNEL_POSITION_NONE,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_MONO")]
    Mono,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_INVALID")]
    Invalid,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT")]
    FrontLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT")]
    FrontRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER")]
    FrontCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_LFE1")]
    Lfe1,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_REAR_LEFT")]
    RearLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT")]
    RearRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER")]
    FrontLeftOfCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER")]
    FrontRightOfCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_REAR_CENTER")]
    RearCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_LFE2")]
    Lfe2,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT")]
    SideLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT")]
    SideRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT")]
    TopFrontLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT")]
    TopFrontRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER")]
    TopFrontCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_CENTER")]
    TopCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT")]
    TopRearLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT")]
    TopRearRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_LEFT")]
    TopSideLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_RIGHT")]
    TopSideRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER")]
    TopRearCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_CENTER")]
    BottomFrontCenter,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_LEFT")]
    BottomFrontLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_RIGHT")]
    BottomFrontRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_WIDE_LEFT")]
    WideLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_WIDE_RIGHT")]
    WideRight,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_SURROUND_LEFT")]
    SurroundLeft,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_SURROUND_RIGHT")]
    SurroundRight = ffi::GST_AUDIO_CHANNEL_POSITION_SURROUND_RIGHT,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_SURROUND_LEFT")]
    TopSurroundLeft = 28,
    #[doc(alias = "GST_AUDIO_CHANNEL_POSITION_TOP_SURROUND_RIGHT")]
    TopSurroundRight = 29,
    #[doc(hidden)]
    UnknownChannel30 = 30,
    #[doc(hidden)]
    UnknownChannel31 = 31,
    #[doc(hidden)]
    UnknownChannel32 = 32,
    #[doc(hidden)]
    UnknownChannel33 = 33,
    #[doc(hidden)]
    UnknownChannel34 = 34,
    #[doc(hidden)]
    UnknownChannel35 = 35,
    #[doc(hidden)]
    UnknownChannel36 = 36,
    #[doc(hidden)]
    UnknownChannel37 = 37,
    #[doc(hidden)]
    UnknownChannel38 = 38,
    #[doc(hidden)]
    UnknownChannel39 = 39,
    #[doc(hidden)]
    UnknownChannel40 = 40,
    #[doc(hidden)]
    UnknownChannel41 = 41,
    #[doc(hidden)]
    UnknownChannel42 = 42,
    #[doc(hidden)]
    UnknownChannel43 = 43,
    #[doc(hidden)]
    UnknownChannel44 = 44,
    #[doc(hidden)]
    UnknownChannel45 = 45,
    #[doc(hidden)]
    UnknownChannel46 = 46,
    #[doc(hidden)]
    UnknownChannel47 = 47,
    #[doc(hidden)]
    UnknownChannel48 = 48,
    #[doc(hidden)]
    UnknownChannel49 = 49,
    #[doc(hidden)]
    UnknownChannel50 = 50,
    #[doc(hidden)]
    UnknownChannel51 = 51,
    #[doc(hidden)]
    UnknownChannel52 = 52,
    #[doc(hidden)]
    UnknownChannel53 = 53,
    #[doc(hidden)]
    UnknownChannel54 = 54,
    #[doc(hidden)]
    UnknownChannel55 = 55,
    #[doc(hidden)]
    UnknownChannel56 = 56,
    #[doc(hidden)]
    UnknownChannel57 = 57,
    #[doc(hidden)]
    UnknownChannel58 = 58,
    #[doc(hidden)]
    UnknownChannel59 = 59,
    #[doc(hidden)]
    UnknownChannel60 = 60,
    #[doc(hidden)]
    UnknownChannel61 = 61,
    #[doc(hidden)]
    UnknownChannel62 = 62,
    #[doc(hidden)]
    UnknownChannel63 = 63,
    #[doc(hidden)]
    UnknownChannel64 = 64,
}

impl std::fmt::Display for AudioChannelPosition {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        <AudioChannelPosition as std::fmt::Debug>::fmt(self, f)
    }
}

unsafe impl TransparentType for AudioChannelPosition {
    type GlibType = ffi::GstAudioChannelPosition;
}

#[doc(hidden)]
impl IntoGlib for AudioChannelPosition {
    type GlibType = ffi::GstAudioChannelPosition;

    #[inline]
    fn into_glib(self) -> ffi::GstAudioChannelPosition {
        self as ffi::GstAudioChannelPosition
    }
}

#[doc(hidden)]
impl FromGlib<ffi::GstAudioChannelPosition> for AudioChannelPosition {
    #[inline]
    unsafe fn from_glib(value: ffi::GstAudioChannelPosition) -> Self {
        unsafe {
            skip_assert_initialized!();
            debug_assert!((ffi::GST_AUDIO_CHANNEL_POSITION_NONE..=64).contains(&value));
            mem::transmute::<ffi::GstAudioChannelPosition, AudioChannelPosition>(value)
        }
    }
}

impl StaticType for AudioChannelPosition {
    #[inline]
    fn static_type() -> Type {
        unsafe { from_glib(ffi::gst_audio_channel_position_get_type()) }
    }
}

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

unsafe impl<'a> FromValue<'a> for AudioChannelPosition {
    type Checker = glib::value::GenericValueTypeChecker<Self>;

    #[inline]
    unsafe fn from_value(value: &'a glib::Value) -> Self {
        unsafe {
            skip_assert_initialized!();
            from_glib(glib::gobject_ffi::g_value_get_enum(value.to_glib_none().0))
        }
    }
}

impl ToValue for AudioChannelPosition {
    #[inline]
    fn to_value(&self) -> glib::Value {
        let mut value = glib::Value::for_value_type::<Self>();
        unsafe {
            glib::gobject_ffi::g_value_set_enum(value.to_glib_none_mut().0, self.into_glib());
        }
        value
    }

    #[inline]
    fn value_type(&self) -> glib::Type {
        Self::static_type()
    }
}

impl From<AudioChannelPosition> for glib::Value {
    #[inline]
    fn from(v: AudioChannelPosition) -> Self {
        skip_assert_initialized!();
        ToValue::to_value(&v)
    }
}

impl AudioChannelPosition {
    pub fn to_mask(self) -> u64 {
        let pos = self.into_glib();
        if pos < 0 {
            return 0;
        }

        1 << (pos as u32)
    }

    #[doc(alias = "gst_audio_channel_positions_to_mask")]
    pub fn positions_to_mask(
        positions: &[Self],
        force_order: bool,
    ) -> Result<u64, glib::error::BoolError> {
        assert_initialized_main_thread!();

        let len = positions.len();
        if len > 64 {
            return Err(glib::bool_error!("Invalid number of channels"));
        }

        unsafe {
            let mut mask = mem::MaybeUninit::uninit();
            let valid: bool = from_glib(ffi::gst_audio_channel_positions_to_mask(
                positions.as_ptr() as *mut _,
                len as i32,
                force_order.into_glib(),
                mask.as_mut_ptr(),
            ));
            if valid {
                Ok(mask.assume_init())
            } else {
                Err(glib::bool_error!(
                    "Couldn't convert channel positions to mask"
                ))
            }
        }
    }

    #[doc(alias = "gst_audio_channel_positions_from_mask")]
    pub fn positions_from_mask(mask: u64, positions: &mut [Self]) -> Result<(), glib::BoolError> {
        assert_initialized_main_thread!();

        if positions.len() > 64 {
            return Err(glib::bool_error!("Invalid number of channels"));
        }

        let len = positions.len();
        let valid: bool = unsafe {
            from_glib(ffi::gst_audio_channel_positions_from_mask(
                len as i32,
                mask,
                positions.as_mut_ptr() as *mut _,
            ))
        };

        if valid {
            Ok(())
        } else {
            Err(glib::bool_error!(
                "Couldn't convert channel positions to mask",
            ))
        }
    }

    #[doc(alias = "gst_audio_channel_positions_to_valid_order")]
    pub fn positions_to_valid_order(positions: &mut [Self]) -> Result<(), glib::BoolError> {
        assert_initialized_main_thread!();

        if positions.len() > 64 {
            return Err(glib::bool_error!("Invalid number of channels"));
        }

        let len = positions.len();
        let valid: bool = unsafe {
            from_glib(ffi::gst_audio_channel_positions_to_valid_order(
                positions.as_mut_ptr() as *mut _,
                len as i32,
            ))
        };

        if valid {
            Ok(())
        } else {
            Err(glib::bool_error!(
                "Couldn't convert channel positions to mask",
            ))
        }
    }

    #[doc(alias = "get_fallback_mask")]
    #[doc(alias = "gst_audio_channel_get_fallback_mask")]
    pub fn fallback_mask(channels: u32) -> u64 {
        assert_initialized_main_thread!();

        unsafe { ffi::gst_audio_channel_get_fallback_mask(channels as i32) }
    }

    #[doc(alias = "gst_audio_check_valid_channel_positions")]
    pub fn check_valid_channel_positions(positions: &[Self], force_order: bool) -> bool {
        assert_initialized_main_thread!();

        if positions.len() > 64 {
            return false;
        }

        let len = positions.len();
        unsafe {
            from_glib(ffi::gst_audio_check_valid_channel_positions(
                positions.as_ptr() as *mut _,
                len as i32,
                force_order.into_glib(),
            ))
        }
    }
}

#[doc(alias = "gst_audio_buffer_reorder_channels")]
pub fn buffer_reorder_channels(
    buffer: &mut gst::BufferRef,
    format: crate::AudioFormat,
    channels: u32,
    from: &[AudioChannelPosition],
    to: &[AudioChannelPosition],
) -> Result<(), glib::BoolError> {
    skip_assert_initialized!();

    assert!(channels > 0 && channels <= 64);

    if from.len() != to.len() || from.len() > 64 {
        return Err(glib::bool_error!("Invalid number of channels"));
    }

    let formatinfo = crate::AudioFormatInfo::from_format(format);
    if !buffer
        .size()
        .is_multiple_of((formatinfo.width() * channels) as usize)
    {
        return Err(glib::bool_error!("Incomplete number of samples in buffer"));
    }

    let valid: bool = unsafe {
        from_glib(ffi::gst_audio_buffer_reorder_channels(
            buffer.as_mut_ptr(),
            format.into_glib(),
            channels as i32,
            from.as_ptr() as *mut _,
            to.as_ptr() as *mut _,
        ))
    };

    if valid {
        Ok(())
    } else {
        Err(glib::bool_error!("Failed to reorder channels"))
    }
}

#[doc(alias = "gst_audio_reorder_channels")]
pub fn reorder_channels(
    data: &mut [u8],
    format: crate::AudioFormat,
    channels: u32,
    from: &[AudioChannelPosition],
    to: &[AudioChannelPosition],
) -> Result<(), glib::BoolError> {
    assert_initialized_main_thread!();

    if from.len() != to.len() || from.len() > 64 {
        return Err(glib::bool_error!("Invalid number of channels"));
    }
    assert!(channels > 0 && channels <= 64);

    let formatinfo = crate::AudioFormatInfo::from_format(format);
    if !data
        .len()
        .is_multiple_of((formatinfo.width() * channels) as usize)
    {
        return Err(glib::bool_error!("Incomplete number of samples in buffer"));
    }

    let valid: bool = unsafe {
        from_glib(ffi::gst_audio_reorder_channels(
            data.as_mut_ptr() as *mut _,
            data.len(),
            format.into_glib(),
            channels as i32,
            from.as_ptr() as *mut _,
            to.as_ptr() as *mut _,
        ))
    };

    if valid {
        Ok(())
    } else {
        Err(glib::bool_error!("Failed to reorder channels"))
    }
}

#[doc(alias = "get_channel_reorder_map")]
#[doc(alias = "gst_audio_get_channel_reorder_map")]
pub fn channel_reorder_map(
    from: &[AudioChannelPosition],
    to: &[AudioChannelPosition],
    reorder_map: &mut [usize],
) -> Result<(), glib::BoolError> {
    assert_initialized_main_thread!();

    if from.len() != to.len() || from.len() != reorder_map.len() || from.len() > 64 {
        return Err(glib::bool_error!("Invalid number of channels"));
    }

    let mut reorder_map_raw = mem::MaybeUninit::<[i32; 64]>::uninit();
    let valid: bool = unsafe {
        from_glib(ffi::gst_audio_get_channel_reorder_map(
            from.len() as i32,
            from.as_ptr() as *mut _,
            to.as_ptr() as *mut _,
            reorder_map_raw.as_mut_ptr() as *mut i32,
        ))
    };

    if valid {
        let reorder_map_raw =
            unsafe { slice::from_raw_parts(reorder_map_raw.as_ptr() as *const i32, from.len()) };
        for (d, s) in reorder_map.iter_mut().zip(reorder_map_raw.iter()) {
            *d = *s as usize;
        }
        Ok(())
    } else {
        Err(glib::bool_error!("Failed to reorder channels"))
    }
}

#[cfg(feature = "v1_26")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_26")))]
#[doc(alias = "gst_audio_reorder_channels_with_reorder_map")]
pub fn reorder_channels_with_reorder_map(
    data: &mut [u8],
    bps: usize,
    channels: u32,
    reorder_map: &[usize],
) -> Result<(), glib::BoolError> {
    skip_assert_initialized!();

    assert!(bps > 0 && bps <= 64);
    assert!(channels > 0 && channels <= 64);
    if !data.len().is_multiple_of(bps * channels as usize) {
        return Err(glib::bool_error!("Incomplete number of samples in buffer"));
    }
    if reorder_map.len() < channels as usize {
        return Err(glib::bool_error!("Too small reorder map"));
    }

    let mut reorder_map_raw = mem::MaybeUninit::<[i32; 64]>::uninit();
    for (i, c) in reorder_map[..channels as usize].iter().enumerate() {
        if *c >= channels as usize {
            return Err(glib::bool_error!("Invalid channel id in reorder map"));
        }
        unsafe {
            *(reorder_map_raw.as_mut_ptr() as *mut i32).add(i) = *c as i32;
        }
    }

    unsafe {
        ffi::gst_audio_reorder_channels_with_reorder_map(
            data.as_mut_ptr() as *mut _,
            data.len(),
            bps as i32,
            channels as i32,
            reorder_map_raw.as_ptr() as *const i32,
        );
    };

    Ok(())
}
