///|
/// One SRV alternative, in connection-attempt order. Addresses belong only to
/// this target; an empty list means address resolution is still pending.
pub struct SrvCandidate {
  priority : Int32
  weight : Int32
  port : Int32
  target : @model.DnsName
  addresses : Array[Bytes]
}

///|
fn clone_candidate(candidate : SrvCandidate) -> SrvCandidate {
  { ..candidate, addresses: candidate.addresses.copy(), }
}

///|
fn srv_key(candidate : SrvCandidate) -> String {
  "\{candidate.priority}:\{candidate.weight}:\{candidate.port}:" +
  @model.bytes_hex(candidate.target.canonical_key())
}

///|
// RFC 2782: ascending priority, randomized zero-weight-first arrangement,
// inclusive [0, sum] tickets, then remove and repeat without replacement.
// This stream is separate from query jitter and stable across TTL refreshes.
fn order_srv(
  candidates : Array[SrvCandidate],
  seed : UInt32,
) -> Array[SrvCandidate] {
  if candidates.length() <= 1 {
    return candidates.copy()
  }
  let pending = candidates.copy()
  pending.sort_by((a, b) => {
    let priority = a.priority.compare(b.priority)
    if priority != 0 {
      priority
    } else {
      srv_key(a).compare(srv_key(b))
    }
  })
  let mut state = seed
  for candidate in pending {
    for c in srv_key(candidate) {
      state = (state ^ c.to_int().reinterpret_as_uint()) * 16777619U
    }
  }
  fn draw(bound : UInt32) -> Int32 {
    // Rejection sampling avoids modulo bias, including weight-sum boundaries.
    let threshold = (0U - bound) % bound
    for ;; {
      state = state * 1664525U + 1013904223U
      if state >= threshold {
        return (state % bound).reinterpret_as_int()
      }
    }
  }
  let result : Array[SrvCandidate] = []
  while !pending.is_empty() {
    let priority = pending[0].priority
    let group : Array[SrvCandidate] = []
    while !pending.is_empty() && pending[0].priority == priority {
      group.push(pending.remove(0))
    }
    for i in 1.. c.weight == 0)
    for candidate in group {
      if candidate.weight > 0 {
        pool.push(candidate)
      }
    }
    let mut sum = 0
    for candidate in pool {
      sum += candidate.weight
    }
    while !pool.is_empty() {
      let ticket = draw((sum + 1).reinterpret_as_uint())
      let mut running = 0
      let mut index = 0
      for i, candidate in pool {
        running += candidate.weight
        if running >= ticket {
          index = i
          break
        }
      }
      let chosen = pool.remove(index)
      sum -= chosen.weight
      result.push(chosen)
    }
  }
  result
}

///|
fn Engine::srv_candidates(
  self : Engine,
  scope : String,
  instance : @model.DnsName,
) -> Array[SrvCandidate] {
  let candidates : Array[SrvCandidate] = []
  // A cache-flush RRSet may span packets. The cache owns the one-second
  // grace/rescue rules; every unexpired SRV remains a live alternative.
  for entry in self.cache.records(scope, instance, 33) {
    if entry.record.data is @wire.Srv(priority, weight, port, target) {
      let addresses : Array[Bytes] = []
      for rr_type in [1, 28] {
        for address in self.cache.records(scope, target, rr_type) {
          match address.record.data {
            @wire.A(bytes) | @wire.Aaaa(bytes) => addresses.push(bytes)
            _ => ()
          }
        }
      }
      addresses.sort()
      candidates.push({ priority, weight, port, target, addresses, })
    }
  }
  let mut seed = self.srv_seed
  for c in scope {
    seed = (seed ^ c.to_int().reinterpret_as_uint()) * 16777619U
  }
  for byte in instance.canonical_key() {
    seed = (seed ^ byte.to_int().reinterpret_as_uint()) * 16777619U
  }
  order_srv(candidates, seed)
}

///|
fn same_candidates(a : Array[SrvCandidate], b : Array[SrvCandidate]) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for i, candidate in a {
    if srv_key(candidate) != srv_key(b[i]) ||
      candidate.addresses != b[i].addresses {
      return false
    }
  }
  true
}