///|
// D is part of the original general YMD bookkeeping. The default grammar
// currently labels only Y/M; the independent original-class tests cover D.
#warnings("-unused_constructor")
priv enum ParserDateLabel {
  Y
  M
  D
}

///|
priv struct ParserYmd {
  values : Array[Int32]
  mut century : Bool
  mut year : Int32?
  mut month : Int32?
  mut day : Int32?
}

///|
fn parser_ymd() -> ParserYmd {
  { values: [], century: false, year: None, month: None, day: None, }
}

///|
fn ParserYmd::append_value(
  self : ParserYmd,
  value : Int32,
  label : ParserDateLabel?,
) -> Unit raise DateError {
  self.values.push(value)
  let index = self.values.length() - 1
  match label {
    Some(Y) => {
      if self.year is Some(_) {
        raise DateError::InvalidDate(message="year is already set")
      }
      self.year = Some(index)
    }
    Some(M) => {
      if self.month is Some(_) {
        raise DateError::InvalidDate(message="month is already set")
      }
      self.month = Some(index)
    }
    Some(D) => {
      if self.day is Some(_) {
        raise DateError::InvalidDate(message="day is already set")
      }
      self.day = Some(index)
    }
    None => ()
  }
}

///|
fn ParserYmd::append_text(
  self : ParserYmd,
  text : String,
  label? : ParserDateLabel,
) -> Unit raise DateError {
  let chars = text.to_array()
  let explicit_year = chars.length() > 2 && chars.iter().all(parser_is_digit)
  let label = if explicit_year {
    if label is Some(M) || label is Some(D) {
      raise DateError::InvalidDate(message="invalid explicit year label")
    }
    self.century = true
    Some(Y)
  } else {
    label
  }
  self.append_value(parser_integer(text), label)
}

///|
fn ParserYmd::append_number(
  self : ParserYmd,
  value : Int32,
  label? : ParserDateLabel,
) -> Unit raise DateError {
  let label = if value > 100 {
    if label is Some(M) || label is Some(D) {
      raise DateError::InvalidDate(message="invalid explicit year label")
    }
    self.century = true
    Some(Y)
  } else {
    label
  }
  self.append_value(value, label)
}

///|
fn ParserYmd::could_be_day(self : ParserYmd, value : Int32) -> Bool {
  if self.day is Some(_) {
    return false
  }
  match self.month {
    None => value >= 1 && value <= 31
    Some(month_index) => {
      let year = self.year.map(index => self.values[index]).unwrap_or(2000)
      value >= 1 && value <= days_in_month(year, self.values[month_index])
    }
  }
}

///|
fn ParserYmd::resolve(
  self : ParserYmd,
  yearfirst~ : Bool = false,
  dayfirst~ : Bool = false,
) -> (Int32?, Int32?, Int32?) raise DateError {
  let count = self.values.length()
  let known = (if self.year is Some(_) { 1 } else { 0 }) +
    (if self.month is Some(_) { 1 } else { 0 }) +
    (if self.day is Some(_) { 1 } else { 0 })
  if (count == known && count > 0) || (count == 3 && known == 2) {
    let mut yi = self.year
    let mut mi = self.month
    let mut di = self.day
    if count == 3 && known == 2 {
      for index in 0..<3 {
        if yi != Some(index) && mi != Some(index) && di != Some(index) {
          if yi is None {
            yi = Some(index)
          } else if mi is None {
            mi = Some(index)
          } else {
            di = Some(index)
          }
          break
        }
      }
    }
    return (
      yi.map(index => self.values[index]),
      mi.map(index => self.values[index]),
      di.map(index => self.values[index]),
    )
  }
  if count > 3 {
    raise DateError::InvalidDate(message="more than three date fields")
  }
  let mut year : Int32? = None
  let mut month : Int32? = None
  let mut day : Int32? = None
  if count == 1 || (self.month is Some(_) && count == 2) {
    let other = match self.month {
      Some(index) => {
        month = Some(self.values[index])
        self.values[if index == 0 { count - 1 } else { index - 1 }]
      }
      None => self.values[0]
    }
    if count > 1 || self.month is None {
      if other > 31 {
        year = Some(other)
      } else {
        day = Some(other)
      }
    }
  } else if count == 2 {
    if self.values[0] > 31 {
      year = Some(self.values[0])
      month = Some(self.values[1])
    } else if self.values[1] > 31 {
      month = Some(self.values[0])
      year = Some(self.values[1])
    } else if dayfirst && self.values[1] <= 12 {
      day = Some(self.values[0])
      month = Some(self.values[1])
    } else {
      month = Some(self.values[0])
      day = Some(self.values[1])
    }
  } else if count == 3 {
    let a = self.values[0]
    let b = self.values[1]
    let c = self.values[2]
    if self.month == Some(0) {
      month = Some(a)
      if b > 31 {
        year = Some(b)
        day = Some(c)
      } else {
        day = Some(b)
        year = Some(c)
      }
    } else if self.month == Some(1) {
      month = Some(b)
      if a > 31 || (yearfirst && c <= 31) {
        year = Some(a)
        day = Some(c)
      } else {
        day = Some(a)
        year = Some(c)
      }
    } else if self.month == Some(2) {
      month = Some(c)
      if b > 31 {
        day = Some(a)
        year = Some(b)
      } else {
        year = Some(a)
        day = Some(b)
      }
    } else if a > 31 ||
      self.year == Some(0) ||
      (yearfirst && b <= 12 && c <= 31) {
      year = Some(a)
      if dayfirst && c <= 12 {
        day = Some(b)
        month = Some(c)
      } else {
        month = Some(b)
        day = Some(c)
      }
    } else if a > 12 || (dayfirst && b <= 12) {
      day = Some(a)
      month = Some(b)
      year = Some(c)
    } else {
      month = Some(a)
      day = Some(b)
      year = Some(c)
    }
  }
  (year, month, day)
}