use std::convert::TryInto;

use crate::coords::{Axial, Cube};

pub trait Distance {
  /// Return the grid distance from `self` to `other`.
  fn dist(&self, other: &Self) -> u8;
}

// Implementations can be simplified once this lands in stable:
// https://github.com/rust-lang/rust/issues/74913

impl Distance for Cube {
  /// ```
  /// use bestagon::{coords::Cube, distance::Distance};
  /// assert_eq!(Cube(0, 4, -4).dist(&Cube(1, 2, -3)), 2);
  /// assert_eq!(Cube(-3, 2, 1).dist(&Cube(3, -1, -2)), 6);
  /// assert_eq!(Cube(-4, 4, 0).dist(&Cube(4, -4, 0)), 8);
  /// ```
  fn dist(&self, other: &Cube) -> u8 {
    // https://www.redblobgames.com/grids/hexagons/#distances-cube
    (((other.0 - self.0).abs() + (other.1 - self.1).abs() + (other.2 - self.2).abs()) / 2)
      .try_into().unwrap()
  }
}

impl Distance for Axial {
  /// ```
  /// use bestagon::{coords::Axial, distance::Distance};
  /// assert_eq!(Axial(0, 4).dist(&Axial(1, 2)), 2);
  /// assert_eq!(Axial(-3, 2).dist(&Axial(3, -1)), 6);
  /// ```
  fn dist(&self, other: &Axial) -> u8 {
    // Simply convert to Cube and use that implementation.
    // Could be improved by manually calculating but this is easier.
    let self_c: Cube = (*self).into();
    let other_c: Cube = (*other).into();
    self_c.dist(&other_c)
  }
}
