use image::{flat::SampleLayout, GenericImageView, ImageBuffer, Pixel};
use ndarray::{Array3, ShapeBuilder};

pub fn make_border<I: GenericImageView>(
    image: &I,
    top: u32,
    bottom: u32,
    left: u32,
    right: u32,
    color: I::Pixel,
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
where
    I::Pixel: 'static,
    <I::Pixel as Pixel>::Subpixel: 'static,
{
    let (width, height) = image.dimensions();

    let mut new_image = ImageBuffer::new(width + left + right, height + top + bottom);

    for (x, y, pixel) in new_image.enumerate_pixels_mut() {
        if x < left || x >= width + left || y < top || y >= height + top {
            *pixel = color;
        } else {
            *pixel = image.get_pixel(x - left, y - top);
        }
    }
    new_image
}

pub trait ToRgb8 {
    fn to_rgb8(&self) -> ImageBuffer<image::Rgb<u8>, Vec<u8>>;
}

impl ToRgb8 for Array3<u8> {
    fn to_rgb8(&self) -> ImageBuffer<image::Rgb<u8>, Vec<u8>> {
        let (height, width, _) = self.dim();
        let mut image = ImageBuffer::new(width as u32, height as u32);
        for (x, y, pixel) in image.enumerate_pixels_mut() {
            let r = self[[y as usize, x as usize, 0]];
            let g = self[[y as usize, x as usize, 1]];
            let b = self[[y as usize, x as usize, 2]];
            *pixel = image::Rgb([r, g, b]);
        }
        image
    }
}

/// Uses this trait to convert an image to an ndarray::Array3.
/// Note that conversion must keep HxWxC order.
pub trait ToArray3 {
    type Out;

    fn into_array3(self) -> Self::Out;
}

impl<P> ToArray3 for ImageBuffer<P, Vec<P::Subpixel>>
where
    P: Pixel + 'static,
{
    type Out = Array3<P::Subpixel>;

    fn into_array3(self) -> Self::Out {
        let SampleLayout {
            channels,
            channel_stride,
            height,
            height_stride,
            width,
            width_stride,
        } = self.sample_layout();
        let shape = (height as usize, width as usize, channels as usize);
        let strides = (height_stride, width_stride, channel_stride);
        Array3::from_shape_vec(shape.strides(strides), self.into_raw()).unwrap()
    }
}
