///|
pub struct ScopeCount {
  scope : Ipv4Scope
  count : Int
} derive(Eq, Debug)

///|
pub struct NetworkHit {
  network : Ipv4Network
  count : Int
} derive(Eq, Debug)

///|
pub struct AddressScanReport {
  total : Int
  allowed : Int
  denied : Int
  scope_counts : Array[ScopeCount]
  network_hits : Array[NetworkHit]
  unmatched : Array[Ipv4Address]
} derive(Debug)

///|
pub fn ScopeCount::scope(self : ScopeCount) -> Ipv4Scope {
  self.scope
}

///|
pub fn ScopeCount::count(self : ScopeCount) -> Int {
  self.count
}

///|
pub fn ScopeCount::to_string(self : ScopeCount) -> String {
  "\{self.scope.to_string()}=\{self.count}"
}

///|
pub fn NetworkHit::network(self : NetworkHit) -> Ipv4Network {
  self.network
}

///|
pub fn NetworkHit::count(self : NetworkHit) -> Int {
  self.count
}

///|
pub fn NetworkHit::to_string(self : NetworkHit) -> String {
  "\{self.network.to_string()}=\{self.count}"
}

///|
pub fn AddressScanReport::total(self : AddressScanReport) -> Int {
  self.total
}

///|
pub fn AddressScanReport::allowed(self : AddressScanReport) -> Int {
  self.allowed
}

///|
pub fn AddressScanReport::denied(self : AddressScanReport) -> Int {
  self.denied
}

///|
pub fn AddressScanReport::scope_counts(
  self : AddressScanReport,
) -> Array[ScopeCount] {
  self.scope_counts.copy()
}

///|
pub fn AddressScanReport::network_hits(
  self : AddressScanReport,
) -> Array[NetworkHit] {
  self.network_hits.copy()
}

///|
pub fn AddressScanReport::unmatched(
  self : AddressScanReport,
) -> Array[Ipv4Address] {
  self.unmatched.copy()
}

///|
pub fn AddressScanReport::to_string(self : AddressScanReport) -> String {
  let mut text = "total=\{self.total}, allowed=\{self.allowed}, denied=\{self.denied}"
  text = text + "\nscopes: " + join_scope_counts(self.scope_counts)
  text = text + "\nnetworks: " + join_network_hits(self.network_hits)
  text = text + "\nunmatched: " + join_addresses(self.unmatched)
  text
}

///|
pub fn scan_addresses(
  addresses : Array[Ipv4Address],
  policy : AclPolicy,
  networks : Ipv4NetworkSet,
) -> AddressScanReport {
  let scope_counts = Array::new()
  let network_hits = Array::new()
  let unmatched = Array::new()
  let mut allowed = 0
  let mut denied = 0
  for address in addresses {
    if policy.allows(address) {
      allowed = allowed + 1
    } else {
      denied = denied + 1
    }
    add_scope_count(scope_counts, address.scope())
    match networks.match_network(address) {
      Some(network) => add_network_hit(network_hits, network)
      None => unmatched.push(address)
    }
  }
  sort_scope_counts(scope_counts)
  sort_network_hits(network_hits)
  {
    total: addresses.length(),
    allowed,
    denied,
    scope_counts,
    network_hits,
    unmatched,
  }
}

///|
pub fn parse_ipv4_addresses(
  items : Array[String],
) -> Result[Array[Ipv4Address], CidrError] {
  let addresses = Array::new()
  for item in items {
    match Ipv4Address::parse(item) {
      Err(error) => return Err(error)
      Ok(address) => addresses.push(address)
    }
  }
  Ok(addresses)
}

///|
pub fn filter_by_scope(
  addresses : Array[Ipv4Address],
  scope : Ipv4Scope,
) -> Array[Ipv4Address] {
  let result = Array::new()
  for address in addresses {
    if address.scope() == scope {
      result.push(address)
    }
  }
  result
}

///|
pub fn filter_matching_set(
  addresses : Array[Ipv4Address],
  networks : Ipv4NetworkSet,
) -> Array[Ipv4Address] {
  let result = Array::new()
  for address in addresses {
    if networks.contains(address) {
      result.push(address)
    }
  }
  result
}

///|
pub fn filter_not_matching_set(
  addresses : Array[Ipv4Address],
  networks : Ipv4NetworkSet,
) -> Array[Ipv4Address] {
  let result = Array::new()
  for address in addresses {
    if !networks.contains(address) {
      result.push(address)
    }
  }
  result
}

///|
pub fn summarize_addresses(addresses : Array[Ipv4Address]) -> Ipv4Summary {
  Ipv4Summary::from_addresses(addresses)
}

///|
fn add_scope_count(counts : Array[ScopeCount], scope : Ipv4Scope) -> Unit {
  let mut index = 0
  while index < counts.length() {
    if counts[index].scope == scope {
      counts[index] = { scope, count: counts[index].count + 1 }
      return
    }
    index = index + 1
  }
  counts.push({ scope, count: 1 })
}

///|
fn add_network_hit(hits : Array[NetworkHit], network : Ipv4Network) -> Unit {
  let mut index = 0
  while index < hits.length() {
    if hits[index].network == network {
      hits[index] = { network, count: hits[index].count + 1 }
      return
    }
    index = index + 1
  }
  hits.push({ network, count: 1 })
}

///|
fn sort_scope_counts(counts : Array[ScopeCount]) -> Unit {
  let mut i = 0
  while i < counts.length() {
    let mut j = i + 1
    while j < counts.length() {
      if counts[i].scope.to_string() > counts[j].scope.to_string() {
        let tmp = counts[i]
        counts[i] = counts[j]
        counts[j] = tmp
      }
      j = j + 1
    }
    i = i + 1
  }
}

///|
fn sort_network_hits(hits : Array[NetworkHit]) -> Unit {
  let mut i = 0
  while i < hits.length() {
    let mut j = i + 1
    while j < hits.length() {
      if hits[i].network.address().to_uint() >
        hits[j].network.address().to_uint() {
        let tmp = hits[i]
        hits[i] = hits[j]
        hits[j] = tmp
      }
      j = j + 1
    }
    i = i + 1
  }
}

///|
fn join_scope_counts(counts : Array[ScopeCount]) -> String {
  let mut text = ""
  let mut index = 0
  for item in counts {
    if index > 0 {
      text = text + ", "
    }
    text = text + item.to_string()
    index = index + 1
  }
  text
}

///|
fn join_network_hits(hits : Array[NetworkHit]) -> String {
  let mut text = ""
  let mut index = 0
  for item in hits {
    if index > 0 {
      text = text + ", "
    }
    text = text + item.to_string()
    index = index + 1
  }
  text
}

///|
fn join_addresses(addresses : Array[Ipv4Address]) -> String {
  let mut text = ""
  let mut index = 0
  for address in addresses {
    if index > 0 {
      text = text + ", "
    }
    text = text + address.to_string()
    index = index + 1
  }
  text
}