///|
pub(all) struct DocumentTime {
  year : Int
  month : Int
  day : Int
  hour : Int
  minute : Int
  second : Int
} derive(Eq, Debug, ToJson)

///|
fn time_destination(name : String) -> Bool {
  name == "creatim" || name == "revtim" || name == "printim" || name == "buptim"
}

///|
fn month_days(year : Int, month : Int) -> Int {
  match month {
    1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
    4 | 6 | 9 | 11 => 30
    2 =>
      if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
        29
      } else {
        28
      }
    _ => 0
  }
}

///|
pub fn DocumentTime::valid(self : DocumentTime) -> Bool {
  self.year >= 1 &&
  self.year <= 9999 &&
  self.month >= 1 &&
  self.month <= 12 &&
  self.day >= 1 &&
  self.day <= month_days(self.year, self.month) &&
  self.hour >= 0 &&
  self.hour <= 23 &&
  self.minute >= 0 &&
  self.minute <= 59 &&
  self.second >= 0 &&
  self.second <= 59
}

///|
fn decimal_padded(value : Int, width : Int) -> String {
  let value = value.to_string()
  let out = StringBuilder()
  for _ in 0..<(width - value.length()).max(0) {
    out.write_char('0')
  }
  out.write_string(value)
  out.to_string()
}

///|
/// RTF timestamps do not declare a timezone; no UTC suffix is fabricated.
pub fn DocumentTime::to_iso(self : DocumentTime) -> String {
  "\{decimal_padded(self.year, 4)}-\{decimal_padded(self.month, 2)}-\{decimal_padded(self.day, 2)}T\{decimal_padded(self.hour, 2)}:\{decimal_padded(self.minute, 2)}:\{decimal_padded(self.second, 2)}"
}

///|
fn decimal_slice(chars : Array[Char], start : Int, count : Int) -> Int? {
  let mut value = 0
  if start < 0 || start + count > chars.length() {
    return None
  }
  for i in start..<(start + count) {
    let digit = chars[i].to_int() - 48
    if digit < 0 || digit > 9 {
      return None
    }
    value = value * 10 + digit
  }
  Some(value)
}

///|
pub fn DocumentTime::from_iso(text : String) -> DocumentTime? {
  let c = text.to_array()
  if c.length() != 19 ||
    c[4] != '-' ||
    c[7] != '-' ||
    c[10] != 'T' ||
    c[13] != ':' ||
    c[16] != ':' {
    return None
  }
  match
    (
      decimal_slice(c, 0, 4),
      decimal_slice(c, 5, 2),
      decimal_slice(c, 8, 2),
      decimal_slice(c, 11, 2),
      decimal_slice(c, 14, 2),
      decimal_slice(c, 17, 2),
    ) {
    (Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second)) => {
      let time : DocumentTime = { year, month, day, hour, minute, second, }
      if time.valid() {
        Some(time)
      } else {
        None
      }
    }
    _ => None
  }
}

///|
pub fn Document::created_time(self : Document) -> DocumentTime? {
  match self.metadata_value("creatim") {
    Some(value) => DocumentTime::from_iso(value)
    None => None
  }
}

///|
pub fn Document::revised_time(self : Document) -> DocumentTime? {
  match self.metadata_value("revtim") {
    Some(value) => DocumentTime::from_iso(value)
    None => None
  }
}

///|
fn Parser::time_control(
  self : Parser,
  name : String,
  value : Int?,
  span : Span,
) -> Bool {
  if !time_destination(self.state.destination) {
    return false
  }
  let current = self.state.time
  match name {
    "yr" | "mo" | "dy" | "hr" | "min" | "sec" => {
      if self.parameter(name, value, span) is Some(v) {
        self.state.time = match name {
          "yr" => { ..current, year: v, }
          "mo" => { ..current, month: v, }
          "dy" => { ..current, day: v, }
          "hr" => { ..current, hour: v, }
          "min" => { ..current, minute: v, }
          _ => { ..current, second: v, }
        }
      }
      true
    }
    _ => false
  }
}