///|
fn parse_encoding_range_item(
  raw : String,
  order : Int,
) -> Result[EncodingRange, ParseError] {
  let pieces = split_quoted(raw, ';')
  let coding = clean(pieces[0][:]).to_lower()
  if coding.is_empty() {
    return Err(EmptyItem(order))
  }
  if coding != "*" && !is_token(coding) {
    return Err(InvalidToken(order, "content coding", coding))
  }
  let mut q = 1000
  for i in 1..
          if param.name == "q" {
            match parse_q(param.value, order) {
              Ok(parsed) => q = parsed
              Err(err) => return Err(err)
            }
          } else {
            return Err(InvalidParameter(order, piece))
          }
        Err(err) => return Err(err)
      }
    }
  }
  Ok({ coding, q, order, raw: clean(raw[:]) })
}

///|
fn parse_encoding_offer_item(
  raw : String,
  index : Int,
) -> Result[EncodingOffer, ParseError] {
  let coding = clean(raw[:]).to_lower()
  if coding.is_empty() || coding == "*" {
    return Err(InvalidOffer(index, raw))
  }
  if !is_token(coding) {
    return Err(InvalidToken(index, "content coding", coding))
  }
  Ok({ coding, index })
}

///|
pub fn parse_accept_encoding(
  header : StringView,
) -> Result[Array[EncodingRange], ParseError] {
  let text = clean(header)
  let ranges : Array[EncodingRange] = []
  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_encoding(
  value : StringView,
) -> Result[EncodingOffer, ParseError] {
  parse_encoding_offer_item(clean(value), 0)
}

///|
fn default_encoding_range() -> EncodingRange {
  { coding: "*", q: 1000, order: 0, raw: "*" }
}

///|
fn identity_encoding_range(order : Int, q : Int) -> EncodingRange {
  { coding: "identity", q, order, raw: "identity" }
}

///|
fn encoding_specificity(range : EncodingRange) -> Int {
  if range.coding == "*" {
    0
  } else {
    1
  }
}

///|
fn encoding_range_matches(range : EncodingRange, offer : EncodingOffer) -> Bool {
  range.coding == "*" || range.coding == offer.coding
}

///|
fn find_encoding_range(
  ranges : Array[EncodingRange],
  coding : String,
) -> EncodingRange? {
  for range in ranges {
    if range.coding == coding {
      return Some(range)
    }
  }
  None
}

///|
fn encoding_wildcard(ranges : Array[EncodingRange]) -> EncodingRange? {
  find_encoding_range(ranges, "*")
}

///|
fn normalize_encoding_ranges(
  ranges : Array[EncodingRange],
) -> Array[EncodingRange] {
  if ranges.length() == 0 {
    return [default_encoding_range()]
  }
  let normalized : Array[EncodingRange] = []
  for r in ranges {
    normalized.push(r)
  }
  let has_identity = find_encoding_range(normalized, "identity") is Some(_)
  let wildcard = encoding_wildcard(normalized)
  if !has_identity {
    match wildcard {
      Some(range) =>
        if range.q > 0 {
          normalized.push(identity_encoding_range(normalized.length(), 1000))
        }
      None =>
        normalized.push(identity_encoding_range(normalized.length(), 1000))
    }
  }
  normalized
}

///|
fn format_encoding_range(range : EncodingRange) -> String {
  let buf = StringBuilder()
  buf.write_string(range.coding)
  if range.q != 1000 {
    buf.write_string(";q=")
    buf.write_string(quality_to_string(range.q))
  }
  buf.to_string()
}

///|
fn encoding_reason(
  offer : EncodingOffer,
  range : EncodingRange,
  specificity : Int,
) -> String {
  let buf = StringBuilder()
  buf.write_string(offer.coding)
  buf.write_string(" matched ")
  buf.write_string(format_encoding_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.to_string()
}

///|
fn better_encoding(
  candidate : EncodingCandidate,
  current : EncodingCandidate,
) -> Bool {
  if candidate.quality != current.quality {
    candidate.quality > current.quality
  } else if candidate.specificity != current.specificity {
    candidate.specificity > current.specificity
  } 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_encoding_range_for_offer(
  candidate : EncodingRange,
  current : EncodingRange,
) -> Bool {
  let candidate_specificity = encoding_specificity(candidate)
  let current_specificity = encoding_specificity(current)
  if candidate_specificity != current_specificity {
    candidate_specificity > current_specificity
  } else {
    candidate.order < current.order
  }
}

///|
fn effective_encoding_range(
  offer : EncodingOffer,
  ranges : Array[EncodingRange],
) -> EncodingRange? {
  let mut best : EncodingRange? = None
  for range in ranges {
    if encoding_range_matches(range, offer) {
      if (match best {
          None => true
          Some(current) => better_encoding_range_for_offer(range, current)
        }) {
        best = Some(range)
      }
    }
  }
  best
}

///|
fn encoding_explanation(best : EncodingCandidate?) -> String {
  match best {
    Some(candidate) => {
      let buf = StringBuilder()
      buf.write_string("selected ")
      buf.write_string(candidate.offer.coding)
      buf.write_string(": ")
      buf.write_string(candidate.reason)
      buf.to_string()
    }
    None => "no acceptable content encoding matched"
  }
}

///|
pub fn negotiate_encoding(
  accept_encoding : StringView,
  offers : Array[String],
) -> Result[EncodingDecision, ParseError] {
  let ranges = match parse_accept_encoding(accept_encoding) {
    Ok(parsed) => normalize_encoding_ranges(parsed)
    Err(err) => return Err(err)
  }
  let matches : Array[EncodingCandidate] = []
  let mut best : EncodingCandidate? = None
  for offer_index in 0.. offer
      Err(err) => return Err(err)
    }
    match effective_encoding_range(offer, ranges) {
      Some(range) =>
        if range.q > 0 {
          let specificity = encoding_specificity(range)
          let candidate : EncodingCandidate = {
            offer,
            matched_range: range,
            quality: range.q,
            specificity,
            reason: encoding_reason(offer, range, specificity),
          }
          matches.push(candidate)
          if (match best {
              None => true
              Some(current) => better_encoding(candidate, current)
            }) {
            best = Some(candidate)
          }
        }
      None => ()
    }
  }
  let selected = match best {
    Some(candidate) => Some(candidate.offer.coding)
    None => None
  }
  let quality = match best {
    Some(candidate) => candidate.quality
    None => 0
  }
  Ok({
    selected,
    quality,
    candidate: best,
    matches,
    explanation: encoding_explanation(best),
  })
}

///|
pub fn encoding_is_acceptable(
  accept_encoding : StringView,
  coding : StringView,
) -> Result[Bool, ParseError] {
  match negotiate_encoding(accept_encoding, [coding.to_owned()]) {
    Ok(decision) => Ok(decision.selected is Some(_))
    Err(err) => Err(err)
  }
}

///|
pub fn format_encoding_decision(decision : EncodingDecision) -> String {
  let buf = StringBuilder()
  buf.write_string(decision.explanation)
  buf.write_string(" (matches=")
  buf.write_string(decision.matches.length().to_string())
  buf.write_char(')')
  buf.to_string()
}