import Vapor

private var eventLogHandle: FileHandle?

private var registeredGuests = Set<String>()

private var declinedGuests = Set<String>()

private var guestListPath: URL!

private var declinedListPath: URL!

private let df: DateFormatter = {
    let df = DateFormatter()
    df.dateFormat = "dd.MM. HH:mm"
    return df
}()

func guestCount() -> Int {
    registeredGuests
        .reduce([]) { $0 + $1.components(separatedBy: "+") }
        .reduce([]) { $0 + $1.components(separatedBy: "-") }
        .reduce([]) { $0 + $1.components(separatedBy: "und ") }
        .reduce([]) { $0 + $1.components(separatedBy: "&") }
        //.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
        .count
}


func add(guest: String) -> String {
    registeredGuests.insert(guest)
    declinedGuests.remove(guest)
    defer { saveLists() }
    return log(event: "\(guest) registered")
}

func remove(guest: String) -> String {
    registeredGuests.remove(guest)
    declinedGuests.insert(guest)
    defer { saveLists() }
    return log(event: "\(guest) declined")
}

private func saveLists() {
    saveList(registeredGuests, named: "guest", to: guestListPath)
    saveList(declinedGuests, named: "declined", to: declinedListPath)
}

private func saveList(_ set: Set<String>, named name: String, to url: URL) {
    guard let list = set.sorted().joined(separator: "\n").data(using: .utf8) else {
        log("Failed to save \(name) list, no data")
        return
    }
    do {
        try list.write(to: url)
        log("Saved \(name) list with \(set.count) entries")
    } catch {
        log("Failed to write \(name) list: \(error)")
    }
}

private func loadList(from url: URL) throws -> Set<String> {
    let users = try String(contentsOf: url)
        .split(separator: "\n")
        .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
        .filter { !$0.isEmpty }
    log("Loaded list \(url.path) (\(users.count) entries)")
    return .init(users)
}

private func log(event: String) -> String {
    guard let handle = eventLogHandle else {
        return "No handle"
    }
    let date = Date()
    let dateString = df.string(from: date)
    guard let entry = "[\(dateString)] \(event)\n".data(using: .utf8) else {
        return "Invalid name"
    }
    guard #available(macOS 10.15.4, *) else {
        handle.write(entry)
        handle.synchronizeFile()
        return "Success"
    }
    do {
        try handle.write(contentsOf: entry)
        handle.synchronizeFile()
        return "Success"
    } catch {
        return "Save failed"
    }
}

private func createFileIfNeeded(at path: URL) throws {
    guard !FileManager.default.fileExists(atPath: path.path) else {
        return
    }
    try Data().write(to: path)
}

private func readConfig(at path: URL) throws -> URL {
    do {
        let logPath = try String(contentsOf: path)
            .trimmingCharacters(in: .whitespacesAndNewlines)
        return URL(fileURLWithPath: logPath)
    } catch {
        log("Failed to read configuration file at \(path.path): \(error)")
        throw error
    }
}

// configures your application
public func configure(_ app: Application) throws {
    // uncomment to serve files from /Public folder
    // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    app.http.server.configuration.port = 9001

    let configPath = URL(fileURLWithPath: app.directory.resourcesDirectory)
        .appendingPathComponent("paths.conf")
    let logFile = try readConfig(at: configPath)
    let listDirectory = URL(fileURLWithPath: app.directory.publicDirectory)
        .appendingPathComponent("lists")

    try Log.set(logFile: logFile.path)

    let eventLog = listDirectory.appendingPathComponent("events.txt")
    guestListPath = listDirectory.appendingPathComponent("registered.txt")
    declinedListPath = listDirectory.appendingPathComponent("declined.txt")
    
    // Create the files
    try createFileIfNeeded(at: eventLog)
    try createFileIfNeeded(at: guestListPath)
    try createFileIfNeeded(at: declinedListPath)
    
    // Create handle to write events
    eventLogHandle = try FileHandle(forWritingTo: eventLog)
    if #available(macOS 10.15.4, *) {
        try eventLogHandle?.seekToEnd()
    } else {
        // Fallback on earlier versions
        eventLogHandle?.seekToEndOfFile()
    }
    
    registeredGuests = try loadList(from: guestListPath)
    declinedGuests = try loadList(from: declinedListPath)
    
    // register routes
    try routes(app)
    
    let date = Date()
    let dateString = df.string(from: date)
    log("[\(dateString)] Server started: \(registeredGuests.count) registered, \(declinedGuests.count) declined, event log open: \(eventLogHandle != nil)")

}
