use crate::definitions::Image;
use crate::drawing::Canvas;
use crate::drawing::draw_if_in_bounds;
use crate::drawing::line::draw_line_segment_mut;
use image::GenericImage;

/// Draws the outline of an ellipse on an image.
///
/// Draws as much of an ellipse as lies inside the image bounds.
///
/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
/// (Modified from Bresenham's algorithm)
///
/// The ellipse is axis-aligned and satisfies the following equation:
///
/// (`x^2 / width_radius^2) + (y^2 / height_radius^2) = 1`
#[must_use = "the function does not modify the original image"]
pub fn draw_hollow_ellipse<I>(
    image: &I,
    center: (i32, i32),
    width_radius: i32,
    height_radius: i32,
    color: I::Pixel,
) -> Image<I::Pixel>
where
    I: GenericImage,
{
    let mut out = Image::new(image.width(), image.height());
    out.copy_from(image, 0, 0).unwrap();
    draw_hollow_ellipse_mut(&mut out, center, width_radius, height_radius, color);
    out
}
#[doc=generate_mut_doc_comment!("draw_hollow_ellipse")]
pub fn draw_hollow_ellipse_mut<C>(
    canvas: &mut C,
    center: (i32, i32),
    width_radius: i32,
    height_radius: i32,
    color: C::Pixel,
) where
    C: Canvas,
{
    // Circle drawing algorithm is faster, so use it if the given ellipse is actually a circle.
    if width_radius == height_radius {
        draw_hollow_circle_mut(canvas, center, width_radius, color);
        return;
    }

    let draw_quad_pixels = |x0: i32, y0: i32, x: i32, y: i32| {
        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);
        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
    };

    draw_ellipse(draw_quad_pixels, center, width_radius, height_radius);
}

/// Draws an ellipse and its contents on an image.
///
/// Draw as much of the ellipse and its contents as lies inside the image bounds.
///
/// Uses the [Midpoint Ellipse Drawing Algorithm](https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/).
/// (Modified from Bresenham's algorithm)
///
/// The ellipse is axis-aligned and satisfies the following equation:
///
/// `(x^2 / width_radius^2) + (y^2 / height_radius^2) <= 1`
#[must_use = "the function does not modify the original image"]
pub fn draw_filled_ellipse<I>(
    image: &I,
    center: (i32, i32),
    width_radius: i32,
    height_radius: i32,
    color: I::Pixel,
) -> Image<I::Pixel>
where
    I: GenericImage,
{
    let mut out = Image::new(image.width(), image.height());
    out.copy_from(image, 0, 0).unwrap();
    draw_filled_ellipse_mut(&mut out, center, width_radius, height_radius, color);
    out
}
#[doc=generate_mut_doc_comment!("draw_filled_ellipse")]
pub fn draw_filled_ellipse_mut<C>(
    canvas: &mut C,
    center: (i32, i32),
    width_radius: i32,
    height_radius: i32,
    color: C::Pixel,
) where
    C: Canvas,
{
    // Circle drawing algorithm is faster, so use it if the given ellipse is actually a circle.
    if width_radius == height_radius {
        draw_filled_circle_mut(canvas, center, width_radius, color);
        return;
    }

    let draw_line_pairs = |x0: i32, y0: i32, x: i32, y: i32| {
        draw_line_segment_mut(
            canvas,
            ((x0 - x) as f32, (y0 + y) as f32),
            ((x0 + x) as f32, (y0 + y) as f32),
            color,
        );
        draw_line_segment_mut(
            canvas,
            ((x0 - x) as f32, (y0 - y) as f32),
            ((x0 + x) as f32, (y0 - y) as f32),
            color,
        );
    };

    draw_ellipse(draw_line_pairs, center, width_radius, height_radius);
}

