///|
fn parse_rule_int(text : String, field : String) -> Int raise RRuleParseError {
  @string.parse_int(text, base=10) catch {
    _ => raise InvalidInteger(field, text)
  }
}

///|
fn parse_int_list(
  text : String,
  field : String,
) -> Array[Int] raise RRuleParseError {
  if text.length() == 0 {
    raise InvalidValue(field, "list cannot be empty")
  }
  let result : Array[Int] = []
  for item in text.split(",") {
    if item.length() == 0 {
      raise InvalidValue(field, "list contains an empty item")
    }
    result.push(parse_rule_int(item.to_owned(), field))
  }
  result
}

///|
fn parse_byday_item(text : String) -> ByDay raise RRuleParseError {
  if text.length() < 2 {
    raise InvalidWeekday(text)
  }
  let weekday_text = text[text.length() - 2:].to_owned()
  let weekday = match weekday_from_code(weekday_text) {
    Some(value) => value
    None => raise InvalidWeekday(text)
  }
  if text.length() == 2 {
    return ByDay::new(weekday)
  }
  let ordinal_text = text[:text.length() - 2].to_owned()
  ByDay::new(weekday, ordinal=parse_rule_int(ordinal_text, "BYDAY"))
}

///|
fn parse_byday_list(text : String) -> Array[ByDay] raise RRuleParseError {
  if text.length() == 0 {
    raise InvalidValue("BYDAY", "list cannot be empty")
  }
  let result : Array[ByDay] = []
  for item in text.split(",") {
    result.push(parse_byday_item(item.to_owned()))
  }
  result
}

///|
fn parse_until_value(text : String) -> DateTime raise RRuleParseError {
  let date_text = if text.has_suffix("Z") {
    text[:text.length() - 1].to_owned()
  } else {
    text
  }
  parse_datetime(date_text) catch {
    _ => raise InvalidUntil(text)
  }
}

///|
fn has_name(names : Array[String], name : String) -> Bool {
  for candidate in names {
    if candidate == name {
      return true
    }
  }
  false
}

///|
/// Parse an RFC 5545 recurrence rule. The optional RRULE: prefix is accepted.
pub fn parse_rrule(source : String) -> RRule raise RRuleParseError {
  let trimmed = source.trim().to_owned()
  if trimmed.length() == 0 {
    raise EmptyRule
  }
  let body = if trimmed.to_upper().has_prefix("RRULE:") {
    trimmed[6:].to_owned()
  } else {
    trimmed
  }
  let names : Array[String] = []
  let mut frequency : Frequency? = None
  let mut interval = 1
  let mut count : Int? = None
  let mut until : DateTime? = None
  let mut week_start = Monday
  let mut by_second : Array[Int] = []
  let mut by_minute : Array[Int] = []
  let mut by_hour : Array[Int] = []
  let mut by_day : Array[ByDay] = []
  let mut by_month_day : Array[Int] = []
  let mut by_year_day : Array[Int] = []
  let mut by_week_number : Array[Int] = []
  let mut by_month : Array[Int] = []
  let mut by_set_position : Array[Int] = []
  for raw_part in body.split(";") {
    let part = raw_part.to_owned()
    let pair : Array[String] = part
      .split("=")
      .map(value => value.to_owned())
      .collect()
    if pair.length() != 2 || pair[0].length() == 0 || pair[1].length() == 0 {
      raise InvalidPart(part)
    }
    let name = pair[0].to_upper()
    let value = pair[1].to_upper()
    if has_name(names, name) {
      raise DuplicateField(name)
    }
    names.push(name)
    match name {
      "FREQ" =>
        frequency = match frequency_from_code(value) {
          Some(item) => Some(item)
          None => raise UnknownFrequency(value)
        }
      "INTERVAL" => interval = parse_rule_int(value, name)
      "COUNT" => count = Some(parse_rule_int(value, name))
      "UNTIL" => until = Some(parse_until_value(value))
      "WKST" =>
        week_start = match weekday_from_code(value) {
          Some(item) => item
          None => raise InvalidWeekday(value)
        }
      "BYSECOND" => by_second = parse_int_list(value, name)
      "BYMINUTE" => by_minute = parse_int_list(value, name)
      "BYHOUR" => by_hour = parse_int_list(value, name)
      "BYDAY" => by_day = parse_byday_list(value)
      "BYMONTHDAY" => by_month_day = parse_int_list(value, name)
      "BYYEARDAY" => by_year_day = parse_int_list(value, name)
      "BYWEEKNO" => by_week_number = parse_int_list(value, name)
      "BYMONTH" => by_month = parse_int_list(value, name)
      "BYSETPOS" => by_set_position = parse_int_list(value, name)
      _ => raise InvalidPart(part)
    }
  }
  let required_frequency = match frequency {
    Some(value) => value
    None => raise MissingFrequency
  }
  {
    frequency: required_frequency,
    interval,
    count,
    until,
    week_start,
    by_second,
    by_minute,
    by_hour,
    by_day,
    by_month_day,
    by_year_day,
    by_week_number,
    by_month,
    by_set_position,
  }
}

