///|
/// A small, dependency-free DBC reader for message and signal definitions.
pub suberror DbcError {
  InvalidMessageLine(String)
  InvalidSignalLine(String)
  InvalidNumber(String)
}

///|
/// Parse the message/signal subset commonly used by simulation tools.
pub fn parse_dbc(input : String) -> Array[Message] raise DbcError {
  let messages : Array[Message] = []
  let mut current : Message? = None
  for raw in input.split("\n") {
    let line = raw.trim()
    if line.has_prefix("BO_ ") {
      let fields = split_words(line)
      if fields.length() < 4 {
        raise DbcError::InvalidMessageLine(line.to_owned())
      }
      let id = parse_uint(fields[1])
      let name = first_part(fields[2], ":")
      let dlc = parse_int(fields[3])
      let next = message(id, name, dlc)
      match current {
        Some(previous) => messages.push(previous)
        None => ()
      }
      current = Some(next)
    } else if line.has_prefix("SG_ ") {
      let message = match current {
        Some(value) => value
        None => raise DbcError::InvalidSignalLine(line.to_owned())
      }
      let fields = split_words(line)
      if fields.length() < 4 {
        raise DbcError::InvalidSignalLine(line.to_owned())
      }
      let name = fields[1]
      let layout = split_array(fields[3], "|")
      if layout.length() != 2 {
        raise DbcError::InvalidSignalLine(line.to_owned())
      }
      let start_bit = parse_int(layout[0])
      let size_and_endian = split_array(layout[1], "@")
      let size = parse_int(size_and_endian[0])
      let endian_sign = if size_and_endian.length() > 1 {
        size_and_endian[1]
      } else {
        "1+"
      }
      let little_endian = endian_sign.has_prefix("1")
      let signed = endian_sign.has_suffix("-")
      message.add_signal(signal(name, start_bit, size, little_endian~, signed~))
    }
  }
  match current {
    Some(last) => messages.push(last)
    None => ()
  }
  messages
}

///|
fn split_words(input : StringView) -> Array[String] {
  let result : Array[String] = []
  for part in input.split(" ") {
    if !part.is_empty() {
      result.push(part.to_owned())
    }
  }
  result
}

///|
fn split_array(input : String, separator : String) -> Array[String] {
  let result : Array[String] = []
  for part in input.split(separator) {
    result.push(part.to_owned())
  }
  result
}

///|
fn first_part(input : String, separator : String) -> String {
  let parts = split_array(input, separator)
  if parts.is_empty() {
    input
  } else {
    parts[0]
  }
}

///|
fn parse_int(value : String) -> Int raise DbcError {
  @strconv.from_str(value) catch {
    _ => raise DbcError::InvalidNumber(value)
  }
}

///|
fn parse_uint(value : String) -> UInt raise DbcError {
  let number : Int = @strconv.from_str(value) catch {
    _ => raise DbcError::InvalidNumber(value)
  }
  number.reinterpret_as_uint()
}

///|
/// The message identifier.
pub fn Message::id(self : Message) -> UInt {
  self.id
}

///|
/// The message name.
pub fn Message::name(self : Message) -> String {
  self.name
}

///|
/// The configured payload length.
pub fn Message::dlc(self : Message) -> Int {
  self.dlc
}

///|
/// The signal name.
pub fn Signal::name(self : Signal) -> String {
  self.name
}

///|
/// The signal's start bit.
pub fn Signal::start_bit(self : Signal) -> Int {
  self.start_bit
}