///|
fn optional_offer_values(values : Array[String]) -> Array[String?] {
  let result : Array[String?] = []
  if values.length() == 0 {
    result.push(None)
  } else {
    for value in values {
      result.push(Some(value))
    }
  }
  result
}

///|
fn generated_variant_id(index : Int) -> String {
  let buf = StringBuilder()
  buf.write_string("variant-")
  buf.write_string(index.to_string())
  buf.to_string()
}

///|
fn build_response_variant(
  index : Int,
  media : String,
  language : String?,
  encoding : String?,
  charset : String?,
) -> ServerVariant {
  {
    id: generated_variant_id(index),
    media,
    language,
    encoding,
    charset,
    server_weight: 1000,
    index,
  }
}

///|
pub fn build_response_variants(
  media_offers : Array[String],
  language_offers : Array[String],
  encoding_offers : Array[String],
  charset_offers : Array[String],
) -> Array[ServerVariant] {
  let variants : Array[ServerVariant] = []
  let languages = optional_offer_values(language_offers)
  let encodings = optional_offer_values(encoding_offers)
  let charsets = optional_offer_values(charset_offers)
  let mut index = 0
  for media in media_offers {
    for language in languages {
      for encoding in encodings {
        for charset in charsets {
          variants.push(
            build_response_variant(index, media, language, encoding, charset),
          )
          index = index + 1
        }
      }
    }
  }
  variants
}

///|
fn has_charset_param(content_type : String) -> Bool {
  match parse_content_type(content_type[:]) {
    Ok(offer) => {
      for param in offer.params {
        if param.name == "charset" {
          return true
        }
      }
      false
    }
    Err(_) => content_type.to_lower().contains("charset=")
  }
}

///|
fn content_type_header_value(
  content_type : String,
  charset : String?,
) -> String {
  match charset {
    Some(value) =>
      if has_charset_param(content_type) {
        content_type
      } else {
        let buf = StringBuilder()
        buf.write_string(content_type)
        buf.write_string("; charset=")
        buf.write_string(value)
        buf.to_string()
      }
    None => content_type
  }
}

///|
fn push_header(headers : Array[Param], name : String, value : String) -> Unit {
  if !value.is_empty() {
    headers.push({ name, value })
  }
}

///|
pub fn response_headers(summary : ResponseSummary) -> Array[Param] {
  let headers : Array[Param] = []
  match summary.content_type {
    Some(value) =>
      push_header(
        headers,
        "Content-Type",
        content_type_header_value(value, summary.charset),
      )
    None => ()
  }
  match summary.content_language {
    Some(value) => push_header(headers, "Content-Language", value)
    None => ()
  }
  match summary.content_encoding {
    Some(value) =>
      if value != "identity" {
        push_header(headers, "Content-Encoding", value)
      }
    None => ()
  }
  if summary.vary.length() > 0 {
    push_header(headers, "Vary", join_header_names(summary.vary))
  }
  headers
}

///|
pub fn response_header_value(
  summary : ResponseSummary,
  name : StringView,
) -> String? {
  let wanted = match normalize_header_name(name) {
    Ok(value) => value
    Err(_) => return None
  }
  for header in response_headers(summary) {
    if normalize_header_name(header.name[:]) == Ok(wanted) {
      return Some(header.value)
    }
  }
  None
}

///|
pub fn format_response_headers(headers : Array[Param]) -> String {
  let buf = StringBuilder()
  for i in 0.. 0 {
      buf.write_char('\n')
    }
    buf.write_string(headers[i].name)
    buf.write_string(": ")
    buf.write_string(headers[i].value)
  }
  buf.to_string()
}

///|
pub fn response_header_block(summary : ResponseSummary) -> String {
  format_response_headers(response_headers(summary))
}

///|
pub fn negotiate_response_summary(
  preferences : RequestPreferences,
  media_offers : Array[String],
  language_offers : Array[String],
  encoding_offers : Array[String],
  charset_offers : Array[String],
) -> Result[ResponseSummary, ParseError] {
  let variants = build_response_variants(
    media_offers, language_offers, encoding_offers, charset_offers,
  )
  match negotiate_variant(preferences, variants) {
    Ok(decision) => Ok(summarize_response(decision))
    Err(err) => Err(err)
  }
}

///|
pub fn negotiate_response(
  preferences : RequestPreferences,
  media_offers : Array[String],
  language_offers : Array[String],
  encoding_offers : Array[String],
  charset_offers : Array[String],
) -> Result[ResponseNegotiation, ParseError] {
  let variants = build_response_variants(
    media_offers, language_offers, encoding_offers, charset_offers,
  )
  match negotiate_variant(preferences, variants) {
    Ok(decision) => {
      let summary = summarize_response(decision)
      let headers = response_headers(summary)
      Ok({
        decision,
        summary,
        headers,
        header_block: format_response_headers(headers),
      })
    }
    Err(err) => Err(err)
  }
}