///|
pub(all) enum ValidationIssue {
  InvalidMessageIdentifierIssue(UInt)
  DuplicateMessageIdIssue(UInt)
  InvalidPayloadSizeIssue(UInt, Int)
  DuplicateSignalNameIssue(UInt, String)
  InvalidSignalLayoutIssue(UInt, String, Int, Int)
  InvalidSignalValueTypeIssue(UInt, String, SignalValueType, Int)
  FloatingMultiplexerIssue(UInt, String, SignalValueType)
  SignalOutsidePayloadIssue(UInt, String)
  InvalidScaleIssue(UInt, String, Double, Double)
  InvalidPhysicalRangeIssue(UInt, String, Double, Double)
  MultipleMultiplexersIssue(UInt)
  MissingMultiplexerIssue(UInt, String)
  MultiplexSelectorOutOfRangeIssue(UInt, String, Int)
  InvalidMultiplexRangeIssue(UInt, String, Int, Int)
  UnknownMultiplexSelectorIssue(UInt, String, String)
  OverlappingSignalsIssue(UInt, String, String)
  DuplicateCommentIssue(DbcCommentTarget)
  UnknownCommentNodeIssue(String)
  UnknownCommentMessageIssue(UInt)
  UnknownCommentSignalIssue(UInt, String)
  UnknownCommentEnvironmentIssue(String)
  DuplicateMessageTransmitterIssue(UInt, String)
  DuplicateAttributeDefinitionIssue(String)
  DuplicateAttributeDefaultIssue(String)
  DuplicateAttributeAssignmentIssue(String, DbcAttributeTarget)
  UnknownAttributeDefinitionIssue(String)
  InvalidAttributeTargetIssue(String, DbcAttributeTarget)
  AttributeScopeMismatchIssue(String, DbcAttributeTarget)
  InvalidAttributeValueIssue(String)
  DuplicateSignalGroupIssue(UInt, String)
  InvalidSignalGroupIssue(UInt, String)
  UnknownSignalGroupMessageIssue(UInt, String)
  UnknownSignalGroupMemberIssue(UInt, String, String)
  DuplicateGlobalValueTableIssue(String)
  InvalidGlobalValueTableIssue(String)
  DuplicateEnvironmentVariableIssue(String)
  InvalidEnvironmentVariableIssue(String)
} derive(Eq, Debug)

///|
fn signal_bits(signal : Signal, available_bits : Int) -> Array[Int]? {
  if signal.start_bit < 0 || signal.bit_length < 1 || signal.bit_length > 64 {
    return None
  }
  let bits : Array[Int] = []
  match signal.byte_order {
    Intel => {
      if signal.bit_length > available_bits ||
        signal.start_bit > available_bits - signal.bit_length {
        return None
      }
      for offset = 0; offset < signal.bit_length; offset = offset + 1 {
        bits.push(signal.start_bit + offset)
      }
    }
    Motorola => {
      let mut absolute_bit = signal.start_bit
      for offset = 0; offset < signal.bit_length; offset = offset + 1 {
        if absolute_bit >= available_bits {
          return None
        }
        bits.push(absolute_bit)
        if offset + 1 < signal.bit_length {
          absolute_bit = if absolute_bit % 8 == 0 {
            absolute_bit + 15
          } else {
            absolute_bit - 1
          }
        }
      }
    }
  }
  Some(bits)
}

///|
fn signals_can_coexist(left : MultiplexRole, right : MultiplexRole) -> Bool {
  match (left, right) {
    (Multiplexed(left_selector), Multiplexed(right_selector)) =>
      left_selector == right_selector
    (Multiplexed(selector), MultiplexedRanges(_, ranges)) =>
      selector_in_range_definitions(selector, ranges)
    (MultiplexedRanges(_, ranges), Multiplexed(selector)) =>
      selector_in_range_definitions(selector, ranges)
    (
      MultiplexedRanges(left_name, left_ranges),
      MultiplexedRanges(right_name, right_ranges),
    ) =>
      if left_name != right_name {
        true
      } else {
        ranges_intersect(left_ranges, right_ranges)
      }
    _ => true
  }
}

///|
fn selector_in_range_definitions(
  selector : Int,
  ranges : Array[MultiplexRange],
) -> Bool {
  for range in ranges {
    if selector >= range.start && selector <= range.end {
      return true
    }
  }
  false
}

///|
fn ranges_intersect(
  left : Array[MultiplexRange],
  right : Array[MultiplexRange],
) -> Bool {
  for a in left {
    for b in right {
      if a.start <= b.end && b.start <= a.end {
        return true
      }
    }
  }
  false
}

