///|
/// DBC uses `@0` for Motorola bit order and `@1` for Intel bit order.
pub(all) enum ByteOrder {
  Motorola
  Intel
} derive(Eq, Debug)

///|
pub(all) enum ValueKind {
  Unsigned
  Signed
} derive(Eq, Debug)

///|
/// Physical storage selected by an optional `SIG_VALTYPE_` declaration.
pub(all) enum SignalValueType {
  Integer
  Float32
  Float64
} derive(Eq, Debug)

///|
pub(all) enum MultiplexRole {
  Plain
  Multiplexer
  Multiplexed(Int)
  MultiplexedRanges(String, Array[MultiplexRange])
} derive(Eq, Debug)

///|
/// Inclusive selector interval in an `SG_MUL_VAL_` declaration.
pub(all) struct MultiplexRange {
  start : Int
  end : Int
} derive(Eq, Debug)

///|
pub(all) struct Signal {
  name : String
  start_bit : Int
  bit_length : Int
  byte_order : ByteOrder
  value_kind : ValueKind
  value_type : SignalValueType
  factor : Double
  offset : Double
  minimum : Double
  maximum : Double
  unit : String
  receivers : Array[String]
  multiplex : MultiplexRole
} derive(Eq, Debug)

///|
pub(all) struct Message {
  /// Identifier as stored in DBC declarations. Extended frames set bit 31.
  id : UInt
  name : String
  payload_size : Int
  transmitter : String
  additional_transmitters : Array[String]
  signals : Array[Signal]
} derive(Eq, Debug)

///|
pub(all) struct ValueDescription {
  value : Int64
  label : String
} derive(Eq, Debug)

///|
pub(all) struct ValueTable {
  message_id : UInt
  signal_name : String
  entries : Array[ValueDescription]
} derive(Eq, Debug)

///|
/// A reusable named `VAL_TABLE_` enumeration declared at database scope.
pub(all) struct GlobalValueTable {
  name : String
  entries : Array[ValueDescription]
} derive(Eq, Debug)

///|
/// DBC environment variable; an optional data size comes from ENVVAR_DATA_.
pub(all) struct EnvironmentVariable {
  name : String
  value_type : Int
  minimum : Double
  maximum : Double
  unit : String
  initial_value : Double
  id : UInt
  access_type : Int
  access_nodes : Array[String]
  data_size : UInt?
} derive(Eq, Debug)

///|
/// A named DBC signal group. Groups are descriptive metadata used by editors,
/// diagnostics, and generated integrations; they do not alter CAN bit layout.
pub(all) struct SignalGroup {
  message_id : UInt
  name : String
  repetition : Int
  signals : Array[String]
} derive(Eq, Debug)

///|
pub(all) enum DbcCommentTarget {
  DatabaseComment
  NodeComment(String)
  MessageComment(UInt)
  SignalComment(UInt, String)
  EnvironmentComment(String)
} derive(Eq, Debug)

///|
pub(all) struct DbcComment {
  target : DbcCommentTarget
  text : String
} derive(Eq, Debug)

///|
pub(all) enum DbcAttributeScope {
  DatabaseAttributeScope
  NodeAttributeScope
  MessageAttributeScope
  SignalAttributeScope
} derive(Eq, Debug)

///|
pub(all) enum DbcAttributeType {
  AttributeIntegerType(Int64, Int64)
  AttributeHexType(Int64, Int64)
  AttributeFloatType(Double, Double)
  AttributeStringType
  AttributeEnumType(Array[String])
} derive(Eq, Debug)

///|
pub(all) enum DbcAttributeValue {
  AttributeInteger(Int64)
  AttributeFloat(Double)
  AttributeText(String)
} derive(Eq, Debug)

///|
pub(all) enum DbcAttributeTarget {
  DatabaseAttributeTarget
  NodeAttributeTarget(String)
  MessageAttributeTarget(UInt)
  SignalAttributeTarget(UInt, String)
} derive(Eq, Debug)

