import Foundation

/**
 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: TableId, name: TableName, isPublic: Bool, players: [PlayerName]) {
        let players = players.map { WaitingPlayer(name: $0) }
        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 name: TableName, isPublic: Bool, creator: PlayerName) {
        let player = WaitingPlayer(name: creator)
        player.isNextActor = true
        super.init(id: .newToken(), name: name, isPublic: 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) {
        let players = oldTable.allPlayers
            .filter {
                guard $0.name == player else {
                    return true
                }
                _ = $0.disconnect()
                return false
            }
            .map { WaitingPlayer(name: $0.name, socket: $0.socket) }
        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 oldTable: The table to convert
     */
    init(oldTableAdvancedByOne table: ManageableTable) {
        let players = table.allPlayers
            .rotatedByOne()
            .map { WaitingPlayer(name: $0.name, socket: $0.socket) }
        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) -> Bool {
        guard !isFull else {
            return false
        }
        let player = WaitingPlayer(name: player)
        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)
    }
}
