use thiserror::Error;
use webrtc_sys::yuv_helper as yuv_sys;

#[derive(Error, Debug)]
pub enum ConvertError {
    #[error("conversion failed: {0}")]
    Convert(&'static str),
}

#[inline]
fn argb_assert_safety(
    src: &[u8],
    src_stride: i32,
    _width: i32,
    height: i32,
) -> Result<(), ConvertError> {
    let min = (src_stride * height) as usize;

    if src.len() < min {
        return Err(ConvertError::Convert("dst isn't large enough"));
    }

    Ok(())
}

#[inline]
fn i420_assert_safety(
    src_y: &[u8],
    src_stride_y: i32,
    src_u: &[u8],
    src_stride_u: i32,
    src_v: &[u8],
    src_stride_v: i32,
    _width: i32,
    height: i32,
) -> Result<(), ConvertError> {
    let chroma_height = (height + 1) / 2;
    let min_y = (src_stride_y * height) as usize;
    let min_u = (src_stride_u * chroma_height) as usize;
    let min_v = (src_stride_v * chroma_height) as usize;

    if src_y.len() < min_y {
        return Err(ConvertError::Convert("src_y isn't large enough"));
    }

    if src_u.len() < min_u {
        return Err(ConvertError::Convert("src_u isn't large enough"));
    }

    if src_v.len() < min_v {
        return Err(ConvertError::Convert("src_v isn't large enough"));
    }

    Ok(())
}

macro_rules! i420_to_x {
    ($x:ident) => {
        pub fn $x(
            src_y: &[u8],
            src_stride_y: i32,
            src_u: &[u8],
            src_stride_u: i32,
            src_v: &[u8],
            src_stride_v: i32,
            dst: &mut [u8],
            dst_stride: i32,
            width: i32,
            height: i32,
        ) -> Result<(), ConvertError> {
            argb_assert_safety(dst, dst_stride, width, height)?;
            i420_assert_safety(
                src_y,
                src_stride_y,
                src_u,
                src_stride_u,
                src_v,
                src_stride_v,
                width,
                height,
            )?;

            unsafe {
                yuv_sys::ffi::$x(
                    src_y.as_ptr(),
                    src_stride_y,
                    src_u.as_ptr(),
                    src_stride_u,
                    src_v.as_ptr(),
                    src_stride_v,
                    dst.as_mut_ptr(),
                    dst_stride,
                    width,
                    height,
                )
                .unwrap();
            }

            Ok(())
        }
    };
}

macro_rules! x_to_i420 {
    ($x:ident) => {
        pub fn $x(
            src_argb: &[u8],
            src_stride_argb: i32,
            dst_y: &mut [u8],
            dst_stride_y: i32,
            dst_u: &mut [u8],
            dst_stride_u: i32,
            dst_v: &mut [u8],
            dst_stride_v: i32,
            width: i32,
            height: i32,
        ) -> Result<(), ConvertError> {
            argb_assert_safety(src_argb, src_stride_argb, width, height)?;
            i420_assert_safety(
                dst_y,
                dst_stride_y,
                dst_u,
                dst_stride_u,
                dst_v,
                dst_stride_v,
                width,
                height,
            )?;

            unsafe {
                yuv_sys::ffi::$x(
                    src_argb.as_ptr(),
                    src_stride_argb,
                    dst_y.as_mut_ptr(),
                    dst_stride_y,
                    dst_u.as_mut_ptr(),
                    dst_stride_u,
                    dst_v.as_mut_ptr(),
                    dst_stride_v,
                    width,
                    height,
                )
                .unwrap();
            }

            Ok(())
        }
    };
}

pub fn argb_to_rgb24(
    src_argb: &[u8],
    src_stride_argb: i32,
    dst_rgb24: &mut [u8],
    dst_stride_rgb24: i32,
    width: i32,
    height: i32,
) -> Result<(), ConvertError> {
    argb_assert_safety(src_argb, src_stride_argb, width, height)?;
    argb_assert_safety(dst_rgb24, dst_stride_rgb24, width, height)?;

    unsafe {
        yuv_sys::ffi::argb_to_rgb24(
            src_argb.as_ptr(),
            src_stride_argb,
            dst_rgb24.as_mut_ptr(),
            dst_stride_rgb24,
            width,
            height,
        )
        .unwrap();
    }

    Ok(())
}

x_to_i420!(argb_to_i420);
x_to_i420!(abgr_to_i420);

i420_to_x!(i420_to_argb);
i420_to_x!(i420_to_bgra);
i420_to_x!(i420_to_abgr);
i420_to_x!(i420_to_rgba);