// Implements the Midpoint Ellipse Drawing Algorithm https://web.archive.org/web/20160128020853/http://tutsheap.com/c/mid-point-ellipse-drawing-algorithm/). (Modified from Bresenham's algorithm)
//
// Takes a function that determines how to render the points on the ellipse.
fn draw_ellipse<F>(mut render_func: F, center: (i32, i32), width_radius: i32, height_radius: i32)
where
    F: FnMut(i32, i32, i32, i32),
{
    let (x0, y0) = center;
    let w2 = (width_radius * width_radius) as f32;
    let h2 = (height_radius * height_radius) as f32;
    let mut x = 0;
    let mut y = height_radius;
    let mut px = 0.0;
    let mut py = 2.0 * w2 * y as f32;

    render_func(x0, y0, x, y);

    // Top and bottom regions.
    let mut p = h2 - (w2 * height_radius as f32) + (0.25 * w2);
    while px < py {
        x += 1;
        px += 2.0 * h2;
        if p < 0.0 {
            p += h2 + px;
        } else {
            y -= 1;
            py += -2.0 * w2;
            p += h2 + px - py;
        }

        render_func(x0, y0, x, y);
    }

    // Left and right regions.
    p = h2 * (x as f32 + 0.5).powi(2) + (w2 * (y - 1).pow(2) as f32) - w2 * h2;
    while y > 0 {
        y -= 1;
        py += -2.0 * w2;
        if p > 0.0 {
            p += w2 - py;
        } else {
            x += 1;
            px += 2.0 * h2;
            p += w2 - py + px;
        }

        render_func(x0, y0, x, y);
    }
}

/// Draws the outline of a circle on an image.
///
/// Draw as much of the circle as lies inside the image bounds.
#[must_use = "the function does not modify the original image"]
pub fn draw_hollow_circle<I>(
    image: &I,
    center: (i32, i32),
    radius: i32,
    color: I::Pixel,
) -> Image<I::Pixel>
where
    I: GenericImage,
{
    let mut out = Image::new(image.width(), image.height());
    out.copy_from(image, 0, 0).unwrap();
    draw_hollow_circle_mut(&mut out, center, radius, color);
    out
}
#[doc=generate_mut_doc_comment!("draw_hollow_circle")]
pub fn draw_hollow_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
where
    C: Canvas,
{
    let mut x = 0i32;
    let mut y = radius;
    let mut p = 1 - radius;
    let x0 = center.0;
    let y0 = center.1;

    while x <= y {
        draw_if_in_bounds(canvas, x0 + x, y0 + y, color);
        draw_if_in_bounds(canvas, x0 + y, y0 + x, color);
        draw_if_in_bounds(canvas, x0 - y, y0 + x, color);
        draw_if_in_bounds(canvas, x0 - x, y0 + y, color);
        draw_if_in_bounds(canvas, x0 - x, y0 - y, color);
        draw_if_in_bounds(canvas, x0 - y, y0 - x, color);
        draw_if_in_bounds(canvas, x0 + y, y0 - x, color);
        draw_if_in_bounds(canvas, x0 + x, y0 - y, color);

        x += 1;
        if p < 0 {
            p += 2 * x + 1;
        } else {
            y -= 1;
            p += 2 * (x - y) + 1;
        }
    }
}

