use crate::range::Range;
use crate::vec2::vec2;
use crate::*;
use core::convert::TryInto;
#[inline(always)]
pub fn rect<T>(xstart: T, xend: T, ystart: T, yend: T) -> Rect<T> {
    Rect::new(xstart, xend, ystart, yend)
}
#[derive(Default,Hash,Copy, Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct Rect<T> {
    pub x: Range<T>,
    pub y: Range<T>,
}
impl<S> Rect<S> {
    #[inline(always)]
    pub fn inner_into<A>(self) -> Rect<A>
    where
        S: Into<A>,
    {
        let x = self.x.inner_into();
        let y = self.y.inner_into();
        Rect { x, y }
    }
    #[inline(always)]
    pub fn inner_try_into<A>(self) -> Result<Rect<A>, S::Error>
    where
        S: TryInto<A>,
    {
        let x = self.x.inner_try_into();
        let y = self.y.inner_try_into();
        match (x, y) {
            (Ok(x), Ok(y)) => Ok(Rect { x, y }),
            (Ok(_), Err(e)) => Err(e),
            (Err(e), Ok(_)) => Err(e),
            (Err(e1), Err(_)) => Err(e1),
        }
    }
}
impl<T: Copy + core::ops::Sub<Output = T> + core::ops::Add<Output = T>> Rect<T> {
    #[inline(always)]
    pub fn from_point(point: Vec2<T>, radius: Vec2<T>) -> Rect<T> {
        let x = Range::from_point(point.x, radius.x);
        let y = Range::from_point(point.y, radius.y);
        Rect { x, y }
    }
}
impl<B> From<[B; 4]> for Rect<B> {
    #[inline(always)]
    fn from(a: [B; 4]) -> Self {
        let [a, b, c, d] = a;
        Rect::new(a, b, c, d)
    }
}
impl<B> From<Rect<B>> for [B; 4] {
    #[inline(always)]
    fn from(a: Rect<B>) -> Self {
        [a.x.start, a.x.end, a.y.start, a.y.end]
    }
}
impl<B: Copy> From<&Rect<B>> for [B; 4] {
    #[inline(always)]
    fn from(a: &Rect<B>) -> Self {
        [a.x.start, a.x.end, a.y.start, a.y.end]
    }
}
impl<T> Rect<T> {
    #[inline(always)]
    pub fn get_range(&self, axis: impl Axis) -> &Range<T> {
        if axis.is_xaxis() {
            &self.x
        } else {
            &self.y
        }
    }
    #[inline(always)]
    pub fn get_range_mut(&mut self, axis: impl Axis) -> &mut Range<T> {
        if axis.is_xaxis() {
            &mut self.x
        } else {
            &mut self.y
        }
    }
}
impl<T> Rect<T> {
    #[inline(always)]
    pub fn new(xstart: T, xend: T, ystart: T, yend: T) -> Rect<T> {
        Rect {
            x: Range {
                start: xstart,
                end: xend,
            },
            y: Range {
                start: ystart,
                end: yend,
            },
        }
    }
}
impl<T: Copy> Rect<T> {
    #[inline(always)]
    pub fn top_left(&self) -> Vec2<T> {
        vec2(self.x.start, self.y.start)
    }
    pub fn get_corners(&self) -> [Vec2<T>; 4] {
        [
            vec2(self.x.start, self.y.start),
            vec2(self.x.end, self.y.start),
            vec2(self.x.end, self.y.end),
            vec2(self.x.start, self.y.end),
        ]
    }
    #[inline(always)]
    pub fn inner_as<B: 'static + Copy>(&self) -> Rect<B>
    where
        T: num_traits::AsPrimitive<B>,
    {
        Rect {
            x: self.x.inner_as(),
            y: self.y.inner_as(),
        }
    }
    #[inline(always)]
    pub fn get(&self) -> ((T, T), (T, T)) {
        let f = self;
        ((f.x.start, f.x.end), (f.y.start, f.y.end))
    }
}
impl<T: PartialOrd + Copy> Rect<T> {
    #[inline(always)]
    pub fn contains_point(&self, a: Vec2<T>) -> bool {
        self.x.contains(a.x) && self.y.contains(a.y)
    }
}
impl<T: Copy + core::ops::Sub<Output = T> + core::ops::Add<Output = T>> Rect<T> {
    #[inline(always)]
    pub fn grow(self, radius: T) -> Self {
        Rect {
            x: self.x.grow(radius),
            y: self.y.grow(radius),
        }
    }
}
impl<
        T: Copy
            + PartialOrd
            + core::ops::Sub<Output = T>
            + core::ops::Mul<Output = T>
            + core::ops::Add<Output = T>,
    > Rect<T>
{
    #[inline(always)]
    pub fn distance_squared_to_point(&self, point: Vec2<T>) -> Option<T> {
        let (px, py) = (point.x, point.y);
        let ((a, b), (c, d)) = self.get();
        let xx = num_traits::clamp(px, a, b);
        let yy = num_traits::clamp(py, c, d);
        let dis = (xx - px) * (xx - px) + (yy - py) * (yy - py);
        if xx > a && xx < b && yy > c && yy < d {
            None
        } else {
            Some(dis)
        }
    }
    #[inline(always)]
    pub fn furthest_distance_squared_to_point(&self, point: Vec2<T>) -> T {
        let (px, py) = (point.x, point.y);
        let ((a, b), (c, d)) = self.get();
        fn reverse_clamp<N: PartialOrd + core::ops::Sub<Output = N> + Copy>(
            px: N,
            a: N,
            b: N,
        ) -> N {
            let aa = px - a;
            let bb = b - px;
            if bb > aa {
                b
            } else {
                a
            }
        }
        let xx = reverse_clamp(px, a, b);
        let yy = reverse_clamp(py, c, d);
        (xx - px) * (xx - px) + (yy - py) * (yy - py)
    }
}
impl<T: num_traits::Num + Copy> Rect<T> {
    #[inline(always)]
    pub fn derive_center(&self) -> Vec2<T> {
        let two = T::one() + T::one();
        let ((a, b), (c, d)) = self.get();
        vec2(a + (b - a) / two, c + (d - c) / two)
    }
}
impl<T: PartialOrd + Copy> Rect<T> {
    #[inline(always)]
    pub fn subdivide<A: Axis>(&self, axis: A, divider: T) -> (Rect<T>, Rect<T>) {
        let ca = axis;
        let na = axis.next();
        let rel = self.get_range(ca);
        let carry_thru = *self.get_range(na);
        let (l, r) = rel.subdivide(divider);
        if axis.is_xaxis() {
            (
                Rect {
                    x: l,
                    y: carry_thru,
                },
                Rect {
                    x: r,
                    y: carry_thru,
                },
            )
        } else {
            (
                Rect {
                    x: carry_thru,
                    y: l,
                },
                Rect {
                    x: carry_thru,
                    y: r,
                },
            )
        }
    }
    #[inline(always)]
    pub fn is_valid(&self) -> bool {
        self.x.is_valid() && self.y.is_valid()
    }
    #[inline(always)]
    pub fn contains_rect(&self, rect: &Rect<T>) -> bool {
        self.x.contains_range(&rect.x) && self.y.contains_range(&rect.y)
    }
    #[inline(always)]
    pub fn grow_to_fit_point(&mut self, point: Vec2<T>) -> &mut Self {
        if point.x < self.x.start {
            self.x.start = point.x
        } else if self.x.end < point.x {
            self.x.end = point.x
        }
        if point.y < self.y.start {
            self.y.start = point.y
        } else if self.y.end < point.y {
            self.y.end = point.y
        }
        self
    }
    #[inline(always)]
    pub fn grow_to_fit(&mut self, rect: &Rect<T>) -> &mut Self {
        {
            macro_rules! macro_axis {
                ($axis:ident) => {{
                    let sx = self.get_range_mut($axis);
                    let rx = rect.get_range($axis);
                    sx.grow_to_fit(rx);
                }};
            }
            macro_axis!(XAXIS);
            macro_axis!(YAXIS);
        }
        self
    }
    #[inline(always)]
    pub fn intersects_rect(&self, other: &Rect<T>) -> bool {
        other.x.intersects(&self.x) && other.y.intersects(&self.y)
    }
}
impl<T: PartialOrd + Copy> Rect<T> {
    #[inline(always)]
    pub fn get_intersect_rect(&self, other: &Rect<T>) -> Option<Rect<T>> {
        macro_rules! macro_axis {
            ($axis:ident) => {{
                let xr = other.get_range($axis);
                let xf = self.get_range($axis);
                let range = Range {
                    start: partial_min_max::max(xr.start, xf.start),
                    end: partial_min_max::min(xr.end, xf.end),
                };
                if range.end <= range.start {
                    return None;
                }
                range
            }};
        }
        let x = macro_axis!(XAXIS);
        let y = macro_axis!(YAXIS);
        Some(Rect { x, y })
    }
}