///|
fn frequency_en(frequency : Frequency) -> String {
  match frequency {
    Secondly => "second"
    Minutely => "minute"
    Hourly => "hour"
    Daily => "day"
    Weekly => "week"
    Monthly => "month"
    Yearly => "year"
  }
}

///|
fn frequency_zh(frequency : Frequency) -> String {
  match frequency {
    Secondly => "秒"
    Minutely => "分钟"
    Hourly => "小时"
    Daily => "天"
    Weekly => "周"
    Monthly => "月"
    Yearly => "年"
  }
}

///|
fn weekday_en(day : Weekday) -> String {
  match day {
    Monday => "Monday"
    Tuesday => "Tuesday"
    Wednesday => "Wednesday"
    Thursday => "Thursday"
    Friday => "Friday"
    Saturday => "Saturday"
    Sunday => "Sunday"
  }
}

///|
fn weekday_zh(day : Weekday) -> String {
  match day {
    Monday => "周一"
    Tuesday => "周二"
    Wednesday => "周三"
    Thursday => "周四"
    Friday => "周五"
    Saturday => "周六"
    Sunday => "周日"
  }
}

///|
pub fn RRule::explain_en(self : RRule) -> String {
  let unit = frequency_en(self.frequency)
  let mut text = if self.interval == 1 {
    "Every " + unit
  } else {
    "Every " + self.interval.to_string() + " " + unit + "s"
  }
  if self.by_day.length() > 0 {
    let days = self.by_day
      .map(item => {
        match item.ordinal {
          Some(value) => value.to_string() + " " + weekday_en(item.weekday)
          None => weekday_en(item.weekday)
        }
      })
      .join(", ")
    text = text + " on " + days
  }
  if self.by_month_day.length() > 0 {
    text = text + " on month day " + join_ints(self.by_month_day)
  }
  if self.by_month.length() > 0 {
    text = text + " in month " + join_ints(self.by_month)
  }
  match self.count {
    Some(value) => text = text + ", for " + value.to_string() + " occurrences"
    None => ()
  }
  match self.until {
    Some(value) => text = text + ", until " + value.to_iso_string()
    None => ()
  }
  text
}

///|
pub fn RRule::explain_zh(self : RRule) -> String {
  let unit = frequency_zh(self.frequency)
  let mut text = if self.interval == 1 {
    "每" + unit
  } else {
    "每 " + self.interval.to_string() + " " + unit
  }
  if self.by_day.length() > 0 {
    let days = self.by_day
      .map(item => {
        match item.ordinal {
          Some(value) =>
            "第 " + value.to_string() + " 个" + weekday_zh(item.weekday)
          None => weekday_zh(item.weekday)
        }
      })
      .join("、")
    text = text + "的" + days
  }
  if self.by_month_day.length() > 0 {
    text = text + ",日期为 " + join_ints(self.by_month_day)
  }
  if self.by_month.length() > 0 {
    text = text + ",月份为 " + join_ints(self.by_month)
  }
  match self.count {
    Some(value) => text = text + ",共 " + value.to_string() + " 次"
    None => ()
  }
  match self.until {
    Some(value) => text = text + ",截止 " + value.to_iso_string()
    None => ()
  }
  text
}