///|
fn share_occupied_bit(left : Array[Int], right : Array[Int]) -> Bool {
  for left_bit in left {
    for right_bit in right {
      if left_bit == right_bit {
        return true
      }
    }
  }
  false
}

///|
fn multiplex_selector_fits(signal : Signal, selector : Int) -> Bool {
  if selector < 0 || signal.bit_length < 1 || signal.bit_length > 64 {
    return false
  }
  match signal.value_kind {
    Unsigned =>
      if signal.bit_length == 64 {
        true
      } else {
        selector.to_uint64() <= (1UL << signal.bit_length) - 1UL
      }
    Signed =>
      if signal.bit_length == 64 {
        true
      } else {
        selector.to_int64() < 1L << (signal.bit_length - 1)
      }
  }
}

///|
fn validate_message(message : Message, issues : Array[ValidationIssue]) -> Unit {
  if dbc_message_id_parts(message.id) is None {
    issues.push(InvalidMessageIdentifierIssue(message.id))
  }
  if message.payload_size < 0 || message.payload_size > 64 {
    issues.push(InvalidPayloadSizeIssue(message.id, message.payload_size))
  }
  let available_bits = if message.payload_size >= 0 &&
    message.payload_size <= 64 {
    message.payload_size * 8
  } else {
    0
  }
  let occupied : Array[Array[Int]?] = []
  let mut multiplexer : Signal? = None
  let mut multiplexer_count = 0
  for index, transmitter in message.additional_transmitters {
    if transmitter == message.transmitter {
      issues.push(DuplicateMessageTransmitterIssue(message.id, transmitter))
    }
    for previous = 0; previous < index; previous = previous + 1 {
      if message.additional_transmitters[previous] == transmitter {
        issues.push(DuplicateMessageTransmitterIssue(message.id, transmitter))
      }
    }
  }
  for index, signal in message.signals {
    for previous = 0; previous < index; previous = previous + 1 {
      if message.signals[previous].name == signal.name {
        issues.push(DuplicateSignalNameIssue(message.id, signal.name))
      }
    }
    let layout_valid = signal.start_bit >= 0 &&
      signal.bit_length >= 1 &&
      signal.bit_length <= 64
    if !layout_valid {
      issues.push(
        InvalidSignalLayoutIssue(
          message.id,
          signal.name,
          signal.start_bit,
          signal.bit_length,
        ),
      )
      occupied.push(None)
    } else {
      let bits = signal_bits(signal, available_bits)
      if bits is None {
        issues.push(SignalOutsidePayloadIssue(message.id, signal.name))
      }
      occupied.push(bits)
    }
    match signal.value_type {
      Float32 if signal.bit_length != 32 =>
        issues.push(
          InvalidSignalValueTypeIssue(
            message.id,
            signal.name,
            signal.value_type,
            signal.bit_length,
          ),
        )
      Float64 if signal.bit_length != 64 =>
        issues.push(
          InvalidSignalValueTypeIssue(
            message.id,
            signal.name,
            signal.value_type,
            signal.bit_length,
          ),
        )
      _ => ()
    }
    if signal.factor == 0.0 ||
      signal.factor.is_nan() ||
      signal.factor.is_inf() ||
      signal.offset.is_nan() ||
      signal.offset.is_inf() {
      issues.push(
        InvalidScaleIssue(message.id, signal.name, signal.factor, signal.offset),
      )
    }
    if signal.minimum.is_nan() ||
      signal.minimum.is_inf() ||
      signal.maximum.is_nan() ||
      signal.maximum.is_inf() ||
      signal.minimum > signal.maximum {
      issues.push(
        InvalidPhysicalRangeIssue(
          message.id,
          signal.name,
          signal.minimum,
          signal.maximum,
        ),
      )
    }
    match signal.multiplex {
      Multiplexer => {
        if signal.value_type != Integer {
          issues.push(
            FloatingMultiplexerIssue(message.id, signal.name, signal.value_type),
          )
        }
        multiplexer_count += 1
        if multiplexer is None {
          multiplexer = Some(signal)
        }
      }
      _ => ()
    }
  }
  if multiplexer_count > 1 {
    issues.push(MultipleMultiplexersIssue(message.id))
  }
  for signal in message.signals {
    match signal.multiplex {
      Multiplexed(selector) =>
        match multiplexer {
          None => issues.push(MissingMultiplexerIssue(message.id, signal.name))
          Some(mux) =>
            if multiplexer_count == 1 && !multiplex_selector_fits(mux, selector) {
              issues.push(
                MultiplexSelectorOutOfRangeIssue(
                  message.id,
                  signal.name,
                  selector,
                ),
              )
            }
        }
      MultiplexedRanges(selector_name, ranges) =>
        match multiplexer {
          None => issues.push(MissingMultiplexerIssue(message.id, signal.name))
          Some(mux) => {
            if mux.name != selector_name {
              issues.push(
                UnknownMultiplexSelectorIssue(
                  message.id,
                  signal.name,
                  selector_name,
                ),
              )
            }
            if ranges.is_empty() {
              issues.push(
                InvalidMultiplexRangeIssue(message.id, signal.name, -1, -1),
              )
            }
            for range in ranges {
              if range.start < 0 || range.end < range.start {
                issues.push(
                  InvalidMultiplexRangeIssue(
                    message.id,
                    signal.name,
                    range.start,
                    range.end,
                  ),
                )
              } else if multiplexer_count == 1 &&
                !multiplex_selector_fits(mux, range.end) {
                issues.push(
                  MultiplexSelectorOutOfRangeIssue(
                    message.id,
                    signal.name,
                    range.end,
                  ),
                )
              }
            }
          }
        }
      _ => ()
    }
  }
  for right = 0; right < message.signals.length(); right = right + 1 {
    for left = 0; left < right; left = left + 1 {
      if signals_can_coexist(
          message.signals[left].multiplex,
          message.signals[right].multiplex,
        ) {
        match (occupied[left], occupied[right]) {
          (Some(left_bits), Some(right_bits)) =>
            if share_occupied_bit(left_bits, right_bits) {
              issues.push(
                OverlappingSignalsIssue(
                  message.id,
                  message.signals[left].name,
                  message.signals[right].name,
                ),
              )
            }
          _ => ()
        }
      }
    }
  }
}

