// DNS transport that retries UDP over TCP after a TC=1 response.

///|
pub struct FallbackTransport {
  udp : UdpTransport
  tcp : TcpTransport
}

///|
pub fn FallbackTransport::new(server : String) -> FallbackTransport {
  FallbackTransport::with_options(server, default_timeout_ms, default_retries)
}

///|
pub fn FallbackTransport::with_options(
  server : String,
  timeout_ms : Int,
  retries : Int,
  udp_payload_size? : UInt16 = 1232,
) -> FallbackTransport {
  {
    udp: UdpTransport::with_options(
      server,
      timeout_ms,
      retries,
      udp_payload_size~,
    ),
    tcp: TcpTransport::with_options(server, timeout_ms, retries),
  }
}

///|
pub async fn FallbackTransport::send(
  self : FallbackTransport,
  payload : Array[Byte],
) -> Result[Array[Byte], TransportError] {
  match self.udp.send(payload) {
    Err(Truncated) =>
      match self.tcp.send(payload) {
        Ok(response) => Ok(response)
        Err(_) => Err(Truncated)
      }
    result => result
  }
}

///|
pub fn FallbackTransport::max_payload(self : FallbackTransport) -> UInt16 {
  self.udp.max_payload()
}

///|
pub fn FallbackTransport::as_transport(
  self : FallbackTransport,
) -> DnsTransport {
  DnsTransport::from_request(
    payload => self.send(payload),
    udp_payload_size=self.max_payload(),
  )
}

///|
pub struct FallbackStats {
  udp_successes : Ref[Int]
  udp_truncated : Ref[Int]
  tcp_successes : Ref[Int]
  tcp_failures : Ref[Int]
}

///|
pub fn FallbackStats::new() -> FallbackStats {
  {
    udp_successes: Ref(0),
    udp_truncated: Ref(0),
    tcp_successes: Ref(0),
    tcp_failures: Ref(0),
  }
}

///|
pub fn FallbackStats::record_udp_success(self : FallbackStats) -> Unit {
  self.udp_successes.val += 1
}

///|
pub fn FallbackStats::record_truncation(self : FallbackStats) -> Unit {
  self.udp_truncated.val += 1
}

///|
pub fn FallbackStats::record_tcp_success(self : FallbackStats) -> Unit {
  self.tcp_successes.val += 1
}

///|
pub fn FallbackStats::record_tcp_failure(self : FallbackStats) -> Unit {
  self.tcp_failures.val += 1
}

///|
pub fn FallbackStats::truncation_rate(self : FallbackStats) -> Int {
  let total = self.udp_successes.val + self.udp_truncated.val
  if total == 0 {
    0
  } else {
    self.udp_truncated.val * 100 / total
  }
}