// bytes.mbt — octet-level primitives for SIP parsing.
//
// SIP is an octet protocol, not a text protocol. A message may legally carry
// octets that are not valid UTF-8 (RFC 3261 §7.1 requires UTF-8 only for the
// body when it is textual; headers are defined over octets). Every layer below
// therefore works on `Bytes` and never materialises a `String`, so that
// parsing cannot fail or silently rewrite bytes for reasons the wire format
// does not sanction.

///|
/// Octet constants used throughout the parser.
pub const CR : Byte = b'\r'

///|
pub const LF : Byte = b'\n'

///|
pub const SP : Byte = b' '

///|
pub const HTAB : Byte = b'\t'

///|
pub const DQUOTE : Byte = b'"'

///|
pub const COLON : Byte = b':'

///|
pub const LT : Byte = b'<'

///|
pub const GT : Byte = b'>'

///|
pub const COMMA : Byte = b','

///|
pub const SEMI : Byte = b';'

///|
pub const BSLASH : Byte = b'\\'

///|
pub const AT : Byte = b'@'

///|
pub const PERCENT : Byte = b'%'

///|
pub const EQUALS : Byte = b'='

///|
pub const LPAREN : Byte = b'('

///|
pub const RPAREN : Byte = b')'

///|
pub const SLASH : Byte = b'/'

///|
pub const LBRACKET : Byte = b'['

///|
pub const RBRACKET : Byte = b']'

///|
pub const DOT : Byte = b'.'

///|
pub const MINUS : Byte = b'-'

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

///|
/// True for ASCII letters.
pub fn is_alpha(b : Byte) -> Bool {
  (b >= b'a' && b <= b'z') || (b >= b'A' && b <= b'Z')
}

///|
/// True for ASCII letters and digits.
pub fn is_alnum(b : Byte) -> Bool {
  is_digit(b) || is_alpha(b)
}

///|
/// True for ASCII hexadecimal digits in either case.
pub fn is_hexdig(b : Byte) -> Bool {
  is_digit(b) || (b >= b'A' && b <= b'F') || (b >= b'a' && b <= b'f')
}

///|
/// Linear white space: SP and HTAB. CRLF that is followed by LWS is handled by
/// the folding logic in `token.mbt`, not here.
pub fn is_lws(b : Byte) -> Bool {
  b == SP || b == HTAB
}

///|
/// Control octets. RFC 3261 forbids raw CTLs in most productions; they may only
/// appear as `quoted-pair` escapes (`\` followed by %x00-09 / %x0B-0C / %x0E-7F).
pub fn is_ctl(b : Byte) -> Bool {
  b < 0x20 || b == 0x7F
}

///|
/// `%x80-FF`. RFC 3261 §25.1 allows these in `qdtext`, `ctext` and `token`-adjacent
/// productions through the UTF8-NONASCII rule, so they must survive round-trip.
pub fn is_high_octet(b : Byte) -> Bool {
  b >= 0x80
}

///|
/// RFC 3261 §25.1 `token` character set. Note this deliberately differs from
/// HTTP's token set: SIP excludes `|`, `&`, `^`, `$`, `#` and includes `%`, `*`,
/// `+`, `` ` ``, `'`, `~`.
pub fn is_token_char(b : Byte) -> Bool {
  if is_alnum(b) {
    return true
  }
  match b {
    b'-' | b'.' | b'!' | b'%' | b'*' | b'_' | b'+' | b'`' | b'\'' | b'~' => true
    _ => false
  }
}

///|
/// RFC 3261 §25.1 `separators`. These may not appear inside a token.
pub fn is_separator(b : Byte) -> Bool {
  match b {
    b'('
    | b')'
    | b'<'
    | b'>'
    | b'@'
    | b','
    | b';'
    | b':'
    | b'\\'
    | b'"'
    | b'/'
    | b'['
    | b']'
    | b'?'
    | b'='
    | b'{'
    | b'}' => true
    _ => false
  }
}

///|
/// Case-insensitive comparison of ASCII letters.
pub fn eq_ignore_ascii_case(a : Byte, b : Byte) -> Bool {
  lower_ascii(a) == lower_ascii(b)
}

///|
/// Lowercases an ASCII letter; any other octet passes through.
pub fn lower_ascii(b : Byte) -> Byte {
  if b >= b'A' && b <= b'Z' {
    b + 32
  } else {
    b
  }
}

