use crate::ui::{
    display::Color,
    geometry::{Offset, Point, Rect},
};

use super::super::{
    utils::{circle_points, line_points, sin_f32},
    Bitmap, BitmapFormat, BitmapView, Viewport,
};

#[cfg(feature = "ui_blurring")]
use crate::ui::shape::DrawingCache;

pub trait BasicCanvas {
    /// Returns dimensions of the canvas in pixels.
    fn size(&self) -> Offset;

    /// Returns the dimensions of the canvas as a rectangle with
    /// the top-left at (0,0).
    fn bounds(&self) -> Rect {
        Rect::from_size(self.size())
    }

    /// Returns the width of the canvas in pixels.
    fn width(&self) -> i16 {
        self.size().x
    }

    /// Returns the height of the canvas in pixels.
    fn height(&self) -> i16 {
        self.size().y
    }

    /// Gets the current drawing viewport previously set by `set_viewport()`
    /// function.
    fn viewport(&self) -> Viewport;

    /// Sets the active viewport valid for all subsequent drawing operations.
    fn set_viewport(&mut self, vp: Viewport);

    /// Sets the new viewport that's intersection of the
    /// current viewport and the `window` rectangle relative
    /// to the current viewport. The viewport's origin is
    /// set to the top-left corener of the `window`.
    fn set_window(&mut self, window: Rect) -> Viewport {
        let viewport = self.viewport();
        self.set_viewport(viewport.relative_window(window));
        viewport
    }

    /// Sets the new viewport that's intersection of the
    /// current viewport and the `clip` rectangle relative
    /// to the current viewport. The viewport's origin is
    /// not changed.
    fn set_clip(&mut self, clip: Rect) -> Viewport {
        let viewport = self.viewport();
        self.set_viewport(viewport.relative_clip(clip));
        viewport
    }

    /// Draws a filled rectangle with the specified color.
    fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8);

    /// Fills the canvas background with the specified color.
    fn fill_background(&mut self, color: Color) {
        let vp = self.viewport();
        self.fill_rect(vp.clip.translate(-vp.origin), color, 255);
    }

    /// Draws a bitmap into to the rectangle.
    fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView);
}

pub trait CanvasBuilder<'a> {
    /// Returns a format of the underlying bitmap.
    fn format() -> BitmapFormat;

    /// Creates a new canvas from the specified bitmap.
    ///
    /// The bitmap must be mutable and have the same format as the canvas.
    fn from_bitmap(bitmap: Bitmap<'a>) -> Self;
}

pub trait Canvas: BasicCanvas {
    /// Returns a non-mutable view of the underlying bitmap.
    fn view(&self) -> BitmapView;

    /// Draw a pixel at specified coordinates.
    fn draw_pixel(&mut self, pt: Point, color: Color);

