///|
fn parse_non_negative_int(text : String, item : Int) -> Result[Int, ParseError] {
  if text.is_empty() {
    return Err(InvalidParameter(item, text))
  }
  let mut value = 0
  for c in text.iter() {
    match digit_value(c) {
      Some(d) => value = value * 10 + d
      None => return Err(InvalidParameter(item, text))
    }
  }
  Ok(value)
}

///|
fn parse_cache_directive_item(
  raw : String,
  order : Int,
) -> Result[CacheDirective, ParseError] {
  let piece = clean(raw[:])
  if piece.is_empty() {
    return Err(EmptyItem(order))
  }
  match piece.split_once("=") {
    Some((name_view, value_view)) => {
      let name = clean(name_view).to_lower()
      let value = unquote(clean(value_view))
      if !is_token(name) {
        return Err(InvalidToken(order, "cache directive", name))
      }
      Ok({ name, value: Some(value), order, raw: piece })
    }
    None => {
      let name = piece.to_lower()
      if !is_token(name) {
        return Err(InvalidToken(order, "cache directive", name))
      }
      Ok({ name, value: None, order, raw: piece })
    }
  }
}

///|
pub fn parse_cache_control(
  header : StringView,
) -> Result[Array[CacheDirective], ParseError] {
  let text = clean(header)
  let directives : Array[CacheDirective] = []
  if text.is_empty() {
    return Ok(directives)
  }
  let parts = split_quoted(text, ',')
  for i in 0.. directives.push(directive)
      Err(err) => return Err(err)
    }
  }
  Ok(directives)
}

///|
pub fn cache_control_has(
  directives : Array[CacheDirective],
  name : StringView,
) -> Bool {
  let wanted = clean(name).to_lower()
  for directive in directives {
    if directive.name == wanted {
      return true
    }
  }
  false
}

///|
pub fn cache_control_value(
  directives : Array[CacheDirective],
  name : StringView,
) -> String? {
  let wanted = clean(name).to_lower()
  for directive in directives {
    if directive.name == wanted {
      return directive.value
    }
  }
  None
}

///|
fn numeric_directive(
  directives : Array[CacheDirective],
  name : String,
  item : Int,
) -> Result[Int?, ParseError] {
  match cache_control_value(directives, name[:]) {
    Some(value) =>
      match parse_non_negative_int(value, item) {
        Ok(n) => Ok(Some(n))
        Err(err) => Err(err)
      }
    None => Ok(None)
  }
}

///|
fn is_known_cache_directive(name : String) -> Bool {
  name == "max-age" ||
  name == "s-maxage" ||
  name == "no-store" ||
  name == "no-cache" ||
  name == "private" ||
  name == "public" ||
  name == "must-revalidate" ||
  name == "proxy-revalidate" ||
  name == "immutable" ||
  name == "stale-while-revalidate" ||
  name == "stale-if-error" ||
  name == "must-understand" ||
  name == "no-transform" ||
  name == "only-if-cached"
}

///|
pub fn summarize_cache_control(
  directives : Array[CacheDirective],
) -> Result[CachePolicySummary, ParseError] {
  let unknown : Array[CacheDirective] = []
  for directive in directives {
    if !is_known_cache_directive(directive.name) {
      unknown.push(directive)
    }
  }
  let max_age = match numeric_directive(directives, "max-age", 0) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let s_maxage = match numeric_directive(directives, "s-maxage", 0) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let stale_while_revalidate = match
    numeric_directive(directives, "stale-while-revalidate", 0) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let stale_if_error = match
    numeric_directive(directives, "stale-if-error", 0) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  Ok({
    no_store: cache_control_has(directives, "no-store"),
    no_cache: cache_control_has(directives, "no-cache"),
    is_public: cache_control_has(directives, "public"),
    is_private: cache_control_has(directives, "private"),
    must_revalidate: cache_control_has(directives, "must-revalidate") ||
    cache_control_has(directives, "proxy-revalidate"),
    immutable: cache_control_has(directives, "immutable"),
    max_age,
    s_maxage,
    stale_while_revalidate,
    stale_if_error,
    unknown,
  })
}

///|
pub fn parse_cache_policy(
  header : StringView,
) -> Result[CachePolicySummary, ParseError] {
  match parse_cache_control(header) {
    Ok(directives) => summarize_cache_control(directives)
    Err(err) => Err(err)
  }
}

///|
pub fn cache_is_storable(summary : CachePolicySummary) -> Bool {
  !summary.no_store
}

///|
pub fn cache_requires_revalidation(summary : CachePolicySummary) -> Bool {
  summary.no_cache ||
  summary.must_revalidate ||
  summary.max_age == Some(0) ||
  summary.s_maxage == Some(0)
}

///|
pub fn cache_shared_fresh_lifetime(summary : CachePolicySummary) -> Int? {
  match summary.s_maxage {
    Some(value) => Some(value)
    None => summary.max_age
  }
}

///|
pub fn cache_private_fresh_lifetime(summary : CachePolicySummary) -> Int? {
  summary.max_age
}

///|
fn write_cache_directive(
  buf : StringBuilder,
  directive : CacheDirective,
) -> Unit {
  buf.write_string(directive.name)
  match directive.value {
    Some(value) => {
      buf.write_char('=')
      if value.contains_any(chars=",;\" ") {
        buf.write_char('"')
        for c in value.iter() {
          if c == '"' || c == '\\' {
            buf.write_char('\\')
          }
          buf.write_char(c)
        }
        buf.write_char('"')
      } else {
        buf.write_string(value)
      }
    }
    None => ()
  }
}

///|
pub fn format_cache_control(directives : Array[CacheDirective]) -> String {
  let buf = StringBuilder()
  for i in 0.. 0 {
      buf.write_string(", ")
    }
    write_cache_directive(buf, directives[i])
  }
  buf.to_string()
}

///|
pub fn format_cache_summary(summary : CachePolicySummary) -> String {
  let buf = StringBuilder()
  buf.write_string("storable=")
  buf.write_string(cache_is_storable(summary).to_string())
  buf.write_string(", revalidate=")
  buf.write_string(cache_requires_revalidation(summary).to_string())
  match cache_shared_fresh_lifetime(summary) {
    Some(value) => {
      buf.write_string(", shared-max-age=")
      buf.write_string(value.to_string())
    }
    None => ()
  }
  match summary.stale_while_revalidate {
    Some(value) => {
      buf.write_string(", stale-while-revalidate=")
      buf.write_string(value.to_string())
    }
    None => ()
  }
  match summary.stale_if_error {
    Some(value) => {
      buf.write_string(", stale-if-error=")
      buf.write_string(value.to_string())
    }
    None => ()
  }
  if summary.unknown.length() > 0 {
    buf.write_string(", unknown=")
    buf.write_string(summary.unknown.length().to_string())
  }
  buf.to_string()
}