///|
/// Content-Encoding header parsing errors (RFC 9110 Section 8.4)
pub(all) enum ContentEncodingError {
  Empty
  InvalidFormat
  InvalidEncoding
} derive(Show, Eq)

///|
/// Content Coding (compression algorithm)
pub(all) enum ContentCoding {
  Gzip
  Deflate
  Compress
  Identity
  Br
  Zstd
  Other(String)
} derive(Show, Eq)

///|
pub fn ContentCoding::to_string(self : ContentCoding) -> String {
  match self {
    Gzip => "gzip"
    Deflate => "deflate"
    Compress => "compress"
    Identity => "identity"
    Br => "br"
    Zstd => "zstd"
    Other(s) => s
  }
}

///|
/// Content-Encoding header (RFC 9110 Section 8.4)
pub(all) struct ContentEncoding {
  encodings : Array[ContentCoding]
} derive(Eq)

///|
pub fn ContentEncoding::parse(
  input : String,
) -> Result[ContentEncoding, ContentEncodingError] {
  let s = ce_trim_string(input)
  if s.length() == 0 {
    return Err(Empty)
  }
  let parts = ce_split_string(s, ",")
  let encodings : Array[ContentCoding] = []
  let mut part_idx = 0
  while part_idx < parts.length() {
    let part = ce_trim_string(parts[part_idx])
    if part.length() > 0 {
      let parse_result = ce_parse_coding(part)
      match parse_result {
        Ok(coding) => encodings.push(coding)
        Err(e) => return Err(e)
      }
    }
    part_idx = part_idx + 1
  }
  if encodings.length() == 0 {
    return Err(Empty)
  }
  Ok({ encodings, })
}

///|
pub fn ContentEncoding::encodings(
  self : ContentEncoding,
) -> Array[ContentCoding] {
  self.encodings
}

///|
pub fn ContentEncoding::has_gzip(self : ContentEncoding) -> Bool {
  ce_contains(self, Gzip)
}

///|
pub fn ContentEncoding::has_deflate(self : ContentEncoding) -> Bool {
  ce_contains(self, Deflate)
}

///|
pub fn ContentEncoding::has_compress(self : ContentEncoding) -> Bool {
  ce_contains(self, Compress)
}

///|
pub fn ContentEncoding::has_identity(self : ContentEncoding) -> Bool {
  ce_contains(self, Identity)
}

///|
pub fn ContentEncoding::has_br(self : ContentEncoding) -> Bool {
  ce_contains(self, Br)
}

///|
pub fn ContentEncoding::has_zstd(self : ContentEncoding) -> Bool {
  ce_contains(self, Zstd)
}

///|
pub fn ContentEncoding::to_string(self : ContentEncoding) -> String {
  let strs = ce_map_encodings(self.encodings, fn(c) { c.to_string() })
  ce_join_strings(strs, ", ")
}

///|
/// Helper functions
///

///|
fn ce_contains(encoding : ContentEncoding, target : ContentCoding) -> Bool {
  let mut idx = 0
  while idx < encoding.encodings.length() {
    let current = encoding.encodings[idx]
    if current == target {
      return true
    }
    idx = idx + 1
  }
  false
}

///|
fn ce_parse_coding(
  token : String,
) -> Result[ContentCoding, ContentEncodingError] {
  let trimmed = ce_trim_string(token)
  if trimmed.length() == 0 {
    return Err(InvalidFormat)
  }
  if !ce_is_valid_token(trimmed) {
    return Err(InvalidEncoding)
  }
  let lower = ce_to_lower_case(trimmed)
  match lower {
    "gzip" => Ok(Gzip)
    "deflate" => Ok(Deflate)
    "compress" => Ok(Compress)
    "identity" => Ok(Identity)
    "br" => Ok(Br)
    "zstd" => Ok(Zstd)
    _ => Ok(Other(lower))
  }
}

///|
fn ce_is_valid_token(s : String) -> Bool {
  if s.length() == 0 {
    return false
  }
  let mut idx = 0
  while idx < s.length() {
    if !ce_is_token_char(ce_char_at(s, idx)) {
      return false
    }
    idx = idx + 1
  }
  true
}

///|
fn ce_is_token_char(b : Int) -> Bool {
  b == 33 ||
  b == 35 ||
  b == 36 ||
  b == 37 ||
  b == 38 ||
  b == 39 ||
  b == 42 ||
  b == 43 ||
  b == 45 ||
  b == 46 ||
  (b >= 48 && b <= 57) || // 0-9
  (b >= 65 && b <= 90) || // A-Z
  b == 94 ||
  b == 95 ||
  b == 96 ||
  (b >= 97 && b <= 122) || // a-z
  b == 124 ||
  b == 126
}

///|
fn ce_char_at(s : String, idx : Int) -> Int {
  str_char_at(s, idx)
}

///|
fn ce_to_lower_case(s : String) -> String {
  str_to_lower_case(s)
}

///|
fn ce_trim_string(s : String) -> String {
  str_trim_string(s)
}

///|
fn ce_split_string(s : String, sep : String) -> Array[String] {
  str_split_string(s, sep)
}

///|
fn[T, U] ce_map_encodings(arr : Array[T], f : (T) -> U) -> Array[U] {
  let result : Array[U] = []
  let mut idx = 0
  while idx < arr.length() {
    result.push(f(arr[idx]))
    idx = idx + 1
  }
  result
}

///|
fn ce_join_strings(strs : Array[String], sep : String) -> String {
  let mut result = ""
  let mut idx = 0
  while idx < strs.length() {
    if idx > 0 {
      result = result + sep
    }
    result = result + strs[idx]
    idx = idx + 1
  }
  result
}