import Foundation

// MARK: - Models

public enum AgeGroup: Int, CustomStringConvertible, CaseIterable {
    case junior = 1
    case adult = 2
    case senior = 3

    public var description: String {
        switch self {
        case .junior: return "Junior"
        case .adult: return "Adult"
        case .senior: return "Senior"
        }
    }
}

public struct Runner: Equatable {
    public let name: String
    public let ageGroup: AgeGroup
    public let time: String
    public let number: Int

    public init(name: String, ageGroup: AgeGroup, time: String, number: Int) {
        self.name = name
        self.ageGroup = ageGroup
        self.time = time
        self.number = number
    }
}

public enum SortKey: String, CaseIterable {
    case name
    case ageGroup
    case time
    case number

    public func sort(_ lhs: Runner, _ rhs: Runner) -> Bool {
        switch self {
        case .name:     return lhs.name < rhs.name
        case .ageGroup: return lhs.ageGroup.rawValue < rhs.ageGroup.rawValue
        case .time:     return lhs.time < rhs.time
        case .number:   return lhs.number < rhs.number
        }
    }
}

// MARK: - IO

public func readFile(_ path: String) -> String {
    (try? String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8)) ?? ""
}

// MARK: - Parsing

public func parseResults(_ content: String, separator: String) -> [Int: String] {
    let pairs = content
        .components(separatedBy: .newlines)
        .compactMap { line -> (Int, String)? in
            let cols = line.components(separatedBy: separator)
            guard cols.count >= 2, let key = Int(cols[0]) else { return nil }
            return (key, cols[1])
        }
    return Dictionary(pairs, uniquingKeysWith: { _, last in last })
}

public func parseStarters(_ content: String, separator: String) -> [(nr: Int, ageGroup: Int, name: String)] {
    content
        .components(separatedBy: .newlines)
        .compactMap { line in
            let cols = line.components(separatedBy: separator)
            guard cols.count >= 3, let nr = Int(cols[0]), let ageGroup = Int(cols[1])
            else { return nil }
            return (nr: nr, ageGroup: ageGroup, name: cols[2])
        }
}

public func assembleRunners(
    starters: [(nr: Int, ageGroup: Int, name: String)], results: [Int: String]
) -> [Runner] {
    starters.compactMap { starter in
        guard let time = results[starter.nr],
            let group = AgeGroup(rawValue: starter.ageGroup)
        else {
            return nil
        }
        return Runner(name: starter.name, ageGroup: group, time: time, number: starter.nr)
    }
}

public func nonParticipants(
    starters: [(nr: Int, ageGroup: Int, name: String)], results: [Int: String]
) -> [Runner] {
    starters.compactMap { starter in
        guard results[starter.nr] == nil,
            let group = AgeGroup(rawValue: starter.ageGroup)
        else {
            return nil
        }
        return Runner(name: starter.name, ageGroup: group, time: "DNS", number: starter.nr)
    }
}

// MARK: - Transforms

public func visibleRunners(
    _ runners: [Runner],
    query: String,
    includedGroups: Set<AgeGroup>,
    sortKey: SortKey
) -> [Runner] {
    let needle = query.lowercased()
    return runners
        .filter { includedGroups.contains($0.ageGroup) }
        .filter { needle.isEmpty || $0.name.lowercased().contains(needle) }
        .sorted(by: sortKey.sort)
}

public func toggling(_ groups: Set<AgeGroup>, _ group: AgeGroup) -> Set<AgeGroup> {
    groups.contains(group) ? groups.subtracting([group]) : groups.union([group])
}

// MARK: - Formatting

public func menuLabel(_ text: String, on: Bool) -> String {
    on ? "[\(text)]" : " \(text) "
}

public func formatRow(_ nr: String, _ name: String, _ group: String, _ time: String) -> String {
    nr.padding(toLength: 6, withPad: " ", startingAt: 0)
        + name.padding(toLength: 22, withPad: " ", startingAt: 0)
        + group.padding(toLength: 9, withPad: " ", startingAt: 0)
        + time
}
