// DNS Resource Records (RFC 1035 section 4.1.3).

///|
pub struct RR {
  name : String
  rtype : UInt16
  rclass : UInt16
  ttl : UInt
  rdlength : UInt16
  rdata : RData
}

///|
pub fn RR::encode_checked(self : RR) -> Result[Array[Byte], String] {
  match validate_rr_rdata(self.rtype, self.rdata) {
    Ok(_) => ()
    Err(err) => return Err("DNS RR RDATA: " + err)
  }
  if self.rtype == qtype_opt && self.name != "" {
    return Err("OPT owner name must be the root domain")
  }
  let name_bytes = match encode_name_checked(self.name) {
    Ok(bytes) => bytes
    Err(err) => return Err("DNS RR owner: " + err)
  }
  let rdata_bytes = match self.rdata.encode_checked() {
    Ok(bytes) => bytes
    Err(err) => return Err("DNS RR RDATA: " + err)
  }
  if rdata_bytes.length() > 65535 {
    return Err("DNS RR RDATA exceeds 65535 octets")
  }
  let out : Array[Byte] = Array::new(
    capacity=name_bytes.length() + 10 + rdata_bytes.length(),
  )
  append_bytes(out, name_bytes)
  append_u16(out, self.rtype.to_int())
  append_u16(out, self.rclass.to_int())
  append_u32(out, self.ttl)
  append_u16(out, rdata_bytes.length())
  append_bytes(out, rdata_bytes)
  Ok(out)
}

///|
pub fn RR::encode(self : RR) -> Array[Byte] {
  match self.encode_checked() {
    Ok(bytes) => bytes
    Err(error) => abort(error)
  }
}

///|
pub fn decode_rr(
  bytes : Array[Byte],
  offset : Int,
  msg_start : Int,
) -> Result[(RR, Int), String] {
  let (name, after_name) = match decode_name(bytes, offset, msg_start) {
    Ok(value) => value
    Err(err) => return Err("DNS RR owner: " + err)
  }
  match wire_check_range(bytes, after_name, 10) {
    Err(err) => return Err("truncated DNS RR header: " + err)
    Ok(_) => ()
  }
  let rtype = ((bytes[after_name].to_int() << 8) |
  bytes[after_name + 1].to_int()).to_uint16()
  let rclass = ((bytes[after_name + 2].to_int() << 8) |
  bytes[after_name + 3].to_int()).to_uint16()
  let ttl = match wire_get_u32(bytes, after_name + 4) {
    Ok((value, _)) => value
    Err(err) => return Err(err)
  }
  let rdlength = ((bytes[after_name + 8].to_int() << 8) |
  bytes[after_name + 9].to_int()).to_uint16()
  let rdata_offset = after_name + 10
  match wire_check_range(bytes, rdata_offset, rdlength.to_int()) {
    Err(err) => return Err("truncated DNS RR RDATA: " + err)
    Ok(_) => ()
  }
  let (rdata, next) = match
    decode_rdata(rtype, bytes, rdata_offset, rdlength, msg_start) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let end = rdata_offset + rdlength.to_int()
  if next != end {
    return Err("DNS RDATA parser did not consume exactly RDLENGTH octets")
  }
  if rtype == qtype_opt && name != "" {
    return Err("OPT owner name must be the root domain")
  }
  Ok(({ name, rtype, rclass, ttl, rdlength, rdata }, end))
}

///|
pub fn decode_rrs(
  bytes : Array[Byte],
  offset : Int,
  count : UInt16,
  msg_start : Int,
) -> Result[(Array[RR], Int), String] {
  if count.to_int() > max_section_records {
    return Err("DNS resource-record count exceeds codec limit")
  }
  let records : Array[RR] = Array::new(capacity=count.to_int())
  let pos = Ref(offset)
  for _i in 0.. value
      Err(err) => return Err(err)
    }
    records.push(record)
    pos.val = next
  }
  Ok((records, pos.val))
}

