///|
pub struct SignalReference {
  message_id_value : Int
  message_name_value : String
  signal_value : DbcSignal
} derive(Eq, Debug)

///|
pub(all) struct MessageFilter {
  transmitter : String?
  receiver : String?
  name_contains : String?
  minimum_size : Int?
  maximum_size : Int?
}

///|
pub fn MessageFilter::all() -> MessageFilter {
  {
    transmitter: None,
    receiver: None,
    name_contains: None,
    minimum_size: None,
    maximum_size: None,
  }
}

///|
pub fn SignalReference::message_id(self : SignalReference) -> Int {
  self.message_id_value
}

///|
pub fn SignalReference::message_name(self : SignalReference) -> String {
  self.message_name_value
}

///|
pub fn SignalReference::signal(self : SignalReference) -> DbcSignal {
  self.signal_value
}

///|
fn string_contains_case_insensitive(value : String, needle : String) -> Bool {
  if needle == "" {
    return true
  }
  let lower_value = value.to_lower()
  let lower_needle = needle.to_lower()
  if lower_needle.length() > lower_value.length() {
    return false
  }
  for index in 0..<(lower_value.length() - lower_needle.length() + 1) {
    if lower_value[index:index + lower_needle.length()].to_owned() ==
      lower_needle {
      return true
    }
  }
  false
}

///|
fn signal_has_receiver(signal : DbcSignal, receiver : String) -> Bool {
  for candidate in signal.receivers() {
    if candidate == receiver {
      return true
    }
  }
  false
}

///|
fn message_has_receiver(message : DbcMessage, receiver : String) -> Bool {
  for signal in message.signals() {
    if signal_has_receiver(signal, receiver) {
      return true
    }
  }
  false
}

///|
fn message_matches_filter(message : DbcMessage, filter : MessageFilter) -> Bool {
  match filter.transmitter {
    Some(value) if message.transmitter() != value => return false
    _ => ()
  }
  match filter.receiver {
    Some(value) if !message_has_receiver(message, value) => return false
    _ => ()
  }
  match filter.name_contains {
    Some(value) if !string_contains_case_insensitive(message.name(), value) =>
      return false
    _ => ()
  }
  match filter.minimum_size {
    Some(value) if message.size() < value => return false
    _ => ()
  }
  match filter.maximum_size {
    Some(value) if message.size() > value => return false
    _ => ()
  }
  true
}

///|
pub fn select_messages(
  database : DbcDatabase,
  filter : MessageFilter,
) -> Array[DbcMessage] {
  let selected : Array[DbcMessage] = []
  for message in database.messages() {
    if message_matches_filter(message, filter) {
      selected.push(message)
    }
  }
  selected
}

///|
pub fn find_signals_by_name(
  database : DbcDatabase,
  query : String,
) -> Array[SignalReference] {
  let found : Array[SignalReference] = []
  for message in database.messages() {
    for signal in message.signals() {
      if string_contains_case_insensitive(signal.name(), query) {
        found.push({
          message_id_value: message.id(),
          message_name_value: message.name(),
          signal_value: signal,
        })
      }
    }
  }
  found
}

///|
pub fn messages_transmitted_by(
  database : DbcDatabase,
  node : String,
) -> Array[DbcMessage] {
  select_messages(database, { ..MessageFilter::all(), transmitter: Some(node) })
}

///|
pub fn messages_received_by(
  database : DbcDatabase,
  node : String,
) -> Array[DbcMessage] {
  select_messages(database, { ..MessageFilter::all(), receiver: Some(node) })
}