///|
pub(all) enum ValidationSeverity {
  Error
  Warning
} derive(Eq, Debug)

///|
pub struct DbcValidationIssue {
  severity_value : ValidationSeverity
  code_value : String
  message_value : String
  message_id_value : Int?
  signal_name_value : String?
  source_line_value : Int
} derive(Eq, Debug)

///|
pub(all) struct DbcValidationPolicy {
  allow_extended_ids : Bool
  allow_can_fd : Bool
  require_known_nodes : Bool
  detect_signal_overlap : Bool
  warn_empty_units : Bool
}

///|
pub struct DbcValidationResult {
  issues_value : Array[DbcValidationIssue]
  error_count_value : Int
  warning_count_value : Int
} derive(Eq, Debug)

///|
pub fn DbcValidationPolicy::strict_classic_can() -> DbcValidationPolicy {
  {
    allow_extended_ids: true,
    allow_can_fd: false,
    require_known_nodes: true,
    detect_signal_overlap: true,
    warn_empty_units: false,
  }
}

///|
pub fn DbcValidationPolicy::can_fd() -> DbcValidationPolicy {
  { ..DbcValidationPolicy::strict_classic_can(), allow_can_fd: true }
}

///|
pub fn DbcValidationIssue::severity(
  self : DbcValidationIssue,
) -> ValidationSeverity {
  self.severity_value
}

///|
pub fn DbcValidationIssue::code(self : DbcValidationIssue) -> String {
  self.code_value
}

///|
pub fn DbcValidationIssue::message(self : DbcValidationIssue) -> String {
  self.message_value
}

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

///|
pub fn DbcValidationIssue::signal_name(self : DbcValidationIssue) -> String? {
  self.signal_name_value
}

///|
pub fn DbcValidationIssue::source_line(self : DbcValidationIssue) -> Int {
  self.source_line_value
}

///|
pub fn DbcValidationResult::issues(
  self : DbcValidationResult,
) -> Array[DbcValidationIssue] {
  self.issues_value.copy()
}

///|
pub fn DbcValidationResult::is_valid(self : DbcValidationResult) -> Bool {
  self.error_count_value == 0
}

///|
pub fn DbcValidationResult::error_count(self : DbcValidationResult) -> Int {
  self.error_count_value
}

///|
pub fn DbcValidationResult::warning_count(self : DbcValidationResult) -> Int {
  self.warning_count_value
}

///|
fn validation_issue(
  severity : ValidationSeverity,
  code : String,
  message : String,
  source_line : Int,
  message_id? : Int? = None,
  signal_name? : String? = None,
) -> DbcValidationIssue {
  {
    severity_value: severity,
    code_value: code,
    message_value: message,
    message_id_value: message_id,
    signal_name_value: signal_name,
    source_line_value: source_line,
  }
}

///|
fn known_node(database : DbcDatabase, name : String) -> Bool {
  if name == "Vector__XXX" {
    return true
  }
  for node in database.nodes() {
    if node.name() == name {
      return true
    }
  }
  false
}

