///|
pub(all) struct ResponseInfo {
  sample : Sample
  minimum_error_seconds : Double
  server_transmit_unix : Double
  reference_unix : Double
  precision_seconds : Double
  poll_seconds : Double
  root_distance_seconds : Double
  reference_string : String
} derive(Debug)

///|
fn finite(n : Double) -> Bool {
  !n.is_nan() && !n.is_inf()
}

///|
/// Resolve within half an NTP era of a trusted local pivot (pivot in eras 0/1).
pub fn Timestamp::near_unix(
  self : Timestamp,
  pivot : Double,
) -> Double raise NtpError {
  let (stamp, _) = from_unix(pivot)
  pivot + self.difference(stamp)
}

///|
pub fn Packet::reference_string(self : Packet) -> String {
  if self.stratum == 0 {
    return self.kiss_code().unwrap_or("")
  }
  let bytes = [24, 16, 8, 0].map(shift => {
    ((self.reference_id >> shift) & 255U).reinterpret_as_int()
  })
  if self.stratum == 1 {
    let text = StringBuilder()
    text.write_string(".")
    for byte in bytes {
      if byte == 0 {
        break
      }
      text.write_char(
        if byte >= 32 && byte <= 126 {
          byte.unsafe_to_char()
        } else {
          '⋅'
        },
      )
    }
    text.write_string(".")
    text.to_string()
  } else {
    bytes.map(n => n.to_string()).join(".")
  }
}

///|
/// Rich SNTP analysis. A small negative measured network delay is clamped to
/// zero, as in beevik/ntp; strict measure() retains its existing rejection.
pub fn analyze(
  sent : Timestamp,
  reply : Packet,
  arrived : Timestamp,
  pivot_unix : Double,
) -> ResponseInfo raise NtpError {
  if reply.version < 2 || reply.version > 4 || reply.mode != 4 {
    raise Invalid("not an NTPv2/v3/v4 server response")
  }
  if reply.origin != sent {
    raise Invalid("origin mismatch")
  }
  if reply.receive == Timestamp::zero() || reply.transmit == Timestamp::zero() {
    raise Invalid("missing server timestamp")
  }
  let elapsed = arrived.difference(sent)
  let processing = reply.transmit.difference(reply.receive)
  if elapsed < 0.0 ||
    elapsed > 86400.0 ||
    processing < 0.0 ||
    processing > 86400.0 {
    raise Invalid("invalid time ordering or excessive interval")
  }
  let a = reply.receive.difference(sent)
  let b = reply.transmit.difference(arrived)
  let sample : Sample = {
    offset_seconds: a / 2.0 + b / 2.0,
    delay_seconds: (elapsed - processing).max(0.0),
  }
  {
    sample,
    minimum_error_seconds: (-a).max(b).max(0.0),
    server_transmit_unix: reply.transmit.near_unix(pivot_unix),
    reference_unix: reply.reference.near_unix(pivot_unix),
    precision_seconds: @math.pow(2.0, reply.precision.to_double()),
    poll_seconds: @math.pow(2.0, reply.poll.to_double()),
    root_distance_seconds: reply.root_distance(sample),
    reference_string: reply.reference_string(),
  }
}