//! Integer Interval and Aabb primitives.
//!
//! Note that unlike Aabbs over scalar types, Aabbs over Integers are *closed* (they
//! include their boundary points). This means that there can be degenerate Aabbs
//! containing a single point (when `min = max`), or line or plane in higher dimensions.
// TODO: more types? ortho lines, planes?

#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};

use crate::*;
use crate::geometry::intersect;

/// 1D interval
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Interval <I> {
  min : I,
  max : I
}

/// 2D axis-aligned bounding box
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Aabb2 <I> {
  min : Point2 <I>,
  max : Point2 <I>
}

/// 3D axis-aligned bounding box
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Aabb3 <I> {
  min : Point3 <I>,
  max : Point3 <I>
}

/// Coordinate-wise min
pub fn point2_min <I : Integer> (a : Point2 <I>, b : Point2 <I>) -> Point2 <I> {
  [ Ord::min (a.0.x, b.0.x),
    Ord::min (a.0.y, b.0.y)
  ].into()
}
/// Coordinate-wise max
pub fn point2_max <I : Integer> (a : Point2 <I>, b : Point2 <I>) -> Point2 <I> {
  [ Ord::max (a.0.x, b.0.x),
    Ord::max (a.0.y, b.0.y)
  ].into()
}
/// Minimum representable point
pub fn point2_min_value <I : Integer> () -> Point2 <I> {
  [ I::min_value(), I::min_value() ].into()
}
/// Maximum representable point
pub fn point2_max_value <I : Integer> () -> Point2 <I> {
  [ I::max_value(), I::max_value() ].into()
}

/// Coordinate-wise min
pub fn point3_min <I : Integer> (a : Point3 <I>, b : Point3 <I>) -> Point3 <I> {
  [ Ord::min (a.0.x, b.0.x),
    Ord::min (a.0.y, b.0.y),
    Ord::min (a.0.z, b.0.z)
  ].into()
}
/// Coordinate-wise max
pub fn point3_max <I : Integer> (a : Point3 <I>, b : Point3 <I>) -> Point3 <I> {
  [ Ord::max (a.0.x, b.0.x),
    Ord::max (a.0.y, b.0.y),
    Ord::max (a.0.z, b.0.z)
  ].into()
}
/// Minimum representable point
pub fn point3_min_value <I : Integer> () -> Point3 <I> {
  [ I::min_value(), I::min_value(), I::min_value() ].into()
}
/// Maximum representable point
pub fn point3_max_value <I : Integer> () -> Point3 <I> {
  [ I::max_value(), I::max_value(), I::max_value() ].into()
}

