// Raw byte helpers used by the WARC parser.
//
// WARC content blocks may hold arbitrary binary (images, audio,
// compressed data, HTTP message bytes), so framing decisions are made
// exclusively on byte values. All positions are byte offsets.

///|
/// ASCII carriage return (13).
pub fn is_cr(b : Byte) -> Bool {
  b == b'\r'
}

///|
/// ASCII line feed (10).
pub fn is_lf(b : Byte) -> Bool {
  b == b'\n'
}

///|
/// ASCII space (32).
pub fn is_sp(b : Byte) -> Bool {
  b == b' '
}

///|
/// ASCII horizontal tab (9).
pub fn is_ht(b : Byte) -> Bool {
  b == b'\t'
}

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

///|
/// US-ASCII upper-case letter.
pub fn is_upper_ascii(b : Byte) -> Bool {
  b >= b'A' && b <= b'Z'
}

///|
/// US-ASCII lower-case letter.
pub fn is_lower_ascii(b : Byte) -> Bool {
  b >= b'a' && b <= b'z'
}

///|
/// US-ASCII control character (0..31 or 127).
pub fn is_ctl(b : Byte) -> Bool {
  b < b' ' || b == b'\x7f'
}

///|
/// Lower-case an US-ASCII letter; other bytes are returned unchanged.
pub fn lower_ascii(b : Byte) -> Byte {
  if b >= b'A' && b <= b'Z' {
    b + 32
  } else {
    b
  }
}

///|
/// The decimal value of an ASCII digit byte, or -1 if it is no digit.
pub fn digit_value(b : Byte) -> Int {
  if is_digit(b) {
    (b - b'0').to_int()
  } else {
    -1
  }
}

///|
/// Whether `data[start:end]` byte-equals `needle` ignoring ASCII case.
pub fn eq_ignore_ascii_case(
  data : Bytes,
  start : Int,
  end : Int,
  needle : String,
) -> Bool {
  let nb = @utf8.encode(needle)
  if end - start != nb.length() {
    return false
  }
  let mut i = 0
  while i < nb.length() {
    if lower_ascii(data[start + i]) != lower_ascii(nb[i]) {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Whether `data` starts with the byte sequence `prefix`.
pub fn starts_with(data : Bytes, prefix : Bytes) -> Bool {
  if data.length() < prefix.length() {
    return false
  }
  let mut i = 0
  while i < prefix.length() {
    if data[i] != prefix[i] {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Index of the first `needle` byte at or after `from`, or -1.
pub fn index_of_byte(data : Bytes, from : Int, needle : Byte) -> Int {
  let mut i = from
  while i < data.length() {
    if data[i] == needle {
      return i
    }
    i = i + 1
  }
  -1
}

///|
/// Index of the CR that starts a CRLF pair at or after `from`, or -1.
///
/// A trailing CR without a following LF is never reported: it cannot
/// form a line ending and the caller must treat it as data (or EOF).
pub fn find_crlf(data : Bytes, from : Int) -> Int {
  let mut i = from
  while i + 1 < data.length() {
    if data[i] == b'\r' && data[i + 1] == b'\n' {
      return i
    }
    i = i + 1
  }
  -1
}

///|
/// Whether `data[start:end]` consists solely of US-ASCII digits.
pub fn all_digits(data : Bytes, start : Int, end : Int) -> Bool {
  let mut i = start
  while i < end {
    if !is_digit(data[i]) {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Whether `data[start:end]` byte-equals the UTF-8 encoding of `needle`
/// (exact case-sensitive comparison).
pub fn eq_bytes(data : Bytes, start : Int, end : Int, needle : String) -> Bool {
  let nb = @utf8.encode(needle)
  if end - start != nb.length() {
    return false
  }
  let mut i = 0
  while i < nb.length() {
    if data[start + i] != nb[i] {
      return false
    }
    i = i + 1
  }
  true
}