///|
/// A normalized IPv4 CIDR block.
pub struct CidrBlock {
  network : IPv4
  prefix : Int
} derive(Eq, Debug)

///|
pub fn CidrBlock::new_unchecked(network : IPv4, prefix : Int) -> CidrBlock {
  { network: IPv4::new(network.value() & prefix_mask(prefix)), prefix }
}

///|
pub fn CidrBlock::new(
  address : IPv4,
  prefix : Int,
) -> Result[CidrBlock, String] {
  guard prefix >= 0 && prefix <= 32 else {
    return Err("CIDR prefix must be between 0 and 32")
  }
  Ok(CidrBlock::new_unchecked(address, prefix))
}

///|
pub fn CidrBlock::parse(input : String) -> Result[CidrBlock, String] {
  let text = trim_ascii(input)
  let chars = text.to_array()
  guard chars.length() > 0 else { return Err("empty CIDR block") }
  let mut slash = -1
  for index = 0; index < chars.length(); index = index + 1 {
    if chars[index] == '/' {
      guard slash == -1 else {
        return Err("CIDR block contains more than one slash: " + input)
      }
      slash = index
    }
  }
  if slash == -1 {
    match IPv4::parse(text) {
      Ok(ip) => CidrBlock::new(ip, 32)
      Err(err) => Err(err)
    }
  } else {
    let ip_text = String::from_array(chars[0:slash])
    let prefix_text = String::from_array(chars[slash + 1:])
    match IPv4::parse(ip_text) {
      Err(err) => Err(err)
      Ok(ip) =>
        match parse_prefix(prefix_text) {
          Err(err) => Err(err)
          Ok(prefix) => CidrBlock::new(ip, prefix)
        }
    }
  }
}

///|
pub fn CidrBlock::network(self : CidrBlock) -> IPv4 {
  self.network
}

///|
pub fn CidrBlock::prefix(self : CidrBlock) -> Int {
  self.prefix
}

///|
pub fn CidrBlock::broadcast(self : CidrBlock) -> IPv4 {
  IPv4::new(self.network.value() | host_mask(self.prefix))
}

///|
pub fn CidrBlock::contains_ip(self : CidrBlock, ip : IPv4) -> Bool {
  ip.value() >= self.network.value() && ip.value() <= self.broadcast().value()
}

///|
pub fn CidrBlock::contains_block(self : CidrBlock, other : CidrBlock) -> Bool {
  self.contains_ip(other.network()) && self.contains_ip(other.broadcast())
}

///|
pub fn CidrBlock::overlaps(self : CidrBlock, other : CidrBlock) -> Bool {
  self.network.value() <= other.broadcast().value() &&
  other.network().value() <= self.broadcast().value()
}

///|
pub fn CidrBlock::equal_range(self : CidrBlock, other : CidrBlock) -> Bool {
  self.network.value() == other.network().value() &&
  self.broadcast().value() == other.broadcast().value()
}

///|
pub fn CidrBlock::has_host_bits(input : String) -> Bool {
  match CidrBlock::parse_raw(input) {
    Ok((ip, prefix)) => ip.value() != (ip.value() & prefix_mask(prefix))
    Err(_) => false
  }
}

///|
pub fn CidrBlock::parse_raw(input : String) -> Result[(IPv4, Int), String] {
  let text = trim_ascii(input)
  let chars = text.to_array()
  let mut slash = -1
  for index = 0; index < chars.length(); index = index + 1 {
    if chars[index] == '/' {
      slash = index
    }
  }
  if slash == -1 {
    match IPv4::parse(text) {
      Ok(ip) => Ok((ip, 32))
      Err(err) => Err(err)
    }
  } else {
    match IPv4::parse(String::from_array(chars[0:slash])) {
      Err(err) => Err(err)
      Ok(ip) =>
        match parse_prefix(String::from_array(chars[slash + 1:])) {
          Ok(prefix) => Ok((ip, prefix))
          Err(err) => Err(err)
        }
    }
  }
}

///|
pub fn CidrBlock::to_string(self : CidrBlock) -> String {
  self.network.to_dotted() + "/" + self.prefix.to_string()
}

///|
pub fn CidrBlock::size_label(self : CidrBlock) -> String {
  if self.prefix == 0 {
    "all_ipv4"
  } else if self.prefix <= 8 {
    "very_wide"
  } else if self.prefix <= 16 {
    "wide"
  } else if self.prefix <= 24 {
    "medium"
  } else {
    "narrow"
  }
}

///|
fn parse_prefix(input : String) -> Result[Int, String] {
  let text = trim_ascii(input)
  let chars = text.to_array()
  guard chars.length() > 0 else { return Err("empty CIDR prefix") }
  let mut value = 0
  for char in chars {
    guard is_ascii_digit(char) else {
      return Err("CIDR prefix contains an invalid character: " + input)
    }
    value = value * 10 + digit_value(char)
    guard value <= 32 else {
      return Err("CIDR prefix must be between 0 and 32")
    }
  }
  Ok(value)
}

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

///|
fn host_mask(prefix : Int) -> UInt {
  if prefix == 32 {
    0U
  } else {
    0xffffffffU >> prefix
  }
}