// How far this server's clock is off: one NTP server asked (its stratum, reference and the
// round trip), several asked at once and the best chosen (a Kiss-o'-Death and an
// unsynchronized server set aside), NTP timestamps read, and a small SNTP server of this
// program's own on a UDP port, for machines on a LAN without internet.
//
//   quillo run ntp.qwe      then open http://localhost:8080/clock (pool.ntp.org)
//   NTP_SERVER=time.example.com quillo run ntp.qwe      to ask another

use "ntp.qwer"

let SERVER = env("NTP_SERVER", "pool.ntp.org")

route GET /clock {
    let t = ntp_query(SERVER, { timeout_ms: 2000 })?
    {
        server: t.server, stratum: t.stratum, reference: t.reference_id, synchronized: t.synchronized,
        off_by_seconds: round(t.offset_ms / 1000), fast_answer: t.delay_ms < 200, server_time: t.server_time,
        warning: abs(t.offset_ms) > 1000 ? "this clock is more than a second off: check its NTP client" : null,
    }
}

route GET /best {
    let servers = [SERVER, env("NTP_SERVER_SLOW", "time.cloudflare.com"), env("NTP_SERVER_KOD", "time.google.com"), env("NTP_SERVER_UNSYNCED", "time.apple.com")]
    let b = ntp_best(servers, { timeout_ms: 2000 })?
    {
        best: b.best, off_by_seconds: round(b.offset_ms / 1000), spread_seconds: round(b.spread_ms / 1000),
        set_aside: b.servers | filter((s) => s.error != null || s.rejected != null) | map((s) => s.error ?? `${s.server}: ${s.rejected}`),
    }
}

route GET /timestamps = {
    read: ntp_from_timestamp("ee8e5c80.80000000", 0)?,
    after_2036: ntp_from_timestamp("00000064.00000000", 2100000000000)?.iso,
    made: ntp_timestamp(1793318400000),
}

// A time server for the LAN: answers each client from this machine's clock.
udp.listen(int(env("NTP_LISTEN_PORT", "1123")), (p) => {
    let r = ntp_reply(p.bytes, { stratum: 3, reference: "10.0.0.1" })
    if r { udp.reply(p, r) }
})

route GET /served {
    let t = ntp_query("127.0.0.1", { port: int(env("NTP_LISTEN_PORT", "1123")) })?
    { stratum: t.stratum, reference: t.reference_id, within_a_second: abs(t.offset_ms) < 1000 }
}
