// Small ASCII byte-string helpers mirroring the Python `bytes` methods that
// h11 relies on (`lower`, `strip`, `split`, `title`, `repr`, ...).

///|
/// Python's `bytes.isspace` set: space, \t, \n, \r, \x0b, \x0c.
fn is_ascii_whitespace(b : Byte) -> Bool {
  b is (b' ' | b'\t' | b'\n' | b'\r' | b'\x0b' | b'\x0c')
}

///|
fn is_ascii_upper(b : Byte) -> Bool {
  b >= b'A' && b <= b'Z'
}

///|
fn is_ascii_lower(b : Byte) -> Bool {
  b >= b'a' && b <= b'z'
}

///|
fn is_digit(b : Byte) -> Bool {
  b >= b'0' && b <= b'9'
}

///|
fn hex_value(b : Byte) -> Int? {
  if is_digit(b) {
    Some((b - b'0').to_int())
  } else if b >= b'a' && b <= b'f' {
    Some((b - b'a').to_int() + 10)
  } else if b >= b'A' && b <= b'F' {
    Some((b - b'A').to_int() + 10)
  } else {
    None
  }
}

///|
fn ascii_lower(s : BytesView) -> Bytes {
  Bytes::makei(s.length(), i => {
    let b = s[i]
    if is_ascii_upper(b) {
      b + 32
    } else {
      b
    }
  })
}

///|
/// Python's `bytes.title()` restricted to ASCII: the first letter of every
/// run of letters is upper-cased, the rest lower-cased.
fn ascii_title(s : BytesView) -> Bytes {
  let out = Array(capacity=s.length())
  let mut prev_is_letter = false
  for b in s {
    let is_letter = is_ascii_upper(b) || is_ascii_lower(b)
    if is_letter && !prev_is_letter && is_ascii_lower(b) {
      out.push(b - 32)
    } else if is_letter && prev_is_letter && is_ascii_upper(b) {
      out.push(b + 32)
    } else {
      out.push(b)
    }
    prev_is_letter = is_letter
  }
  Bytes::from_array(out)
}

///|
/// Python's `bytes.strip()` with no arguments.
fn ascii_strip(s : BytesView) -> BytesView {
  let mut start = 0
  let mut end = s.length()
  while start < end && is_ascii_whitespace(s[start]) {
    start += 1
  }
  while end > start && is_ascii_whitespace(s[end - 1]) {
    end -= 1
  }
  s[start:end]
}

///|
/// Python's `bytes.split(sep)` for a single-byte separator.
fn split_on(s : BytesView, sep : Byte) -> Array[BytesView] {
  let out = []
  let mut start = 0
  for i in 0.. Bool {
  a.lexical_compare(b) < 0
}

///|
fn bytes_concat(parts : Array[Bytes]) -> Bytes {
  let buf = @buffer.Buffer()
  for p in parts {
    buf.write_bytes(p)
  }
  buf.to_bytes()
}

///|
/// Parse a non-empty run of digits in `radix` (10 or 16). Returns `None` on
/// an empty input or an invalid digit. Values that do not fit in an `Int64`
/// saturate at `Int64` max: Python accepts arbitrarily large integers here,
/// and no real stream can ever get close to 2^63 bytes, so saturating keeps
/// the observable behavior identical.
fn parse_digits(s : BytesView, radix~ : Int) -> Int64? {
  if s.is_empty() {
    return None
  }
  let r = radix.to_int64()
  let mut acc = 0L
  for b in s {
    let d = match hex_value(b) {
      Some(d) if d < radix => d.to_int64()
      _ => return None
    }
    acc = if acc > (@int64.MAX_VALUE - d) / r {
      @int64.MAX_VALUE
    } else {
      acc * r + d
    }
  }
  Some(acc)
}

///|
fn parse_decimal(s : BytesView) -> Int64? {
  parse_digits(s, radix=10)
}

///|
fn parse_hex(s : BytesView) -> Int64? {
  parse_digits(s, radix=16)
}

///|
/// Render a byte string the way Python's `repr(bytes)` does, e.g.
/// `b'foo\r\n'`. Used for error messages.
fn bytes_repr(s : BytesView) -> String {
  let mut has_single = false
  let mut has_double = false
  for b in s {
    if b == b'\'' {
      has_single = true
    } else if b == b'"' {
      has_double = true
    }
  }
  let quote : Byte = if has_single && !has_double { b'"' } else { b'\'' }
  let sb = StringBuilder()
  sb.write_char('b')
  sb.write_char(quote.to_char())
  for b in s {
    match b {
      b'\\' => sb.write_string("\\\\")
      b'\t' => sb.write_string("\\t")
      b'\n' => sb.write_string("\\n")
      b'\r' => sb.write_string("\\r")
      _ if b == quote => {
        sb.write_char('\\')
        sb.write_char(b.to_char())
      }
      _ if b < b' ' || b >= b'\x7f' => {
        sb.write_string("\\x")
        sb.write_string(b.to_hex())
      }
      _ => sb.write_char(b.to_char())
    }
  }
  sb.write_char(quote.to_char())
  sb.to_string()
}

///|
fn bytes_debug(s : BytesView) -> Repr {
  Repr::literal(bytes_repr(s))
}