use num_integer::Roots;
use std::cmp::{Ordering, PartialEq};
use std::fmt::Debug;

use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct Point {
    pub x: u16,
    pub y: u16,
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl Point {
    #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
    pub fn new(x: u16, y: u16) -> Self {
        Self { x, y }
    }

    pub fn right(&self) -> Option<Point> {
        self.x.checked_add(1).map(|x| Self { x, y: self.y })
    }

    pub fn left(&self) -> Option<Point> {
        self.x.checked_sub(1).map(|x| Self { x, y: self.y })
    }

    pub fn up(&self) -> Option<Point> {
        self.y.checked_sub(1).map(|y| Self { x: self.x, y })
    }

    pub fn down(&self) -> Option<Point> {
        self.y.checked_add(1).map(|y| Self { x: self.x, y })
    }

    pub fn right_up(&self) -> Option<Point> {
        self.right().and_then(|point| point.up())
    }

    pub fn right_down(&self) -> Option<Point> {
        self.right().and_then(|point| point.down())
    }

    pub fn left_up(&self) -> Option<Point> {
        self.left().and_then(|point| point.up())
    }

    pub fn left_down(&self) -> Option<Point> {
        self.left().and_then(|point| point.down())
    }
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Triangle(pub Point, pub Point, pub Point);

impl IntoIterator for Triangle {
    type Item = Point;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        vec![self.0, self.1, self.2].into_iter()
    }
}

impl PartialEq for Triangle {
    fn eq(&self, other: &Self) -> bool {
        let mut self_points = vec![self.0, self.1, self.2];
        let mut other_points = vec![other.0, other.1, other.2];
        self_points.sort();
        other_points.sort();
        self_points == other_points
    }
}

pub type Polygon = Vec<Point>;

#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub fn distance(a: Point, b: Point) -> u16 {
    let dx = (a.x as i32 - b.x as i32).unsigned_abs() as u16;
    let dy = (a.y as i32 - b.y as i32).unsigned_abs() as u16;
    (dx as u64 * dx as u64 + dy as u64 * dy as u64).sqrt() as u16
}

pub fn cross_product(a: Point, b: Point, c: Point) -> i32 {
    (b.x as i32 - a.x as i32) * (c.y as i32 - b.y as i32)
        - (b.y as i32 - a.y as i32) * (c.x as i32 - b.x as i32)
}

pub fn is_point_inside_triangle(triangle: &Triangle, point: Point) -> bool {
    let a = triangle.0;
    let b = triangle.1;
    let c = triangle.2;

    matches!(
        (
            cross_product(a, b, point).cmp(&0),
            cross_product(b, c, point).cmp(&0),
            cross_product(c, a, point).cmp(&0),
        ),
        (Ordering::Less, Ordering::Less, Ordering::Less)
            | (Ordering::Greater, Ordering::Greater, Ordering::Greater)
    )
}

pub fn is_convex(polygon: &Polygon) -> bool {
    let mut sign = 0;
    for i in 0..polygon.len() {
        let a = polygon[i];
        let b = polygon[(i + 1) % polygon.len()];
        let c = polygon[(i + 2) % polygon.len()];
        let cross = cross_product(a, b, c);
        if cross == 0 {
            continue;
        }
        if sign == 0 {
            sign = cross;
        } else if (sign as i64) * (cross as i64) < 0 {
            return false;
        }
    }
    true
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_distance() {
        let a = Point { x: 0, y: 0 };
        let b = Point { x: 3, y: 4 };
        assert_eq!(distance(a, b), 5);
    }

    #[test]
    fn test_cross_product() {
        let a = Point { x: 0, y: 0 };
        let b = Point { x: 3, y: 4 };
        let c = Point { x: 4, y: 3 };
        assert_eq!(cross_product(a, b, c), -7);
    }

    #[test]
    fn test_is_point_inside_triangle_on_side() {
        let triangle = Triangle(
            Point { x: 0, y: 0 },
            Point { x: 3, y: 4 },
            Point { x: 4, y: 0 },
        );
        let point = Point { x: 2, y: 2 };
        assert!(is_point_inside_triangle(&triangle, point));
    }

    #[test]
    fn test_is_point_inside_triangle() {
        let triangle = Triangle(
            Point { x: 0, y: 0 },
            Point { x: 3, y: 4 },
            Point { x: 4, y: 0 },
        );
        let point = Point { x: 2, y: 1 };
        assert!(is_point_inside_triangle(&triangle, point));
    }

    #[test]
    fn test_is_point_outside_triangle() {
        let triangle = Triangle(
            Point { x: 0, y: 0 },
            Point { x: 3, y: 4 },
            Point { x: 4, y: 0 },
        );
        let point = Point { x: 2, y: 3 };
        assert!(!is_point_inside_triangle(&triangle, point));
    }

    #[test]
    fn test_is_convex() {
        let polygon: Polygon = vec![
            Point { x: 0, y: 0 },
            Point { x: 3, y: 4 },
            Point { x: 5, y: 1 },
            Point { x: 3, y: 0 },
        ];
        assert!(is_convex(&polygon));
    }

    #[test]
    fn test_is_not_convex() {
        let polygon = vec![
            Point { x: 0, y: 0 },
            Point { x: 3, y: 4 },
            Point { x: 4, y: 0 },
            Point { x: 3, y: 2 },
        ];
        assert!(!is_convex(&polygon));
    }
}
