// // AircraftRepository.swift // LearnMapKit // // Aircraft data management and state tracking // import Foundation import Combine // MARK: - Aircraft Model struct Aircraft: Identifiable, Equatable { let id: Int // ICAO address let icaoAddress: Int let icaoName: String? let position: Position? let altitude: Int? let lastSeen: Date let flightInfo: FlightInfo? struct Position: Equatable { let latitude: Double let longitude: Double let accuracy: PositionAccuracy let timestamp: Date enum PositionAccuracy { case unknown, low, medium, high } } struct FlightInfo { let from: String? let to: String? let route: String? } // Computed properties for UI var isActive: Bool { Date().timeIntervalSince(lastSeen) < 300 // 5 minutes } var displayName: String { icaoName ?? "Unknown (\(String(format: "%06X", icaoAddress)))" } } // MARK: - Aircraft Repository Protocol protocol AircraftRepository: AnyObject { var aircraftStream: AnyPublisher<[Aircraft], Never> { get } var aircraftCount: AnyPublisher { get } func getAllAircraft() -> [Aircraft] func getAircraft(withId id: Int) -> Aircraft? func updateAircraft(with event: ADSBDataEvent) func removeInactiveAircraft() func clearAll() } // MARK: - Default Implementation @MainActor class DefaultAircraftRepository: AircraftRepository { // MARK: - Published Properties var aircraftStream: AnyPublisher<[Aircraft], Never> { aircraftSubject.eraseToAnyPublisher() } var aircraftCount: AnyPublisher { aircraftSubject .map { $0.count } .removeDuplicates() .eraseToAnyPublisher() } // MARK: - Private Properties private let aircraftSubject = CurrentValueSubject<[Aircraft], Never>([]) private var aircraftCache: [Int: Aircraft] = [:] // Dependencies private let tracker: AircraftTracker init(tracker: AircraftTracker = DefaultAircraftTracker()) { self.tracker = tracker startCleanupTimer() } // MARK: - AircraftRepository Implementation func getAllAircraft() -> [Aircraft] { Array(aircraftCache.values) .filter { $0.isActive } .sorted { $0.lastSeen > $1.lastSeen } } func getAircraft(withId id: Int) -> Aircraft? { aircraftCache[id] } func updateAircraft(with event: ADSBDataEvent) { switch event { case .aircraftIdentification(let address, let icaoName): updateIdentification(address: address, icaoName: icaoName) case .aircraftPosition(let address, let latitude, let longitude): updatePosition(address: address, latitude: latitude, longitude: longitude) case .aircraftAltitude(let address, let altitude): updateAltitude(address: address, altitude: altitude) case .rawMessage, .error: break // Handle these separately if needed } } func removeInactiveAircraft() { let activeAircraft = aircraftCache.filter { _, aircraft in aircraft.isActive } if activeAircraft.count != aircraftCache.count { aircraftCache = activeAircraft publishUpdate() } } func clearAll() { aircraftCache.removeAll() publishUpdate() } // MARK: - Private Update Methods private func updateIdentification(address: Int, icaoName: String) { let aircraft = getOrCreateAircraft(address: address) let updated = Aircraft( id: aircraft.id, icaoAddress: aircraft.icaoAddress, icaoName: icaoName, position: aircraft.position, altitude: aircraft.altitude, lastSeen: Date(), flightInfo: aircraft.flightInfo ) aircraftCache[address] = updated publishUpdate() } private func updatePosition(address: Int, latitude: Double, longitude: Double) { Task { // Update the tracker for position calculation await tracker.updatePosition(address: address, latitude: latitude, longitude: longitude) // Get calculated position from tracker if let calculatedPosition = await tracker.getPosition(address: address) { let aircraft = getOrCreateAircraft(address: address) let position = Aircraft.Position( latitude: calculatedPosition.0, longitude: calculatedPosition.1, accuracy: .medium, // TODO: Determine accuracy from tracker timestamp: Date() ) let updated = Aircraft( id: aircraft.id, icaoAddress: aircraft.icaoAddress, icaoName: aircraft.icaoName, position: position, altitude: aircraft.altitude, lastSeen: Date(), flightInfo: aircraft.flightInfo ) aircraftCache[address] = updated publishUpdate() } } } private func updateAltitude(address: Int, altitude: Int) { let aircraft = getOrCreateAircraft(address: address) let updated = Aircraft( id: aircraft.id, icaoAddress: aircraft.icaoAddress, icaoName: aircraft.icaoName, position: aircraft.position, altitude: altitude, lastSeen: Date(), flightInfo: aircraft.flightInfo ) aircraftCache[address] = updated publishUpdate() } private func getOrCreateAircraft(address: Int) -> Aircraft { if let existing = aircraftCache[address] { return existing } let newAircraft = Aircraft( id: address, icaoAddress: address, icaoName: nil, position: nil, altitude: nil, lastSeen: Date(), flightInfo: nil ) return newAircraft } private func publishUpdate() { let currentAircraft = getAllAircraft() aircraftSubject.send(currentAircraft) } private func startCleanupTimer() { Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in self?.removeInactiveAircraft() } } } // MARK: - Aircraft Tracker Protocol protocol AircraftTracker: Actor { func updateIdentification(address: Int, icaoName: String) async func updatePosition(address: Int, latitude: Double, longitude: Double) async func updateAltitude(address: Int, altitude: Int) async func getPosition(address: Int) async -> (Double, Double)? func getAltitude(address: Int) async -> Int? func getICAOName(address: Int) async -> String? } // MARK: - Default Aircraft Tracker (Adapter for existing AirPlaneTracker) actor DefaultAircraftTracker: AircraftTracker { private let airplaneTracker = AirPlaneTracker() func updateIdentification(address: Int, icaoName: String) async { airplaneTracker.addDF17Indentification(address, icaoName) } func updatePosition(address: Int, latitude: Double, longitude: Double) async { // Note: The original tracker expects CPR coordinates, not lat/long // This is a simplified adapter - you might need to adjust based on your data flow airplaneTracker.addDF17AirBornPosition(address, latitude, longitude, 0, true) } func updateAltitude(address: Int, altitude: Int) async { // Altitude is typically updated with position in the original tracker // This might need adjustment based on your specific needs } func getPosition(address: Int) async -> (Double, Double)? { return airplaneTracker.getPosition(address) } func getAltitude(address: Int) async -> Int? { return airplaneTracker.getAltitude(address) } func getICAOName(address: Int) async -> String? { return airplaneTracker.getICAOname(address) } }