///|
/// Compares two octet strings case-insensitively over ASCII only, which is the
/// rule RFC 3261 §7.3.1 applies to header field names and to token-valued
/// parameters such as transport names.
pub fn bytes_eq_ignore_ascii_case(a : BytesView, b : BytesView) -> Bool {
  if a.length() != b.length() {
    return false
  }
  let mut i = 0
  while i < a.length() {
    if !eq_ignore_ascii_case(a[i], b[i]) {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Borrows the whole of `b` as a view.
pub fn bytes_view_of(b : Bytes) -> BytesView {
  b.exact_view()
}

///|
/// Copies an octet range into a fresh `Bytes`. Used when a parsed fragment must
/// outlive the input buffer.
pub fn copy_range(src : BytesView, start : Int32, end : Int32) -> Bytes {
  let n = end - start
  if n <= 0 {
    return Bytes::new(0)
  }
  Bytes::makei(n, fn(i) { src[start + i] })
}

///|
/// Copies a view into owned octets.
pub fn bytes_of_view(v : BytesView) -> Bytes {
  copy_range(v, 0, v.length())
}

///|
/// Converts an ASCII-only `String` to octets, taking the low byte of each UTF-16
/// code unit.
///
/// This exists because `String` in this toolchain is UTF-16: `b"sip"`
/// yields six octets, not three. Every `String` this module accepts is an ASCII
/// literal (a header name, a scheme), so the low-byte projection is exact; a
/// non-ASCII input is rejected rather than silently truncated.
pub fn ascii_bytes_of(s : String) -> Bytes {
  let out : Array[Byte] = []
  for u in s.code_units() {
    let v = u.to_int()
    if v <= 0x7F {
      out.push(v.to_byte())
    }
  }
  Bytes::from_array(out)
}

///|
/// Compares a byte range against an ASCII literal, case-insensitively.
pub fn range_eq_ignore_case(
  src : BytesView,
  start : Int32,
  end : Int32,
  lit : String,
) -> Bool {
  let l = ascii_bytes_of(lit).exact_view()
  if end - start != l.length() {
    return false
  }
  let mut i = 0
  while i < l.length() {
    if !eq_ignore_ascii_case(src[start + i], l[i]) {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Hex value of an ASCII hex digit, or -1 when the octet is not a hex digit.
pub fn hex_value(b : Byte) -> Int32 {
  if b >= b'0' && b <= b'9' {
    (b - b'0').to_int()
  } else if b >= b'A' && b <= b'F' {
    (b - b'A').to_int() + 10
  } else if b >= b'a' && b <= b'f' {
    (b - b'a').to_int() + 10
  } else {
    -1
  }
}

///|
/// Renders octets as lowercase hex, used by diagnostics and snapshot tests.
/// Built from `Char`s because `Bytes::to_string` is the `Show` instance and would
/// produce `b"..."` instead of the hex text.
pub fn hex_of(b : BytesView) -> String {
  let digits : Array[Char] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
  ]
  let out : Array[Char] = []
  for i in 0.. String {
  let out : Array[Char] = []
  for i in 0..= 0x20 && v < 0x7F {
      out.push(Int32::unsafe_to_char(v))
    } else {
      out.push('.')
    }
  }
  String::from_array(out)
}

///|
/// Decodes a hex string into octets. Used by the RFC 4475 fixture, which stores
/// vectors as hex so that non-ASCII and control octets survive a source file.
pub fn bytes_from_hex(s : String) -> Bytes {
  let units = s.code_units()
  let out : Array[Byte] = []
  let mut i = 0
  while i + 1 < units.length() {
    let hi = hex_value(units[i].to_int().to_byte())
    let lo = hex_value(units[i + 1].to_int().to_byte())
    if hi < 0 || lo < 0 {
      break
    }
    out.push((hi * 16 + lo).to_byte())
    i = i + 2
  }
  Bytes::from_array(out)
}

///|
/// Case-insensitive (ASCII) substring search. Used by diagnostics that look for a
/// specific escaped octet sequence such as `%3F`.
pub fn contains_ignore_case(hay : BytesView, needle : BytesView) -> Bool {
  let n = needle.length()
  if n == 0 || hay.length() < n {
    return false
  }
  let mut i = 0
  while i + n <= hay.length() {
    if bytes_eq_ignore_ascii_case(hay.exact_view(start=i, end=i + n), needle) {
      return true
    }
    i = i + 1
  }
  false
}

///|
/// Strict UTF-8 well-formedness over a byte range: no overlong encodings, no
/// UTF-16 surrogate halves, no code points beyond U+10FFFF. This is what RFC
/// 3261 §25.1 `TEXT-UTF8-TRIM` demands of free-text fields.
pub fn is_valid_utf8(v : BytesView) -> Bool {
  let n = v.length()
  let mut i = 0
  while i < n {
    let b = v[i].to_int()
    if b < 0x80 {
      i = i + 1
      continue
    }
    // Continuation bytes are 0x80..0xBF.
    let cont = fn(v : BytesView, k : Int32) -> Bool {
      (v[k].to_int() & 0xC0) == 0x80
    }
    if b >= 0xC2 && b <= 0xDF {
      // 2-byte sequence; C1 (0xC0/0xC1) would be overlong.
      if i + 2 > n || !cont(v, i + 1) {
        return false
      }
      i = i + 2
    } else if b >= 0xE0 && b <= 0xEF {
      if i + 3 > n || !cont(v, i + 1) || !cont(v, i + 2) {
        return false
      }
      // E0 must be followed by A0..BF (no overlong), ED by 80..9F (no surrogates).
      if b == 0xE0 && v[i + 1].to_int() < 0xA0 {
        return false
      }
      if b == 0xED && v[i + 1].to_int() >= 0xA0 {
        return false
      }
      i = i + 3
    } else if b >= 0xF0 && b <= 0xF4 {
      if i + 4 > n || !cont(v, i + 1) || !cont(v, i + 2) || !cont(v, i + 3) {
        return false
      }
      // F0 must be followed by 90..BF (no overlong), F4 by 80..8F (beyond U+10FFFF).
      if b == 0xF0 && v[i + 1].to_int() < 0x90 {
        return false
      }
      if b == 0xF4 && v[i + 1].to_int() >= 0x90 {
        return false
      }
      i = i + 4
    } else {
      // 0x80..0xC1 and 0xF5..0xFF cannot start any well-formed sequence.
      return false
    }
  }
  true
}