///|
pub(all) struct DbcAttributeDefinition {
  name : String
  scope : DbcAttributeScope
  value_type : DbcAttributeType
} derive(Eq, Debug)

///|
pub(all) struct DbcAttributeDefault {
  name : String
  value : DbcAttributeValue
} derive(Eq, Debug)

///|
pub(all) struct DbcAttributeAssignment {
  name : String
  target : DbcAttributeTarget
  value : DbcAttributeValue
} derive(Eq, Debug)

///|
pub(all) struct DbcAttributes {
  definitions : Array[DbcAttributeDefinition]
  defaults : Array[DbcAttributeDefault]
  assignments : Array[DbcAttributeAssignment]
} derive(Eq, Debug)

///|
pub fn DbcAttributes::empty() -> DbcAttributes {
  { definitions: [], defaults: [], assignments: [], }
}

///|
pub(all) struct Database {
  version : String?
  nodes : Array[String]
  messages : Array[Message]
  value_tables : Array[ValueTable]
  global_value_tables : Array[GlobalValueTable]
  environment_variables : Array[EnvironmentVariable]
  signal_groups : Array[SignalGroup]
  comments : Array[DbcComment]
  attributes : DbcAttributes
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  line : Int
  column : Int
  code : String
  message : String
} derive(Eq, Debug)

///|
pub(all) struct ParseResult {
  database : Database
  diagnostics : Array[Diagnostic]
} derive(Eq, Debug)

///|
pub fn ParseResult::is_valid(self : ParseResult) -> Bool {
  self.diagnostics.is_empty()
}

///|
fn dbc_message_id_parts(id : UInt) -> (UInt, Bool)? {
  if id >= 0x80000000U {
    let frame_id = id & 0x7FFFFFFFU
    if frame_id <= 0x1FFFFFFFU {
      Some((frame_id, true))
    } else {
      None
    }
  } else if id <= 0x7FFU {
    Some((id, false))
  } else {
    None
  }
}

///|
fn dbc_message_id_from_frame(
  frame_id : UInt,
  is_extended_frame : Bool,
) -> UInt? {
  if is_extended_frame {
    if frame_id <= 0x1FFFFFFFU {
      Some(frame_id | 0x80000000U)
    } else {
      None
    }
  } else if frame_id <= 0x7FFU {
    Some(frame_id)
  } else {
    None
  }
}

///|
/// Return the arbitration identifier placed on the CAN bus.
pub fn Message::frame_id(self : Message) -> UInt {
  match dbc_message_id_parts(self.id) {
    Some((frame_id, _)) => frame_id
    None => self.id
  }
}

///|
/// Return whether the DBC identifier uses the bit-31 extended-frame marker.
pub fn Message::is_extended_frame(self : Message) -> Bool {
  match dbc_message_id_parts(self.id) {
    Some((_, extended)) => extended
    None => false
  }
}

///|
/// Find a message by the identifier stored in its DBC declaration. For a bus
/// arbitration identifier, use `find_can_message`.
pub fn Database::find_message(self : Database, id : UInt) -> Message? {
  for message in self.messages {
    if message.id == id {
      return Some(message)
    }
  }
  None
}

///|
pub fn Database::find_environment_variable(
  self : Database,
  name : StringView,
) -> EnvironmentVariable? {
  for variable in self.environment_variables {
    if variable.name.view() == name {
      return Some(variable)
    }
  }
  None
}

///|
/// Find a message by its bus arbitration identifier and frame format.
pub fn Database::find_can_message(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
) -> Message? {
  let dbc_id = match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(value) => value
    None => return None
  }
  self.find_message(dbc_id)
}

///|
pub fn Message::find_signal(self : Message, name : StringView) -> Signal? {
  for signal in self.signals {
    if signal.name.view() == name {
      return Some(signal)
    }
  }
  None
}

