///|
priv struct FieldParser {
  source : String
  mut pos : Int
}

///|
fn FieldParser::peek(self : FieldParser) -> Int {
  if self.pos >= self.source.length() {
    -1
  } else {
    self.source[self.pos].to_int()
  }
}

///|
fn FieldParser::skip_sp(self : FieldParser) -> Unit {
  while self.peek() == 32 || self.peek() == 9 {
    self.pos += 1
  }
}

///|
fn[T] FieldParser::fail(
  self : FieldParser,
  code : String,
) -> T raise DigestFieldError {
  raise Syntax(code, self.pos)
}

///|
fn FieldParser::parse_key(self : FieldParser) -> String raise DigestFieldError {
  let start = self.pos
  if !is_key_first(self.peek()) {
    self.fail("EXPECTED_ALGORITHM_KEY")
  }
  self.pos += 1
  while is_key_rest(self.peek()) {
    self.pos += 1
  }
  self.source[start:self.pos].to_owned()
}

///|
fn FieldParser::expect(
  self : FieldParser,
  c : Int,
  code : String,
) -> Unit raise DigestFieldError {
  if self.peek() != c {
    self.fail(code)
  }
  self.pos += 1
}

///|
fn digest_member_index(values : Array[DigestMember], key : String) -> Int {
  for i = 0; i < values.length(); i = i + 1 {
    if values[i].algorithm == key {
      return i
    }
  }
  -1
}

///|
fn preference_index(values : Array[Preference], key : String) -> Int {
  for i = 0; i < values.length(); i = i + 1 {
    if values[i].algorithm == key {
      return i
    }
  }
  -1
}

///|
fn FieldParser::parse_byte_sequence(
  self : FieldParser,
) -> Bytes raise DigestFieldError {
  self.expect(58, "EXPECTED_BYTE_SEQUENCE")
  let start = self.pos
  while self.peek() != 58 {
    let c = self.peek()
    if c < 0 {
      self.fail("UNCLOSED_BYTE_SEQUENCE")
    }
    if !((c >= 65 && c <= 90) ||
      (c >= 97 && c <= 122) ||
      is_digit(c) ||
      c == 43 ||
      c == 47 ||
      c == 61) {
      self.fail("INVALID_BASE64_CHARACTER")
    }
    self.pos += 1
  }
  let encoded = self.source[start:self.pos].to_owned()
  self.pos += 1
  let decoded = @base64.std_decode2bytes(encoded) catch {
    _ => raise Syntax("INVALID_BASE64", start)
  }
  if @base64.std_encode2str(decoded.to_fixedarray()) != encoded {
    raise Syntax("NON_CANONICAL_BASE64", start)
  }
  decoded
}

///|
fn FieldParser::after_member(self : FieldParser) -> Bool raise DigestFieldError {
  self.skip_sp()
  if self.peek() < 0 {
    return false
  }
  self.expect(44, "EXPECTED_COMMA")
  self.skip_sp()
  if self.peek() < 0 {
    self.fail("TRAILING_COMMA")
  }
  true
}

///|
/// Parse an RFC 9530 Content-Digest or Repr-Digest value. This release uses a
/// strict Structured Fields subset: dictionary members must be explicit byte
/// sequences and parameters are rejected rather than silently ignored.
pub fn parse_digest_field(input : String) -> DigestField raise DigestFieldError {
  let parser = FieldParser::{ source: input, pos: 0, }
  parser.skip_sp()
  if parser.peek() < 0 {
    parser.fail("EMPTY_FIELD")
  }
  let members : Array[DigestMember] = []
  while true {
    let algorithm = parser.parse_key()
    if digest_member_index(members, algorithm) >= 0 {
      parser.fail("DUPLICATE_ALGORITHM")
    }
    parser.expect(61, "EXPECTED_EQUALS")
    let value = parser.parse_byte_sequence()
    members.push({ algorithm, value, })
    if !parser.after_member() {
      break
    }
  }
  { members, }
}

///|
fn FieldParser::parse_weight(self : FieldParser) -> Int raise DigestFieldError {
  let start = self.pos
  if !is_digit(self.peek()) {
    self.fail("EXPECTED_WEIGHT")
  }
  let mut value = 0
  while is_digit(self.peek()) {
    value = value * 10 + self.peek() - 48
    self.pos += 1
    if value > 10 {
      raise Policy("WEIGHT_OUT_OF_RANGE", start)
    }
  }
  value
}

///|
/// Parse Want-Content-Digest or Want-Repr-Digest. Weights are restricted to
/// RFC 9530's inclusive 0..10 range.
pub fn parse_preference_field(
  input : String,
) -> PreferenceField raise DigestFieldError {
  let parser = FieldParser::{ source: input, pos: 0, }
  parser.skip_sp()
  if parser.peek() < 0 {
    parser.fail("EMPTY_FIELD")
  }
  let members : Array[Preference] = []
  while true {
    let algorithm = parser.parse_key()
    if preference_index(members, algorithm) >= 0 {
      parser.fail("DUPLICATE_ALGORITHM")
    }
    parser.expect(61, "EXPECTED_EQUALS")
    let weight = parser.parse_weight()
    members.push({ algorithm, weight, })
    if !parser.after_member() {
      break
    }
  }
  { members, }
}