///|
fn validate_comments(
  database : Database,
  issues : Array[ValidationIssue],
) -> Unit {
  for index, comment in database.comments {
    for previous = 0; previous < index; previous = previous + 1 {
      if database.comments[previous].target == comment.target {
        issues.push(DuplicateCommentIssue(comment.target))
      }
    }
    match comment.target {
      DatabaseComment => ()
      NodeComment(node) => {
        let mut found = false
        for candidate in database.nodes {
          if candidate == node {
            found = true
          }
        }
        if !found {
          issues.push(UnknownCommentNodeIssue(node))
        }
      }
      MessageComment(message_id) =>
        if database.find_message(message_id) is None {
          issues.push(UnknownCommentMessageIssue(message_id))
        }
      SignalComment(message_id, signal_name) =>
        match database.find_message(message_id) {
          None => issues.push(UnknownCommentMessageIssue(message_id))
          Some(message) =>
            if message.find_signal(signal_name) is None {
              issues.push(UnknownCommentSignalIssue(message_id, signal_name))
            }
        }
      EnvironmentComment(name) =>
        if database.find_environment_variable(name) is None {
          issues.push(UnknownCommentEnvironmentIssue(name))
        }
    }
  }
}

///|
fn validate_attributes(
  database : Database,
  issues : Array[ValidationIssue],
) -> Unit {
  let attributes = database.attributes
  for index, definition in attributes.definitions {
    for previous = 0; previous < index; previous = previous + 1 {
      if attributes.definitions[previous].name == definition.name {
        issues.push(DuplicateAttributeDefinitionIssue(definition.name))
      }
    }
    if definition.name.is_empty() ||
      attribute_type_text(definition.value_type) is Err(_) {
      issues.push(InvalidAttributeValueIssue(definition.name))
    }
  }
  for index, default in attributes.defaults {
    for previous = 0; previous < index; previous = previous + 1 {
      if attributes.defaults[previous].name == default.name {
        issues.push(DuplicateAttributeDefaultIssue(default.name))
      }
    }
    match find_attribute_definition(attributes.definitions, default.name) {
      None => issues.push(UnknownAttributeDefinitionIssue(default.name))
      Some(definition) =>
        if attribute_value_text(default.value, definition.value_type) is Err(_) {
          issues.push(InvalidAttributeValueIssue(default.name))
        }
    }
  }
  for index, assignment in attributes.assignments {
    for previous = 0; previous < index; previous = previous + 1 {
      if attributes.assignments[previous].name == assignment.name &&
        attributes.assignments[previous].target == assignment.target {
        issues.push(
          DuplicateAttributeAssignmentIssue(assignment.name, assignment.target),
        )
      }
    }
    match find_attribute_definition(attributes.definitions, assignment.name) {
      None => issues.push(UnknownAttributeDefinitionIssue(assignment.name))
      Some(definition) => {
        if definition.scope != attribute_target_scope(assignment.target) {
          issues.push(
            AttributeScopeMismatchIssue(assignment.name, assignment.target),
          )
        }
        if !attribute_target_exists(
            database.nodes,
            database.messages,
            assignment.target,
          ) {
          issues.push(
            InvalidAttributeTargetIssue(assignment.name, assignment.target),
          )
        }
        if attribute_value_text(assignment.value, definition.value_type)
          is Err(_) {
          issues.push(InvalidAttributeValueIssue(assignment.name))
        }
      }
    }
  }
}

