use super::PixelConverter;
use crate::Surface;

macro_rules! impl_from_surface {
    ($from:ty, $to:ty, $converter:ident) => {
        #[cfg(feature = "std")]
        impl<S: AsRef<[$from]> + AsMut<[$from]>> From<&Surface<'_, S, $from>>
            for Surface<'_, Vec<$to>, $to>
        {
            fn from(value: &Surface<'_, S, $from>) -> Self {
                let buffer = value
                    .buffer()
                    .iter()
                    .map(|pixel| Surface::<S, $from>::$converter(pixel))
                    .collect();
                Self {
                    size: value.size,
                    buffer,
                    destination_rect: value.destination_rect,
                    blit_area: value.blit_area,
                    _p: std::marker::PhantomData::default(),
                }
            }
        }
    };
}

impl_from_surface!(u8, [u8; 2], pixel_to_la8);
impl_from_surface!(u8, f32, pixel_to_l32);
impl_from_surface!(u8, [f32; 2], pixel_to_la32);
impl_from_surface!(u8, [u8; 3], pixel_to_rgb8);
impl_from_surface!(u8, [u8; 4], pixel_to_rgba8);
impl_from_surface!(u8, [f32; 4], pixel_to_rgba32);

impl_from_surface!([u8; 2], u8, pixel_to_l8);
impl_from_surface!([u8; 2], f32, pixel_to_l32);
impl_from_surface!([u8; 2], [f32; 2], pixel_to_la32);
impl_from_surface!([u8; 2], [u8; 3], pixel_to_rgb8);
impl_from_surface!([u8; 2], [u8; 4], pixel_to_rgba8);
impl_from_surface!([u8; 2], [f32; 4], pixel_to_rgba32);

impl_from_surface!(f32, u8, pixel_to_l8);
impl_from_surface!(f32, [u8; 2], pixel_to_la8);
impl_from_surface!(f32, [f32; 2], pixel_to_la32);
impl_from_surface!(f32, [u8; 3], pixel_to_rgb8);
impl_from_surface!(f32, [u8; 4], pixel_to_rgba8);
impl_from_surface!(f32, [f32; 4], pixel_to_rgba32);

impl_from_surface!([f32; 2], u8, pixel_to_l8);
impl_from_surface!([f32; 2], [u8; 2], pixel_to_la8);
impl_from_surface!([f32; 2], f32, pixel_to_l32);
impl_from_surface!([f32; 2], [u8; 3], pixel_to_rgb8);
impl_from_surface!([f32; 2], [u8; 4], pixel_to_rgba8);
impl_from_surface!([f32; 2], [f32; 4], pixel_to_rgba32);

impl_from_surface!([u8; 3], u8, pixel_to_l8);
impl_from_surface!([u8; 3], [u8; 2], pixel_to_la8);
impl_from_surface!([u8; 3], f32, pixel_to_l32);
impl_from_surface!([u8; 3], [f32; 2], pixel_to_la32);
impl_from_surface!([u8; 3], [u8; 4], pixel_to_rgba8);
impl_from_surface!([u8; 3], [f32; 4], pixel_to_rgba32);

impl_from_surface!([u8; 4], u8, pixel_to_l8);
impl_from_surface!([u8; 4], [u8; 2], pixel_to_la8);
impl_from_surface!([u8; 4], f32, pixel_to_l32);
impl_from_surface!([u8; 4], [f32; 2], pixel_to_la32);
impl_from_surface!([u8; 4], [u8; 3], pixel_to_rgb8);
impl_from_surface!([u8; 4], [f32; 4], pixel_to_rgba32);

impl_from_surface!([f32; 4], u8, pixel_to_l8);
impl_from_surface!([f32; 4], [u8; 2], pixel_to_la8);
impl_from_surface!([f32; 4], f32, pixel_to_l32);
impl_from_surface!([f32; 4], [f32; 2], pixel_to_la32);
impl_from_surface!([f32; 4], [u8; 3], pixel_to_rgb8);
impl_from_surface!([f32; 4], [u8; 4], pixel_to_rgba8);

#[cfg(feature = "softbuffer")]
mod impl_softbuffer {
    use super::*;
    use crate::sb::Zrgb;

    impl_from_surface!(u8, Zrgb, pixel_to_zrgb);
    impl_from_surface!([u8; 2], Zrgb, pixel_to_zrgb);
    impl_from_surface!(f32, Zrgb, pixel_to_zrgb);
    impl_from_surface!([f32; 2], Zrgb, pixel_to_zrgb);
    impl_from_surface!([u8; 3], Zrgb, pixel_to_zrgb);
    impl_from_surface!([u8; 4], Zrgb, pixel_to_zrgb);
    impl_from_surface!([f32; 4], Zrgb, pixel_to_zrgb);

    impl_from_surface!(Zrgb, u8, pixel_to_l8);
    impl_from_surface!(Zrgb, [u8; 2], pixel_to_la8);
    impl_from_surface!(Zrgb, f32, pixel_to_l32);
    impl_from_surface!(Zrgb, [f32; 2], pixel_to_la32);
    impl_from_surface!(Zrgb, [u8; 3], pixel_to_rgb8);
    impl_from_surface!(Zrgb, [u8; 4], pixel_to_rgba8);
}

#[cfg(feature = "png")]
#[cfg(test)]
mod tests {
    use crate::png::Png;
    use crate::{L8Surface, Rgba8Surface};
    use std::env::current_dir;
    use std::io::Cursor;

    #[test]
    fn test_convert() {
        let src =
            Rgba8Surface::read_png(Cursor::new(include_bytes!("../../test_images/plasma.png")))
                .unwrap();
        let dst = L8Surface::from(&src);
        L8Surface::write_png(
            &dst,
            current_dir()
                .unwrap()
                .join("test_output/plasma_grayscale.png"),
        )
        .unwrap();
    }
}
