import Foundation
import Fluent

/**
 Represents a table where players are still joining and leaving.
 */
final class WaitingTable: AbstractTable<WaitingPlayer> {

    /// The table contains enough players to start a game
    var isFull: Bool {
        players.count >= maximumPlayersPerTable
    }

    init(id: UUID, name: TableName, isPublic: Bool, players: [User]) {
        let players = players.map { WaitingPlayer(name: $0.name, points: $0.points) }
        players.first!.isNextActor = true
        super.init(id: id, name: name, isPublic: isPublic, players: players)
        if isFull {
            self.players.forEach { $0.canStartGame = true }
        }
    }

    /**
     Create a new table.
     - Parameter name: The name of the table
     - Parameter isPublic: The table is visible and joinable by everyone
     */
    init(newTable object: Table) {
        let user = object.players[0]
        let player = WaitingPlayer(name: user.name, points: user.points)
        player.isNextActor = true
        super.init(id: object.id!, name: object.name, isPublic: object.isPublic, players: [player])
    }

    /**
     Convert another table to a waiting table.

     This is needed when a player leaves an active table.
     - Parameter oldTable: The table to convert
     - Parameter player: The name of the player to remove from the table.
     */
    init?(oldTable: ManageableTable, removing player: PlayerName) {
        // TODO: End game and distribute points
        let players = oldTable.allPlayers
            .filter {
                guard $0.name == player else {
                    return true
                }
                _ = $0.disconnect()
                return false
            }
            .map { WaitingPlayer(name: $0.name, points: $0.totalPoints, socket: $0.socket) }
        guard !players.isEmpty else {
            return nil
        }
        players.first!.isNextActor = true
        super.init(table: oldTable, players: players)
    }

    /**
     Convert another table to a waiting table.

     This is needed when a player leaves an active table.
     - Parameter table: The table to convert
     */
    init(oldTableAdvancedByOne table: ManageableTable) {
        let players = table.allPlayers
            .rotatedByOne()
            .map(WaitingPlayer.init)
        super.init(table: table, players: players)
        players.forEach { $0.canStartGame = true }
        players.first!.isNextActor = true
    }

    /**
     Add a player to the table.
     - Parameter player: The name of the player to add
     - Returns: `true`, if the player could be added, `false` if the table is full
     */
    func add(player: PlayerName, points: Int) -> Bool {
        guard !isFull else {
            return false
        }
        let player = WaitingPlayer(name: player, points: points)
        players.append(player)
        // Allow dealing of cards if table is full
        if isFull {
            players.forEach { $0.canStartGame = true }
        }
        return true
    }

    /**
     Perform an action on the waiting table.

     Only dealing is a valid action (if the table is full)
     - Parameter action: The action to perform
     - Parameter player: The name of the player
     */
    override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
        // Only dealing is allowed...
        guard action == .deal else {
            return (.tableStateInvalid, nil)
        }
        // and only when table is full
        guard isFull else {
            return (.tableStateInvalid, nil)
        }
        guard let player = players.player(named: name) else {
            print("Unexpected action \(action) for missing player \(name) at table \(self.name)")
            return (.tableStateInvalid, nil)
        }

        guard player.canPerform(.deal) else {
            print("Player \(name) cant perform deal, although table is full")
            return (.tableStateInvalid, nil)
        }
        let table = DealingTable(table: self)
        return (.success, table)
    }

    override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
        // No cards playable while waiting
        (.tableStateInvalid, nil)
    }
}