///|
pub fn rr_new(
  name : String,
  rtype : UInt16,
  rclass : UInt16,
  ttl : UInt,
  rdata : RData,
) -> RR {
  let rdlength = match rdata.encode_checked() {
    Ok(bytes) => bytes.length().to_uint16()
    Err(_) => 0
  }
  { name, rtype, rclass, ttl, rdlength, rdata }
}

///|
pub fn rr_a(name : String, ip : Int, ttl? : UInt = 300) -> RR {
  rr_new(name, qtype_a, qclass_in, ttl, A(ip))
}

///|
pub fn rr_aaaa(
  name : String,
  w1 : Int,
  w2 : Int,
  w3 : Int,
  w4 : Int,
  ttl? : UInt = 300,
) -> RR {
  rr_new(name, qtype_aaaa, qclass_in, ttl, AAAA(w1, w2, w3, w4))
}

///|
pub fn rr_cname(name : String, target : String, ttl? : UInt = 300) -> RR {
  rr_new(name, qtype_cname, qclass_in, ttl, CNAME(target))
}

///|
pub fn rr_ns(name : String, ns : String, ttl? : UInt = 300) -> RR {
  rr_new(name, qtype_ns, qclass_in, ttl, NS(ns))
}

///|
pub fn rr_mx(
  name : String,
  pref : Int,
  exchange : String,
  ttl? : UInt = 300,
) -> RR {
  rr_new(name, qtype_mx, qclass_in, ttl, MX(pref, exchange))
}

///|
pub fn rr_txt(name : String, strings : Array[String], ttl? : UInt = 300) -> RR {
  rr_new(name, qtype_txt, qclass_in, ttl, TXT(strings))
}

///|
pub fn rr_soa(
  name : String,
  mname : String,
  rname : String,
  serial : UInt,
  refresh : UInt,
  retry : UInt,
  expire : UInt,
  minimum : UInt,
  ttl? : UInt = 86400,
) -> RR {
  rr_new(
    name,
    qtype_soa,
    qclass_in,
    ttl,
    SOA(mname, rname, serial, refresh, retry, expire, minimum),
  )
}

///|
pub fn rr_srv(
  name : String,
  priority : UInt,
  weight : UInt,
  port : UInt,
  target : String,
  ttl? : UInt = 300,
) -> RR {
  rr_new(name, qtype_srv, qclass_in, ttl, SRV(priority, weight, port, target))
}

///|
pub fn rr_ptr(name : String, ptr : String, ttl? : UInt = 300) -> RR {
  rr_new(name, qtype_ptr, qclass_in, ttl, PTR(ptr))
}

///|
pub fn rr_is_type(rr : RR, rtype : UInt16) -> Bool {
  rr.rtype == rtype
}

///|
/// Return whether an RR's unsigned TTL has elapsed after `current_time`
/// seconds. Equality is expired. Negative elapsed values are treated as being
/// before the record was inserted.
pub fn rr_is_expired(rr : RR, current_time : Int) -> Bool {
  current_time >= 0 && rr.ttl.to_int64() <= current_time.to_int64()
}

///|
pub fn rr_short_description(rr : RR) -> String {
  rr.name + " " + qtype_to_string(rr.rtype) + " TTL=" + rr.ttl.to_string()
}

///|
/// Compare two DNS TTLs using their RFC 1035 unsigned 32-bit ordering.
/// The result is -1, 0, or 1 and never depends on subtracting values that may
/// overflow a signed `Int`.
pub fn rr_compare_ttl(a : RR, b : RR) -> Int {
  a.ttl.compare(b.ttl)
}

///|
pub fn rr_filter_by_min_ttl(rrs : Array[RR], min_ttl : UInt) -> Array[RR] {
  let result : Array[RR] = Array::new(capacity=rrs.length())
  for rr in rrs {
    if rr.ttl >= min_ttl {
      result.push(rr)
    }
  }
  result
}

///|
pub fn rr_count_by_type(rrs : Array[RR], rtype : UInt16) -> Int {
  let count = Ref(0)
  for rr in rrs {
    if rr.rtype == rtype {
      count.val = count.val + 1
    }
  }
  count.val
}

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