///|
// Only top-level block envelopes and summary blobs are decoded. IR bodies are
// skipped by their declared word length, so discovery does not construct IR.
priv struct BitcodeCursor {
  data : Bytes
  end : Int32
  origin : Int32
  mut byte : Int32
  mut bit : Int32
}

///|
fn BitcodeCursor::fixed(
  self : BitcodeCursor,
  width : Int32,
) -> UInt64 raise InvalidObject {
  let mut value = 0UL
  for i in 0..= self.end {
      object_reject("OB001", self.byte, "truncated bitcode field")
    }
    value = value |
      (((self.data[self.byte].to_uint64() >> self.bit) & 1UL) << i)
    self.bit += 1
    if self.bit == 8 {
      self.bit = 0
      self.byte += 1
    }
  }
  value
}

///|
fn BitcodeCursor::vbr(
  self : BitcodeCursor,
  width : Int32,
) -> UInt64 raise InvalidObject {
  let start = self.byte
  let mut value = 0UL
  let mut shift = 0
  let mask = (1UL << (width - 1)) - 1UL
  while shift < 64 {
    let group = self.fixed(width)
    let payload = group & mask
    if payload > 0xffffffffffffffffUL >> shift {
      object_reject("OB001", start, "bitcode VBR integer overflow")
    }
    value = value | (payload << shift)
    if group >> (width - 1) == 0UL {
      return value
    }
    shift += width - 1
  }
  object_reject("OB001", start, "unterminated bitcode VBR integer")
}

///|
fn BitcodeCursor::number(
  self : BitcodeCursor,
  width : Int32,
  maximum : Int32,
) -> Int32 raise InvalidObject {
  let start = self.byte
  let value = self.vbr(width)
  if value > maximum.to_uint64() {
    object_reject("OB001", start, "bitcode integer exceeds its permitted range")
  }
  value.to_int()
}

///|
fn BitcodeCursor::align(self : BitcodeCursor) -> Unit raise InvalidObject {
  if self.bit != 0 {
    if self.byte >= self.end ||
      self.data[self.byte].to_uint64() >> self.bit != 0UL {
      object_reject("OB001", self.byte, "invalid bitcode alignment bits")
    }
    self.bit = 0
    self.byte += 1
  }
  while (self.byte - self.origin) % 4 != 0 {
    if self.byte >= self.end || self.data[self.byte] != b'\x00' {
      object_reject("OB001", self.byte, "invalid bitcode alignment bytes")
    }
    self.byte += 1
  }
}

///|
fn BitcodeCursor::skip(
  self : BitcodeCursor,
  count : Int32,
  width : Int32,
) -> Unit raise InvalidObject {
  let bits = count.to_uint64() * width.to_uint64()
  let available = (self.end - self.byte).to_uint64() * 8UL -
    self.bit.to_uint64()
  if bits > available {
    object_reject("OB001", self.byte, "bitcode field extends beyond its block")
  }
  let position = self.bit.to_uint64() + bits
  self.byte += (position >> 3).to_int()
  self.bit = (position & 7UL).to_int()
}

///|
fn BitcodeCursor::block(
  self : BitcodeCursor,
) -> (Int32, Int32, Int32) raise InvalidObject {
  let id = self.number(8, 2147483647)
  let width = self.number(4, 32)
  if width < 2 {
    object_reject(
      "OB001",
      self.byte,
      "bitcode block code width must be between 2 and 32",
    )
  }
  self.align()
  let length = self.fixed(32)
  if length > ((self.end - self.byte) / 4).to_uint64() {
    object_reject(
      "OB001",
      self.byte,
      "bitcode block extends beyond its container",
    )
  }
  (id, width, self.byte + length.to_int() * 4)
}

///|
priv enum BitcodeOperand {
  Literal(UInt64)
  Fixed(Int32)
  Vbr(Int32)
  ArrayField
  Char6
  Blob
}

///|
fn BitcodeCursor::abbreviation(
  self : BitcodeCursor,
) -> Array[BitcodeOperand] raise InvalidObject {
  let count = self.number(5, 2147483647)
  if count == 0 {
    object_reject("OB001", self.byte, "empty bitcode abbreviation")
  }
  if count > 64 {
    object_reject(
      "OB003",
      self.byte,
      "bitcode summary abbreviation operand limit exceeded",
    )
  }
  let result : Array[BitcodeOperand] = []
  for _ in 0.. Fixed(self.number(5, 32))
        2 => {
          let width = self.number(5, 32)
          if width < 2 {
            object_reject("OB001", self.byte, "invalid bitcode VBR width")
          }
          Vbr(width)
        }
        3 => ArrayField
        4 => Char6
        5 => Blob
        _ =>
          object_reject(
            "OB001",
            self.byte,
            "unknown bitcode abbreviation encoding",
          )
      },
    )
  }
  if result[0] is (ArrayField | Char6 | Blob) {
    object_reject("OB001", self.byte, "invalid bitcode record-code encoding")
  }
  for i, operand in result {
    match operand {
      Blob =>
        if i != count - 1 {
          object_reject(
            "OB001",
            self.byte,
            "bitcode blob must end an abbreviation",
          )
        }
      ArrayField => {
        if i != count - 2 {
          object_reject(
            "OB001",
            self.byte,
            "bitcode array must end an abbreviation",
          )
        }
        if !(result[i + 1] is (Fixed(_) | Vbr(_) | Char6)) {
          object_reject(
            "OB001",
            self.byte,
            "invalid bitcode array element encoding",
          )
        }
      }
      _ => ()
    }
  }
  result
}

