///|
/// A CAN data frame independent of a specific socket or operating system.
pub(all) struct CanFrame {
  /// Arbitration identifier without SocketCAN or DBC flag bits.
  id : UInt
  /// Distinguishes the extended namespace even when `id` fits in 11 bits.
  is_extended_frame : Bool
  payload : Bytes
} derive(Eq, Debug)

///|
pub(all) enum CanFrameTextError {
  CanFrameMissingSeparator
  CanFrameEmptyIdentifier
  CanFrameIdentifierTooLong(Int)
  CanFrameInvalidIdentifier(Int, Char)
  CanFrameIdentifierOutOfRange(UInt)
  CanFrameOddPayloadLength(Int)
  CanFramePayloadTooLong(Int)
  CanFrameInvalidPayload(Int, Char)
} derive(Eq, Debug)

///|
fn can_hex_nibble(char : Char) -> Int? {
  if char >= '0' && char <= '9' {
    Some(char.to_int() - '0'.to_int())
  } else if char >= 'a' && char <= 'f' {
    Some(char.to_int() - 'a'.to_int() + 10)
  } else if char >= 'A' && char <= 'F' {
    Some(char.to_int() - 'A'.to_int() + 10)
  } else {
    None
  }
}

///|
fn parse_can_identifier(
  text : StringView,
) -> Result[(UInt, Bool), CanFrameTextError] {
  if text.is_empty() {
    return Err(CanFrameEmptyIdentifier)
  }
  if text.length() > 8 {
    return Err(CanFrameIdentifierTooLong(text.length()))
  }
  let mut identifier = 0U
  let mut index = 0
  for char in text.iter() {
    let nibble = match can_hex_nibble(char) {
      Some(value) => value
      None => return Err(CanFrameInvalidIdentifier(index, char))
    }
    identifier = identifier * 16U + nibble.reinterpret_as_uint()
    index += 1
  }
  let is_extended_frame = text.length() > 3
  let maximum = if is_extended_frame { 0x1FFFFFFFU } else { 0x7FFU }
  if identifier > maximum {
    Err(CanFrameIdentifierOutOfRange(identifier))
  } else {
    Ok((identifier, is_extended_frame))
  }
}

///|
fn parse_can_payload(text : StringView) -> Result[Bytes, CanFrameTextError] {
  if text.length() % 2 != 0 {
    return Err(CanFrameOddPayloadLength(text.length()))
  }
  let payload_length = text.length() / 2
  if payload_length > 64 {
    return Err(CanFramePayloadTooLong(payload_length))
  }
  let payload : Array[Byte] = []
  let mut high_nibble : Int? = None
  let mut index = 0
  for char in text.iter() {
    let nibble = match can_hex_nibble(char) {
      Some(value) => value
      None => return Err(CanFrameInvalidPayload(index, char))
    }
    match high_nibble {
      None => high_nibble = Some(nibble)
      Some(high) => {
        payload.push((high * 16 + nibble).to_byte())
        high_nibble = None
      }
    }
    index += 1
  }
  Ok(Bytes::from_array(payload))
}

///|
/// Parse the compact SocketCAN representation `ID#DATA`. One to three ID
/// digits denote a standard frame; four to eight denote an extended frame.
/// The payload may be empty and may contain up to 64 bytes for CAN FD.
pub fn parse_can_frame(
  text : StringView,
) -> Result[CanFrame, CanFrameTextError] {
  let input = text.trim()
  let separator = match input.find("#") {
    Some(index) => index
    None => return Err(CanFrameMissingSeparator)
  }
  let (identifier, is_extended_frame) = match
    parse_can_identifier(input[:separator]) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let payload = match parse_can_payload(input[separator + 1:]) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ id: identifier, is_extended_frame, payload, })
}

///|
fn append_padded_identifier(
  chars : Array[Char],
  identifier : UInt,
  is_extended_frame : Bool,
) -> Unit {
  let text = identifier.to_string(radix=16).to_upper()
  let width = if is_extended_frame { 8 } else { 3 }
  for padding = text.length(); padding < width; padding = padding + 1 {
    chars.push('0')
  }
  for char in text.iter() {
    chars.push(char)
  }
}

///|
fn can_hex_digit(value : Int) -> Char {
  if value < 10 {
    (value + '0'.to_int()).to_char().unwrap()
  } else {
    (value - 10 + 'A'.to_int()).to_char().unwrap()
  }
}

///|
/// Render a frame as canonical compact SocketCAN text. Standard identifiers
/// use three hexadecimal digits and extended identifiers use eight.
pub fn CanFrame::to_text(self : CanFrame) -> Result[String, CanFrameTextError] {
  let maximum = if self.is_extended_frame { 0x1FFFFFFFU } else { 0x7FFU }
  if self.id > maximum {
    return Err(CanFrameIdentifierOutOfRange(self.id))
  }
  if self.payload.length() > 64 {
    return Err(CanFramePayloadTooLong(self.payload.length()))
  }
  let chars : Array[Char] = []
  append_padded_identifier(chars, self.id, self.is_extended_frame)
  chars.push('#')
  for byte in self.payload {
    let value = byte.to_int()
    chars.push(can_hex_digit(value >> 4))
    chars.push(can_hex_digit(value & 0xF))
  }
  Ok(String::from_array(chars))
}

///|
/// Decode a parsed CAN frame using the message identifier in the frame.
pub fn Database::decode_can_frame(
  self : Database,
  frame : CanFrame,
) -> Result[DecodedMessage, FrameDecodeError] {
  self.decode_can_payload(frame.id, frame.is_extended_frame, frame.payload)
}