///|
fn string_payload(obj : DataObject, limits : Limits) -> Bytes raise TdmsError {
  let offsets = Writer::new(false, limits)
  let payload = Writer::new(false, limits)
  for v in obj.values {
    match v {
      Textual(s) => {
        let b = @utf8.encode(s)
        if b.length() > limits.max_string_bytes {
          raise Budget("string value bytes")
        }
        payload.append(b)
        offsets.uint(payload.bytes.length().to_uint64(), 4)
      }
      _ => raise Invalid("non-string in string channel")
    }
  }
  offsets.append(payload.finish())
  offsets.finish()
}

///|
pub fn write(
  doc : Document,
  big_endian~ : Bool = false,
  limits~ : Limits = Limits::default(),
) -> Bytes raise TdmsError {
  doc.validate(limits)
  let meta = Writer::new(big_endian, limits)
  let raw = Writer::new(big_endian, limits)
  meta.uint(doc.objects.length().to_uint64(), 4)
  for obj in doc.objects {
    meta.string(obj.path.text())
    match obj.kind {
      None => meta.uint(0xffffffffUL, 4)
      Some(kind) => {
        meta.uint(if kind == Text { 28UL } else { 20UL }, 4)
        meta.uint(kind.code().to_uint64(), 4)
        meta.uint(1UL, 4)
        meta.uint(obj.values.length().to_uint64(), 8)
        if kind == Text {
          let payload = string_payload(obj, limits)
          meta.uint(payload.length().to_uint64(), 8)
          // The offset table has the same endianness as the segment.
          if big_endian {
            let r = Reader::new(payload, 0, payload.length(), false, limits)
            for n = 0; n < obj.values.length(); n = n + 1 {
              raw.uint(r.uint(4), 4)
            }
            raw.append(r.take(r.end - r.pos))
          } else {
            raw.append(payload)
          }
        } else {
          for v in obj.values {
            raw.scalar(kind, v)
          }
        }
      }
    }
    meta.uint(obj.properties.length().to_uint64(), 4)
    for p in obj.properties {
      write_property(meta, p)
    }
  }
  let m = meta.finish()
  let data = raw.finish()
  let out = Writer::new(false, limits)
  out.append(b"TDSm")
  out.uint(if big_endian { 78UL } else { 14UL }, 4)
  let header = Writer::new(big_endian, limits)
  header.uint(4713UL, 4)
  demand(
    m.length() <= limits.max_bytes - data.length(),
    "segment size overflow",
  )
  header.uint((m.length() + data.length()).to_uint64(), 8)
  header.uint(m.length().to_uint64(), 8)
  out.append(header.finish())
  out.append(m)
  out.append(data)
  out.finish()
}