///|
fn BitcodeCursor::scalar(
  self : BitcodeCursor,
  operand : BitcodeOperand,
) -> UInt64 raise InvalidObject {
  match operand {
    Literal(value) => value
    Fixed(width) => self.fixed(width)
    Vbr(width) => self.vbr(width)
    Char6 => self.fixed(6)
    _ => object_reject("OB001", self.byte, "invalid scalar bitcode operand")
  }
}

///|
fn BitcodeCursor::record(
  self : BitcodeCursor,
  definition : Array[BitcodeOperand],
) -> (UInt64, (Int32, Int32)?) raise InvalidObject {
  let code = self.scalar(definition[0])
  let mut blob : (Int32, Int32)? = None
  let mut i = 1
  while i < definition.length() {
    match definition[i] {
      Blob => {
        let size = self.number(6, self.end - self.byte)
        self.align()
        if size > self.end - self.byte {
          object_reject("OB001", self.byte, "truncated bitcode blob")
        }
        blob = Some((self.byte, size))
        self.byte += size
        self.align()
      }
      ArrayField => {
        let count = self.number(6, 2147483647)
        match definition[i + 1] {
          Fixed(width) => self.skip(count, width)
          Char6 => self.skip(count, 6)
          Vbr(width) => {
            // Each value consumes at least width bits, even when it is zero.
            if count.to_uint64() * width.to_uint64() >
              (self.end - self.byte).to_uint64() * 8UL - self.bit.to_uint64() {
              object_reject(
                "OB001",
                self.byte,
                "bitcode array exceeds its block",
              )
            }
            for _ in 0..
            object_reject("OB001", self.byte, "invalid bitcode array encoding")
        }
        i += 1
      }
      operand => ignore(self.scalar(operand))
    }
    i += 1
  }
  (code, blob)
}

///|
fn BitcodeCursor::unabbreviated(
  self : BitcodeCursor,
) -> UInt64 raise InvalidObject {
  let code = self.vbr(6)
  let count = self.number(6, 2147483647)
  if count.to_uint64() * 6UL >
    (self.end - self.byte).to_uint64() * 8UL - self.bit.to_uint64() {
    object_reject("OB001", self.byte, "bitcode record exceeds its block")
  }
  for _ in 0.. (Int32, Int32) raise InvalidObject {
  let cursor : BitcodeCursor = { data, origin, byte: start, bit: 0, end, }
  let definitions : Array[Array[BitcodeOperand]] = []
  let mut blob : (Int32, Int32)? = None
  while cursor.byte < end {
    match cursor.fixed(width).to_int() {
      0 => {
        cursor.align()
        if cursor.byte != end {
          object_reject(
            "OB001",
            cursor.byte,
            "bitcode block ended before its declared length",
          )
        }
        guard blob is Some(value) else {
          object_reject("OB001", start, "bitcode summary block has no blob")
        }
        return value
      }
      1 => {
        let (_, _, child_end) = cursor.block()
        cursor.byte = child_end
      }
      2 => {
        if definitions.length() == 256 {
          object_reject(
            "OB003",
            cursor.byte,
            "bitcode summary abbreviation count limit exceeded",
          )
        }
        definitions.push(cursor.abbreviation())
      }
      3 =>
        if cursor.unabbreviated() == 1UL {
          object_reject(
            "OB001",
            cursor.byte,
            "bitcode summary requires a blob record",
          )
        }
      id => {
        guard definitions.get(id - 4) is Some(definition) else {
          object_reject(
            "OB002",
            cursor.byte,
            "bitcode summary uses an unavailable abbreviation",
          )
        }
        let (code, value) = cursor.record(definition)
        if code == 1UL {
          if blob is Some(_) || value is None {
            object_reject(
              "OB001",
              cursor.byte,
              "missing or duplicate bitcode summary blob",
            )
          }
          blob = value
        }
      }
    }
  }
  object_reject("OB001", end, "bitcode summary block has no end marker")
}