    /// Draws a single pixel and blends its color with the background.
    ///
    /// - If alpha == 255, the (foreground) pixel color is used.
    /// - If 0 < alpha << 255, pixel and background colors are blended.
    /// - If alpha == 0, the background color is used.
    fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8);

    /// Blends a bitmap with the canvas background
    fn blend_bitmap(&mut self, r: Rect, src: BitmapView);

    /// Applies a blur effect to the specified rectangle.
    ///
    /// The blur effect works properly only when the rectangle is not clipped,
    /// which is a strong constraint that's hard to be met. The function uses a
    /// simple box filter, where the 'radius' argument represents the length
    /// of the sides of this filter.
    ///
    /// It's important to be aware that strong artifacts may appear on images
    /// with horizontal/vertical lines.
    #[cfg(feature = "ui_blurring")]
    fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache);

    /// Draws an outline of a rectangle with rounded corners.
    fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
        let split = unwrap!(circle_points(radius).last()).v;

        let b = Rect {
            y1: r.y0 + radius - split + 1,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
                let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
                if p.v == radius && p.last {
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
                } else {
                    self.draw_pixel(pt_l, color);
                    self.draw_pixel(pt_r, color);
                }
            }
        }

        let b = Rect {
            y0: r.y0 + radius - split + 1,
            y1: r.y0 + radius + 1,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
                self.draw_pixel(pt_l, color);
                self.draw_pixel(pt_r, color);
            }
        }

        self.fill_rect(
            Rect {
                x0: r.x0,
                y0: r.y0 + radius + 1,
                x1: r.x0 + 1,
                y1: r.y1 - radius - 1,
            },
            color,
            255,
        );

        self.fill_rect(
            Rect {
                x0: r.x1 - 1,
                y0: r.y0 + radius + 1,
                x1: r.x1,
                y1: r.y1 - radius - 1,
            },
            color,
            255,
        );

        let b = Rect {
            y0: r.y1 - radius - 1,
            y1: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
                self.draw_pixel(pt_l, color);
                self.draw_pixel(pt_r, color);
            }
        }

        let b = Rect {
            y0: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
                let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);

                if p.v == radius && p.last {
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
                } else {
                    self.draw_pixel(pt_l, color);
                    self.draw_pixel(pt_r, color);
                }
            }
        }
    }

    /// Draws filled rectangle with rounded corners.
    #[cfg(not(feature = "ui_antialiasing"))]
    fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
        let split = unwrap!(circle_points(radius).last()).v;

        let b = Rect {
            y1: r.y0 + radius - split + 1,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                if p.last {
                    let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
                    let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
                }
            }
        }

        let b = Rect {
            y0: r.y0 + radius - split + 1,
            y1: r.y0 + radius + 1,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
                self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
            }
        }

        self.fill_rect(
            Rect {
                x0: r.x0,
                y0: r.y0 + radius + 1,
                x1: r.x1,
                y1: r.y1 - radius - 1,
            },
            color,
            alpha,
        );

        let b = Rect {
            y0: r.y1 - radius - 1,
            y1: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
                self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
            }
        }

        let b = Rect {
            y0: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                if p.last {
                    let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
                    let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
                }
            }
        }
    }

    /// Draws filled rectangle with antialiased rounded corners.
    #[cfg(feature = "ui_antialiasing")]
    fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
        let split = unwrap!(circle_points(radius).last()).v;

        let b = Rect {
            y1: r.y0 + radius - split + 1,
            ..r
        };

        let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
                let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                if p.first {
                    let inner = Rect::new(pt_l.onright(), pt_r.under());
                    self.fill_rect(inner, color, alpha);
                }
            }
        }

        let b = Rect {
            y0: r.y0 + radius - split + 1,
            y1: r.y0 + radius + 1,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                let inner = Rect::new(pt_l.onright(), pt_r.under());
                self.fill_rect(inner, color, alpha);
            }
        }

        self.fill_rect(
            Rect {
                x0: r.x0,
                y0: r.y0 + radius + 1,
                x1: r.x1,
                y1: r.y1 - radius - 1,
            },
            color,
            alpha,
        );

        let b = Rect {
            y0: r.y1 - radius - 1,
            y1: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
                let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                let b = Rect::new(pt_l.onright(), pt_r.under());
                self.fill_rect(b, color, alpha);
            }
        }

        let b = Rect {
            y0: r.y1 - radius - 1 + split,
            ..r
        };

        if self.viewport().contains(b) {
            for p in circle_points(radius) {
                let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                if p.first {
                    let b = Rect::new(pt_l.onright(), pt_r.under());
                    self.fill_rect(b, color, alpha);
                }
            }
        }
    }

    // Draws circle with the specified center and the radius.
    #[cfg(not(feature = "ui_antialiasing"))]
    fn draw_circle(&mut self, center: Point, radius: i16, color: Color, alpha: u8) {
        if radius <= 0 {
            return;
        }

        let split = unwrap!(circle_points(radius).last()).v;

        let r = Rect::new(
            Point::new(center.x - radius, center.y - radius),
            Point::new(center.x + radius + 1, center.y - split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                let pt_l = Point::new(center.x - p.u, center.y - p.v);
                let pt_r = Point::new(center.x + p.u, center.y - p.v);
                self.blend_pixel(pt_l, color, alpha);
                self.blend_pixel(pt_r, color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y - split),
            Point::new(center.x + radius + 1, center.y + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y - p.u);
                let pt_r = Point::new(center.x + p.v, center.y - p.u);
                self.blend_pixel(pt_l, color, alpha);
                self.blend_pixel(pt_r, color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + 1),
            Point::new(center.x + radius + 1, center.y + split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y + p.u);
                let pt_r = Point::new(center.x + p.v, center.y + p.u);
                self.blend_pixel(pt_l, color, alpha);
                self.blend_pixel(pt_r, color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + split),
            Point::new(center.x + radius + 1, center.y + radius + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                let pt_l = Point::new(center.x - p.u, center.y + p.v);
                let pt_r = Point::new(center.x + p.u, center.y + p.v);
                self.blend_pixel(pt_l, color, alpha);
                self.blend_pixel(pt_r, color, alpha);
            }
        }
    }

    /// Draws filled circle with the specified center and the radius.
    #[cfg(not(feature = "ui_antialiasing"))]
    fn fill_circle(&mut self, center: Point, radius: i16, color: Color, alpha: u8) {
        if radius <= 0 {
            return;
        }

        let split = unwrap!(circle_points(radius).last()).v;

        let r = Rect::new(
            Point::new(center.x - radius, center.y - radius),
            Point::new(center.x + radius + 1, center.y - split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                if p.last {
                    let pt_l = Point::new(center.x - p.u, center.y - p.v);
                    let pt_r = Point::new(center.x + p.u, center.y - p.v);
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
                }
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y - split),
            Point::new(center.x + radius + 1, center.y + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y - p.u);
                let pt_r = Point::new(center.x + p.v, center.y - p.u);
                self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + 1),
            Point::new(center.x + radius + 1, center.y + split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y + p.u);
                let pt_r = Point::new(center.x + p.v, center.y + p.u);
                self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + split),
            Point::new(center.x + radius + 1, center.y + radius + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                if p.last {
                    let pt_l = Point::new(center.x - p.u, center.y + p.v);
                    let pt_r = Point::new(center.x + p.u, center.y + p.v);
                    self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
                }
            }
        }
    }

    /// Draws antialiased filled circle with the specified center and the
    /// radius.
    #[cfg(feature = "ui_antialiasing")]
    fn fill_circle(&mut self, center: Point, radius: i16, color: Color, alpha: u8) {
        if radius <= 0 {
            return;
        }

        let split = unwrap!(circle_points(radius).last()).v;

        let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };

        let r = Rect::new(
            Point::new(center.x - radius, center.y - radius),
            Point::new(center.x + radius + 1, center.y - split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                let pt_l = Point::new(center.x - p.u, center.y - p.v);
                let pt_r = Point::new(center.x + p.u, center.y - p.v);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                if pt_l != pt_r {
                    self.blend_pixel(pt_r, color, alpha_mul(p.frac));
                }

                if p.first {
                    let r = Rect::new(pt_l.onright(), pt_r.under());
                    self.fill_rect(r, color, alpha);
                }
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y - split),
            Point::new(center.x + radius + 1, center.y + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y - p.u);
                let pt_r = Point::new(center.x + p.v, center.y - p.u);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                let r = Rect::new(pt_l.onright(), pt_r.under());
                self.fill_rect(r, color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + 1),
            Point::new(center.x + radius + 1, center.y + split + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
                let pt_l = Point::new(center.x - p.v, center.y + p.u);
                let pt_r = Point::new(center.x + p.v, center.y + p.u);
                self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                let r = Rect::new(pt_l.onright(), pt_r.under());
                self.fill_rect(r, color, alpha);
            }
        }

        let r = Rect::new(
            Point::new(center.x - radius, center.y + split),
            Point::new(center.x + radius + 1, center.y + radius + 1),
        );

        if self.viewport().contains(r) {
            for p in circle_points(radius) {
                let pt_l = Point::new(center.x - p.u, center.y + p.v);
                let pt_r = Point::new(center.x + p.u, center.y + p.v);
                if pt_l != pt_r {
                    self.blend_pixel(pt_l, color, alpha_mul(p.frac));
                }
                self.blend_pixel(pt_r, color, alpha_mul(p.frac));

                if p.first {
                    let r = Rect::new(pt_l.onright(), pt_r.under());
                    self.fill_rect(r, color, alpha);
                }
            }
        }
    }

    /// Fills circle sector with a specified color.
    fn fill_sector(
        &mut self,
        center: Point,
        radius: i16,
        mut start: f32,
        mut end: f32,
        color: Color,
    ) {
        if radius <= 0 {
            return;
        }

        start = (360.0 + start % 360.0) % 360.0;
        end = (360.0 + end % 360.0) % 360.0;

        let alpha = 255;
        let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };

        if start != end {
            // The algorithm fills everything except the middle point ;-)
            self.draw_pixel(center, color);
        }

        const PI4: f32 = 45.0;

        for octant in 0..8 {
            let angle = PI4 * octant as f32;

            // Function for calculation of 'u' coordinate inside the circle octant
            // radius * sin(angle)
            let sin = |angle: f32| -> i16 { (sin_f32(angle) * radius as f32 + 0.5) as i16 };

            // Calculate the octant's bounding rectangle
            let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant);
            let r = Rect::new(center, p + center.into());

            // Ensure that `x0`, `y0` represents the top-left corner and
            // `x1`, `y1` represents the bottom-right corner.
            let r = Rect {
                x0: r.x0.min(r.x1),
                y0: r.y0.min(r.y1),
                x1: r.x0.max(r.x1),
                y1: r.y0.max(r.y1),
            };

            // Skip octant if not visible
            if !self.viewport().contains(r) {
                continue;
            }

            // Function for filling a line between two endpoints with antialiasing.
            // The function is special for each octant using 4 different axes of symmetry
            let filler = &mut |p1: Option<Point>, p1_frac, p2: Point, p2_frac| {
                let p2: Point = center + p2.rot(octant).into();
                self.blend_pixel(p2, color, alpha_mul(p2_frac));
                if let Some(p1) = p1 {
                    let p1: Point = center + p1.rot(octant).into();
                    let ofs = Point::new(-1, 0).rot(octant);
                    self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac));
                    if ofs.x + ofs.y < 0 {
                        if ofs.x != 0 {
                            self.fill_rect(Rect::new(p1, p2.under()), color, alpha);
                        } else {
                            self.fill_rect(Rect::new(p1, p2.onright()), color, alpha);
                        }
                    } else {
                        let p1 = p1 + ofs.into();
                        let p2 = p2 + ofs.into();
                        if ofs.x != 0 {
                            self.fill_rect(Rect::new(p2, p1.under()), color, alpha);
                        } else {
                            self.fill_rect(Rect::new(p2, p1.onright()), color, alpha);
                        }
                    }
                }
            };

            let corr = if octant & 1 == 0 {
                // The clockwise octant
                |angle| angle
            } else {
                // The anticlockwise octant
                |angle| PI4 - angle
            };

            if start <= end {
                // Octant may contain 0 or 1 sector
                if start < angle + PI4 && end > angle {
                    if start <= angle && end >= angle + PI4 {
                        // Fill all pixels in the octant
                        fill_octant(radius, 0, sin(PI4), filler);
                    } else {
                        // Partial fill
                        let u1 = if start <= angle {
                            sin(corr(0.0))
                        } else {
                            sin(corr(start - angle))
                        };
                        let u2 = if end <= angle + PI4 {
                            sin(corr(end - angle))
                        } else {
                            sin(corr(PI4))
                        };

                        fill_octant(radius, u1, u2, filler);
                    }
                }
            } else {
                // Octant may contain 0, 1 or 2 sectors
                if end >= angle + PI4 || start <= angle {
                    // Fill all pixels in the octant
                    fill_octant(radius, 0, sin(PI4), filler);
                } else {
                    // Partial fill
                    if (end > angle) && (end < angle + PI4) {
                        // Fill up to `end`
                        fill_octant(radius, sin(corr(0.0)), sin(corr(end - angle)), filler);
                    }
                    if start < angle + PI4 {
                        // Fill all from `start`
                        fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler);
                    }
                }
            }
        }
    }
}

