///|
pub(all) struct SafetyInvariantViolation {
  violation_type : String
  description : String
  affected_route_id : RouteId?
} derive(Debug)

///|
pub fn InterlockingTable::verify_all_safety_invariants(
  self : InterlockingTable,
  station : @topology.Station,
) -> Array[SafetyInvariantViolation] {
  let violations : Array[SafetyInvariantViolation] = []
  let report = self.audit_safety(station)
  for violation in report.violations() {
    violations.push({
      violation_type: violation.kind.to_string(),
      description: violation.description,
      affected_route_id: violation.affected_route_id,
    })
  }

  violations
}

///|
fn InterlockingTable::route_has_emergency_condition(
  self : InterlockingTable,
  station : @topology.Station,
  active_route : ActiveRouteState,
) -> (Bool, Bool) {
  let rule = active_route.rule
  let mut should_drop_signal = false
  let mut should_fault_route = false

  for sw in rule.switches {
    match station.graph.get_turnout(sw.turnout_id) {
      None => {
        should_drop_signal = true
        should_fault_route = true
      }
      Some(turnout) =>
        if turnout.position != sw.required_position || !turnout.is_locked {
          should_drop_signal = true
          should_fault_route = true
        }
    }
  }
  for flank in rule.flank_switches {
    match station.graph.get_turnout(flank.turnout_id) {
      None => {
        should_drop_signal = true
        should_fault_route = true
      }
      Some(turnout) =>
        if turnout.position != flank.required_position || !turnout.is_locked {
          should_drop_signal = true
          should_fault_route = true
        }
    }
  }

  for hostile_id in rule.hostile_route_ids {
    if self.is_route_reserved(hostile_id) {
      should_drop_signal = true
      should_fault_route = true
    }
  }

  match station.graph.get_signal(rule.entry_signal) {
    None => {
      should_drop_signal = true
      should_fault_route = true
    }
    Some(signal) => {
      if active_route.state == RouteFault && signal.current_aspect != Red {
        should_drop_signal = true
        should_fault_route = true
      }
      for track_id in rule.track_sections {
        match station.graph.get_track(track_id) {
          None => {
            should_drop_signal = true
            should_fault_route = true
          }
          Some(track) => {
            if track.state == Fault {
              should_drop_signal = true
              should_fault_route = true
            }
            if signal.current_aspect.is_proceed() &&
              (track.state == Occupied || track.state == Fault) {
              should_drop_signal = true
            }
          }
        }
      }
    }
  }

  if !active_route.lifecycle_snapshot_is_safe() {
    should_drop_signal = true
    should_fault_route = true
  }
  (should_drop_signal, should_fault_route)
}

///|
pub fn InterlockingTable::enforce_emergency_fail_safe(
  self : InterlockingTable,
  station : @topology.Station,
) -> Int {
  let mut count = 0

  for _, active_route in self.active_routes {
    if !self.is_route_reserved(active_route.rule.id) {
      continue
    }
    let (should_drop_signal, should_fault_route) = self.route_has_emergency_condition(
      station, active_route,
    )
    if should_drop_signal {
      let signal_needs_drop = match
        station.graph.get_signal(active_route.rule.entry_signal) {
        Some(signal) => signal.current_aspect.is_proceed()
        None => true
      }
      if signal_needs_drop {
        let _ = station.set_signal_aspect(
          active_route.rule.entry_signal,
          @topology.SignalAspect::Red,
        )
        count = count + 1
      }
      if should_fault_route {
        active_route.state = RouteFault
      }
    }
  }

  count
}