///|
/// A network: an address and the number of leading bits that all addresses in it
/// share, written `192.0.2.0/24`.
///
/// The address is kept as it was given, so `192.0.2.5/24` stands for "this host on
/// that link" the way an interface does; every question about the block itself
/// works on `masked`, which is the same prefix with the host bits cleared.
pub struct Prefix {
  net : Addr
  plen : Int32
}

///|
fn make_prefix(net : Addr, plen : Int32) -> Prefix {
  Prefix::{ net, plen, }
}

///|
/// The top `count` bits of a 64-bit word.
fn top_bits(count : Int32) -> UInt64 {
  if count <= 0 {
    0UL
  } else if count >= 64 {
    0xFFFF_FFFF_FFFF_FFFFUL
  } else {
    0xFFFF_FFFF_FFFF_FFFFUL << (64 - count)
  }
}

///|
/// The mask of an IPv4 address as a 32-bit value.
fn v4_mask(bits : Int32) -> UInt64 {
  top_bits(bits) >> 32
}

///|
/// The top `bits` of an address, as the two words it is kept in.
fn mask_words(addr : Addr, bits : Int32) -> (UInt64, UInt64) {
  if addr.v4 {
    (0UL, u32_of(addr) & v4_mask(bits))
  } else if bits <= 64 {
    (addr.hi & top_bits(bits), 0UL)
  } else {
    (addr.hi, addr.lo & top_bits(bits - 64))
  }
}

///|
/// The same address with every bit below the prefix mask cleared.
fn masked_addr(addr : Addr, bits : Int32) -> Addr {
  let (hi, lo) = mask_words(addr, bits)
  make_addr(hi, lo, addr.v4, None)
}

///|
/// A prefix from an address and a length. The address is kept whole; use `masked`
/// to turn it into the network it names.
pub fn Prefix::new(addr : Addr, bits : Int32) -> Prefix raise Refused {
  let limit = addr.bits()
  if bits < 0 || bits > limit {
    raise Refused::OutOfRange(limit~, got=bits)
  }
  // A zone says which link an address is on, not which block, so it is dropped.
  make_prefix(make_addr(addr.hi, addr.lo, addr.v4, None), bits)
}

///|
/// Read a prefix from `address/length`.
///
/// With `strict` on, bits set below the mask are refused; with it off they are
/// cleared, which is how a block is usually written by hand.
///
/// ```mbt check
/// test {
///   let subnet = @moonip.Prefix::parse("192.0.2.0/24")
///   assert_eq(subnet.to_string(), "192.0.2.0/24")
///   assert_eq(
///     @moonip.Prefix::parse("192.0.2.5/24", strict=false).to_string(),
///     "192.0.2.0/24",
///   )
/// }
/// ```
pub fn Prefix::parse(
  text : StringView,
  strict~ : Bool = true,
) -> Prefix raise Refused {
  let n = text.char_length()
  let mut slash = -1
  let mut i = 0
  while i < n {
    let c = text.get_char(i).unwrap()
    if c == '/' {
      if slash >= 0 {
        raise Refused::Malformed(at=i)
      }
      slash = i
    } else if c == '%' {
      raise Refused::Malformed(at=i)
    }
    i = i + 1
  }
  if slash < 0 {
    raise Refused::Malformed(at=n)
  }
  if slash == 0 {
    raise Refused::Malformed(at=0)
  }
  let addr = Addr::parse(string_between(text, 0, slash))
  let limit = addr.bits()
  let start = slash + 1
  if start >= n {
    raise Refused::Malformed(at=start)
  }
  if n - start > 1 && text.get_char(start).unwrap() == '0' {
    raise Refused::Malformed(at=start)
  }
  let mut bits = 0
  let mut j = start
  while j < n {
    let c = text.get_char(j).unwrap()
    if c < '0' || c > '9' {
      raise Refused::Malformed(at=j)
    }
    bits = bits * 10 + (c.to_int() - 48)
    if bits > limit {
      raise Refused::OutOfRange(limit~, got=bits)
    }
    j = j + 1
  }
  let prefix = make_prefix(addr, bits)
  let masked = prefix.masked()
  if strict && masked.net != prefix.net {
    raise Refused::HostBits
  }
  masked
}

///|
/// The address as written, host bits and all.
pub fn Prefix::addr(self : Prefix) -> Addr {
  self.net
}

///|
/// The length of the mask in bits: 0 to 32 for IPv4, 0 to 128 for IPv6.
pub fn Prefix::bits(self : Prefix) -> Int32 {
  self.plen
}

///|
/// The block itself: the address with every bit below the mask cleared.
pub fn Prefix::masked(self : Prefix) -> Prefix {
  make_prefix(masked_addr(self.net, self.plen), self.plen)
}

///|
/// The mask as an address, `255.255.255.0` or `ffff:ffff:ffff:ffff::`.
pub fn Prefix::netmask(self : Prefix) -> Addr {
  if self.net.v4 {
    addr_of_u32(v4_mask(self.plen))
  } else if self.plen <= 64 {
    make_addr(top_bits(self.plen), 0UL, false, None)
  } else {
    make_addr(0xFFFF_FFFF_FFFF_FFFFUL, top_bits(self.plen - 64), false, None)
  }
}

