///|
/// Minimal pcapng bridge for trace artifact exchange.

///|
pub fn pcapng_custom_block_type() -> Int {
  0x00000BAD
}

///|
pub fn pcapng_linktype_ethernet() -> Int {
  1
}

///|
pub fn TraceLog::to_pcapng(self : TraceLog, seed~ : Int) -> Bytes {
  let artifact = TraceArtifact::from_log(self, seed~)
  let out : Array[Byte] = []
  append_section_header_block(out)
  append_interface_description_block(out)
  for event in self.events {
    append_enhanced_packet_block(out, event)
  }
  append_trace_artifact_custom_block(out, artifact)
  Bytes::from_array(out)
}

///|
pub fn TraceArtifact::to_pcapng(
  self : TraceArtifact,
) -> Bytes raise TraceArtifactError {
  let out : Array[Byte] = []
  append_section_header_block(out)
  append_interface_description_block(out)
  let log = self.import_log()
  for event in log.events {
    append_enhanced_packet_block(out, event)
  }
  append_trace_artifact_custom_block(out, self)
  Bytes::from_array(out)
}

///|
pub fn parse_trace_pcapng(
  bytes : Bytes,
) -> TraceArtifact raise TraceArtifactError {
  let mut offset = 0
  let mut saw_section = false
  let mut saw_interface = false
  let mut artifact : TraceArtifact? = None
  while offset < bytes.length() {
    if offset + 12 > bytes.length() {
      raise TraceArtifactError::InvalidArtifact("truncated pcapng block header")
    }
    let block_type = read_u32_le(bytes, offset)
    let block_len = read_u32_le(bytes, offset + 4)
    if block_len < 12 || offset + block_len > bytes.length() {
      raise TraceArtifactError::InvalidArtifact("invalid pcapng block length")
    }
    let trailing_len = read_u32_le(bytes, offset + block_len - 4)
    if trailing_len != block_len {
      raise TraceArtifactError::InvalidArtifact(
        "pcapng trailing length mismatch",
      )
    }
    if block_type == pcapng_section_header_block_type() {
      validate_section_header(bytes, offset, block_len)
      saw_section = true
    } else if block_type == pcapng_interface_description_block_type() {
      if !saw_section {
        raise TraceArtifactError::InvalidArtifact(
          "pcapng interface before section",
        )
      }
      validate_interface_description(bytes, offset, block_len)
      saw_interface = true
    } else if block_type == pcapng_custom_block_type() {
      artifact = Some(
        parse_trace_artifact_custom_block(bytes, offset, block_len),
      )
    }
    offset += block_len
  }
  if !saw_section {
    raise TraceArtifactError::InvalidArtifact("missing pcapng section header")
  }
  if !saw_interface {
    raise TraceArtifactError::InvalidArtifact("missing pcapng interface block")
  }
  match artifact {
    Some(value) => value
    None =>
      raise TraceArtifactError::InvalidArtifact(
        "missing trace artifact custom block",
      )
  }
}

///|
fn append_section_header_block(out : Array[Byte]) -> Unit {
  let body : Array[Byte] = []
  append_u32_le(body, 0x1A2B3C4D)
  append_u16_le(body, 1)
  append_u16_le(body, 0)
  for _ in 0..<8 {
    body.push(b'\xFF')
  }
  append_block(out, pcapng_section_header_block_type(), body)
}

///|
fn append_interface_description_block(out : Array[Byte]) -> Unit {
  let body : Array[Byte] = []
  append_u16_le(body, pcapng_linktype_ethernet())
  append_u16_le(body, 0)
  append_u32_le(body, 65535)
  append_block(out, pcapng_interface_description_block_type(), body)
}

///|
fn append_enhanced_packet_block(out : Array[Byte], event : TraceEvent) -> Unit {
  let packet = synthetic_ethernet_frame(event)
  let timestamp_us = normalize_timestamp_us(event.raw_ns)
  let body : Array[Byte] = []
  append_u32_le(body, 0)
  append_u32_le(body, int64_high_u32(timestamp_us))
  append_u32_le(body, int64_low_u32(timestamp_us))
  append_u32_le(body, packet.length())
  append_u32_le(body, packet.length())
  append_bytes(body, packet)
  append_padding(body, packet.length())
  append_block(out, pcapng_enhanced_packet_block_type(), body)
}

///|
fn append_trace_artifact_custom_block(
  out : Array[Byte],
  artifact : TraceArtifact,
) -> Unit {
  let payload = @utf8.encode(artifact.to_text())
  let body : Array[Byte] = []
  append_u32_le(body, pcapng_private_enterprise_number())
  append_u32_le(body, payload.length())
  append_bytes(body, payload)
  append_padding(body, payload.length())
  append_block(out, pcapng_custom_block_type(), body)
}

///|
fn append_block(
  out : Array[Byte],
  block_type : Int,
  body : Array[Byte],
) -> Unit {
  let block_len = 12 + body.length()
  append_u32_le(out, block_type)
  append_u32_le(out, block_len)
  for byte in body {
    out.push(byte)
  }
  append_u32_le(out, block_len)
}

