use crate::new_hash_set;
use crate::prelude::*;
use crate::shape_box::ShapeBox;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Ellipse {
    center: Coord,
    top: Coord,
    right: Coord,
    rotation: isize,
}
impl IntersectsContains for Ellipse {}
impl Ellipse {
    #[must_use]
    pub fn new<P: Into<Coord>>(center: P, width: usize, height: usize) -> Self {
        let center = center.into();
        Self {
            center,
            top: center - (0, height / 2),
            right: center + (width / 2, 0),
            rotation: 0,
        }
    }
    pub fn new_rotated<P1: Into<Coord>, P2: Into<Coord>, P3: Into<Coord>>(
        center: P1,
        top: P2,
        right: P3,
    ) -> Self {
        let center = center.into();
        let top = top.into();
        let right = right.into();
        let angle = center.angle_to(top);
        let right = Coord::from_angle(center, center.distance(right), -angle + 90);
        let top = Coord::from_angle(center, center.distance(top), -angle);
        Self {
            center,
            top,
            right,
            rotation: angle,
        }
    }
}
impl Ellipse {
    #[inline]
    #[must_use]
    pub fn width(&self) -> usize {
        (self.right() - self.left()).unsigned_abs()
    }
    #[inline]
    #[must_use]
    pub fn height(&self) -> usize {
        (self.bottom() - self.top()).unsigned_abs()
    }
    pub fn angle(&self) -> isize {
        self.rotation
    }
    #[inline(always)]
    fn no_rotate_point(x: isize, y: isize, _: Coord, _: isize) -> Coord {
        coord!(x, y)
    }
    #[inline(always)]
    fn rotate_point(x: isize, y: isize, center: Coord, degrees: isize) -> Coord {
        let orig = coord!(x, y);
        let offset = center.angle_to(orig) + degrees;
        Coord::from_angle(center, center.distance(orig), offset)
    }
}
impl Shape for Ellipse {
    fn from_points(points: &[Coord]) -> Self
    where
        Self: Sized,
    {
        debug_assert!(points.len() >= 3);
        let center = points[0];
        let top = points[1];
        let right = points[2];
        let rotation = center.angle_to(top);
        let top = Coord::from_angle(center, top.distance(center), 0);
        let right = Coord::from_angle(center, right.distance(center), 90);
        Ellipse {
            center,
            top,
            right,
            rotation,
        }
    }
    fn rebuild(&self, points: &[Coord]) -> Self
    where
        Self: Sized,
    {
        Ellipse::from_points(points)
    }
    fn translate_by(&self, delta: Coord) -> Self {
        let points: Vec<Coord> = self.points().into_iter().map(|c| c + delta).collect();
        Ellipse::from_points(&points)
    }
    fn move_to(&self, point: Coord) -> Self {
        self.move_center_to(point)
    }
    fn contains(&self, point: Coord) -> bool {
        ((point.x - self.center.x) ^ 2) / ((self.width() as isize) ^ 2)
            + ((point.y - self.center.y) ^ 2) / ((self.height() as isize) ^ 2)
            <= 1
    }
    fn points(&self) -> Vec<Coord> {
        vec![
            self.center,
            Coord::from_angle(self.center, self.center.distance(self.top), self.rotation),
            Coord::from_angle(
                self.center,
                self.center.distance(self.right),
                self.rotation + 90,
            ),
        ]
    }
    #[inline]
    fn center(&self) -> Coord {
        self.center
    }
    #[inline]
    fn left(&self) -> isize {
        self.right.x - (self.center.distance(self.right) * 2) as isize
    }
    #[inline]
    fn right(&self) -> isize {
        self.right.x
    }
    #[inline]
    fn top(&self) -> isize {
        self.top.y
    }
    #[inline]
    fn bottom(&self) -> isize {
        self.top.y + (self.center.distance(self.top) * 2) as isize
    }
    fn outline_pixels(&self) -> Vec<Coord> {
        let center = self.center;
        let degrees = self.rotation;
        let rotate = if degrees == 0 {
            Self::no_rotate_point
        } else {
            Self::rotate_point
        };
        let center_x = self.center.x;
        let center_y = self.center.y;
        let rx = (self.width() / 2) as f32;
        let ry = (self.height() / 2) as f32;
        let mut output = new_hash_set();
        let mut x = 0;
        let mut y = ry as isize;
        let mut p1 = ry * ry - (rx * rx) * ry + (rx * rx) * (0.25);
        let mut dx = 2.0 * (ry * ry) * (x as f32);
        let mut dy = 2.0 * (rx * rx) * (y as f32);
        while dx < dy {
            output.insert(rotate(center_x + x, center_y + y, center, degrees));
            output.insert(rotate(center_x - x, center_y + y, center, degrees));
            output.insert(rotate(center_x + x, center_y - y, center, degrees));
            output.insert(rotate(center_x - x, center_y - y, center, degrees));
            if p1 < 0.0 {
                x += 1;
                dx = 2.0 * (ry * ry) * (x as f32);
                p1 += dx + (ry * ry);
            } else {
                x += 1;
                y -= 1;
                dx = 2.0 * (ry * ry) * (x as f32);
                dy = 2.0 * (rx * rx) * (y as f32);
                p1 += dx - dy + (ry * ry);
            }
        }
        let mut p2 = (ry * ry) * ((x as f32) + 0.5) * ((x as f32) + 0.5)
            + (rx * rx) * ((y as f32) - 1.0) * ((y as f32) - 1.0)
            - (rx * rx) * (ry * ry);
        while y >= 0 {
            output.insert(rotate(center_x + x, center_y + y, center, degrees));
            output.insert(rotate(center_x - x, center_y + y, center, degrees));
            output.insert(rotate(center_x + x, center_y - y, center, degrees));
            output.insert(rotate(center_x - x, center_y - y, center, degrees));
            if p2 > 0.0 {
                y -= 1;
                dy = 2.0 * (rx * rx) * (y as f32);
                p2 -= dy + (rx * rx);
            } else {
                x += 1;
                y -= 1;
                dy -= 2.0 * (rx * rx);
                dx += 2.0 * (ry * ry);
                p2 += dx - dy + (rx * rx);
            }
        }
        output.into_iter().collect()
    }
    fn filled_pixels(&self) -> Vec<Coord> {
        let mut output = new_hash_set();
        let height = self.height() as isize / 2;
        let width = self.width() as isize / 2;
        let height_sq = height * height;
        let width_sq = width * width;
        let limit = height_sq * width_sq;
        for y in -height..height {
            let y_amount = y * y * width_sq;
            for x in -width..width {
                if x * x * height_sq + y_amount <= limit {
                    output.insert(coord!(self.center.x + x, self.center.y + y));
                }
            }
        }
        output.into_iter().collect()
    }
    fn to_shape_box(&self) -> ShapeBox {
        ShapeBox::Ellipse(self.clone())
    }
}
impl Ellipse {
    #[must_use]
    pub fn as_rect(&self) -> Rect {
        Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
    }
    #[must_use]
    pub fn as_horizontal_line(&self) -> Line {
        Line::new((self.left(), self.center.y), (self.right(), self.center.y))
    }
    #[must_use]
    pub fn as_vertical_line(&self) -> Line {
        Line::new((self.center.x, self.top()), (self.center.x, self.bottom()))
    }
    #[must_use]
    pub fn as_radius_line(&self) -> Line {
        Line::new((self.center.x, self.center.y), (self.center.x, self.top()))
    }
    #[must_use]
    pub fn as_circle(&self) -> Option<Circle> {
        if self.width() == self.height() {
            Some(Circle::new(self.center, self.width() / 2))
        } else {
            None
        }
    }
    #[must_use]
    pub fn as_largest_circle(&self) -> Circle {
        let radius = self.width().max(self.height());
        Circle::new(self.center, radius)
    }
    #[must_use]
    pub fn as_smallest_circle(&self) -> Circle {
        let radius = self.width().min(self.height());
        Circle::new(self.center, radius)
    }
    #[must_use]
    pub fn as_polygon(&self) -> Polygon {
        let x = self.center.x as f64;
        let y = self.center.y as f64;
        let w = self.width() as f64 / 2.0;
        let h = self.height() as f64 / 2.0;
        let segments = (((w + h) / 2.0) * 20.0).sqrt().floor().max(8.0) as usize;
        let points = discretise_ellipse(x, y, w, h, segments);
        Polygon::from_points(&points).rotate(self.angle())
    }
}
fn discretise_ellipse(x: f64, y: f64, a: f64, b: f64, segments: usize) -> Vec<Coord> {
    let angle_shift = 6.29 / (segments as f64);
    let mut phi = 0.0;
    let mut vertices = vec![];
    for _ in 0..segments {
        phi += angle_shift;
        vertices.push(coord!(x + a * phi.cos(), y + b * phi.sin()));
    }
    vertices
}
#[cfg(test)]
mod test {
    use crate::circle::Circle;
    use crate::ellipse::Ellipse;
    use crate::Shape;
    #[test]
    fn check_circle_ellipse() {
        let ellipse = Ellipse::new((40, 40), 20, 20);
        let gen_circle = ellipse.as_circle().unwrap();
        let expected = Circle::new((40, 40), 10);
        assert_eq!(gen_circle, expected);
    }
    #[test]
    fn translate() {
        let ellipse = Ellipse::new((40, 40), 20, 20);
        let moved = ellipse.move_center_to(coord!(10, 10));
        assert_eq!(ellipse.width(), moved.width());
        assert_eq!(ellipse.height(), moved.height());
        assert_eq!(ellipse.angle(), moved.angle());
    }
    #[test]
    fn to_from_points() {
        let ellipse = Ellipse::new((100, 100), 30, 60);
        assert_eq!(ellipse.center, coord!(100, 100));
        assert_eq!(ellipse.top, coord!(100, 70));
        assert_eq!(ellipse.right, coord!(115, 100));
        let points = ellipse.points();
        assert_eq!(points, coord_vec![(100, 100), (100, 70), (115, 100)]);
        let gen_ellipse = Ellipse::from_points(&points);
        assert_eq!(ellipse.width(), gen_ellipse.width());
        assert_eq!(ellipse.height(), gen_ellipse.height());
        assert_eq!(ellipse, gen_ellipse);
    }
    #[test]
    fn rotation() {
        let ellipse = Ellipse::new((100, 100), 200, 50);
        assert_eq!(
            ellipse.points(),
            vec![coord!(100, 100), coord!(100, 75), coord!(200, 100)]
        );
        assert_eq!(ellipse.center, coord!(100, 100));
        assert_eq!(ellipse.top, coord!(100, 75));
        assert_eq!(ellipse.right, coord!(200, 100));
        assert_eq!(ellipse.rotation, 0);
        let rotated = ellipse.rotate(90);
        assert_eq!(
            rotated.points(),
            vec![coord!(100, 100), coord!(125, 100), coord!(100, 200)]
        );
        assert_eq!(rotated.center, coord!(100, 100));
        assert_eq!(rotated.top, coord!(100, 75));
        assert_eq!(rotated.right, coord!(200, 100));
        assert_eq!(rotated.rotation, 90);
    }
    #[test]
    fn move_center() {
        let ellipse = Ellipse::new((100, 100), 20, 20);
        let moved = ellipse.move_center_to(coord!(50, 50));
        assert_eq!(moved.center, coord!(50, 50));
        let ellipse = Ellipse::new((100, 100), 20, 20);
        let moved = ellipse.move_center_to(coord!(120, 50));
        assert_eq!(moved.center, coord!(120, 50));
    }
    #[test]
    fn move_center_rotated() {
        let ellipse = Ellipse::new((100, 100), 20, 20).rotate(45);
        let moved = ellipse.move_center_to(coord!(50, 50));
        assert_eq!(ellipse.angle(), 45);
        assert_eq!(moved.angle(), 45);
        assert_eq!(moved.center, coord!(50, 50));
    }
}