///|
/// Content-Range value for satisfied or unsatisfied responses.
pub(all) enum ContentRangeValue {
  Satisfied(RangeUnit, Int64, Int64, Int64?)
  Unsatisfied(RangeUnit, Int64)
  OtherContentRange(String, String)
} derive(Eq, Debug)

///|
pub fn parse_content_range(
  input : String,
) -> Result[ContentRangeValue, RangeError] {
  parse_content_range_with_limits(input, Limits::default())
}

///|
pub fn parse_content_range_with_limits(
  input : String,
  limits : Limits,
) -> Result[ContentRangeValue, RangeError] {
  if utf8_length(input) > limits.max_input_bytes() {
    return Err(
      range_error(
        Limit,
        LimitExceeded,
        0,
        "Content-Range exceeds max_input_bytes",
      ),
    )
  }
  let trimmed = trim_ows(input)
  if trimmed.length() == 0 {
    return Err(
      range_error(ContentRange, EmptyInput, 0, "Content-Range is empty"),
    )
  }
  let space = find_ows(trimmed)
  if space < 0 {
    return Err(
      range_error(
        ContentRange,
        InvalidRange,
        trimmed.length(),
        "Content-Range has no range value",
      ),
    )
  }
  let unit_text = trimmed[:space].to_owned()
  for c in unit_text {
    if !is_tchar(c) {
      return Err(
        range_error(ContentRange, InvalidUnit, 0, "invalid Content-Range unit"),
      )
    }
  }
  let response = trim_ows(trimmed[space + 1:].to_owned())
  if !ascii_equal(unit_text, "bytes") {
    return Ok(OtherContentRange(unit_text, response))
  }
  let slash = find_char(response, '/')
  if slash < 0 || find_char_from(response, '/', slash + 1) >= 0 {
    return Err(
      range_error(
        ContentRange,
        InvalidRange,
        space + 1,
        "Content-Range requires one '/'",
      ),
    )
  }
  let range_part = trim_ows(response[:slash].to_owned())
  let length_part = trim_ows(response[slash + 1:].to_owned())
  if range_part == "*" {
    if length_part == "*" {
      return Err(
        range_error(
          ContentRange,
          InvalidCompleteLength,
          space + slash + 2,
          "unsatisfied Content-Range requires known length",
        ),
      )
    }
    return match
      parse_decimal_int64_at(length_part, space + slash + 2, limits) {
      Ok(length) => Ok(Unsatisfied(Bytes, length))
      Err(error) => Err(error)
    }
  }
  let dash = find_char(range_part, '-')
  if dash < 0 {
    return Err(
      range_error(
        ContentRange,
        InvalidRange,
        space + 1,
        "satisfied Content-Range has no '-'",
      ),
    )
  }
  let first = match
    parse_decimal_int64_at(range_part[:dash].to_owned(), space + 1, limits) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let last = match
    parse_decimal_int64_at(
      range_part[dash + 1:].to_owned(),
      space + dash + 2,
      limits,
    ) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if last < first {
    return Err(
      range_error(
        ContentRange,
        LastBeforeFirst,
        space + dash + 2,
        "last position is before first",
      ),
    )
  }
  let complete : Int64? = if length_part == "*" {
    None
  } else {
    match parse_decimal_int64_at(length_part, space + slash + 2, limits) {
      Ok(v) => Some(v)
      Err(e) => return Err(e)
    }
  }
  match complete {
    Some(length) if length <= last =>
      return Err(
        range_error(
          ContentRange,
          InvalidCompleteLength,
          space + slash + 2,
          "complete length must be greater than last position",
        ),
      )
    _ => ()
  }
  Ok(Satisfied(Bytes, first, last, complete))
}

///|
pub fn serialize_content_range(value : ContentRangeValue) -> String {
  match value {
    Satisfied(unit, first, last, complete) => {
      let length = match complete {
        Some(v) => v.to_string()
        None => "*"
      }
      "\{unit.name()} \{first}-\{last}/\{length}"
    }
    Unsatisfied(unit, complete) => "\{unit.name()} */\{complete}"
    OtherContentRange(unit, response) => "\{unit} \{response}"
  }
}

///|
pub fn unsatisfied_content_range(
  length : Int64,
) -> Result[ContentRangeValue, RangeError] {
  if length < 0L {
    Err(
      range_error(
        ContentRange,
        InvalidCompleteLength,
        0,
        "complete length cannot be negative",
      ),
    )
  } else {
    Ok(Unsatisfied(Bytes, length))
  }
}

///|
fn find_ows(input : String) -> Int {
  for i = 0; i < input.length(); i = i + 1 {
    let c = input[i].to_int().unsafe_to_char()
    if is_ows(c) {
      return i
    }
  }
  -1
}