use std::iter::IntoIterator;
use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Sub;
use std::ops::SubAssign;

use super::internal::FromClamped;
use super::internal::Num;
use super::NumTuple;

use super::Point;
use super::Size;

#[derive(Copy, Clone, Debug)]
pub struct Rect<N: Num = f32>(pub Point<N>, pub Size<N>);

impl<N: Num> Rect<N> {
    pub fn new_from_raw(bottom_left_x: N, bottom_left_y: N, width: N, height: N) -> Self {
        Self::new(Point(bottom_left_x, bottom_left_y), Size(width, height))
    }

    pub fn new(bottom_left: Point<N>, size: Size<N>) -> Self {
        Self(bottom_left, size)
    }

    pub fn move_xy(&mut self, xy: Point<N>) {
        self.0 += xy;
    }

    pub fn move_x(&mut self, x: N) {
        self.0.move_x(x);
    }

    pub fn move_y(&mut self, y: N) {
        self.0.move_y(y);
    }

    pub fn width(&self) -> N {
        self.size().width()
    }

    pub fn height(&self) -> N {
        self.size().height()
    }

    pub fn area(&self) -> N {
        self.size().area()
    }

    pub fn size(&self) -> Size<N> {
        self.1
    }

    pub fn bottom_left(&self) -> Point<N> {
        self.0
    }

    pub fn top_y(&self) -> N {
        self.bottom_left().y() + self.size().height()
    }

    pub fn bottom_y(&self) -> N {
        self.bottom_left().y()
    }

    pub fn left_x(&self) -> N {
        self.bottom_left().x()
    }

    pub fn right_x(&self) -> N {
        self.bottom_left().x() + self.size().width()
    }

    pub fn top_right(&self) -> Point<N> {
        self.bottom_left() + self.size()
    }

    pub fn centre(&self) -> Point<N> {
        self.bottom_left() + self.size().half()
    }

    pub fn set_bottom_left(&mut self, xy: Point<N>) {
        self.0 = xy
    }

    pub fn set_centre(&mut self, xy: Point<N>) {
        self.0 = xy - self.size().half()
    }

    pub fn overlaps(&self, other: Self) -> bool {
        let bottom_left_a = self.bottom_left();
        let bottom_left_b = other.bottom_left();

        let top_right_a = self.top_right();
        let top_right_b = other.top_right();

        if top_right_b.x() < bottom_left_a.x() {
            return false;
        }

        if top_right_b.y() < bottom_left_a.y() {
            return false;
        }

        if top_right_a.x() < bottom_left_b.x() {
            return false;
        }

        if top_right_a.y() < bottom_left_b.y() {
            return false;
        }

        true
    }

    pub fn contains(&self, point: Point<N>) -> bool {
        let bottom_left = self.bottom_left();
        let top_right = self.top_right();

        if point.x() < bottom_left.x() {
            return false;
        }

        if point.y() < bottom_left.y() {
            return false;
        }

        if top_right.x() < point.x() {
            return false;
        }

        if top_right.y() < point.y() {
            return false;
        }

        true
    }

    pub fn intersect(&self, other: Self) -> Option<Self> {
        if !self.overlaps(other) {
            return None;
        }

        let new_bottom_left = self.bottom_left().max(other.bottom_left());
        let new_top_right = self.top_right().min(other.top_right());
        let new_size = new_bottom_left.distance_to(new_top_right);
        let new_rect = Rect(new_bottom_left, new_size);

        Some(new_rect)
    }

    pub fn combine(&self, other: Self) -> Self {
        let min_xy = self.bottom_left().min(other.bottom_left());
        let max_xy = self.top_right().max(other.top_right());

        min_xy.rect_to(max_xy)
    }

    pub fn get_scale_diff(&self, other: Self) -> Size<N> {
        self.size().get_scale_diff(other.size())
    }
}

impl<N: Num> Rect<N> {
    pub fn to_clamped<T: Num + FromClamped<N>>(&self) -> Rect<T> {
        Rect(self.bottom_left().to_clamped(), self.size().to_clamped())
    }

    pub fn to<T: Num + From<N>>(&self) -> Rect<T> {
        Rect(self.bottom_left().to(), self.size().to())
    }
}

impl<N: Num> Add<Point<N>> for Rect<N> {
    type Output = Self;

    fn add(self, other: Point<N>) -> Self {
        Rect(self.bottom_left() + other, self.size())
    }
}

impl<N: Num> AddAssign<Point<N>> for Rect<N> {
    fn add_assign(&mut self, other: Point<N>) {
        self.0 += other;
    }
}

impl<N: Num> Add<Size<N>> for Rect<N> {
    type Output = Self;

    fn add(self, other: Size<N>) -> Self {
        Rect(self.bottom_left(), self.size() + other)
    }
}

impl<N: Num> AddAssign<Size<N>> for Rect<N> {
    fn add_assign(&mut self, other: Size<N>) {
        self.1 += other;
    }
}

