///|
/// A validated H3 cell index.
///
/// The bit layout follows the Uber H3 indexing specification. Values of this
/// type always represent a valid H3 hexagonal or pentagonal cell.
pub struct H3Index(UInt64) derive(Eq, Hash)

///|
/// Errors produced while parsing or validating an H3 cell index.
pub enum H3Error {
  EmptyInput
  TooLong(Int)
  InvalidCharacter(Char)
  InvalidIndex
  InvalidResolution(Int)
  ResolutionMismatch(Int, Int)
  InvalidCoordinate
  InvalidGridDiskRadius(Int)
  InvalidDirection(Int)
  UnavailableNeighbor(Int)
  GridDistanceOutOfRange
} derive(Eq, Debug)

///|
/// The highest supported H3 resolution.
pub const MAX_RESOLUTION : Int = 15

///|
/// The number of H3 base cells, including the twelve pentagons.
pub const NUM_BASE_CELLS : Int = 122

///|
fn hex_value(ch : Char) -> UInt64? {
  if ch >= '0' && ch <= '9' {
    Some((ch.to_int() - '0'.to_int()).to_uint64())
  } else if ch >= 'a' && ch <= 'f' {
    Some((ch.to_int() - 'a'.to_int() + 10).to_uint64())
  } else if ch >= 'A' && ch <= 'F' {
    Some((ch.to_int() - 'A'.to_int() + 10).to_uint64())
  } else {
    None
  }
}

///|
fn is_pentagon_base_cell(base_cell : Int) -> Bool {
  match base_cell {
    4 | 14 | 24 | 38 | 49 | 58 | 63 | 72 | 83 | 97 | 107 | 117 => true
    _ => false
  }
}

///|
fn resolution_of(value : UInt64) -> Int {
  ((value >> 52) & 15UL).to_int()
}

///|
fn base_cell_of(value : UInt64) -> Int {
  ((value >> 45) & 127UL).to_int()
}

///|
fn digit_of(value : UInt64, resolution : Int) -> Int {
  ((value >> ((MAX_RESOLUTION - resolution) * 3)) & 7UL).to_int()
}

///|
fn with_resolution(value : UInt64, resolution : Int) -> UInt64 {
  (value & UInt64::lnot(15UL << 52)) | (resolution.to_uint64() << 52)
}

///|
fn parent_tail_mask(resolution : Int) -> UInt64 {
  (1UL << ((MAX_RESOLUTION - resolution) * 3)) - 1UL
}

///|
fn child_digit_mask(current_resolution : Int, child_resolution : Int) -> UInt64 {
  let digits_below_current = (
      1UL << ((MAX_RESOLUTION - current_resolution) * 3)
    ) -
    1UL
  digits_below_current & UInt64::lnot(parent_tail_mask(child_resolution))
}

///|
/// Returns whether `value` is a valid H3 cell index.
///
/// This validates the top byte, base cell range, active digits, unused digits,
/// and the deleted subsequence rule for pentagonal base cells.
pub fn is_valid_index(value : UInt64) -> Bool {
  if value >> 56 != 8UL {
    return false
  }
  let resolution = resolution_of(value)
  let base_cell = base_cell_of(value)
  if base_cell >= NUM_BASE_CELLS {
    return false
  }
  let mut current = 1
  while current <= resolution {
    if digit_of(value, current) == 7 {
      return false
    }
    current += 1
  }
  while current <= MAX_RESOLUTION {
    if digit_of(value, current) != 7 {
      return false
    }
    current += 1
  }
  if is_pentagon_base_cell(base_cell) {
    current = 1
    while current <= MAX_RESOLUTION {
      let digit = digit_of(value, current)
      if digit != 0 {
        return digit != 1
      }
      current += 1
    }
  }
  true
}

///|
/// Creates an H3 index from its raw 64-bit representation.
pub fn H3Index::from_u64(value : UInt64) -> Result[H3Index, H3Error] {
  if is_valid_index(value) {
    Ok(H3Index(value))
  } else {
    Err(InvalidIndex)
  }
}

