///|
/// MPEG-2 PSI CRC-32: poly 04C11DB7, init FFFFFFFF, non-reflected, xorout 0.
pub fn mpeg_crc32(bytes : Bytes) -> UInt {
  let mut crc = 0xffffffffU
  for b in bytes {
    crc = crc ^ (b.to_uint() << 24)
    for _i = 0; _i < 8; _i = _i + 1 {
      crc = if (crc & 0x80000000U) != 0U {
        (crc << 1) ^ 0x04c11db7U
      } else {
        crc << 1
      }
    }
  }
  crc
}

///|
pub struct LongSection {
  table_id : Int
  extension : Int
  version : Int
  current : Bool
  number : Int
  last_number : Int
  body : Bytes
  bytes : Bytes
  offset : Int64
  priv locations : Array[Int64]
} derive(Eq, Debug)

///|
/// Long PSI section limited to 1024 total bytes (PAT/PMT profile).
pub fn parse_section(
  bytes : Bytes,
  offset? : Int64 = 0L,
  pid? : Int = 0,
) -> Result[LongSection, Diagnostic] {
  if offset < 0L || offset > 9223372036854774783L {
    return Err(fault("offset_range", offset, Some(pid)))
  }
  if bytes.length() < 12 || bytes.length() > 1024 {
    return Err(fault("section_size", offset, Some(pid)))
  }
  let length = ((bytes[1].to_int() & 15) << 8) | bytes[2].to_int()
  if length + 3 != bytes.length() {
    return Err(fault("section_length", offset + 1L, Some(pid)))
  }
  if (bytes[1].to_int() & 0xb0) != 0xb0 || (bytes[5].to_int() & 0xc0) != 0xc0 {
    return Err(fault("section_reserved", offset, Some(pid)))
  }
  if bytes[6] > bytes[7] {
    return Err(fault("section_number", offset + 6L, Some(pid)))
  }
  if mpeg_crc32(bytes) != 0U {
    return Err(fault("section_crc", offset, Some(pid)))
  }
  Ok({
    table_id: bytes[0].to_int(),
    extension: (bytes[3].to_int() << 8) | bytes[4].to_int(),
    version: (bytes[5].to_int() >> 1) & 31,
    current: (bytes[5].to_int() & 1) != 0,
    number: bytes[6].to_int(),
    last_number: bytes[7].to_int(),
    body: bytes.view(start=8, end=bytes.length() - 4).to_owned(),
    bytes,
    offset,
    locations: [],
  })
}

///|
fn LongSection::at(self : LongSection, relative : Int) -> Int64 {
  if self.locations.is_empty() {
    self.offset + relative.to_int64()
  } else {
    self.locations[relative]
  }
}