impl <I : Integer> Interval <I> {
  /// Construct a new AABB with given min and max points.
  ///
  /// Debug panic if points are not min/max:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::with_minmax (1, 0);  // panic!
  /// ```
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::with_minmax (0, 0);  // ok
  /// ```
  #[inline]
  pub fn with_minmax (min : I, max : I) -> Self where I : std::fmt::Debug {
    debug_assert_eq!(min, Ord::min (min, max));
    debug_assert_eq!(max, Ord::max (min, max));
    Interval { min, max }
  }
  /// Construct a new AABB using the two given points to determine min/max.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::from_points (0, 0);  // ok!
  /// ```
  #[inline]
  pub fn from_points (a : I, b : I) -> Self {
    let min = Ord::min (a, b);
    let max = Ord::max (a, b);
    Interval { min, max }
  }
  /// Construct the minimum AABB containing the given set of points.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::containing (&[0, 0]);  // ok!
  /// ```
  ///
  /// A single point is allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::containing (&[0]);  // ok!
  /// ```
  ///
  /// Debug panic if no points are given:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::<i32>::containing (&[]);  // panic!
  /// ```
  #[inline]
  pub fn containing (points : &[I]) -> Self where I : std::fmt::Debug {
    debug_assert!(!points.is_empty());
    let mut min = I::max_value();
    let mut max = I::min_value();
    for point in points {
      if *point < min {
        min = *point;
      }
      if *point > max {
        max = *point;
      }
    }
    Interval::with_minmax (min, max)
  }
  /// Create a new AABB that is the union of the two input AABBs
  #[inline]
  pub fn union (a : Interval <I>, b : Interval <I>) -> Self where I : std::fmt::Debug {
    Interval::with_minmax (
      Ord::min (a.min(), b.min()),
      Ord::max (a.max(), b.max())
    )
  }
  #[inline]
  pub const fn min (self) -> I {
    self.min
  }
  #[inline]
  pub const fn max (self) -> I {
    self.max
  }
  #[inline]
  pub fn width (self) -> Positive <I> {
    Positive::unchecked (self.max - self.min)
  }
  #[inline]
  pub fn contains (self, point : I) -> bool {
    self.min <= point && point <= self.max
  }
  /// Clamp a given point to the AABB interval.
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Interval::from_points (-1, 1);
  /// assert_eq!(b.clamp (-2), -1);
  /// assert_eq!(b.clamp ( 2),  1);
  /// assert_eq!(b.clamp ( 0),  0);
  /// ```
  pub fn clamp (self, point : I) -> I {
    Ord::max (Ord::min (self.max, point), self.min)
  }
  /// Generate a random point contained in the AABB.
  ///
  /// ```
  /// # use rand;
  /// # use rand_xorshift;
  /// # use math_utils::geometry::integer::*;
  /// # fn main () {
  /// use rand_xorshift;
  /// use rand::SeedableRng;
  /// // random sequence will be the same each time this is run
  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
  /// let aabb = Interval::<i32>::with_minmax (-10, 10);
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// # }
  /// ```
  #[inline]
  pub fn rand_point <R> (self, rng : &mut R) -> I where
    I : rand::distr::uniform::SampleUniform,
    R : rand::RngExt
  {
    // NOTE: integer aabbs include their max points so we increase the max by 1
    rng.random_range (self.min..self.max + I::one())
  }
  #[inline]
  pub fn intersects (self, other : Interval <I>) -> bool {
    intersect::integer::discrete_interval (self, other)
  }
}

