///|
fn parse_media_parts(
  raw : String,
  item : Int,
  allow_quality : Bool,
) -> Result[(String, String, Array[Param], Int, Array[Param]), ParseError] {
  let pieces = split_quoted(raw, ';')
  if pieces.length() == 0 {
    return Err(EmptyItem(item))
  }
  let main = clean(pieces[0][:])
  if main.is_empty() {
    return Err(EmptyItem(item))
  }
  let (top, sub) = match main.split_once("/") {
    Some((top_view, sub_view)) =>
      (clean(top_view).to_lower(), clean(sub_view).to_lower())
    None => return Err(MissingSlash(item, main))
  }
  if !is_token(top) {
    return Err(InvalidToken(item, "media type", top))
  }
  if !is_token(sub) {
    return Err(InvalidToken(item, "media subtype", sub))
  }
  if top == "*" && sub != "*" {
    return Err(InvalidWildcard(item, main))
  }
  let params : Array[Param] = []
  let extensions : Array[Param] = []
  let mut q = 1000
  let mut after_q = false
  for i in 1..
          if allow_quality && param.name == "q" {
            match parse_q(param.value, item) {
              Ok(parsed) => {
                q = parsed
                after_q = true
              }
              Err(err) => return Err(err)
            }
          } else if allow_quality && after_q {
            extensions.push(param)
          } else {
            params.push(param)
          }
        Err(err) => return Err(err)
      }
    }
  }
  Ok((top, sub, params, q, extensions))
}

///|
fn default_media_range() -> MediaRange {
  {
    top: "*",
    sub: "*",
    params: [],
    extensions: [],
    q: 1000,
    order: 0,
    raw: "*/*",
  }
}

///|
fn parse_media_range_item(
  raw : String,
  order : Int,
) -> Result[MediaRange, ParseError] {
  match parse_media_parts(raw, order, true) {
    Ok((top, sub, params, q, extensions)) =>
      Ok({ top, sub, params, extensions, q, order, raw: clean(raw[:]) })
    Err(err) => Err(err)
  }
}

///|
fn parse_media_offer_item(
  raw : String,
  index : Int,
) -> Result[MediaOffer, ParseError] {
  match parse_media_parts(raw, index, false) {
    Ok((top, sub, params, _, _)) =>
      if top == "*" || sub == "*" {
        Err(InvalidOffer(index, raw))
      } else {
        Ok({ value: clean(raw[:]), top, sub, params, index })
      }
    Err(err) => Err(err)
  }
}

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

///|
pub fn parse_content_type(value : StringView) -> Result[MediaOffer, ParseError] {
  parse_media_offer_item(clean(value), 0)
}

///|
fn media_specificity(range : MediaRange) -> Int {
  if range.top == "*" {
    0
  } else if range.sub == "*" {
    1
  } else {
    2
  }
}

///|
fn media_range_matches(range : MediaRange, offer : MediaOffer) -> Bool {
  if range.top != "*" && range.top != offer.top {
    return false
  }
  if range.sub != "*" && range.sub != offer.sub {
    return false
  }
  params_match(range.params, offer.params)
}

///|
fn format_media_range(range : MediaRange) -> String {
  let buf = StringBuilder()
  buf.write_string(range.top)
  buf.write_char('/')
  buf.write_string(range.sub)
  append_params(buf, range.params)
  if range.q != 1000 || range.extensions.length() > 0 {
    buf.write_string(";q=")
    buf.write_string(quality_to_string(range.q))
    append_params(buf, range.extensions)
  }
  buf.to_string()
}

///|
fn format_media_offer(offer : MediaOffer) -> String {
  let buf = StringBuilder()
  buf.write_string(offer.top)
  buf.write_char('/')
  buf.write_string(offer.sub)
  append_params(buf, offer.params)
  buf.to_string()
}

///|
fn media_reason(
  offer : MediaOffer,
  range : MediaRange,
  specificity : Int,
) -> String {
  let buf = StringBuilder()
  buf.write_string(format_media_offer(offer))
  buf.write_string(" matched ")
  buf.write_string(format_media_range(range))
  buf.write_string(" with q=")
  buf.write_string(quality_to_string(range.q))
  buf.write_string(", specificity=")
  buf.write_string(specificity.to_string())
  buf.write_string(", params=")
  buf.write_string(range.params.length().to_string())
  buf.to_string()
}

///|
fn better_media(candidate : MediaCandidate, current : MediaCandidate) -> Bool {
  if candidate.quality != current.quality {
    candidate.quality > current.quality
  } else if candidate.specificity != current.specificity {
    candidate.specificity > current.specificity
  } else if candidate.parameter_matches != current.parameter_matches {
    candidate.parameter_matches > current.parameter_matches
  } else if candidate.matched_range.order != current.matched_range.order {
    candidate.matched_range.order < current.matched_range.order
  } else {
    candidate.offer.index < current.offer.index
  }
}

///|
fn better_media_range_for_offer(
  candidate : MediaRange,
  current : MediaRange,
) -> Bool {
  let candidate_specificity = media_specificity(candidate)
  let current_specificity = media_specificity(current)
  if candidate_specificity != current_specificity {
    candidate_specificity > current_specificity
  } else if candidate.params.length() != current.params.length() {
    candidate.params.length() > current.params.length()
  } else {
    candidate.order < current.order
  }
}

///|
fn effective_media_range(
  offer : MediaOffer,
  ranges : Array[MediaRange],
) -> MediaRange? {
  let mut best : MediaRange? = None
  for range in ranges {
    if media_range_matches(range, offer) {
      if (match best {
          None => true
          Some(current) => better_media_range_for_offer(range, current)
        }) {
        best = Some(range)
      }
    }
  }
  best
}

///|
fn media_explanation(best : MediaCandidate?) -> String {
  match best {
    Some(candidate) => {
      let buf = StringBuilder()
      buf.write_string("selected ")
      buf.write_string(candidate.offer.value)
      buf.write_string(": ")
      buf.write_string(candidate.reason)
      buf.to_string()
    }
    None => "no acceptable media offer matched"
  }
}

///|
pub fn negotiate_media(
  accept_header : StringView,
  offers : Array[String],
) -> Result[MediaDecision, ParseError] {
  let ranges = match parse_accept(accept_header) {
    Ok(parsed) =>
      if parsed.length() == 0 {
        [default_media_range()]
      } else {
        parsed
      }
    Err(err) => return Err(err)
  }
  let matches : Array[MediaCandidate] = []
  let mut best : MediaCandidate? = None
  for offer_index in 0.. offer
      Err(err) => return Err(err)
    }
    match effective_media_range(offer, ranges) {
      Some(range) =>
        if range.q > 0 {
          let specificity = media_specificity(range)
          let candidate = {
            offer,
            matched_range: range,
            quality: range.q,
            specificity,
            parameter_matches: range.params.length(),
            reason: media_reason(offer, range, specificity),
          }
          matches.push(candidate)
          if (match best {
              None => true
              Some(current) => better_media(candidate, current)
            }) {
            best = Some(candidate)
          }
        }
      None => ()
    }
  }
  let selected = match best {
    Some(candidate) => Some(candidate.offer.value)
    None => None
  }
  let quality = match best {
    Some(candidate) => candidate.quality
    None => 0
  }
  Ok({
    selected,
    quality,
    candidate: best,
    matches,
    explanation: media_explanation(best),
  })
}