///|
/// Parses an H3 cell index from one to sixteen hexadecimal digits.
///
/// Parsing is case-insensitive and accepts leading zeroes. The resulting
/// value is validated before it is returned.
pub fn H3Index::parse(text : String) -> Result[H3Index, H3Error] {
  let chars = text.to_array()
  let length = chars.length()
  if length == 0 {
    return Err(EmptyInput)
  }
  if length > 16 {
    return Err(TooLong(length))
  }
  let mut value = 0UL
  for ch in chars {
    match hex_value(ch) {
      Some(digit) => value = (value << 4) | digit
      None => return Err(InvalidCharacter(ch))
    }
  }
  H3Index::from_u64(value)
}

///|
/// Returns the raw 64-bit H3 representation.
pub fn H3Index::raw(self : H3Index) -> UInt64 {
  self.0
}

///|
/// Returns the canonical lower-case hexadecimal representation.
pub fn H3Index::to_string(self : H3Index) -> String {
  self.0.to_string(radix=16)
}

///|
/// Returns the resolution of this cell, from 0 through 15.
pub fn H3Index::resolution(self : H3Index) -> Int {
  resolution_of(self.0)
}

///|
/// Returns the base cell number of this cell, from 0 through 121.
pub fn H3Index::base_cell(self : H3Index) -> Int {
  base_cell_of(self.0)
}

///|
/// Returns the index digit for `resolution`, when that resolution is present.
///
/// Resolution numbering starts at 1 and ends at `self.resolution()`.
pub fn H3Index::digit(self : H3Index, resolution : Int) -> Int? {
  if resolution < 1 || resolution > self.resolution() {
    None
  } else {
    Some(digit_of(self.0, resolution))
  }
}

///|
/// Returns the ancestor of this cell at `resolution`.
///
/// Requesting the current resolution returns the same cell.
pub fn H3Index::parent(
  self : H3Index,
  resolution : Int,
) -> Result[H3Index, H3Error] {
  let current = self.resolution()
  if resolution < 0 || resolution > MAX_RESOLUTION {
    return Err(InvalidResolution(resolution))
  }
  if resolution > current {
    return Err(ResolutionMismatch(resolution, current))
  }
  if resolution == current {
    return Ok(self)
  }
  let mask = parent_tail_mask(resolution)
  let value = (with_resolution(self.0, resolution) & UInt64::lnot(mask)) | mask
  H3Index::from_u64(value)
}

///|
/// Returns the center descendant of this cell at `resolution`.
///
/// Requesting the current resolution returns the same cell.
pub fn H3Index::center_child(
  self : H3Index,
  resolution : Int,
) -> Result[H3Index, H3Error] {
  let current = self.resolution()
  if resolution < 0 || resolution > MAX_RESOLUTION {
    return Err(InvalidResolution(resolution))
  }
  if resolution < current {
    return Err(ResolutionMismatch(resolution, current))
  }
  if resolution == current {
    return Ok(self)
  }
  let mask = child_digit_mask(current, resolution)
  let value = with_resolution(
    (self.0 & UInt64::lnot(mask)) | parent_tail_mask(resolution),
    resolution,
  )
  H3Index::from_u64(value)
}

///|
/// Returns whether this cell is an ancestor of `other`.
///
/// A cell is considered an ancestor of itself.
pub fn H3Index::is_ancestor_of(self : H3Index, other : H3Index) -> Bool {
  if self.resolution() > other.resolution() {
    return false
  }
  match other.parent(self.resolution()) {
    Ok(parent) => parent == self
    Err(_) => false
  }
}

///|
/// Returns whether this cell is a descendant of `other`.
///
/// A cell is considered a descendant of itself.
pub fn H3Index::is_descendant_of(self : H3Index, other : H3Index) -> Bool {
  other.is_ancestor_of(self)
}

///|
/// Returns whether this cell is one of the twelve pentagons at its resolution.
pub fn H3Index::is_pentagon(self : H3Index) -> Bool {
  if !is_pentagon_base_cell(self.base_cell()) {
    return false
  }
  let mut current = 1
  while current <= self.resolution() {
    if digit_of(self.0, current) != 0 {
      return false
    }
    current += 1
  }
  true
}