///|
/// Errors raised by the complete bit-level wire codec.
pub suberror WireCodecError {
  InvalidEof
  InvalidCrc
  InvalidControl
  InvalidDlc
  InvalidFrameBits
} derive(Debug)

///|
/// Encode identifier, control, data, CRC, stuffing and EOF bits.
pub fn encode_wire(frame : Frame) -> Array[Bool] {
  let writer = new_bit_writer()
  try! writer.write(if frame.is_extended() { 1 } else { 0 }, 1)
  try! writer.write(
    match frame.kind() {
      Data => 0
      Remote => 1
      Error => 2
    },
    2,
  )
  try! writer.write(if frame.protocol() is CanFd { 1 } else { 0 }, 1)
  try! writer.write(if frame.bitrate_switch() { 1 } else { 0 }, 1)
  try! writer.write(if frame.error_state_indicator() { 1 } else { 0 }, 1)
  try! writer.write(frame.id(), 29)
  try! writer.write(frame.dlc().to_uint(), 4)
  for byte in frame.data() {
    try! writer.write(byte.to_uint(), 8)
  }
  let body = writer.finish()
  let crc = match frame.protocol() {
    Can20 => crc15(body)
    CanFd => if frame.data().length() <= 16 { crc17(body) } else { crc21(body) }
  }
  let crc_width = match frame.protocol() {
    Can20 => 15
    CanFd => if frame.data().length() <= 16 { 17 } else { 21 }
  }
  let with_crc = body + bits_of(crc, crc_width)
  stuff(with_crc) + Array::make(7, true)
}

///|
/// Return the stuffed frame length, including SOF-free EOF bits.
pub fn encoded_wire_length(frame : Frame) -> Int {
  encode_wire(frame).length()
}

///|
/// Decode a classic CAN wire sequence produced by `encode_wire`.
pub fn decode_wire_can20(bits : Array[Bool]) -> Frame raise WireCodecError {
  decode_wire(bits, Can20)
}

///|
/// Decode a CAN-FD wire sequence produced by `encode_wire`.
pub fn decode_wire_canfd(bits : Array[Bool]) -> Frame raise WireCodecError {
  decode_wire(bits, CanFd)
}

///|
/// Check CRC and parse a normalized frame from a complete wire sequence.
fn decode_wire(
  bits : Array[Bool],
  protocol : Protocol,
) -> Frame raise WireCodecError {
  if bits.length() < 7 {
    raise InvalidFrameBits
  }
  for bit in bits[bits.length() - 7:] {
    if !bit {
      raise InvalidEof
    }
  }
  let stuffed = bits[0:bits.length() - 7].to_owned()
  let raw = destuff(stuffed) catch { _ => raise InvalidControl }
  let crc_width = match protocol {
    Can20 => 15
    CanFd => if raw.length() > 16 * 8 + 64 { 21 } else { 17 }
  }
  if raw.length() <= crc_width {
    raise InvalidFrameBits
  }
  let body = raw[0:raw.length() - crc_width].to_owned()
  let received = bits_to_uint(raw[raw.length() - crc_width:].to_owned())
  let expected = match protocol {
    Can20 => crc15(body)
    CanFd =>
      if body.length() <= 16 * 8 + 64 {
        crc17(body)
      } else {
        crc21(body)
      }
  }
  if received != expected {
    raise InvalidCrc
  }
  parse_wire_body(body, protocol)
}

///|
fn parse_wire_body(
  body : Array[Bool],
  protocol : Protocol,
) -> Frame raise WireCodecError {
  let reader = new_bit_reader(body)
  let extended = reader.read_bit() catch { _ => raise InvalidFrameBits }
  let kind_code = reader.read(2) catch { _ => raise InvalidFrameBits }
  let is_fd = reader.read_bit() catch { _ => raise InvalidFrameBits }
  let bitrate_switch = reader.read_bit() catch { _ => raise InvalidFrameBits }
  let error_state_indicator = reader.read_bit() catch {
    _ => raise InvalidFrameBits
  }
  if (protocol is CanFd) != is_fd {
    raise InvalidControl
  }
  let id = reader.read(29) catch { _ => raise InvalidFrameBits }
  let dlc = reader.read(4) catch { _ => raise InvalidDlc }
  let data_length = if protocol is Can20 {
    if dlc > 8 {
      raise InvalidDlc
    } else {
      dlc.reinterpret_as_int()
    }
  } else {
    let remaining_bytes = reader.remaining() / 8
    if dlc <= 8 {
      dlc.reinterpret_as_int()
    } else {
      remaining_bytes
    }
  }
  if reader.remaining() < data_length * 8 {
    raise InvalidFrameBits
  }
  let data : Array[Byte] = []
  for _ in 0.. raise InvalidFrameBits }
    data.push(value.to_byte())
  }
  if kind_code == 1 {
    if !data.is_empty() {
      raise InvalidControl
    }
    remote_frame(id, extended~) catch {
      _ => raise InvalidFrameBits
    }
  } else if kind_code == 2 {
    error_frame(if data.is_empty() { 0 } else { data[0] })
  } else if protocol is CanFd {
    fd_frame(id, data, extended~, bitrate_switch~, error_state_indicator~) catch {
      _ => raise InvalidFrameBits
    }
  } else {
    data_frame(id, data, extended~) catch {
      _ => raise InvalidFrameBits
    }
  }
}

///|
fn bits_to_uint(bits : Array[Bool]) -> UInt {
  let mut result : UInt = 0
  for bit in bits {
    result = (result << 1) | (if bit { 1 } else { 0 })
  }
  result
}

///|
/// Verify that a frame's wire encoding can be decoded without loss.
pub fn wire_round_trip(frame : Frame) -> Bool {
  let bits = encode_wire(frame)
  let decoded = match frame.protocol() {
    Can20 => decode_wire_can20(bits) catch { _ => return false }
    CanFd => decode_wire_canfd(bits) catch { _ => return false }
  }
  frame_equal(frame, decoded)
}