use necsim_core_bond::OffByOneU32;
use super::Location;
#[allow(clippy::module_name_repetitions)]
#[derive(PartialEq, Eq, Clone, Debug, serde::Deserialize, serde::Serialize, TypeLayout)]
#[cfg_attr(feature = "cuda", derive(rust_cuda::lend::LendRustToCuda))]
#[cfg_attr(feature = "cuda", cuda(ignore))]
#[serde(rename = "Extent")]
#[serde(deny_unknown_fields)]
pub struct LandscapeExtent {
    #[cfg_attr(feature = "cuda", cuda(embed))]
    origin: Location,
    width: OffByOneU32,
    height: OffByOneU32,
}
impl LandscapeExtent {
    #[must_use]
    pub const fn new(origin: Location, width: OffByOneU32, height: OffByOneU32) -> Self {
        Self {
            origin,
            width,
            height,
        }
    }
    #[must_use]
    pub const fn origin(&self) -> &Location {
        &self.origin
    }
    #[must_use]
    pub const fn width(&self) -> OffByOneU32 {
        self.width
    }
    #[must_use]
    pub const fn height(&self) -> OffByOneU32 {
        self.height
    }
    #[must_use]
    pub const fn contains(&self, location: &Location) -> bool {
        location.x() >= self.origin.x()
            && location.x() <= self.width.add_incl(self.origin.x())
            && location.y() >= self.origin.y()
            && location.y() <= self.height.add_incl(self.origin.y())
    }
    #[must_use]
    pub fn iter(&self) -> LocationIterator {
        LocationIterator {
            location: self.origin.clone(),
            extent: self.clone(),
            first_y: true,
        }
    }
}
impl IntoIterator for &LandscapeExtent {
    type IntoIter = LocationIterator;
    type Item = Location;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
#[derive(Debug, PartialEq, Eq)]
pub struct LocationIterator {
    location: Location,
    extent: LandscapeExtent,
    first_y: bool,
}
impl Iterator for LocationIterator {
    type Item = Location;
    fn next(&mut self) -> Option<Self::Item> {
        if self.location.y() != self.extent.height().add_excl(self.extent.origin().y())
            || self.first_y
        {
            let after =
                if self.location.x() == self.extent.width().add_incl(self.extent.origin().x()) {
                    self.first_y = false;
                    Location::new(self.extent.origin().x(), self.location.y().wrapping_add(1))
                } else {
                    Location::new(self.location.x().wrapping_add(1), self.location.y())
                };
            let next = self.location.clone();
            self.location = after;
            Some(next)
        } else {
            None
        }
    }
}
#[cfg(test)]
mod tests {
    use alloc::{vec, vec::Vec};
    use super::{LandscapeExtent, Location, LocationIterator, OffByOneU32};
    const M1: u32 = 0_u32.wrapping_sub(1);
    const M2: u32 = 0_u32.wrapping_sub(2);
    #[test]
    fn test_single_location() {
        let extent = LandscapeExtent::new(
            Location::new(0, 0),
            OffByOneU32::new(1).unwrap(),
            OffByOneU32::new(1).unwrap(),
        );
        let locations: Vec<Location> = extent.iter().collect();
        assert_eq!(locations, vec![Location::new(0, 0)]);
    }
    #[test]
    fn test_simple_extent() {
        let extent = LandscapeExtent::new(
            Location::new(42, 24),
            OffByOneU32::new(4).unwrap(),
            OffByOneU32::new(2).unwrap(),
        );
        let locations: Vec<Location> = extent.iter().collect();
        assert_eq!(
            locations,
            vec![
                Location::new(42, 24),
                Location::new(43, 24),
                Location::new(44, 24),
                Location::new(45, 24),
                Location::new(42, 25),
                Location::new(43, 25),
                Location::new(44, 25),
                Location::new(45, 25)
            ]
        );
    }
    #[test]
    fn test_wrapping_extent() {
        let extent = LandscapeExtent::new(
            Location::new(M2, M1),
            OffByOneU32::new(4).unwrap(),
            OffByOneU32::new(2).unwrap(),
        );
        let locations: Vec<Location> = extent.iter().collect();
        assert_eq!(
            locations,
            vec![
                Location::new(M2, M1),
                Location::new(M1, M1),
                Location::new(0, M1),
                Location::new(1, M1),
                Location::new(M2, 0),
                Location::new(M1, 0),
                Location::new(0, 0),
                Location::new(1, 0)
            ]
        );
    }
    #[test]
    fn test_full_extent() {
        let extent = LandscapeExtent::new(
            Location::new(0, 0),
            OffByOneU32::new(1 << 32).unwrap(),
            OffByOneU32::new(1 << 32).unwrap(),
        );
        let mut iter = extent.iter();
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(0, 0),
                extent: extent.clone(),
                first_y: true,
            }
        );
        assert_eq!(iter.next(), Some(Location::new(0, 0)));
        iter.location = Location::new(M1, M1);
        assert_eq!(iter.next(), Some(Location::new(M1, M1)));
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(0, 0),
                extent: extent.clone(),
                first_y: false,
            }
        );
        assert_eq!(iter.next(), None);
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(0, 0),
                extent,
                first_y: false,
            }
        );
    }
    #[test]
    fn test_full_wrapping_extent() {
        let extent = LandscapeExtent::new(
            Location::new(1386, 6812),
            OffByOneU32::new(1 << 32).unwrap(),
            OffByOneU32::new(1 << 32).unwrap(),
        );
        let mut iter = extent.iter();
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(1386, 6812),
                extent: extent.clone(),
                first_y: true,
            }
        );
        iter.location = Location::new(M1, iter.location.y());
        assert_eq!(iter.next(), Some(Location::new(M1, 6812)));
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(0, 6812),
                extent: extent.clone(),
                first_y: true,
            }
        );
        assert_eq!(iter.next(), Some(Location::new(0, 6812)));
        iter.location = Location::new(1385, iter.location.y());
        assert_eq!(iter.next(), Some(Location::new(1385, 6812)));
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(1386, 6813),
                extent: extent.clone(),
                first_y: false,
            }
        );
        assert_eq!(iter.next(), Some(Location::new(1386, 6813)));
        iter.location = Location::new(1385, M1);
        assert_eq!(iter.next(), Some(Location::new(1385, M1)));
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(1386, 0),
                extent: extent.clone(),
                first_y: false,
            }
        );
        assert_eq!(iter.next(), Some(Location::new(1386, 0)));
        iter.location = Location::new(1385, 6811);
        assert_eq!(iter.next(), Some(Location::new(1385, 6811)));
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(1386, 6812),
                extent: extent.clone(),
                first_y: false,
            }
        );
        assert_eq!(iter.next(), None);
        assert_eq!(
            iter,
            LocationIterator {
                location: Location::new(1386, 6812),
                extent,
                first_y: false,
            }
        );
    }
}