///|
fn signal_bit_positions(signal : DbcSignal) -> Result[Array[Int], DbcError] {
  if signal.start_bit() < 0 ||
    signal.bit_length() <= 0 ||
    signal.bit_length() > 64 {
    return Err(
      DbcError::new(
        InvalidBitRange,
        "signal bit layout is outside supported bounds",
        signal.source_line(),
        1,
        context=signal.name(),
      ),
    )
  }
  let positions : Array[Int] = []
  match signal.byte_order() {
    Intel =>
      for offset in 0.. {
      let mut current = signal.start_bit()
      for _ in 0.. Unit {
  for index, message in messages {
    for previous in 0.. Unit {
  let signals = message.signals()
  for index, signal in signals {
    for previous in 0.. Unit {
  let maximum_id = if policy.allow_extended_ids { 536870911 } else { 2047 }
  if message.id() < 0 || message.id() > maximum_id {
    issues.push(
      validation_issue(
        Error,
        "message.id_out_of_range",
        "message id exceeds the configured CAN identifier range",
        message.source_line(),
        message_id=Some(message.id()),
      ),
    )
  }
  let maximum_size = if policy.allow_can_fd { 64 } else { 8 }
  if message.size() < 0 || message.size() > maximum_size {
    issues.push(
      validation_issue(
        Error,
        InvalidDataLength.code(),
        "message size exceeds the configured frame size",
        message.source_line(),
        message_id=Some(message.id()),
      ),
    )
  }
  if policy.require_known_nodes && !known_node(database, message.transmitter()) {
    issues.push(
      validation_issue(
        Error,
        "message.unknown_transmitter",
        "message transmitter is not declared in BU_",
        message.source_line(),
        message_id=Some(message.id()),
      ),
    )
  }
}

///|
fn add_signal_policy_issues(
  database : DbcDatabase,
  message : DbcMessage,
  signal : DbcSignal,
  policy : DbcValidationPolicy,
  issues : Array[DbcValidationIssue],
) -> Unit {
  if signal.factor() == 0.0 {
    issues.push(
      validation_issue(
        Error,
        "signal.zero_factor",
        "signal factor cannot be zero",
        signal.source_line(),
        message_id=Some(message.id()),
        signal_name=Some(signal.name()),
      ),
    )
  }
  if signal.minimum() > signal.maximum() {
    issues.push(
      validation_issue(
        Error,
        "signal.invalid_physical_range",
        "signal minimum exceeds maximum",
        signal.source_line(),
        message_id=Some(message.id()),
        signal_name=Some(signal.name()),
      ),
    )
  }
  if policy.warn_empty_units && signal.unit() == "" {
    issues.push(
      validation_issue(
        Warning,
        "signal.empty_unit",
        "signal has no engineering unit",
        signal.source_line(),
        message_id=Some(message.id()),
        signal_name=Some(signal.name()),
      ),
    )
  }
  if policy.require_known_nodes {
    for receiver in signal.receivers() {
      if !known_node(database, receiver) {
        issues.push(
          validation_issue(
            Error,
            "signal.unknown_receiver",
            "signal receiver is not declared in BU_",
            signal.source_line(),
            message_id=Some(message.id()),
            signal_name=Some(signal.name()),
          ),
        )
      }
    }
  }
  match signal_bit_positions(signal) {
    Err(error) =>
      issues.push(
        validation_issue(
          Error,
          error.code(),
          error.message(),
          signal.source_line(),
          message_id=Some(message.id()),
          signal_name=Some(signal.name()),
        ),
      )
    Ok(positions) =>
      for position in positions {
        if position >= message.size() * 8 {
          issues.push(
            validation_issue(
              Error,
              InvalidBitRange.code(),
              "signal extends beyond the message payload",
              signal.source_line(),
              message_id=Some(message.id()),
              signal_name=Some(signal.name()),
            ),
          )
          break
        }
      }
  }
}

///|
fn add_overlap_issues(
  message : DbcMessage,
  issues : Array[DbcValidationIssue],
) -> Unit {
  let owners = Array::make(message.size() * 8, "")
  for signal in message.signals() {
    let positions = match signal_bit_positions(signal) {
      Ok(value) => value
      Err(_) => continue
    }
    for position in positions {
      if position < 0 || position >= owners.length() {
        continue
      }
      if owners[position] != "" && owners[position] != signal.name() {
        issues.push(
          validation_issue(
            Error,
            OverlappingSignal.code(),
            "signal overlaps " +
            owners[position] +
            " at frame bit " +
            position.to_string(),
            signal.source_line(),
            message_id=Some(message.id()),
            signal_name=Some(signal.name()),
          ),
        )
        break
      }
      owners[position] = signal.name()
    }
  }
}

///|
pub fn validate_dbc(
  database : DbcDatabase,
  policy? : DbcValidationPolicy = DbcValidationPolicy::strict_classic_can(),
) -> DbcValidationResult {
  let issues : Array[DbcValidationIssue] = []
  let messages = database.messages()
  add_message_identity_issues(messages, issues)
  for message in messages {
    add_message_policy_issues(database, message, policy, issues)
    add_signal_identity_issues(message, issues)
    for signal in message.signals() {
      add_signal_policy_issues(database, message, signal, policy, issues)
    }
    if policy.detect_signal_overlap {
      add_overlap_issues(message, issues)
    }
  }
  let mut errors = 0
  let mut warnings = 0
  for issue in issues {
    match issue.severity() {
      Error => errors = errors + 1
      Warning => warnings = warnings + 1
    }
  }
  {
    issues_value: issues,
    error_count_value: errors,
    warning_count_value: warnings,
  }
}