///|
const MaxMetadata : Int = 16777216

///|
const MaxBlock : Int = 16777215

///|
const MaxBlocks : Int = 4096

///|
priv struct Reader {
  data : Bytes
  mut pos : Int
}

///|
fn Reader::take(self : Reader, count : Int) -> Bytes raise FlacError {
  guard count >= 0 && count <= self.data.length() - self.pos else {
    raise Invalid(self.pos, "truncated field")
  }
  let result = self.data[self.pos:self.pos + count].to_owned()
  self.pos += count
  result
}

///|
fn Reader::u32(self : Reader, little? : Bool = false) -> UInt raise FlacError {
  let b = self.take(4)
  if little {
    b[0].to_uint() |
    (b[1].to_uint() << 8) |
    (b[2].to_uint() << 16) |
    (b[3].to_uint() << 24)
  } else {
    (b[0].to_uint() << 24) |
    (b[1].to_uint() << 16) |
    (b[2].to_uint() << 8) |
    b[3].to_uint()
  }
}

///|
fn Reader::sized(
  self : Reader,
  little? : Bool = false,
) -> Bytes raise FlacError {
  let n = self.u32(little~)
  guard n <= (self.data.length() - self.pos).reinterpret_as_uint() else {
    raise Invalid(self.pos, "length exceeds block")
  }
  self.take(n.reinterpret_as_int())
}

///|
fn text(data : Bytes) -> String raise FlacError {
  @utf8.decode(data, ignore_bom=false) catch {
    _ => raise Invalid(0, "invalid UTF-8")
  }
}

///|
// Reject malformed UTF-16 before the core UTF-8 encoder, which assumes validity.
fn text_bytes(value : String) -> Bytes raise FlacError {
  let units = value.code_units()
  for i = 0; i < units.length(); {
    let c = units[i].to_int()
    if c >= 0xd800 && c <= 0xdbff {
      guard i + 1 < units.length() &&
        units[i + 1].to_int() >= 0xdc00 &&
        units[i + 1].to_int() <= 0xdfff else {
        raise Invalid(i, "unpaired UTF-16 surrogate")
      }
      continue i + 2
    } else {
      guard c < 0xdc00 || c > 0xdfff else {
        raise Invalid(i, "unpaired UTF-16 surrogate")
      }
      continue i + 1
    }
  }
  @utf8.encode(value)
}

///|
fn put_sized(
  out : @buffer.Buffer,
  data : Bytes,
  little? : Bool = false,
) -> Unit raise FlacError {
  guard data.length() <= MaxBlock else {
    raise Limit("field exceeds 24-bit block limit")
  }
  if little {
    out.write_uint_le(data.length().reinterpret_as_uint())
  } else {
    out.write_uint_be(data.length().reinterpret_as_uint())
  }
  out.write_bytes(data)
}

///|
fn check_block_size(size : Int) -> Unit raise FlacError {
  guard size <= MaxBlock else {
    raise Limit("metadata block exceeds 24-bit size")
  }
}