use crate::{stack_blur, stack_blur_f32, FastBlurChannels, ThreadingPolicy};
use image::{
    DynamicImage, GrayAlphaImage, GrayImage, ImageBuffer, Luma, LumaA, Rgb, Rgb32FImage, RgbImage,
    Rgba, Rgba32FImage, RgbaImage,
};
#[must_use]
pub fn stack_blur_image(
    image: DynamicImage,
    radius: u32,
    threading_policy: ThreadingPolicy,
) -> Option<DynamicImage> {
    match image {
        DynamicImage::ImageLuma8(gray) => {
            let mut new_image = gray.as_raw().to_vec();
            stack_blur(
                &mut new_image,
                gray.width(),
                gray.width(),
                gray.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            let new_gray_image = GrayImage::from_raw(gray.width(), gray.height(), new_image)?;
            Some(DynamicImage::ImageLuma8(new_gray_image))
        }
        DynamicImage::ImageLumaA8(luma_alpha_image) => {
            let mut intensity_plane =
                vec![0u8; luma_alpha_image.width() as usize * luma_alpha_image.height() as usize];
            let mut alpha_plane =
                vec![0u8; luma_alpha_image.width() as usize * luma_alpha_image.height() as usize];
            let raw_buffer = luma_alpha_image.as_raw();
            for ((intensity, alpha), raw_buffer) in intensity_plane
                .iter_mut()
                .zip(alpha_plane.iter_mut())
                .zip(raw_buffer.chunks_exact(2))
            {
                *intensity = raw_buffer[0];
                *alpha = raw_buffer[1];
            }
            stack_blur(
                &mut intensity_plane,
                luma_alpha_image.width(),
                luma_alpha_image.width(),
                luma_alpha_image.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            stack_blur(
                &mut alpha_plane,
                luma_alpha_image.width(),
                luma_alpha_image.width(),
                luma_alpha_image.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            let mut new_raw_buffer =
                vec![
                    0u8;
                    luma_alpha_image.width() as usize * luma_alpha_image.height() as usize * 2
                ];
            for ((intensity, alpha), raw_buffer) in intensity_plane
                .iter()
                .zip(alpha_plane.iter())
                .zip(new_raw_buffer.chunks_exact_mut(2))
            {
                raw_buffer[0] = *intensity;
                raw_buffer[1] = *alpha;
            }
            let new_gray_image = GrayAlphaImage::from_raw(
                luma_alpha_image.width(),
                luma_alpha_image.height(),
                new_raw_buffer,
            )?;
            Some(DynamicImage::ImageLumaA8(new_gray_image))
        }
        DynamicImage::ImageRgb8(rgb_image) => {
            let mut new_image = rgb_image.as_raw().to_vec();
            stack_blur(
                &mut new_image,
                rgb_image.width() * 3,
                rgb_image.width(),
                rgb_image.height(),
                radius,
                FastBlurChannels::Channels3,
                threading_policy,
            );
            let new_rgb_image =
                RgbImage::from_raw(rgb_image.width(), rgb_image.height(), new_image)?;
            Some(DynamicImage::ImageRgb8(new_rgb_image))
        }
        DynamicImage::ImageRgba8(rgba_image) => {
            let mut new_image = rgba_image.as_raw().to_vec();
            stack_blur(
                &mut new_image,
                rgba_image.width() * 4,
                rgba_image.width(),
                rgba_image.height(),
                radius,
                FastBlurChannels::Channels4,
                threading_policy,
            );
            let new_rgba_image =
                RgbaImage::from_raw(rgba_image.width(), rgba_image.height(), new_image)?;
            Some(DynamicImage::ImageRgba8(new_rgba_image))
        }
        DynamicImage::ImageLuma16(luma_16) => {
            let mut new_image = luma_16
                .as_raw()
                .iter()
                .map(|&x| x as f32 * (1. / (u16::MAX as f32)))
                .collect::<Vec<_>>();
            stack_blur_f32(
                &mut new_image,
                luma_16.width(),
                luma_16.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            let rolled_back_image = new_image
                .iter()
                .map(|&x| (x * (u16::MAX as f32)).round().min(u16::MAX as f32) as u16)
                .collect::<Vec<_>>();
            let new_rgb_image = ImageBuffer::<Luma<u16>, Vec<u16>>::from_raw(
                luma_16.width(),
                luma_16.height(),
                rolled_back_image,
            )?;
            Some(DynamicImage::ImageLuma16(new_rgb_image))
        }
        DynamicImage::ImageLumaA16(gray_alpha_16) => {
            let mut intensity_plane =
                vec![0f32; gray_alpha_16.width() as usize * gray_alpha_16.height() as usize];
            let mut alpha_plane =
                vec![0f32; gray_alpha_16.width() as usize * gray_alpha_16.height() as usize];
            let raw_buffer = gray_alpha_16.as_raw();
            for ((intensity, alpha), raw_buffer) in intensity_plane
                .iter_mut()
                .zip(alpha_plane.iter_mut())
                .zip(raw_buffer.chunks_exact(2))
            {
                *intensity = raw_buffer[0] as f32 * (1. / u16::MAX as f32);
                *alpha = raw_buffer[1] as f32 * (1. / u16::MAX as f32);
            }
            stack_blur_f32(
                &mut intensity_plane,
                gray_alpha_16.width(),
                gray_alpha_16.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            stack_blur_f32(
                &mut alpha_plane,
                gray_alpha_16.width(),
                gray_alpha_16.height(),
                radius,
                FastBlurChannels::Plane,
                threading_policy,
            );
            let mut new_raw_buffer =
                vec![0u16; gray_alpha_16.width() as usize * gray_alpha_16.height() as usize * 2];
            for ((intensity, alpha), raw_buffer) in intensity_plane
                .iter()
                .zip(alpha_plane.iter())
                .zip(new_raw_buffer.chunks_exact_mut(2))
            {
                raw_buffer[0] = ((*intensity) * (u16::MAX as f32))
                    .round()
                    .min(u16::MAX as f32) as u16;
                raw_buffer[1] = ((*alpha) * (u16::MAX as f32)).round().min(u16::MAX as f32) as u16;
            }
            let new_gray_image = ImageBuffer::<LumaA<u16>, Vec<u16>>::from_raw(
                gray_alpha_16.width(),
                gray_alpha_16.height(),
                new_raw_buffer,
            )?;
            Some(DynamicImage::ImageLumaA16(new_gray_image))
        }
        DynamicImage::ImageRgb16(rgb_16_image) => {
            let mut new_image = rgb_16_image
                .as_raw()
                .iter()
                .map(|&x| x as f32 * (1. / (u16::MAX as f32)))
                .collect::<Vec<_>>();
            stack_blur_f32(
                &mut new_image,
                rgb_16_image.width(),
                rgb_16_image.height(),
                radius,
                FastBlurChannels::Channels3,
                threading_policy,
            );
            let rolled_back_image = new_image
                .iter()
                .map(|&x| (x * (u16::MAX as f32)).round().min(u16::MAX as f32) as u16)
                .collect::<Vec<_>>();
            let new_rgb_image = ImageBuffer::<Rgb<u16>, Vec<u16>>::from_raw(
                rgb_16_image.width(),
                rgb_16_image.height(),
                rolled_back_image,
            )?;
            Some(DynamicImage::ImageRgb16(new_rgb_image))
        }
        DynamicImage::ImageRgba16(rgba_16_image) => {
            let mut new_image = rgba_16_image
                .as_raw()
                .iter()
                .map(|&x| x as f32 * (1. / (u16::MAX as f32)))
                .collect::<Vec<_>>();
            stack_blur_f32(
                &mut new_image,
                rgba_16_image.width(),
                rgba_16_image.height(),
                radius,
                FastBlurChannels::Channels3,
                threading_policy,
            );
            let rolled_back_image = new_image
                .iter()
                .map(|&x| (x * (u16::MAX as f32)).round().min(u16::MAX as f32) as u16)
                .collect::<Vec<_>>();
            let new_rgba_image = ImageBuffer::<Rgba<u16>, Vec<u16>>::from_raw(
                rgba_16_image.width(),
                rgba_16_image.height(),
                rolled_back_image,
            )?;
            Some(DynamicImage::ImageRgba16(new_rgba_image))
        }
        DynamicImage::ImageRgb32F(rgb_image_f32) => {
            let mut new_image = rgb_image_f32.as_raw().to_vec();
            stack_blur_f32(
                &mut new_image,
                rgb_image_f32.width(),
                rgb_image_f32.height(),
                radius,
                FastBlurChannels::Channels3,
                threading_policy,
            );
            let new_rgb_image =
                Rgb32FImage::from_raw(rgb_image_f32.width(), rgb_image_f32.height(), new_image)?;
            Some(DynamicImage::ImageRgb32F(new_rgb_image))
        }
        DynamicImage::ImageRgba32F(rgba_image_f32) => {
            let mut new_image = rgba_image_f32.as_raw().to_vec();
            stack_blur_f32(
                &mut new_image,
                rgba_image_f32.width(),
                rgba_image_f32.height(),
                radius,
                FastBlurChannels::Channels4,
                threading_policy,
            );
            let new_rgb_image =
                Rgba32FImage::from_raw(rgba_image_f32.width(), rgba_image_f32.height(), new_image)?;
            Some(DynamicImage::ImageRgba32F(new_rgb_image))
        }
        _ => None,
    }
}