///|
/// Return the primary transmitter followed by transmitters declared through
/// `BO_TX_BU_`.
pub fn Message::transmitters(self : Message) -> Array[String] {
  let result = [self.transmitter]
  for transmitter in self.additional_transmitters {
    result.push(transmitter)
  }
  result
}

///|
pub fn Message::is_transmitted_by(self : Message, node : StringView) -> Bool {
  if self.transmitter.view() == node {
    return true
  }
  for transmitter in self.additional_transmitters {
    if transmitter.view() == node {
      return true
    }
  }
  false
}

///|
pub fn Database::find_value_table(
  self : Database,
  message_id : UInt,
  signal_name : StringView,
) -> ValueTable? {
  for table in self.value_tables {
    if table.message_id == message_id && table.signal_name.view() == signal_name {
      return Some(table)
    }
  }
  None
}

///|
/// Find a reusable database-level `VAL_TABLE_` enumeration by name.
pub fn Database::find_global_value_table(
  self : Database,
  name : StringView,
) -> GlobalValueTable? {
  for table in self.global_value_tables {
    if table.name.view() == name {
      return Some(table)
    }
  }
  None
}

///|
/// Find a signal group by its message identifier and group name.
pub fn Database::find_signal_group(
  self : Database,
  message_id : UInt,
  name : StringView,
) -> SignalGroup? {
  for group in self.signal_groups {
    if group.message_id == message_id && group.name.view() == name {
      return Some(group)
    }
  }
  None
}

///|
pub fn Database::find_comment(
  self : Database,
  target : DbcCommentTarget,
) -> DbcComment? {
  for comment in self.comments {
    if comment.target == target {
      return Some(comment)
    }
  }
  None
}

///|
pub fn Database::database_comment(self : Database) -> String? {
  match self.find_comment(DatabaseComment) {
    Some(comment) => Some(comment.text)
    None => None
  }
}

///|
pub fn Database::node_comment(self : Database, name : StringView) -> String? {
  match self.find_comment(NodeComment(name.to_owned())) {
    Some(comment) => Some(comment.text)
    None => None
  }
}

///|
pub fn Database::message_comment(self : Database, id : UInt) -> String? {
  match self.find_comment(MessageComment(id)) {
    Some(comment) => Some(comment.text)
    None => None
  }
}

///|
pub fn Database::can_message_comment(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
) -> String? {
  match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(id) => self.message_comment(id)
    None => None
  }
}

///|
pub fn Database::signal_comment(
  self : Database,
  message_id : UInt,
  signal_name : StringView,
) -> String? {
  match self.find_comment(SignalComment(message_id, signal_name.to_owned())) {
    Some(comment) => Some(comment.text)
    None => None
  }
}

///|
pub fn Database::environment_comment(
  self : Database,
  name : StringView,
) -> String? {
  match self.find_comment(EnvironmentComment(name.to_owned())) {
    Some(comment) => Some(comment.text)
    None => None
  }
}

///|
pub fn Database::can_signal_comment(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
  signal_name : StringView,
) -> String? {
  match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(id) => self.signal_comment(id, signal_name)
    None => None
  }
}

///|
pub fn ValueTable::find_label(self : ValueTable, value : Int64) -> String? {
  for entry in self.entries {
    if entry.value == value {
      return Some(entry.label)
    }
  }
  None
}

///|
pub fn Database::value_label(
  self : Database,
  message_id : UInt,
  signal_name : StringView,
  value : Int64,
) -> String? {
  match self.find_value_table(message_id, signal_name) {
    Some(table) => table.find_label(value)
    None => None
  }
}

///|
pub fn Database::can_value_label(
  self : Database,
  frame_id : UInt,
  is_extended_frame : Bool,
  signal_name : StringView,
  value : Int64,
) -> String? {
  match dbc_message_id_from_frame(frame_id, is_extended_frame) {
    Some(id) => self.value_label(id, signal_name, value)
    None => None
  }
}