///|
fn validate_signal_groups(
  database : Database,
  issues : Array[ValidationIssue],
) -> Unit {
  for index, group in database.signal_groups {
    for previous = 0; previous < index; previous = previous + 1 {
      let candidate = database.signal_groups[previous]
      if candidate.message_id == group.message_id &&
        candidate.name == group.name {
        issues.push(DuplicateSignalGroupIssue(group.message_id, group.name))
      }
    }
    if group.name.is_empty() || group.repetition < 0 || group.signals.is_empty() {
      issues.push(InvalidSignalGroupIssue(group.message_id, group.name))
    }
    match database.find_message(group.message_id) {
      None =>
        issues.push(
          UnknownSignalGroupMessageIssue(group.message_id, group.name),
        )
      Some(message) =>
        for signal_name in group.signals {
          if message.find_signal(signal_name) is None {
            issues.push(
              UnknownSignalGroupMemberIssue(
                group.message_id,
                group.name,
                signal_name,
              ),
            )
          }
        }
    }
  }
}

///|
fn validate_global_value_tables(
  database : Database,
  issues : Array[ValidationIssue],
) -> Unit {
  for index, table in database.global_value_tables {
    for previous = 0; previous < index; previous = previous + 1 {
      if database.global_value_tables[previous].name == table.name {
        issues.push(DuplicateGlobalValueTableIssue(table.name))
      }
    }
    if table.name.is_empty() || table.entries.is_empty() {
      issues.push(InvalidGlobalValueTableIssue(table.name))
    }
    for entry_index, entry in table.entries {
      for previous = 0; previous < entry_index; previous = previous + 1 {
        if table.entries[previous].value == entry.value {
          issues.push(InvalidGlobalValueTableIssue(table.name))
        }
      }
    }
  }
}

///|
fn validate_environment_variables(
  database : Database,
  issues : Array[ValidationIssue],
) -> Unit {
  for index, variable in database.environment_variables {
    for previous = 0; previous < index; previous = previous + 1 {
      if database.environment_variables[previous].name == variable.name {
        issues.push(DuplicateEnvironmentVariableIssue(variable.name))
      }
    }
    let mut invalid = !dbc_token_is_valid(variable.name) ||
      variable.value_type < 0 ||
      variable.value_type > 2 ||
      variable.minimum.is_nan() ||
      variable.minimum.is_inf() ||
      variable.maximum.is_nan() ||
      variable.maximum.is_inf() ||
      variable.initial_value.is_nan() ||
      variable.initial_value.is_inf() ||
      variable.minimum > variable.maximum ||
      variable.initial_value < variable.minimum ||
      variable.initial_value > variable.maximum ||
      variable.access_type < 0 ||
      variable.access_type > 3 ||
      variable.access_nodes.is_empty()
    for node in variable.access_nodes {
      if !dbc_token_is_valid(node) {
        invalid = true
      }
    }
    if invalid {
      issues.push(InvalidEnvironmentVariableIssue(variable.name))
    }
  }
}

///|
/// Validate cross-field and bit-layout constraints that are not syntax errors.
pub fn Database::validate(self : Database) -> Array[ValidationIssue] {
  let issues : Array[ValidationIssue] = []
  validate_comments(self, issues)
  validate_attributes(self, issues)
  validate_global_value_tables(self, issues)
  validate_environment_variables(self, issues)
  validate_signal_groups(self, issues)
  for index, message in self.messages {
    for previous = 0; previous < index; previous = previous + 1 {
      if self.messages[previous].id == message.id {
        issues.push(DuplicateMessageIdIssue(message.id))
      }
    }
    validate_message(message, issues)
  }
  issues
}