/// Calculates endpoints of a single octant of a circle
///
/// Used internally by `Canvas::fill_sector()`.
fn fill_octant(
    radius: i16,
    mut u1: i16,
    mut u2: i16,
    fill: &mut impl FnMut(Option<Point>, u8, Point, u8),
) {
    if radius <= 0 {
        return;
    }

    // Starting end ending points on
    if u1 > u2 {
        (u1, u2) = (u2, u1);
    }

    let mut iter = circle_points(radius).skip(u1 as usize);

    // Intersection of the p1 line and the circle
    let p1_start = unwrap!(iter.next());

    // Intersection of the p1 line and the circle
    let mut p2_start = p1_start;

    for p in iter.by_ref() {
        if p.u > u2 {
            break;
        }
        p2_start = p;
    }

    // Flag if we draw section up to 45degs
    let join_flag = iter.next().is_none();

    // Process area between a p1 line and the circle
    let mut p1_iter = line_points(p1_start.v, p1_start.u, 0);
    let mut first = true;
    let mut skip = 0;

    for c in circle_points(radius)
        .skip(p1_start.u as usize)
        .take((p2_start.u - p1_start.u) as usize)
    {
        let p2_coord = Point::new(c.u, -c.v);

        if c.first || first {
            let p1 = unwrap!(p1_iter.next());
            let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
            first = false;

            fill(Some(p1_coord), p1.frac, p2_coord, c.frac);
        } else {
            fill(None, 0, p2_coord, c.frac);
        }

        skip = if c.last { 0 } else { 1 };
    }

    // Process area between a p1 and p2 lines
    let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip);
    let mut first = true;
    for (p1, p2) in p1_iter.zip(p2_iter) {
        let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
        let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u);
        let p2_frac = if first {
            p2_start.frac
        } else if join_flag {
            255
        } else {
            255 - p2.frac
        };

        fill(Some(p1_coord), p1.frac, p2_coord, p2_frac);
        first = false;
    }
}

// Private extension functions for the `Point` type not useful
// enough to be exposed in the public API.

impl Point {
    /// Returns point on the left side of the current point.
    fn onleft(self) -> Self {
        Self {
            x: self.x - 1,
            ..self
        }
    }

    /// Returns point on the right side of the current point.
    fn onright(self) -> Self {
        Self {
            x: self.x + 1,
            ..self
        }
    }

    /// Returns point above the current point.
    fn above(self) -> Self {
        Self {
            y: self.y - 1,
            ..self
        }
    }

    /// Returns point below the current point.
    fn under(self) -> Self {
        Self {
            y: self.y + 1,
            ..self
        }
    }

    /// Rotates the point around (0,0) by `octant` * 45 degrees.
    fn rot(self, octant: i16) -> Self {
        let mut result = self;

        if (octant + 1) & 2 != 0 {
            result = Point::new(-result.y, -result.x);
        }

        if octant & 4 != 0 {
            result = Point::new(-result.x, result.y);
        }

        if (octant + 2) & 4 != 0 {
            result = Point::new(result.x, -result.y);
        }

        result
    }
}
