///|
pub(all) enum DenialKind {
  NoData
  NxDomain
  InsecureDelegation
} derive(Eq, Debug)

///|
pub extend DenialKind with Eq::{equal, not_equal}

///|
pub extend DenialKind with @debug.Debug::{to_repr}

///|
pub struct Denial {
  priv kind : DenialKind
  priv lifetime : UInt32
} derive(Debug)

///|
pub extend Denial with @debug.Debug::{to_repr}

///|
pub fn Denial::kind(self : Denial) -> DenialKind {
  self.kind
}

///|
pub fn Denial::lifetime(self : Denial) -> UInt32 {
  self.lifetime
}

///|
pub fn Denial::verdict(self : Denial) -> Verdict {
  if self.kind == InsecureDelegation {
    Insecure
  } else {
    Secure
  }
}

///|
pub struct Nsec {
  priv owner : Name
  priv next : Name
  priv bitmap : TypeBitmap
} derive(Debug)

///|
pub extend Nsec with @debug.Debug::{to_repr}

///|
/// Parsing alone does not authenticate this record.
pub fn Nsec::parse(record : Record) -> Result[Nsec, DnsError] {
  if record.rrtype != 47 {
    return Err(Malformed("expected NSEC"))
  }
  let (next, p) = match read_name(record.data, 0) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  let bitmap = match TypeBitmap::parse(record.data[p:].to_owned()) {
    Ok(b) => b
    Err(e) => return Err(e)
  }
  if !bitmap.contains(47) || !bitmap.contains(46) {
    return Err(Malformed("NSEC bitmap must include NSEC and RRSIG"))
  }
  Ok({ owner: record.owner, next, bitmap, })
}

///|
pub fn Nsec::covers(self : Nsec, name : Name) -> Bool {
  let a = self.owner.canonical_compare(name)
  let b = name.canonical_compare(self.next)
  let order = self.owner.canonical_compare(self.next)
  if order < 0 {
    a < 0 && b < 0
  } else {
    a < 0 || b < 0
  }
}

///|
fn valid_query(
  zone : Name,
  name : Name,
  rrtype : Int32,
) -> Result[Unit, DnsError] {
  if !name.is_subdomain(zone) {
    return Err(Bogus("query outside proof zone"))
  }
  if rrtype < 1 ||
    rrtype > 65535 ||
    rrtype == 41 ||
    (rrtype >= 249 && rrtype <= 255) {
    return Err(Unsupported("meta query type cannot use this denial API"))
  }
  Ok(())
}

///|
fn absence_kind(
  bitmap : TypeBitmap,
  rrtype : Int32,
  at_apex : Bool,
) -> Result[DenialKind, DnsError] {
  if bitmap.contains(rrtype) || bitmap.contains(5) {
    return Err(Bogus("requested type or CNAME exists"))
  }
  let delegation = bitmap.contains(2) && !bitmap.contains(6)
  if rrtype == 43 {
    if at_apex {
      return Err(Indeterminate("DS absence must come from parent zone"))
    }
    if delegation {
      return Ok(InsecureDelegation)
    }
  }
  if delegation {
    return Err(
      Indeterminate("parent proof is not authoritative below delegation"),
    )
  }
  Ok(NoData)
}

///|
pub fn nsec_nodata(
  proof : AuthenticatedSet,
  zone : Name,
  name : Name,
  rrtype : Int32,
  now : UInt32,
) -> Result[Denial, DnsError] {
  match valid_query(zone, name, rrtype) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }
  let record = match ensure_proof(proof, zone, 47, now) {
    Ok(r) => r
    Err(e) => return Err(e)
  }
  let nsec = match Nsec::parse(record) {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  if nsec.owner != name {
    return Err(Indeterminate("exact-name NSEC needed for NODATA"))
  }
  if !nsec.next.is_subdomain(zone) {
    return Err(Bogus("NSEC successor outside zone"))
  }
  let kind = match absence_kind(nsec.bitmap, rrtype, name == zone) {
    Ok(k) => k
    Err(e) => return Err(e)
  }
  let lifetime = match proof.remaining(now) {
    Ok(n) => n
    Err(e) => return Err(e)
  }
  Ok({ kind, lifetime, })
}