///|
/// The complement of the mask, `0.0.0.255` or `::ffff:ffff:ffff:ffff`.
pub fn Prefix::hostmask(self : Prefix) -> Addr {
  let mask = self.netmask()
  if self.net.v4 {
    addr_of_u32(UInt64::lnot(u32_of(mask)) & 0xFFFF_FFFFUL)
  } else {
    make_addr(UInt64::lnot(mask.hi), UInt64::lnot(mask.lo), false, None)
  }
}

///|
/// The last address of the block, or `None` for IPv6, which has no broadcast
/// address: the top of an IPv6 block is an ordinary unicast address.
pub fn Prefix::broadcast(self : Prefix) -> Addr? {
  if self.net.v4 {
    let block = self.masked()
    Some(
      addr_of_u32(
        u32_of(block.net) | (UInt64::lnot(v4_mask(self.plen)) & 0xFFFF_FFFFUL),
      ),
    )
  } else {
    None
  }
}

///|
/// Whether the address falls inside the block. An IPv4-mapped IPv6 address is
/// matched against IPv6 blocks only; `unmap` first if you mean the IPv4 one.
pub fn Prefix::contains(self : Prefix, addr : Addr) -> Bool {
  if self.net.v4 != addr.v4 {
    return false
  }
  let here = masked_addr(self.net, self.plen)
  let there = masked_addr(addr, self.plen)
  here.hi == there.hi && here.lo == there.lo
}

///|
/// Whether two blocks share at least one address. Blocks of different families
/// never do.
pub fn Prefix::overlaps(self : Prefix, other : Prefix) -> Bool {
  if self.net.v4 != other.net.v4 {
    return false
  }
  let a = self.masked()
  let b = other.masked()
  let a_end = last_addr(a)
  let b_end = last_addr(b)
  a.net.compare(b_end) <= 0 && b.net.compare(a_end) <= 0
}

///|
/// The last address in the block, broadcast or not.
fn last_addr(prefix : Prefix) -> Addr {
  if prefix.net.v4 {
    addr_of_u32(
      u32_of(prefix.net) | (UInt64::lnot(v4_mask(prefix.plen)) & 0xFFFF_FFFFUL),
    )
  } else {
    let mask = prefix.netmask()
    make_addr(
      prefix.net.hi | UInt64::lnot(mask.hi),
      prefix.net.lo | UInt64::lnot(mask.lo),
      false,
      None,
    )
  }
}

///|
/// Whether this block sits inside the other one, equal blocks included.
pub fn Prefix::is_subnet_of(self : Prefix, other : Prefix) -> Bool {
  if self.net.v4 != other.net.v4 {
    return false
  }
  self.plen >= other.plen && other.contains(self.net)
}

///|
/// Whether this block holds the other one, equal blocks included. The same test as
/// `is_subnet_of` with the two sides the other way round, for callers who read
/// better that way.
///
/// ```mbt check
/// test {
///   let wide = @moonip.Prefix::parse("10.0.0.0/8")
///   assert_true(wide.is_supernet_of(@moonip.Prefix::parse("10.1.0.0/16")))
///   assert_false(wide.is_supernet_of(@moonip.Prefix::parse("11.0.0.0/8")))
/// }
/// ```
pub fn Prefix::is_supernet_of(self : Prefix, other : Prefix) -> Bool {
  other.is_subnet_of(self)
}

///|
/// Whether the block is a single address, that is, a `/32` or a `/128`.
pub fn Prefix::is_single_ip(self : Prefix) -> Bool {
  self.plen == self.net.bits()
}

///|
/// How many addresses the block holds, or `None` when that is more than a `UInt64`
/// can count: an IPv6 block shorter than `/64`. An IPv4 block always answers,
/// `0.0.0.0/0` with 4294967296.
///
/// ```mbt check
/// test {
///   assert_eq(@moonip.Prefix::parse("192.0.2.0/24").num_addresses(), Some(256UL))
///   assert_eq(@moonip.Prefix::parse("2001:db8::/32").num_addresses(), None)
/// }
/// ```
pub fn Prefix::num_addresses(self : Prefix) -> UInt64? {
  let space = self.net.bits() - self.plen
  if space >= 64 {
    None
  } else {
    Some(1UL << space)
  }
}

///|
pub impl Eq for Prefix with fn equal(self, other) {
  self.plen == other.plen && self.net == other.net
}

///|
pub extend Prefix with Eq::{equal, not_equal}

///|
pub impl Hash for Prefix with fn hash_combine(self, hasher) {
  Hash::hash_combine(self.net, hasher)
  Hash::hash_combine(self.plen, hasher)
}

///|
pub extend Prefix with Hash::{hash, hash_combine}

///|
/// IPv4 before IPv6, then the block's first address, then the mask from long to
/// short. That ordering puts a block before everything inside it, which is what
/// `collapse` needs.
pub impl Compare for Prefix with fn compare(self, other) {
  let order = self.net.compare(other.net)
  if order != 0 {
    order
  } else {
    self.plen.compare(other.plen)
  }
}

///|
pub extend Prefix with Compare::{compare, op_lt, op_gt, op_le, op_ge}

///|
/// The text form, `address/length`, with the address as it was given.
pub impl Show for Prefix with fn to_string(self) {
  self.net.to_string() + "/" + self.plen.to_string()
}

///|
pub extend Prefix with Show::{to_string, output}

///|
pub impl Debug for Prefix with fn to_repr(self) {
  Repr::Repr(self.to_string())
}

///|
pub extend Prefix with @debug.Debug::{to_repr}