///|
/// A canonical IPv4 network prefix.
pub struct Ipv4Prefix {
  address : UInt
  length : Int
} derive(Eq, Debug)

///|
fn prefix_mask(length : Int) -> UInt {
  if length == 0 {
    0U
  } else {
    0xffffffffU << (32 - length)
  }
}

///|
fn parse_int_part(text : StringView, label : String) -> Result[Int, String] {
  Ok(@strconv.parse_int(text)) catch {
    _ => Err("invalid \{label}: \{text}")
  }
}

///|
fn parse_canonical_decimal_part(
  text : StringView,
  label : String,
) -> Result[Int, String] {
  if text.is_empty() || (text.length() > 1 && text[0] == '0') {
    return Err("invalid \{label}: \{text}")
  }
  for ch in text {
    if ch < '0' || ch > '9' {
      return Err("invalid \{label}: \{text}")
    }
  }
  parse_int_part(text, label)
}

///|
fn parse_ipv4_address(text : StringView) -> Result[UInt, String] {
  let parts : Array[StringView] = []
  for part in text.split(".") {
    parts.push(part)
  }
  if parts.length() != 4 {
    return Err("IPv4 address must contain four octets: \{text}")
  }
  let mut value = 0U
  for part in parts {
    let octet = match parse_canonical_decimal_part(part, "IPv4 octet") {
      Ok(value) => value
      Err(message) => return Err(message)
    }
    if octet < 0 || octet > 255 {
      return Err("IPv4 octet is out of range: \{part}")
    }
    value = (value << 8) | octet.reinterpret_as_uint()
  }
  Ok(value)
}

///|
/// Parse a canonical IPv4 CIDR prefix such as `203.0.113.0/24`.
/// Decimal octets and prefix lengths must not have signs or leading zeroes.
pub fn Ipv4Prefix::parse(text : StringView) -> Result[Ipv4Prefix, String] {
  let input = text.trim()
  let parts : Array[StringView] = []
  for part in input.split("/") {
    parts.push(part)
  }
  if parts.length() != 2 {
    return Err("IPv4 prefix must have address/length form: \{input}")
  }
  let address = match parse_ipv4_address(parts[0]) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let length = match parse_canonical_decimal_part(parts[1], "prefix length") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  if length < 0 || length > 32 {
    return Err("IPv4 prefix length is out of range: \{parts[1]}")
  }
  let mask = prefix_mask(length)
  if (address & mask) != address {
    return Err("IPv4 prefix has host bits set: \{input}")
  }
  Ok({ address, length, })
}

///|
pub fn Ipv4Prefix::length(self : Ipv4Prefix) -> Int {
  self.length
}

///|
pub fn Ipv4Prefix::covers(self : Ipv4Prefix, candidate : Ipv4Prefix) -> Bool {
  self.length <= candidate.length &&
  (candidate.address & prefix_mask(self.length)) == self.address
}

///|
pub fn Ipv4Prefix::to_string(self : Ipv4Prefix) -> String {
  let first = ((self.address >> 24) & 255U).reinterpret_as_int()
  let second = ((self.address >> 16) & 255U).reinterpret_as_int()
  let third = ((self.address >> 8) & 255U).reinterpret_as_int()
  let fourth = (self.address & 255U).reinterpret_as_int()
  "\{first}.\{second}.\{third}.\{fourth}/\{self.length}"
}