/// Draws a circle and its contents on an image.
///
/// Draws as much of a circle and its contents as lies inside the image bounds.
#[must_use = "the function does not modify the original image"]
pub fn draw_filled_circle<I>(
    image: &I,
    center: (i32, i32),
    radius: i32,
    color: I::Pixel,
) -> Image<I::Pixel>
where
    I: GenericImage,
{
    let mut out = Image::new(image.width(), image.height());
    out.copy_from(image, 0, 0).unwrap();
    draw_filled_circle_mut(&mut out, center, radius, color);
    out
}
#[doc=generate_mut_doc_comment!("draw_filled_circle")]
pub fn draw_filled_circle_mut<C>(canvas: &mut C, center: (i32, i32), radius: i32, color: C::Pixel)
where
    C: Canvas,
{
    let mut x = 0i32;
    let mut y = radius;
    let mut p = 1 - radius;
    let x0 = center.0;
    let y0 = center.1;

    while x <= y {
        draw_line_segment_mut(
            canvas,
            ((x0 - x) as f32, (y0 + y) as f32),
            ((x0 + x) as f32, (y0 + y) as f32),
            color,
        );
        draw_line_segment_mut(
            canvas,
            ((x0 - y) as f32, (y0 + x) as f32),
            ((x0 + y) as f32, (y0 + x) as f32),
            color,
        );
        draw_line_segment_mut(
            canvas,
            ((x0 - x) as f32, (y0 - y) as f32),
            ((x0 + x) as f32, (y0 - y) as f32),
            color,
        );
        draw_line_segment_mut(
            canvas,
            ((x0 - y) as f32, (y0 - x) as f32),
            ((x0 + y) as f32, (y0 - x) as f32),
            color,
        );

        x += 1;
        if p < 0 {
            p += 2 * x + 1;
        } else {
            y -= 1;
            p += 2 * (x - y) + 1;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{
        draw_filled_circle, draw_filled_circle_mut, draw_filled_ellipse, draw_filled_ellipse_mut,
        draw_hollow_circle, draw_hollow_circle_mut, draw_hollow_ellipse,
    };
    use image::{GenericImage, GrayImage, Luma};

    struct Ellipse {
        center: (i32, i32),
        width_radius: i32,
        height_radius: i32,
    }

    impl Ellipse {
        fn normalized_distance_from_center(&self, (x, y): (i32, i32)) -> f32 {
            let (cx, cy) = self.center;
            let (w, h) = (self.width_radius as f32, self.height_radius as f32);
            ((cx - x) as f32 / w).powi(2) + ((cy - y) as f32 / h).powi(2)
        }
        fn is_boundary_point(&self, (x, y): (i32, i32), boundary_eps: f32) -> bool {
            assert!(boundary_eps >= 0.0);
            (self.normalized_distance_from_center((x, y)) - 1.0).abs() < boundary_eps
        }
        fn is_inner_point(&self, (x, y): (i32, i32)) -> bool {
            self.normalized_distance_from_center((x, y)) < 1.0
        }
    }

    fn check_filled_ellipse<I: GenericImage>(
        img: &I,
        ellipse: Ellipse,
        inner_color: I::Pixel,
        outer_color: I::Pixel,
        boundary_eps: f32,
    ) where
        I::Pixel: core::fmt::Debug + PartialEq,
    {
        for x in 0..img.width() as i32 {
            for y in 0..img.height() as i32 {
                if ellipse.is_boundary_point((x, y), boundary_eps) {
                    continue;
                }
                let pixel = img.get_pixel(x as u32, y as u32);
                if ellipse.is_inner_point((x, y)) {
                    assert_eq!(pixel, inner_color);
                } else {
                    assert_eq!(pixel, outer_color);
                }
            }
        }
    }

    #[cfg_attr(miri, ignore = "slow [>1480s]")]
    #[test]
    fn test_draw_filled_ellipse() {
        let ellipse = Ellipse {
            center: (960, 540),
            width_radius: 960,
            height_radius: 540,
        };
        let inner_color = image::Rgb([255, 0, 0]);
        let outer_color = image::Rgb([0, 0, 0]);
        let mut img = image::RgbImage::new(1920, 1080);
        draw_filled_ellipse_mut(
            &mut img,
            ellipse.center,
            ellipse.width_radius,
            ellipse.height_radius,
            inner_color,
        );
        const EPS: f32 = 0.0019;
        check_filled_ellipse(&img, ellipse, inner_color, outer_color, EPS);
    }

    #[test]
    fn test_draw_hollow_ellipse_matches_circle_when_radii_equal() {
        let background = 1u8;
        let shape = 7u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let image = GrayImage::from_pixel(size, size, Luma([background]));
        let ellipse = draw_hollow_ellipse(&image, center, radius, radius, Luma([shape]));
        let circle = draw_hollow_circle(&image, center, radius, Luma([shape]));

        assert_pixels_eq!(ellipse, circle);
    }

    #[test]
    fn test_draw_filled_ellipse_matches_circle_when_radii_equal() {
        let background = 1u8;
        let shape = 7u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let image = GrayImage::from_pixel(size, size, Luma([background]));
        let ellipse = draw_filled_ellipse(&image, center, radius, radius, Luma([shape]));
        let circle = draw_filled_circle(&image, center, radius, Luma([shape]));

        assert_pixels_eq!(ellipse, circle);
    }

    #[test]
    fn test_draw_hollow_circle() {
        let background = 1u8;
        let outline = 7u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let image = GrayImage::from_pixel(size, size, Luma([background]));

        #[rustfmt::skip]
        let expected = gray_image!(
            background, background, background, background, background, background, background;
            background, background, outline, outline, outline, background, background;
            background, outline, background, background, background, outline, background;
            background, outline, background, background, background, outline, background;
            background, outline, background, background, background, outline, background;
            background, background, outline, outline, outline, background, background;
            background, background, background, background, background, background, background);

        let actual = draw_hollow_circle(&image, center, radius, Luma([outline]));

        assert_pixels_eq!(actual, expected);
    }

    #[test]
    fn test_draw_hollow_circle_mut() {
        let background = 1u8;
        let outline = 5u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let mut image = GrayImage::from_pixel(size, size, Luma([background]));

        #[rustfmt::skip]
        let expected = gray_image!(
            background, background, background, background, background, background, background;
            background, background, outline, outline, outline, background, background;
            background, outline, background, background, background, outline, background;
            background, outline, background, background, background, outline, background;
            background, outline, background, background, background, outline, background;
            background, background, outline, outline, outline, background, background;
            background, background, background, background, background, background, background);

        draw_hollow_circle_mut(&mut image, center, radius, Luma([outline]));

        assert_pixels_eq!(image, expected);
    }

    #[test]
    fn test_draw_filled_circle() {
        let background = 1u8;
        let fill = 4u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let image = GrayImage::from_pixel(size, size, Luma([background]));

        #[rustfmt::skip]
        let expected = gray_image!(
            background, background, background, background, background, background, background;
            background, background, fill, fill, fill, background, background;
            background, fill, fill, fill, fill, fill, background;
            background, fill, fill, fill, fill, fill, background;
            background, fill, fill, fill, fill, fill, background;
            background, background, fill, fill, fill, background, background;
            background, background, background, background, background, background, background);

        let actual = draw_filled_circle(&image, center, radius, Luma([fill]));

        assert_pixels_eq!(actual, expected);
    }

    #[test]
    fn test_draw_filled_circle_mut() {
        let background = 1u8;
        let fill = 3u8;
        let size = 7u32;
        let center = (3, 3);
        let radius = 2;

        let mut image = GrayImage::from_pixel(size, size, Luma([background]));

        #[rustfmt::skip]
        let expected = gray_image!(
            background, background, background, background, background, background, background;
            background, background, fill, fill, fill, background, background;
            background, fill, fill, fill, fill, fill, background;
            background, fill, fill, fill, fill, fill, background;
            background, fill, fill, fill, fill, fill, background;
            background, background, fill, fill, fill, background, background;
            background, background, background, background, background, background, background);

        draw_filled_circle_mut(&mut image, center, radius, Luma([fill]));

        assert_pixels_eq!(image, expected);
    }
}

#[cfg(not(miri))]
#[cfg(test)]
mod benches {
    use image::{GrayImage, Luma};

    macro_rules! bench_hollow_ellipse {
        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
            #[bench]
            fn $name(b: &mut test::Bencher) {
                use super::draw_hollow_ellipse_mut;

                let mut image = GrayImage::new(500, 500);
                let color = Luma([50u8]);
                b.iter(|| {
                    draw_hollow_ellipse_mut(
                        &mut image,
                        $center,
                        $width_radius,
                        $height_radius,
                        color,
                    );
                    test::black_box(&image);
                });
            }
        };
    }

    bench_hollow_ellipse!(bench_bench_hollow_ellipse_circle, (200, 200), 80, 80);
    bench_hollow_ellipse!(bench_bench_hollow_ellipse_vertical, (200, 200), 40, 100);
    bench_hollow_ellipse!(bench_bench_hollow_ellipse_horizontal, (200, 200), 100, 40);

    macro_rules! bench_filled_ellipse {
        ($name:ident, $center:expr, $width_radius:expr, $height_radius:expr) => {
            #[bench]
            fn $name(b: &mut test::Bencher) {
                use super::draw_filled_ellipse_mut;

                let mut image = GrayImage::new(500, 500);
                let color = Luma([50u8]);
                b.iter(|| {
                    draw_filled_ellipse_mut(
                        &mut image,
                        $center,
                        $width_radius,
                        $height_radius,
                        color,
                    );
                    test::black_box(&image);
                });
            }
        };
    }

    bench_filled_ellipse!(bench_bench_filled_ellipse_circle, (200, 200), 80, 80);
    bench_filled_ellipse!(bench_bench_filled_ellipse_vertical, (200, 200), 40, 100);
    bench_filled_ellipse!(bench_bench_filled_ellipse_horizontal, (200, 200), 100, 40);
}
