import Foundation
import Vapor

final class Database {

    private let players: PlayerManagement

    private let tables: TableManagement

    init(storageFolder: URL) throws {
        self.players = try PlayerManagement(storageFolder: storageFolder)
        self.tables = try TableManagement(storageFolder: storageFolder)
    }

    // MARK: Players & Sessions

    func registerPlayer(named name: PlayerName, hash: PasswordHash) -> SessionToken? {
        players.registerPlayer(named: name, hash: hash)
    }

    func passwordHashForExistingPlayer(named name: PlayerName) -> PasswordHash? {
        players.passwordHash(ofRegisteredPlayer: name)
    }

    func deletePlayer(named name: PlayerName) {
        _ = players.deletePlayer(named: name)
        tables.leaveTable(player: name)
    }

    func isValid(sessionToken token: SessionToken) -> Bool {
        players.isValid(sessionToken: token)
    }

    func startSession(socket: WebSocket, sessionToken: SessionToken) -> Bool {
        guard let player = players.registeredPlayerExists(withSessionToken: sessionToken) else {
            return false
        }
        return tables.connect(player: player, using: socket)
    }

    private func didReceive(message: String, forSessionToken token: SessionToken) {
        // TODO: Handle client requests
        print("Session \(token.prefix(6)): \(message)")
    }

    func endSession(forSessionToken sessionToken: SessionToken) {
        guard let player = players.endSession(forSessionToken: sessionToken) else {
            return
        }
        tables.disconnect(player: player)
    }

    /**
     Start a new session for an existing user.
     - Parameter name: The user name
     - Returns: The generated access token for the session
     */
    func startNewSessionForRegisteredPlayer(named name: PlayerName) -> SessionToken {
        players.startNewSessionForRegisteredPlayer(named: name)
    }

    func registeredPlayerExists(withSessionToken token: SessionToken) -> PlayerName? {
        players.registeredPlayerExists(withSessionToken: token)
    }

    func currentTableOfPlayer(named player: PlayerName) -> TableInfo? {
        tables.tableInfo(player: player)
    }

    // MARK: Tables

    /**
     Create a new table with optional players.
     - Parameter name: The name of the table
     - Parameter players: The player creating the table
     - Parameter isPublic: Indicates that this is a game joinable by everyone
     - Returns: The table id
     */
    func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableInfo {
        tables.createTable(named: name, player: player, isPublic: isPublic)
    }

    func getPublicTableInfos() -> [PublicTableInfo] {
        tables.publicTableList
    }

    func join(tableId: TableId, playerToken: SessionToken) -> Result<TableInfo,JoinTableResult> {
        guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
            return .failure(.invalidToken)
        }
        return tables.join(tableId: tableId, player: player)
    }
    
    func leaveTable(playerToken: SessionToken) -> Bool {
        guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
            return false
        }
        tables.leaveTable(player: player)
        return true
    }
    
    func performAction(playerToken: SessionToken, action: PlayerAction) -> PlayerActionResult {
        guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
            return .invalidToken
        }
        return tables.performAction(player: player, action: action)
    }

    func select(game: GameType, playerToken: SessionToken) -> PlayerActionResult {
        guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
            return .invalidToken
        }
        return tables.select(game: game, player: player)
    }

    func play(card: Card, playerToken: SessionToken) -> PlayerActionResult {
        guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
            return .invalidToken
        }
        return tables.play(card: card, player: player)
    }
}
