///|
pub enum CidrError {
  EmptyInput
  InvalidCharacter(String)
  InvalidOctetCount(Int)
  EmptyOctet(Int)
  OctetOutOfRange(Int, Int)
  InvalidPrefix(String)
  PrefixOutOfRange(Int)
  InvalidRange(String)
  TooManySubnets(Int)
} derive(Eq, Debug)

///|
pub struct Ipv4Address {
  value : UInt
} derive(Eq, Compare, Debug)

///|
pub struct Ipv4Network {
  address : Ipv4Address
  prefix : Int
} derive(Eq, Debug)

///|
pub fn Ipv4Address::from_octets(
  a : Int,
  b : Int,
  c : Int,
  d : Int,
) -> Result[Ipv4Address, CidrError] {
  match guard_octet(a, 0) {
    Err(error) => Err(error)
    Ok(_) =>
      match guard_octet(b, 1) {
        Err(error) => Err(error)
        Ok(_) =>
          match guard_octet(c, 2) {
            Err(error) => Err(error)
            Ok(_) =>
              match guard_octet(d, 3) {
                Err(error) => Err(error)
                Ok(_) =>
                  Ok({
                    value: (a.reinterpret_as_uint() << 24) |
                    (b.reinterpret_as_uint() << 16) |
                    (c.reinterpret_as_uint() << 8) |
                    d.reinterpret_as_uint(),
                  })
              }
          }
      }
  }
}

///|
pub fn Ipv4Address::parse(input : String) -> Result[Ipv4Address, CidrError] {
  if input.length() == 0 {
    return Err(EmptyInput)
  }
  let octets = Array::new()
  let mut current = 0
  let mut has_digit = false
  let mut index = 0
  for ch in input {
    if ch >= '0' && ch <= '9' {
      current = current * 10 + char_digit(ch)
      if current > 255 {
        return Err(OctetOutOfRange(index, current))
      }
      has_digit = true
    } else if ch == '.' {
      if !has_digit {
        return Err(EmptyOctet(index))
      }
      octets.push(current)
      current = 0
      has_digit = false
      index = index + 1
    } else {
      return Err(InvalidCharacter(ch.to_string()))
    }
  }
  if !has_digit {
    return Err(EmptyOctet(index))
  }
  octets.push(current)
  if octets.length() != 4 {
    return Err(InvalidOctetCount(octets.length()))
  }
  Ipv4Address::from_octets(octets[0], octets[1], octets[2], octets[3])
}

///|
pub fn Ipv4Address::to_uint(self : Ipv4Address) -> UInt {
  self.value
}

///|
pub fn Ipv4Address::octet(self : Ipv4Address, index : Int) -> Int {
  match index {
    0 => ((self.value >> 24) & 0xff).reinterpret_as_int()
    1 => ((self.value >> 16) & 0xff).reinterpret_as_int()
    2 => ((self.value >> 8) & 0xff).reinterpret_as_int()
    3 => (self.value & 0xff).reinterpret_as_int()
    _ => 0
  }
}

///|
pub fn Ipv4Address::to_string(self : Ipv4Address) -> String {
  "\{self.octet(0)}.\{self.octet(1)}.\{self.octet(2)}.\{self.octet(3)}"
}

///|
pub fn Ipv4Network::new(
  address : Ipv4Address,
  prefix : Int,
) -> Result[Ipv4Network, CidrError] {
  match guard_prefix(prefix) {
    Err(error) => Err(error)
    Ok(_) => Ok({ address: address.mask(prefix), prefix })
  }
}

///|
pub fn Ipv4Network::parse(input : String) -> Result[Ipv4Network, CidrError] {
  match find_single_slash(input) {
    Err(error) => Err(error)
    Ok(slash) => {
      let address_text = input[:slash].to_owned()
      let prefix_text = input[slash + 1:].to_owned()
      match Ipv4Address::parse(address_text) {
        Err(error) => Err(error)
        Ok(address) =>
          match parse_prefix(prefix_text) {
            Err(error) => Err(error)
            Ok(prefix) => Ipv4Network::new(address, prefix)
          }
      }
    }
  }
}

///|
pub fn Ipv4Network::address(self : Ipv4Network) -> Ipv4Address {
  self.address
}

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

///|
pub fn Ipv4Network::contains(self : Ipv4Network, address : Ipv4Address) -> Bool {
  address.mask(self.prefix).value == self.address.value
}

///|
pub fn Ipv4Network::contains_network(
  self : Ipv4Network,
  other : Ipv4Network,
) -> Bool {
  self.prefix <= other.prefix && self.contains(other.address)
}

///|
pub fn Ipv4Network::is_more_specific_than(
  self : Ipv4Network,
  other : Ipv4Network,
) -> Bool {
  self.prefix > other.prefix && other.contains_network(self)
}

///|
pub fn Ipv4Network::to_string(self : Ipv4Network) -> String {
  "\{self.address.to_string()}/\{self.prefix}"
}

///|
pub fn Ipv4Network::broadcast(self : Ipv4Network) -> Ipv4Address {
  let host_mask = if self.prefix == 32 {
    0U
  } else {
    4294967295U >> self.prefix
  }
  { value: self.address.value | host_mask }
}

///|
pub fn Ipv4Network::first(self : Ipv4Network) -> Ipv4Address {
  self.address
}

///|
pub fn Ipv4Network::last(self : Ipv4Network) -> Ipv4Address {
  self.broadcast()
}

///|
fn Ipv4Address::mask(self : Ipv4Address, prefix : Int) -> Ipv4Address {
  let mask = ipv4_mask(prefix)
  { value: self.value & mask }
}

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

///|
fn guard_octet(value : Int, index : Int) -> Result[Unit, CidrError] {
  if value < 0 || value > 255 {
    Err(OctetOutOfRange(index, value))
  } else {
    Ok(())
  }
}

///|
fn guard_prefix(prefix : Int) -> Result[Unit, CidrError] {
  if prefix < 0 || prefix > 32 {
    Err(PrefixOutOfRange(prefix))
  } else {
    Ok(())
  }
}

///|
fn char_digit(ch : Char) -> Int {
  ch.to_int() - '0'.to_int()
}

///|
fn find_single_slash(input : String) -> Result[Int, CidrError] {
  let mut slash = -1
  let mut index = 0
  for ch in input {
    if ch == '/' {
      if slash != -1 {
        return Err(InvalidPrefix(input))
      }
      slash = index
    }
    index = index + 1
  }
  if slash == -1 {
    Err(InvalidPrefix(input))
  } else {
    Ok(slash)
  }
}

///|
fn parse_prefix(input : String) -> Result[Int, CidrError] {
  if input.length() == 0 {
    return Err(InvalidPrefix(input))
  }
  let mut value = 0
  for ch in input {
    if ch < '0' || ch > '9' {
      return Err(InvalidPrefix(input))
    }
    value = value * 10 + char_digit(ch)
  }
  match guard_prefix(value) {
    Err(error) => Err(error)
    Ok(_) => Ok(value)
  }
}