impl <I : Integer> Aabb2 <I> {
  /// Construct a new AABB with given min and max points.
  ///
  /// Debug panic if points are not min/max:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::with_minmax ([1, 1].into(), [0, 0].into());  // panic!
  /// ```
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::with_minmax ([0, 0].into(), [0, 0].into());  // ok
  /// ```
  #[inline]
  pub fn with_minmax (min : Point2 <I>, max : Point2 <I>) -> Self where
    I : std::fmt::Debug
  {
    debug_assert_eq!(min, point2_min (min, max));
    debug_assert_eq!(max, point2_max (min, max));
    Aabb2 { min, max }
  }
  /// Construct a new AABB using the two given points to determine min/max.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::from_points ([0, 0].into(), [0, 0].into());  // ok
  /// ```
  #[inline]
  pub fn from_points (a : Point2 <I>, b : Point2 <I>) -> Self {
    let min = point2_min (a, b);
    let max = point2_max (a, b);
    Aabb2 { min, max }
  }
  /// Construct the minimum AABB containing the given set of points.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::containing (&[[0, 0].into(), [0, 0].into()]);  // ok!
  /// ```
  ///
  /// A single point is allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::containing (&[[0, 0].into()]);  // ok!
  /// ```
  ///
  /// Debug panic if no points are given:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::<i32>::containing (&[]);  // panic!
  /// ```
  #[inline]
  pub fn containing (points : &[Point2 <I>]) -> Self where I : std::fmt::Debug {
    debug_assert!(!points.is_empty());
    let mut min = point2_max_value();
    let mut max = point2_min_value();
    for point in points {
      min = point2_min (min, *point);
      max = point2_max (max, *point);
    }
    Aabb2::with_minmax (min, max)
  }
  /// Create a new AABB that is the union of the two input AABBs
  #[inline]
  pub fn union (a : Aabb2 <I>, b : Aabb2 <I>) -> Self where I : std::fmt::Debug {
    Aabb2::with_minmax (point2_min (a.min(), b.min()), point2_max (a.max(), b.max()))
  }
  #[inline]
  pub const fn min (self) -> Point2 <I> {
    self.min
  }
  #[inline]
  pub const fn max (self) -> Point2 <I> {
    self.max
  }
  #[inline]
  pub fn width (self) -> Positive <I> {
    Positive::unchecked (self.max.0.x - self.min.0.x)
  }
  #[inline]
  pub fn height (self) -> Positive <I> {
    Positive::unchecked (self.max.0.y - self.min.0.y)
  }
  #[inline]
  pub fn contains (self, point : Point2 <I>) -> bool {
    self.min.0.x <= point.0.x && point.0.x <= self.max.0.x &&
    self.min.0.y <= point.0.y && point.0.y <= self.max.0.y
  }
  /// Clamp a given point to the AABB.
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb2::from_points ([-1, -1].into(), [1, 1].into());
  /// assert_eq!(b.clamp ([-2, 0].into()), [-1, 0].into());
  /// assert_eq!(b.clamp ([ 2, 2].into()), [ 1, 1].into());
  /// assert_eq!(b.clamp ([ 0, 0].into()), [ 0, 0].into());
  /// ```
  pub fn clamp (self, point : Point2 <I>) -> Point2 <I> {
    [ Ord::max (Ord::min (self.max.0.x, point.0.x), self.min.0.x),
      Ord::max (Ord::min (self.max.0.y, point.0.y), self.min.0.y),
    ].into()
  }
  /// Generate a random point contained in the AABB
  ///
  /// ```
  /// # use rand;
  /// # use rand_xorshift;
  /// # use math_utils::geometry::integer::*;
  /// # fn main () {
  /// use rand_xorshift;
  /// use rand::SeedableRng;
  /// // random sequence will be the same each time this is run
  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
  /// let aabb = Aabb2::<i32>::with_minmax (
  ///   [-10, -10].into(),
  ///   [ 10,  10].into());
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// # }
  /// ```
  #[inline]
  pub fn rand_point <R> (self, rng : &mut R) -> Point2 <I> where
    I : rand::distr::uniform::SampleUniform,
    R : rand::RngExt
  {
    // NOTE: integer aabbs include their max points so we increase the max by 1
    [ rng.random_range (self.min.0.x..self.max.0.x+I::one()),
      rng.random_range (self.min.0.y..self.max.0.y+I::one())
    ].into()
  }
  #[inline]
  pub fn intersects (self, other : Aabb2 <I>) -> bool {
    intersect::integer::discrete_aabb2_aabb2 (self, other)
  }
}

