///|
pub struct CanFrame {
  id_value : Int
  data_value : Bytes
} derive(Eq, Debug)

///|
pub struct DecodedFrame {
  frame_value : CanFrame
  message_value : DbcMessage
  signals_value : Array[DecodedSignal]
} derive(Eq, Debug)

///|
pub fn CanFrame::new(id : Int, data : Bytes) -> Result[CanFrame, DbcError] {
  if id < 0 || id > 536870911 {
    Err(
      DbcError::new(
        InvalidMessage,
        "CAN frame id is outside the 29-bit range",
        1,
        1,
      ),
    )
  } else if data.length() > 64 {
    Err(
      DbcError::new(InvalidDataLength, "CAN frame data exceeds 64 bytes", 1, 1),
    )
  } else {
    Ok({
      id_value: id,
      data_value: Bytes::makei(data.length(), index => data[index]),
    })
  }
}

///|
pub fn CanFrame::id(self : CanFrame) -> Int {
  self.id_value
}

///|
pub fn CanFrame::data(self : CanFrame) -> Bytes {
  Bytes::makei(self.data_value.length(), index => self.data_value[index])
}

///|
pub fn DecodedFrame::frame(self : DecodedFrame) -> CanFrame {
  self.frame_value
}

///|
pub fn DecodedFrame::message(self : DecodedFrame) -> DbcMessage {
  self.message_value
}

///|
pub fn DecodedFrame::signals(self : DecodedFrame) -> Array[DecodedSignal] {
  self.signals_value.copy()
}

///|
pub fn DecodedFrame::find_signal(
  self : DecodedFrame,
  name : String,
) -> DecodedSignal? {
  for signal in self.signals_value {
    if signal.name() == name {
      return Some(signal)
    }
  }
  None
}

///|
fn hex_value(code : Int) -> Int {
  if code >= 48 && code <= 57 {
    code - 48
  } else if code >= 65 && code <= 70 {
    code - 55
  } else if code >= 97 && code <= 102 {
    code - 87
  } else {
    -1
  }
}

///|
fn hex_character(value : Int) -> String {
  "0123456789ABCDEF"[value & 15].to_char().unwrap().to_string()
}

///|
pub fn parse_frame_hex(id : Int, text : String) -> Result[CanFrame, DbcError] {
  if text.length() % 2 != 0 {
    return Err(
      DbcError::new(
        InvalidDataLength,
        "hex frame data must have an even digit count",
        1,
        1,
      ),
    )
  }
  if text.length() > 128 {
    return Err(
      DbcError::new(InvalidDataLength, "hex frame data exceeds 64 bytes", 1, 1),
    )
  }
  for index in 0.. {
    let high = hex_value(text[index * 2].to_int())
    let low = hex_value(text[index * 2 + 1].to_int())
    (high * 16 + low).to_byte()
  })
  CanFrame::new(id, data)
}

///|
pub fn render_frame_hex(frame : CanFrame) -> String {
  let mut output = ""
  for byte in frame.data() {
    output = output +
      hex_character(byte.to_int() >> 4) +
      hex_character(byte.to_int())
  }
  output
}

///|
pub fn decode_frame(
  database : DbcDatabase,
  frame : CanFrame,
) -> Result[DecodedFrame, DbcError] {
  let message = match database.find_message_by_id(frame.id()) {
    Some(value) => value
    None =>
      return Err(
        DbcError::new(
          UnknownMessage,
          "frame id is not defined in the DBC database",
          1,
          1,
          context=frame.id().to_string(),
        ),
      )
  }
  let signals = match decode_message(message, frame.data()) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ frame_value: frame, message_value: message, signals_value: signals })
}

///|
pub fn encode_frame(
  database : DbcDatabase,
  message_id : Int,
  assignments : Array[SignalAssignment],
) -> Result[CanFrame, DbcError] {
  let message = match database.find_message_by_id(message_id) {
    Some(value) => value
    None =>
      return Err(
        DbcError::new(
          UnknownMessage,
          "message id is not defined in the DBC database",
          1,
          1,
          context=message_id.to_string(),
        ),
      )
  }
  let data = match encode_message(message, assignments) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  CanFrame::new(message_id, data)
}

///|
pub fn decode_frame_hex(
  database : DbcDatabase,
  message_id : Int,
  hex_data : String,
) -> Result[DecodedFrame, DbcError] {
  let frame = match parse_frame_hex(message_id, hex_data) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  decode_frame(database, frame)
}