impl<N: Num> Sub<Point<N>> for Rect<N> {
    type Output = Self;

    fn sub(self, other: Point<N>) -> Self {
        Rect(self.bottom_left() - other, self.size())
    }
}

impl<N: Num> SubAssign<Point<N>> for Rect<N> {
    fn sub_assign(&mut self, other: Point<N>) {
        self.0 -= other;
    }
}

impl<N: Num> Sub<Size<N>> for Rect<N> {
    type Output = Self;

    fn sub(self, other: Size<N>) -> Self {
        Rect(self.bottom_left(), self.size() - other)
    }
}

impl<N: Num> SubAssign<Size<N>> for Rect<N> {
    fn sub_assign(&mut self, other: Size<N>) {
        self.1 -= other;
    }
}

impl<N: Num> PartialEq for Rect<N> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0 && self.1 == other.1
    }
}

impl<N: Num> IntoIterator for Rect<N> {
    type Item = Point<N>;
    type IntoIter = RectIter<N>;

    fn into_iter(self) -> Self::IntoIter {
        RectIter::new(self)
    }
}

pub struct RectIter<N: Num = f32> {
    bottom_left: Point<N>,
    top_right: Point<N>,
    current: Point<N>,
}

impl<N: Num> RectIter<N> {
    fn new(rect: Rect<N>) -> Self {
        Self {
            bottom_left: rect.bottom_left(),
            top_right: rect.top_right(),
            current: rect.bottom_left(),
        }
    }
}

impl<N: Num> Iterator for RectIter<N> {
    type Item = Point<N>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.top_right.x() <= self.current.x() {
            self.current = Point(self.bottom_left.x(), self.current.y() + N::one());
        }

        if self.top_right.y() <= self.current.y() {
            return None;
        }

        let r = self.current;
        self.current += Point(N::one(), N::zero());
        Some(r)
    }
}

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

    #[test]
    fn rect_overlap_outside_left() {
        let a = Rect(Point(20, 0), Size(10, 10));
        let b = Rect(Point(0, 0), Size(10, 10));

        assert_eq!(a.overlaps(b), false);
    }

    #[test]
    fn rect_overlap_outside_right() {
        let a = Rect(Point(0, 0), Size(10, 10));
        let b = Rect(Point(20, 0), Size(10, 10));

        assert_eq!(a.overlaps(b), false);
    }

    #[test]
    fn rect_overlap_inside_left() {
        let a = Rect(Point(0, 0), Size(10, 10));
        let b = Rect(Point(-5, 0), Size(10, 10));

        assert_eq!(a.overlaps(b), true);
    }

    #[test]
    fn rect_overlap_inside_left_above() {
        let a = Rect(Point(0, 0), Size(10, 10));
        let b = Rect(Point(-5, 5), Size(10, 10));

        assert_eq!(a.overlaps(b), true);
    }

    #[test]
    fn rect_overlap_inside_right_below() {
        let a = Rect(Point(0, 0), Size(10, 10));
        let b = Rect(Point(5, -5), Size(10, 10));

        assert_eq!(a.overlaps(b), true);
    }

    #[test]
    fn rect_overlap_inside() {
        let a = Rect(Point(0, 0), Size(10, 10));
        let b = Rect(Point(3, 3), Size(6, 6));

        assert_eq!(a.overlaps(b), true);
    }

    #[test]
    fn it_should_not_iterate_empty_rect() {
        let mut xs = vec![];
        let mut ys = vec![];

        for Point(x, y) in Rect(Point(2, 3), Size(0, 0)) {
            xs.push(x);
            ys.push(y);
        }

        assert_eq!(xs, []);
        assert_eq!(ys, []);
    }

    #[test]
    fn it_should_iterate_over_rect() {
        let mut xs = vec![];
        let mut ys = vec![];

        for Point(x, y) in Rect(Point(2, 3), Size(3, 4)) {
            xs.push(x);
            ys.push(y);
        }

        #[rustfmt::skip]
        assert_eq!(xs, [
            2, 3, 4,
            2, 3, 4,
            2, 3, 4,
            2, 3, 4,
        ]);

        #[rustfmt::skip]
        assert_eq!(ys, [
            3, 3, 3,
            4, 4, 4,
            5, 5, 5,
            6, 6, 6,
        ]);
    }

    #[test]
    fn it_should_iterate_over_rect_usize() {
        let mut xs = vec![];
        let mut ys = vec![];

        for Point(x, y) in Rect(Point(0, 0), Size(2, 2)) {
            xs.push(x);
            ys.push(y);
        }

        #[rustfmt::skip]
        assert_eq!(xs, [
            0, 1,
            0, 1,
        ]);

        #[rustfmt::skip]
        assert_eq!(ys, [
            0, 0,
            1, 1,
        ]);
    }
}