///|
fn synthetic_ethernet_frame(event : TraceEvent) -> Bytes {
  let packet : Array[Byte] = []
  packet.push(b'\x02')
  packet.push(b'\x00')
  packet.push(b'\x00')
  packet.push(b'\x00')
  packet.push(event.direction.code().to_byte())
  packet.push(normalize_u8(event.event_id))
  packet.push(b'\x02')
  packet.push(b'\x00')
  packet.push(b'\x00')
  packet.push(b'\x01')
  packet.push(normalize_u8(event.rng_step))
  packet.push(normalize_u8(event.seed))
  packet.push(b'\x88')
  packet.push(b'\xB5')
  let line = @utf8.encode(event.to_golden_line())
  append_bytes(packet, line)
  Bytes::from_array(packet)
}

///|
fn append_bytes(out : Array[Byte], bytes : Bytes) -> Unit {
  for i in 0.. Unit {
  let pad = padded_len(payload_len) - payload_len
  for _ in 0.. Unit {
  out.push((value & 0xFF).to_byte())
  out.push(((value >> 8) & 0xFF).to_byte())
}

///|
fn append_u32_le(out : Array[Byte], value : Int) -> Unit {
  out.push((value & 0xFF).to_byte())
  out.push(((value >> 8) & 0xFF).to_byte())
  out.push(((value >> 16) & 0xFF).to_byte())
  out.push(((value >> 24) & 0xFF).to_byte())
}

///|
fn read_u32_le(bytes : Bytes, offset : Int) -> Int raise TraceArtifactError {
  if offset < 0 || offset + 4 > bytes.length() {
    raise TraceArtifactError::InvalidArtifact("truncated pcapng u32")
  }
  bytes[offset].to_int() |
  (bytes[offset + 1].to_int() << 8) |
  (bytes[offset + 2].to_int() << 16) |
  (bytes[offset + 3].to_int() << 24)
}

///|
fn validate_section_header(
  bytes : Bytes,
  offset : Int,
  block_len : Int,
) -> Unit raise TraceArtifactError {
  if block_len < 28 {
    raise TraceArtifactError::InvalidArtifact("short pcapng section header")
  }
  if read_u32_le(bytes, offset + 8) != 0x1A2B3C4D {
    raise TraceArtifactError::InvalidArtifact("unsupported pcapng byte order")
  }
  if read_u16_le(bytes, offset + 12) != 1 {
    raise TraceArtifactError::InvalidArtifact(
      "unsupported pcapng major version",
    )
  }
}

///|
fn validate_interface_description(
  bytes : Bytes,
  offset : Int,
  block_len : Int,
) -> Unit raise TraceArtifactError {
  if block_len < 20 {
    raise TraceArtifactError::InvalidArtifact("short pcapng interface block")
  }
  if read_u16_le(bytes, offset + 8) != pcapng_linktype_ethernet() {
    raise TraceArtifactError::InvalidArtifact("unsupported pcapng linktype")
  }
}

///|
fn read_u16_le(bytes : Bytes, offset : Int) -> Int raise TraceArtifactError {
  if offset < 0 || offset + 2 > bytes.length() {
    raise TraceArtifactError::InvalidArtifact("truncated pcapng u16")
  }
  bytes[offset].to_int() | (bytes[offset + 1].to_int() << 8)
}

///|
fn parse_trace_artifact_custom_block(
  bytes : Bytes,
  offset : Int,
  block_len : Int,
) -> TraceArtifact raise TraceArtifactError {
  if block_len < 20 {
    raise TraceArtifactError::InvalidArtifact(
      "short trace artifact custom block",
    )
  }
  let pen = read_u32_le(bytes, offset + 8)
  if pen != pcapng_private_enterprise_number() {
    raise TraceArtifactError::InvalidArtifact("unexpected trace artifact PEN")
  }
  let payload_len = read_u32_le(bytes, offset + 12)
  let payload_offset = offset + 16
  if payload_offset + payload_len > offset + block_len - 4 {
    raise TraceArtifactError::InvalidArtifact(
      "truncated trace artifact payload",
    )
  }
  let payload : Array[Byte] = []
  for i in 0..
      raise TraceArtifactError::InvalidArtifact("malformed trace artifact utf8")
  }
  parse_trace_artifact_text(text)
}

///|
fn normalize_timestamp_us(raw_ns : Int64) -> Int64 {
  if raw_ns <= 0L {
    0L
  } else {
    raw_ns / 1000L
  }
}

///|
fn int64_high_u32(value : Int64) -> Int {
  (value / 4294967296L % 4294967296L).to_int()
}

///|
fn int64_low_u32(value : Int64) -> Int {
  (value % 4294967296L).to_int()
}

///|
fn normalize_u8(value : Int) -> Byte {
  let mut out = value % 256
  if out < 0 {
    out += 256
  }
  out.to_byte()
}

///|
fn padded_len(length : Int) -> Int {
  let rem = length % 4
  if rem == 0 {
    length
  } else {
    length + 4 - rem
  }
}

///|
fn pcapng_section_header_block_type() -> Int {
  0x0A0D0D0A
}

///|
fn pcapng_interface_description_block_type() -> Int {
  0x00000001
}

///|
fn pcapng_enhanced_packet_block_type() -> Int {
  0x00000006
}

///|
fn pcapng_private_enterprise_number() -> Int {
  0
}