///|
priv struct TableView {
  data : Bytes
  position : Int32
  vtable : Int32
  fields : Int32
  size : Int32
}

///|
fn TableView::open(data : Bytes, position : Int32) -> TableView raise FgbError {
  require_range(data, position, 4)
  let distance = i32(data, position)
  let wide = position.to_int64() - distance.to_int64()
  if wide < 0L || wide > 2147483647L {
    raise InvalidData(position, "vtable offset overflow")
  }
  let vtable = wide.to_int()
  let length = u16(data, vtable)
  let size = u16(data, vtable + 2)
  if length < 4 || length % 2 != 0 || size < 4 {
    raise InvalidData(vtable, "invalid vtable or object length")
  }
  require_range(data, vtable, length)
  require_range(data, position, size)
  { data, position, vtable, fields: (length - 4) / 2, size, }
}

///|
fn TableView::root(data : Bytes) -> TableView raise FgbError {
  let offset = size32(data, 0)
  if offset < 4 {
    raise InvalidData(0, "invalid root offset")
  }
  TableView::open(data, offset)
}

///|
fn TableView::field(
  self : TableView,
  id : Int32,
  width : Int32,
) -> Int32 raise FgbError {
  if id >= self.fields {
    return 0
  }
  let offset = u16(self.data, self.vtable + 4 + id * 2)
  if offset == 0 {
    return 0
  }
  if offset < 4 || offset > self.size || width > self.size - offset {
    raise InvalidData(self.position, "field exceeds object boundary")
  }
  self.position + offset
}

///|
fn TableView::byte(
  self : TableView,
  id : Int32,
  fallback~ : Int32 = 0,
) -> Int32 raise FgbError {
  let at = self.field(id, 1)
  if at == 0 {
    fallback
  } else {
    u8(self.data, at)
  }
}

///|
fn TableView::short(
  self : TableView,
  id : Int32,
  fallback~ : Int32 = 0,
) -> Int32 raise FgbError {
  let at = self.field(id, 2)
  if at == 0 {
    fallback
  } else {
    u16(self.data, at)
  }
}

///|
fn TableView::int(
  self : TableView,
  id : Int32,
  fallback~ : Int32 = 0,
) -> Int32 raise FgbError {
  let at = self.field(id, 4)
  if at == 0 {
    fallback
  } else {
    i32(self.data, at)
  }
}

///|
fn TableView::count(self : TableView, id : Int32) -> Int32 raise FgbError {
  let at = self.field(id, 8)
  if at == 0 {
    0
  } else {
    size64(self.data, at)
  }
}

///|
fn indirect(data : Bytes, at : Int32) -> Int32 raise FgbError {
  let delta = size32(data, at)
  if delta < 4 {
    raise InvalidData(at, "invalid forward reference")
  }
  let target = checked_add(at, delta)
  require_range(data, target, 4)
  target
}

///|
fn TableView::reference(self : TableView, id : Int32) -> Int32 raise FgbError {
  let at = self.field(id, 4)
  if at == 0 {
    0
  } else {
    indirect(self.data, at)
  }
}

///|
fn text_at(data : Bytes, at : Int32, limits : Limits) -> String raise FgbError {
  let size = size32(data, at)
  let start = checked_add(at, 4)
  require_range(data, start, checked_add(size, 1))
  if u8(data, start + size) != 0 {
    raise InvalidData(start + size, "missing FlatBuffers string terminator")
  }
  decode_text(data, start, size, limits)
}

///|
fn TableView::text(
  self : TableView,
  id : Int32,
  limits : Limits,
) -> String raise FgbError {
  let at = self.reference(id)
  if at == 0 {
    ""
  } else {
    text_at(self.data, at, limits)
  }
}

///|
fn TableView::vector(
  self : TableView,
  id : Int32,
  width : Int32,
  limit : Int32,
) -> (Int32, Int32) raise FgbError {
  let at = self.reference(id)
  if at == 0 {
    return (0, 0)
  }
  let count = size32(self.data, at)
  enforce(count, limit, "vector elements")
  let bytes = checked_mul(count, width)
  let start = checked_add(at, 4)
  require_range(self.data, start, bytes)
  (start, count)
}

///|
priv struct FbBuilder {
  writer : ByteWriter
}

///|
fn FbBuilder::new() -> FbBuilder {
  let writer = ByteWriter::new()
  writer.zeros(8)
  { writer, }
}

///|
fn FbBuilder::table(self : FbBuilder, fields : Int32) -> Int32 {
  self.writer.align(2)
  let vt = self.writer.position()
  self.writer.short(4 + 2 * fields)
  self.writer.short(8 + 8 * fields)
  self.writer.zeros(fields * 2)
  self.writer.align(8)
  let table = self.writer.position()
  self.writer.word((table - vt).reinterpret_as_uint())
  self.writer.zeros(4 + fields * 8)
  table
}

///|
fn FbBuilder::slot(self : FbBuilder, table : Int32, field : Int32) -> Int32 {
  let at = table + 8 + field * 8
  let distance = self.writer.data[table].to_int() |
    (self.writer.data[table + 1].to_int() << 8) |
    (self.writer.data[table + 2].to_int() << 16) |
    (self.writer.data[table + 3].to_int() << 24)
  self.writer.patch_short(table - distance + 4 + field * 2, 8 + field * 8)
  at
}

///|
fn FbBuilder::scalar(
  self : FbBuilder,
  table : Int32,
  field : Int32,
  value : Int32,
) -> Unit {
  let at = self.slot(table, field)
  self.writer.patch_word(at, value)
}

///|
fn FbBuilder::long(
  self : FbBuilder,
  table : Int32,
  field : Int32,
  value : Int32,
) -> Unit {
  let at = self.slot(table, field)
  self.writer.patch_long(at, value.to_uint64())
}

///|
fn FbBuilder::reference(
  self : FbBuilder,
  table : Int32,
  field : Int32,
  target : Int32,
) -> Unit {
  let at = self.slot(table, field)
  self.writer.patch_word(at, target - at)
}

///|
fn FbBuilder::text(
  self : FbBuilder,
  table : Int32,
  field : Int32,
  text : String,
) -> Unit {
  self.writer.align(4)
  let at = self.writer.position()
  let bytes = @utf8.encode(text)
  self.writer.word(bytes.length().reinterpret_as_uint())
  self.writer.bytes(bytes)
  self.writer.byte(0)
  self.reference(table, field, at)
}

///|
fn FbBuilder::vector(self : FbBuilder, count : Int32, width : Int32) -> Int32 {
  self.writer.align(if width == 8 { 8 } else { 4 }, bias=4)
  let at = self.writer.position()
  self.writer.word(count.reinterpret_as_uint())
  at
}

///|
fn FbBuilder::finish(self : FbBuilder, root : Int32) -> Bytes {
  self.writer.align(8)
  self.writer.patch_word(4, root - 4)
  self.writer.patch_word(0, self.writer.position() - 4)
  self.writer.finish()
}