// Typed convenience APIs and DNS record presentation helpers.

///|
pub async fn Resolver::resolve_a(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_a) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let ips : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      A(ip) => ips.push(format_ipv4(ip))
      _ => ()
    }
  }
  Ok(ips)
}

///|
pub async fn Resolver::resolve_aaaa(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_aaaa) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let ips : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      AAAA(w1, w2, w3, w4) => ips.push(format_ipv6(w1, w2, w3, w4))
      _ => ()
    }
  }
  Ok(ips)
}

///|
pub async fn Resolver::resolve_mx(
  self : Resolver,
  name : String,
) -> Result[Array[(Int, String)], ResolveError] {
  let result = match self.resolve(name, qtype_mx) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let entries : Array[(Int, String)] = Array::new(
    capacity=result.answers.length(),
  )
  for rr in result.answers {
    match rr.rdata {
      MX(preference, exchange) =>
        entries.push((preference, strip_trailing_dot(exchange)))
      _ => ()
    }
  }
  entries.sort_by((left, right) => left.0 - right.0)
  Ok(entries)
}

///|
pub async fn Resolver::resolve_ns(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_ns) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let names : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      NS(target) => names.push(strip_trailing_dot(target))
      _ => ()
    }
  }
  Ok(names)
}

///|
pub async fn Resolver::resolve_cname(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_cname) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let names : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      CNAME(target) => names.push(strip_trailing_dot(target))
      _ => ()
    }
  }
  Ok(names)
}

///|
pub async fn Resolver::resolve_txt(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_txt) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let texts : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      TXT(strings) => texts.push(strings.join(""))
      _ => ()
    }
  }
  Ok(texts)
}

///|
pub(all) struct SoaResult {
  mname : String
  rname : String
  serial : UInt
  refresh : UInt
  retry : UInt
  expire : UInt
  minimum : UInt
}

///|
pub async fn Resolver::resolve_soa(
  self : Resolver,
  name : String,
) -> Result[Array[SoaResult], ResolveError] {
  let result = match self.resolve(name, qtype_soa) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let values : Array[SoaResult] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      SOA(mname, rname, serial, refresh, retry, expire, minimum) =>
        values.push({
          mname: strip_trailing_dot(mname),
          rname: strip_trailing_dot(rname),
          serial,
          refresh,
          retry,
          expire,
          minimum,
        })
      _ => ()
    }
  }
  Ok(values)
}

///|
pub async fn Resolver::resolve_ptr(
  self : Resolver,
  name : String,
) -> Result[Array[String], ResolveError] {
  let result = match self.resolve(name, qtype_ptr) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let names : Array[String] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      PTR(target) => names.push(strip_trailing_dot(target))
      _ => ()
    }
  }
  Ok(names)
}

///|
pub(all) struct SrvResult {
  priority : UInt
  weight : UInt
  port : UInt
  target : String
}

///|
pub async fn Resolver::resolve_srv(
  self : Resolver,
  name : String,
) -> Result[Array[SrvResult], ResolveError] {
  let result = match self.resolve(name, qtype_srv) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let values : Array[SrvResult] = Array::new(capacity=result.answers.length())
  for rr in result.answers {
    match rr.rdata {
      SRV(priority, weight, port, target) =>
        values.push({
          priority,
          weight,
          port,
          target: strip_trailing_dot(target),
        })
      _ => ()
    }
  }
  // Deterministic presentation order. RFC 2782 weighted selection belongs to
  // connection establishment, so this API exposes all candidates instead.
  values.sort_by((left, right) => {
    let priority = left.priority.reinterpret_as_int() -
      right.priority.reinterpret_as_int()
    if priority != 0 {
      priority
    } else {
      right.weight.reinterpret_as_int() - left.weight.reinterpret_as_int()
    }
  })
  Ok(values)
}

///|
pub async fn Resolver::resolve_ipv4(
  self : Resolver,
  host : String,
) -> Result[String, ResolveError] {
  let addresses = match self.resolve_a(host) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match addresses.get(0) {
    Some(address) => Ok(address)
    None => Err(NoData)
  }
}

///|
fn format_ipv4(ip : Int) -> String {
  let a = (ip >> 24) & 0xFF
  let b = (ip >> 16) & 0xFF
  let c = (ip >> 8) & 0xFF
  let d = ip & 0xFF
  a.to_string() +
  "." +
  b.to_string() +
  "." +
  c.to_string() +
  "." +
  d.to_string()
}

///|
fn format_ipv6(w1 : Int, w2 : Int, w3 : Int, w4 : Int) -> String {
  let groups = [
    hex16((w1 >> 16) & 0xFFFF),
    hex16(w1 & 0xFFFF),
    hex16((w2 >> 16) & 0xFFFF),
    hex16(w2 & 0xFFFF),
    hex16((w3 >> 16) & 0xFFFF),
    hex16(w3 & 0xFFFF),
    hex16((w4 >> 16) & 0xFFFF),
    hex16(w4 & 0xFFFF),
  ]
  groups.join(":")
}

///|
fn hex16(value : Int) -> String {
  if value == 0 {
    return "0"
  }
  let out = StringBuilder()
  let mut shift = 12
  let started = Ref(false)
  while shift >= 0 {
    let nibble = (value >> shift) & 0xF
    if nibble != 0 || started.val {
      started.val = true
      out.write_char(
        if nibble < 10 {
          (b'0'.to_int() + nibble).unsafe_to_char()
        } else {
          (b'a'.to_int() + nibble - 10).unsafe_to_char()
        },
      )
    }
    shift = shift - 4
  }
  out.to_string()
}

///|
fn strip_trailing_dot(name : String) -> String {
  if name.length() > 0 && name[name.length() - 1] == '.' {
    name[:name.length() - 1].to_owned()
  } else {
    name
  }
}