use std::{cmp::max, collections::HashSet, cmp::min};

use crate::coords::Cube;

// https://www.redblobgames.com/grids/hexagons/#range-coordinate
pub trait Range {
  fn range(&self, r: u8) -> HashSet<Self> where Self: Sized;
}

impl Range for Cube {
  fn range(&self, r: u8) -> HashSet<Cube> {
    let mut in_range = HashSet::with_capacity(hex_range_size(r));
    for x in -(r as i8)..=r as i8 {
      for y in max(-(r as i8), -x - r as i8)..=min(r as i8, -x + r as i8) {
        in_range.insert(Cube(
          self.0 + x,
          self.1 + y,
          self.2 + (-x - y)
        ));
      }
    }
    in_range
  }
}

fn hex_range_size(r: u8) -> usize {
  (1 + 6 * r * (r + 1) / 2) as usize
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::coords::Cube;
  use crate::distance::Distance;

  #[test]
  fn hex_range_size_cases() {
    assert_eq!(1, hex_range_size(0));
    assert_eq!(7, hex_range_size(1));
    assert_eq!(19, hex_range_size(2));
    assert_eq!(37, hex_range_size(3));
  }

  #[test]
  fn range_case_0() {
    let center = Cube(0, 0, 0);
    let radius = 3;
    let range = center.range(radius);
    verify_range(range, center, radius);
  }

  #[test]
  fn range_case_1() {
    let center = Cube(2, 1, -3);
    let radius = 5;
    let range = center.range(radius);
    verify_range(range, center, radius);
  }

  fn verify_range(range: HashSet<Cube>, center: Cube, radius: u8) {
    assert_eq!(hex_range_size(radius), range.len());
    assert!(range.contains(&center));
    for x in range {
      assert!(x.dist(&center) <= radius);
    }
  }
}
