///|
pub struct Batch {
  header : Header
  microseconds : Int?
  events : Array[RouteEvent]
  states : Array[StateChange]
  skipped : Bool
  ignored_families : Int
  message_type : Int?
  diagnostics : Array[Diagnostic]
} derive(ToJson)

///|
pub extend Batch with ToJson::{to_json}

///|
pub struct Decoder {
  priv mut peers : Array[Peer]
  priv mut has_peers : Bool
}

///|
pub fn Decoder::new() -> Decoder {
  { peers: [], has_peers: false, }
}

///|
/// 新文件必须清除 peer 表,防止沿用前一文件的索引。
pub fn Decoder::reset(self : Decoder) -> Unit {
  self.peers = []
  self.has_peers = false
}

///|
pub fn Decoder::decode(self : Decoder, data : Bytes) -> Batch raise ParseError {
  let frame = read_frame(data)
  let header = frame.header
  let empty : Batch = {
    header,
    microseconds: frame.microseconds,
    events: [],
    states: [],
    skipped: false,
    ignored_families: 0,
    message_type: None,
    diagnostics: [],
  }
  if header.kind == 13 {
    if header.subtype == 1 {
      let table = parse_peers(frame.payload)
      self.peers = table.peers
      self.has_peers = true
      return empty
    }
    if header.subtype == 2 ||
      header.subtype == 4 ||
      header.subtype == 8 ||
      header.subtype == 10 {
      if !self.has_peers {
        raise Invalid(12, "RIB 之前缺少 PEER_INDEX_TABLE")
      }
      return { ..empty, events: parse_rib(frame, self.peers), }
    }
  }
  if (header.kind == 16 || header.kind == 17) &&
    (
      header.subtype == 0 ||
      header.subtype == 1 ||
      header.subtype == 4 ||
      header.subtype == 5 ||
      header.subtype == 6 ||
      header.subtype == 7 ||
      (header.subtype >= 8 && header.subtype <= 11)
    ) {
    let parsed = parse_bgp4mp(frame)
    return {
      ..empty,
      events: parsed.events,
      states: parsed.states,
      ignored_families: parsed.ignored_families,
      message_type: Some(parsed.message_type),
      diagnostics: parsed.ignored.map(fn(pair) {
        diagnostic(header, Some(pair.0), Some(pair.1))
      }),
    }
  }
  { ..empty, skipped: true, diagnostics: [diagnostic(header, None, None)], }
}

///|
pub struct StreamDecoder {
  priv decoder : Decoder
  priv mut pending : @buffer.Buffer
  priv mut expected : Int
  priv mut header_ready : Bool
  priv mut failed : Bool
}

///|
pub fn StreamDecoder::new() -> StreamDecoder {
  {
    decoder: Decoder::new(),
    pending: @buffer.Buffer(size_hint=12),
    expected: 12,
    header_ready: false,
    failed: false,
  }
}

///|
/// 输入分块上限 1 MiB,失败后需新建解码器。
pub fn StreamDecoder::feed(
  self : StreamDecoder,
  chunk : Bytes,
) -> Array[Batch] raise ParseError {
  if self.failed {
    raise Invalid(0, "解码器已失败,请新建会话")
  }
  errdefer {
    self.failed = true
  }
  if chunk.length() > 1048576 {
    raise Invalid(0, "输入分块超过 1 MiB")
  }
  let batches = []
  let mut offset = 0
  while offset < chunk.length() {
    let wanted = self.expected - self.pending.length()
    let available = chunk.length() - offset
    let take = if wanted < available { wanted } else { available }
    self.pending.write_bytes(chunk[offset:offset + take])
    offset += take
    if !self.header_ready && self.pending.length() == 12 {
      let header = self.pending.to_bytes()
      let c : Cursor = { data: header, pos: 8, end: 12, }
      let length = c.u32()
      if length > 16777216U {
        raise Invalid(8, "MRT 记录超过 16 MiB 上限")
      }
      self.expected = length.reinterpret_as_int() + 12
      self.header_ready = true
      // 验证长度后一次分配记录容量,碎片输入只复制新增字节。
      self.pending = @buffer.Buffer(size_hint=self.expected)
      self.pending.write_bytes(header[:])
    }
    if self.header_ready && self.pending.length() == self.expected {
      batches.push(self.decoder.decode(self.pending.to_bytes()))
      self.pending = @buffer.Buffer(size_hint=12)
      self.header_ready = false
      self.expected = 12
    }
  }
  batches
}

///|
pub fn StreamDecoder::finish(self : StreamDecoder) -> Unit raise ParseError {
  if self.failed {
    raise Invalid(0, "解码器已失败")
  }
  if self.pending.length() != 0 {
    self.failed = true
    raise Invalid(0, "文件结束时存在截断记录")
  }
}