// // ConvertingServersOperation.swift // PrivadoVPN // // Created by Juraldinio on 3/21/21. // Copyright © 2021 Privado LLC. All rights reserved. // import Foundation protocol ConvertingServersOperationOutput { var result: MainOperationsResult? { get } } final class ConvertingServersOperation: Operation { private(set) var result: MainOperationsResult? private let interactor: MainInteractorInput init(interactor: MainInteractorInput) { self.interactor = interactor } override func main() { guard let fetchingOutput = self.dependencies.first as? FetchingServersOperationOutput , let interactorResult = fetchingOutput.result , let result = interactorResult.optional else { return } let serverRecords = ConvertingServersOperation.convert(serverGroups: result.serverGroups) let preferred = self.interactor.preferredServer let context = ConvertingServersOperation.prepareContext(using: result.geoLocation, serverRecords: serverRecords, preferred: preferred, interactor: self.interactor) self.result = MainOperationsResult(serverRecords: serverRecords, context: context) } // MARK: - Private static private static func convert(serverGroups: [ServerGroup]) -> [ServerRecord] { return serverGroups.reduce(into: []) { acc, group in group.locations.forEach { location in let records = location.cities .sorted(by: <) .compactMap { city -> ServerRecord? in let leisureServer: Server? = nil guard let server = location.servers(in: city) .reduce(into: leisureServer, { acc, server in if let srv = acc, srv.capacity < server.capacity { acc = srv } else { acc = server } }) else { return nil } if acc.first(where: { $0.group == group.group && $0.countryCode == location.country && $0.city == city }).isExist { return nil } return ServerRecord(group: group.group, city: city, countryCode: location.country, server: server) } acc.append(contentsOf: records) } } } private static func prepareContext(using geoLocation: GeoLocation?, serverRecords: [ServerRecord], preferred: PreferredServerType?, interactor: MainInteractorInput) -> MainRecordsContext { // Best server let bestRecord = ConvertingServersOperation.calculateBestRecord(using: interactor, for: serverRecords, current: geoLocation) // Preferred var record: ServerRecord? if preferred == .last , let server = interactor.restoreServer() , let found = ConvertingServersOperation.searchRecord(for: server, in: serverRecords) { record = found } else if preferred == .random { let availableRecords: [ServerRecord] if interactor.isFreemium { availableRecords = serverRecords.filter { $0.isFreemium } } else if interactor.isOverquota { availableRecords = serverRecords.filter { $0.isOverquota } } else { availableRecords = serverRecords.filter { $0.isPremium } } let randomIndex = Int.random(in: 0..<(availableRecords.count)) if let searched = availableRecords[safe: randomIndex] { record = searched } else { record = bestRecord } } else { record = bestRecord } return MainRecordsContext(geoLocation: geoLocation, bestRecord: bestRecord, selectedRecord: record) } private static func calculateBestRecord(using interactor: MainInteractorInput, for records: [ServerRecord]?, current location: GeoLocation?) -> ServerRecord? { enum FindBetterserver: Error { case better } guard let records = records else { return nil } let availableRecords: [ServerRecord] if interactor.isFreemium { availableRecords = records.filter { $0.isFreemium } } else if interactor.isOverquota { availableRecords = records.filter { $0.isOverquota } } else { availableRecords = records.filter { $0.isPremium } } guard let location = location , let latitude = Double(location.lat) , let longitude = Double(location.long) , let bestRecord = BestRecord.calculate(for: availableRecords, latitude: latitude, longitude: longitude) else { return nil } return bestRecord.record } private static func searchRecord(for server: Server, in records: [ServerRecord]) -> ServerRecord? { records.first(where: { $0.server == server}) } } extension ConvertingServersOperation: ConvertingServersOperationOutput {}