#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

impl Point {
    pub fn distance(&self, p: &Point) -> f64 {
        if self.x == p.x {
            return (p.y - self.y).abs().into();
        }

        if self.y == p.y {
            return (p.x - self.x).abs().into();
        }

        let x_distance = p.x - self.x;
        let y_distance = p.y - self.y;

        return f64::from(x_distance.pow(2) + y_distance.pow(2)).sqrt();
    }

    pub(crate) fn new(positions: (i32, i32)) -> Self {
        Self {
            x: positions.0,
            y: positions.1,
        }
    }

    pub(crate) fn movement(&self, towards: Point, distance: u16) -> Point {
        let dst = self.distance(&towards);
        if dst == distance as f64 {
            return towards;
        }

        let ratio = distance as f64 / dst;
        // info!("X delta : {}", towards.x - self.x);
        // info!("Y delta : {}", towards.y - self.y);

        Point {
            x: ((towards.x - self.x) as f64 * ratio + self.x as f64).round() as i32,
            y: ((towards.y - self.y) as f64 * ratio + self.y as f64).round() as i32,
        }
    }
}

impl Ord for Point {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        if self.x > other.x {
            return std::cmp::Ordering::Greater;
        }
        if self.x < other.x {
            return std::cmp::Ordering::Less;
        }
        if self.y > other.y {
            return std::cmp::Ordering::Greater;
        }
        if self.y < other.y {
            return std::cmp::Ordering::Less;
        }

        std::cmp::Ordering::Equal
    }
}

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

    #[test]
    fn distance_to_itself_is_zero() {
        let p = Point { x: 1, y: 1 };
        assert_eq!(p.distance(&p), 0.0);
    }

    #[test]
    fn distance_same_x_is_y() {
        let p1 = Point { x: 1, y: 1 };

        let p2 = Point { x: 1, y: 5 };

        assert_eq!(p1.distance(&p2), 4.0);
    }

    #[test]
    fn distance_same_y_is_x() {
        let p1 = Point::new((1, 1));
        let p2 = Point::new((5, 1));

        assert_eq!(p1.distance(&p2), 4.0);
    }

    #[test]
    fn distance_3_4_5() {
        let p1 = Point::new((0, 0));
        let p2 = Point::new((3, 4));

        assert_eq!(p1.distance(&p2), 5.0);
    }

    #[test]
    fn distance_is_always_positive() {
        let p1 = Point::new((10, 10));
        let p2 = Point::new((5, 10));

        assert_eq!(p1.distance(&p2), 5.0);

        let p1 = Point::new((10, 10));
        let p2 = Point::new((10, 5));

        assert_eq!(p1.distance(&p2), 5.0);
    }

    #[test]
    fn distance_quadrant3() {
        let p1 = Point { x: 3, y: 4 };

        let p2 = Point { x: 0, y: 0 };

        assert_eq!(p1.distance(&p2), 5.0);
    }

    #[test]
    fn distance_quadrant2() {
        let p1 = Point { x: 3, y: 4 };

        let p2 = Point { x: 0, y: 100 };

        assert_eq!(p1.distance(&p2) as f32, 96.04687);
    }

    #[test]
    fn distance_quadrant2_fast() {
        let p1 = Point { x: 3, y: 4 };

        let p2 = Point { x: 3, y: 50 };

        assert_eq!(p1.distance(&p2), 46.0);
    }

    #[test]
    fn distance_quadrant4() {
        let p1 = Point { x: 3, y: 4 };

        let p2 = Point { x: 50, y: -50 };

        assert_eq!(p1.distance(&p2) as f32, 71.5891);
    }

    #[test]
    fn movement_does_nothing_when_right_length() {
        let root = Point::new((0, 0));
        let node = Point::new((0, 1));
        assert_eq!(root.movement(node.clone(), 1), node);
    }

    #[test]
    fn movement_does_not_overlap() {
        let root = Point::new((0, 1));
        let node = Point::new((0, 0));
        assert_eq!(root.movement(node.clone(), 2), Point::new((0, -1)));
    }

    #[test]
    fn movement_adjusts_to_asked_length() {
        let root = Point::new((0, 0));
        let node = Point::new((0, 1));
        assert_eq!(root.movement(node, 10), Point::new((0, 10)));
    }

    #[test]
    fn movement_works_away_from_origin() {
        let root = Point::new((10, 10));
        let node = Point::new((10, 11));
        assert_eq!(root.movement(node, 10), Point::new((10, 20)));
    }

    #[test]
    fn movement_works_in_two_dimension() {
        let root = Point::new((10, 10));
        let node = Point::new((40, 50));
        assert_eq!(root.movement(node, 50), Point::new((40, 50)));

        let root = Point::new((10, 10));
        let node = Point::new((40, 50));
        assert_eq!(root.movement(node, 5), Point::new((13, 14)));
    }

    #[test]
    fn movement_works_in_all_directions() {
        let root = Point::new((40, 50));
        let node = Point::new((10, 10));
        assert_eq!(root.movement(node, 5), Point::new((37, 46)));

        let root = Point::new((50, 10));
        let node = Point::new((10, 10));
        assert_eq!(root.movement(node, 5), Point::new((45, 10)));

        let root = Point::new((10, 50));
        let node = Point::new((10, 10));
        assert_eq!(root.movement(node, 5), Point::new((10, 45)));
    }
}
