///|
pub struct Ipv4Summary {
  networks : Array[Ipv4Network]
} derive(Debug)

///|
pub fn Ipv4Summary::from_ranges(
  ranges : Array[Ipv4Range],
) -> Result[Ipv4Summary, CidrError] {
  let result = Array::new()
  for range in ranges {
    append_range_summary(result, range)
  }
  Ok({ networks: normalize_summary_networks(result) })
}

///|
pub fn Ipv4Summary::from_addresses(
  addresses : Array[Ipv4Address],
) -> Ipv4Summary {
  let networks = Array::new()
  for address in addresses {
    networks.push(Ipv4Network::new(address, 32).unwrap())
  }
  { networks: normalize_summary_networks(networks) }
}

///|
pub fn Ipv4Summary::from_networks(networks : Array[Ipv4Network]) -> Ipv4Summary {
  let sorted = networks.copy()
  { networks: normalize_summary_networks(sorted) }
}

///|
pub fn Ipv4Summary::networks(self : Ipv4Summary) -> Array[Ipv4Network] {
  self.networks.copy()
}

///|
pub fn Ipv4Summary::len(self : Ipv4Summary) -> Int {
  self.networks.length()
}

///|
pub fn Ipv4Summary::contains(self : Ipv4Summary, address : Ipv4Address) -> Bool {
  for network in self.networks {
    if network.contains(address) {
      return true
    }
  }
  false
}

///|
pub fn Ipv4Summary::to_string(self : Ipv4Summary) -> String {
  let mut text = ""
  let mut index = 0
  for network in self.networks {
    if index > 0 {
      text = text + ", "
    }
    text = text + network.to_string()
    index = index + 1
  }
  text
}

///|
fn normalize_summary_networks(
  networks : Array[Ipv4Network],
) -> Array[Ipv4Network] {
  let normalized = Ipv4NetworkSet::from_array(networks).to_array()
  sort_networks(normalized)
  collapse_adjacent_networks(normalized)
}

///|
fn append_range_summary(result : Array[Ipv4Network], range : Ipv4Range) -> Unit {
  let mut current = range.start().to_uint()
  let end = range.end().to_uint()
  if current == 0U && end == 4294967295U {
    result.push(Ipv4Network::new(Ipv4Address::zero(), 0).unwrap())
    return
  }
  while current <= end {
    let max_size = largest_aligned_block(current)
    let remaining = end - current + 1U
    let block_size = clamp_block_size(max_size, remaining)
    let prefix = prefix_from_block_size(block_size)
    result.push(
      Ipv4Network::new(Ipv4Address::from_uint(current), prefix).unwrap(),
    )
    if block_size > 4294967295U - current {
      return
    }
    current = current + block_size
  }
}

///|
fn largest_aligned_block(value : UInt) -> UInt {
  if value == 0U {
    2147483648U
  } else {
    let mut size = 1U
    while (value & size) == 0U && size < 2147483648U {
      size = size << 1
    }
    size
  }
}

///|
fn clamp_block_size(size : UInt, remaining : UInt) -> UInt {
  let mut block = size
  while block > remaining {
    block = block >> 1
  }
  block
}

///|
fn prefix_from_block_size(size : UInt) -> Int {
  let mut bits = 0
  let mut value = size
  while value > 1U {
    bits = bits + 1
    value = value >> 1
  }
  32 - bits
}

///|
fn collapse_adjacent_networks(input : Array[Ipv4Network]) -> Array[Ipv4Network] {
  let current = input.copy()
  let mut changed = true
  while changed {
    changed = false
    let next = Array::new()
    let mut index = 0
    while index < current.length() {
      if index + 1 < current.length() {
        match try_merge_pair(current[index], current[index + 1]) {
          Some(merged) => {
            next.push(merged)
            index = index + 2
            changed = true
          }
          None => {
            next.push(current[index])
            index = index + 1
          }
        }
      } else {
        next.push(current[index])
        index = index + 1
      }
    }
    current.clear()
    for item in next {
      current.push(item)
    }
    sort_networks(current)
  }
  current
}

///|
fn try_merge_pair(left : Ipv4Network, right : Ipv4Network) -> Ipv4Network? {
  if left.prefix() != right.prefix() ||
    !left.is_adjacent_to(right) ||
    left.prefix() == 0 {
    None
  } else {
    let parent = left.parent().unwrap()
    if parent.contains_network(left) && parent.contains_network(right) {
      Some(parent)
    } else {
      None
    }
  }
}