// The `Date` response header (RFC 7231 §7.1.1.1). uvicorn stamps one on every response and
// refreshes it once a second rather than per request, because formatting a date is dearer than
// comparing two integers and a response is only ever dated to the second anyway.

///|
/// Floor division, so a pre-epoch instant lands on the day that contains it rather than the one
/// after. Truncating division would round negatives towards zero.
fn floor_div(a : Int64, b : Int64) -> Int64 {
  if a >= 0L {
    a / b
  } else {
    (a - b + 1L) / b
  }
}

///|
/// Format a Unix-epoch millisecond count as an HTTP-date in the IMF-fixdate form RFC 7231 §7.1.1.1
/// makes mandatory for a `Date` header: `Sun, 06 Nov 1994 08:49:37 GMT`, always GMT, always
/// fixed-width.
///
/// The calendar and the format are moondate's; what is here is the millisecond clock
/// mooncat reads, brought down to the whole second the header is written to.
pub fn http_date(ms : Int64) -> String {
  @moondate.Moment::of_epoch(floor_div(ms, 1000L)).http_text() catch {
    // Only a bare time has no HTTP date, and an epoch instant is never one.
    _ => abort("an epoch instant always has an HTTP date")
  }
}

///|
/// The formatted date and the epoch second it was formatted for, so a second's worth of responses
/// share one formatting pass (← uvicorn, which refreshes its `Date` on a one-second tick).
let date_cache : Ref[(Int64, String)] = Ref((-1L, ""))

///|
/// The current instant as an HTTP-date, reformatted only when the epoch second has moved on.
pub fn http_date_now() -> String {
  let ms = @async.now()
  let sec = floor_div(ms, 1000L)
  let (at, text) = date_cache.val
  if at == sec {
    return text
  }
  let fresh = http_date(ms)
  date_cache.val = (sec, fresh)
  fresh
}

///|
/// The IMF-fixdate examples RFC 7231 §7.1.1.1 itself gives, plus the epoch, a leap day, and a
/// pre-epoch instant that only floor division dates correctly.
test "http_date renders the RFC 7231 §7.1.1.1 fixdate form" {
  // The spec's own example: Sun, 06 Nov 1994 08:49:37 GMT.
  assert_eq(http_date(784111777000L), "Sun, 06 Nov 1994 08:49:37 GMT")
  // The epoch itself was a Thursday.
  assert_eq(http_date(0L), "Thu, 01 Jan 1970 00:00:00 GMT")
  // A leap day, which the 400-year era arithmetic has to place.
  assert_eq(http_date(951782400000L), "Tue, 29 Feb 2000 00:00:00 GMT")
  // Sub-second remainders are dropped, not rounded.
  assert_eq(http_date(999L), "Thu, 01 Jan 1970 00:00:00 GMT")
  // One millisecond before the epoch belongs to the previous day, not the epoch day.
  assert_eq(http_date(-1L), "Wed, 31 Dec 1969 23:59:59 GMT")
}

///|
/// Every field is fixed-width, so a parser can index into the string: `Sun, 06 Nov 1994 08:49:37
/// GMT` is 29 characters and nothing shorter is legal.
test "http_date pads every field to the fixed width" {
  let d = http_date(1L)
  assert_eq(d.length(), 29)
  assert_eq(d, "Thu, 01 Jan 1970 00:00:00 GMT")
  // A single-digit hour, minute and second all keep their leading zero.
  assert_eq(http_date(3723000L), "Thu, 01 Jan 1970 01:02:03 GMT")
}

///|
/// The cache hands back the same second's rendering and it is a well-formed date, whatever the
/// clock happens to say when the suite runs.
test "http_date_now caches within the second and stays well-formed" {
  let a = http_date_now()
  let b = http_date_now()
  assert_eq(a, b)
  assert_eq(a.length(), 29)
  assert_eq(a[25:].to_owned(), " GMT")
}