impl <I : Integer> Aabb3 <I> {
  /// Construct a new AABB with given min and max points.
  ///
  /// Debug panic if points are not min/max:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::with_minmax ([1, 1, 1].into(), [0, 0, 0].into());  // panic!
  /// ```
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::with_minmax ([0, 0, 0].into(), [0, 0, 0].into());  // ok
  /// ```
  #[inline]
  pub fn with_minmax (min : Point3 <I>, max : Point3 <I>) -> Self where
    I : std::fmt::Debug
  {
    debug_assert_eq!(min, point3_min (min, max));
    debug_assert_eq!(max, point3_max (min, max));
    Aabb3 { min, max }
  }
  /// Construct a new AABB using the two given points to determine min/max.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::from_points ([0, 0, 0].into(), [0, 0, 0].into());  // ok
  /// ```
  #[inline]
  pub fn from_points (a : Point3 <I>, b : Point3 <I>) -> Self {
    let min = point3_min (a, b);
    let max = point3_max (a, b);
    Aabb3 { min, max }
  }
  /// Construct the minimum AABB containing the given set of points.
  ///
  /// Identical points are allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::containing (&[[0, 0, 0].into(), [0, 0, 0].into()]);  // ok!
  /// ```
  ///
  /// A single point is allowed:
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::containing (&[[0, 0, 0].into()]);  // ok!
  /// ```
  ///
  /// Debug panic if no points are given:
  ///
  /// ```should_panic
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::<i32>::containing (&[]);  // panic!
  /// ```
  #[inline]
  pub fn containing (points : &[Point3 <I>]) -> Self where I : std::fmt::Debug {
    debug_assert!(!points.is_empty());
    let mut min = point3_max_value();
    let mut max = point3_min_value();
    for point in points {
      min = point3_min (min, *point);
      max = point3_max (max, *point);
    }
    Aabb3::with_minmax (min, max)
  }
  /// Create a new AABB that is the union of the two input AABBs
  #[inline]
  pub fn union (a : Aabb3 <I>, b : Aabb3 <I>) -> Self where I : std::fmt::Debug {
    Aabb3::with_minmax (point3_min (a.min(), b.min()), point3_max (a.max(), b.max()))
  }
  #[inline]
  pub const fn min (self) -> Point3 <I> {
    self.min
  }
  #[inline]
  pub const fn max (self) -> Point3 <I> {
    self.max
  }
  #[inline]
  pub fn width (self) -> Positive <I> {
    Positive::unchecked (self.max.0.x - self.min.0.x)
  }
  #[inline]
  pub fn height (self) -> Positive <I> {
    Positive::unchecked (self.max.0.y - self.min.0.y)
  }
  #[inline]
  pub fn depth (self) -> Positive <I> {
    Positive::unchecked (self.max.0.z - self.min.0.z)
  }
  #[inline]
  pub fn contains (self, point : Point3 <I>) -> bool {
    self.min.0.x <= point.0.x && point.0.x <= self.max.0.x &&
    self.min.0.y <= point.0.y && point.0.y <= self.max.0.y &&
    self.min.0.z <= point.0.z && point.0.z <= self.max.0.z
  }
  /// Clamp a given point to the AABB.
  ///
  /// ```
  /// # use math_utils::geometry::integer::*;
  /// let b = Aabb3::with_minmax ([-1, -1, -1].into(), [1, 1, 1].into());
  /// assert_eq!(b.clamp ([-2, 0, 0].into()), [-1, 0, 0].into());
  /// assert_eq!(b.clamp ([ 2, 2, 0].into()), [ 1, 1, 0].into());
  /// assert_eq!(b.clamp ([-1, 2, 3].into()), [-1, 1, 1].into());
  /// assert_eq!(b.clamp ([ 0, 0, 0].into()), [ 0, 0, 0].into());
  /// ```
  pub fn clamp (self, point : Point3 <I>) -> Point3 <I> {
    [ Ord::max (Ord::min (self.max.0.x, point.0.x), self.min.0.x),
      Ord::max (Ord::min (self.max.0.y, point.0.y), self.min.0.y),
      Ord::max (Ord::min (self.max.0.z, point.0.z), self.min.0.z)
    ].into()
  }
  /// Generate a random point contained in the AABB
  ///
  /// ```
  /// # use rand;
  /// # use rand_xorshift;
  /// # use math_utils::geometry::integer::*;
  /// # fn main () {
  /// use rand::SeedableRng;
  /// // random sequence will be the same each time this is run
  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
  /// let aabb = Aabb3::<i32>::with_minmax (
  ///   [-10, -10, -10].into(),
  ///   [ 10,  10,  10].into());
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// let point = aabb.rand_point (&mut rng);
  /// assert!(aabb.contains (point));
  /// # }
  /// ```
  #[inline]
  pub fn rand_point <R> (self, rng : &mut R) -> Point3 <I> where
    I : rand::distr::uniform::SampleUniform,
    R : rand::RngExt
  {
    // NOTE: integer aabbs include their max points so we increase the max by 1
    [ rng.random_range (self.min.0.x..self.max.0.x+I::one()),
      rng.random_range (self.min.0.y..self.max.0.y+I::one()),
      rng.random_range (self.min.0.z..self.max.0.z+I::one())
    ].into()
  }
  #[inline]
  pub fn intersects (self, other : Aabb3 <I>) -> bool {
    intersect::integer::discrete_aabb3_aabb3 (self, other)
  }
}
