import Foundation

final class BiddingTable: AbstractTable<BiddingPlayer> {
    
    var gameToOutbid: GameType.GameClass = .none

    var indexOfHighestBidder = 0

    var remainingBidders: Int {
        players.filter { $0.isStillBidding }.count
    }

    var isWaitingForGameSelection: Bool {
        players.contains { $0.selectsGame }
    }

    init(table: DealingTable) {
        // Add new cards to the players
        let newCards = Dealer.dealRemainingCards(of: table.players.map { $0.cards })
        let players: [BiddingPlayer] = table.players.enumerated().map { index, player in
            player.cards = (player.cards + newCards[index])
                .sortedCards(order: NormalCardOrder.self)
            player.isNextActor = false
            return BiddingPlayer(player: player)
        }
        players.first!.isNextActor = true
        super.init(table: table, players: players)
    }

    init(wedding table: WeddingTable, outbidBy player: WeddingPlayer) {
        gameToOutbid = .hochzeit
        indexOfHighestBidder = table.players.index(of: player)
        // All players can bid again, except the wedding offerer
        let players = table.players.map(BiddingPlayer.init)
        players[indexOfHighestBidder].isNextActor = true
        super.init(table: table, players: players)

        // Choose the player after the one who discarded the wedding
        selectNextBidder()
    }

    func select(game: GameType, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard let player = players.player(named: name) else {
            print("Player \(name) unexpectedly missing from bidding table \(self.name)")
            return (.tableStateInvalid, nil)
        }
        guard player.selectsGame else {
            print("Player \(name) does not select the game")
            return (.tableStateInvalid, nil)
        }
        guard gameToOutbid.allows(game: game) else {
            print("Game \(game) not allowed for class \(gameToOutbid)")
            return (.tableStateInvalid, nil)
        }
        guard player.canPlay(game: game) else {
            print("Player \(game) can't play game \(game)")
            return (.tableStateInvalid, nil)
        }

        let table = PlayingTable(table: self, game: game, playedBy: player)
        return (.success, table)
    }

    @discardableResult
    private func selectNextBidder() -> Bool {
        guard let index = players.firstIndex(where: { $0.isNextActor }) else {
            print("Bidding: No current actor found to select next bidder")
            return false
        }
        players[index].isNextActor = false
        let newActor = players.rotated(toStartAt: (index + 1) % 4).first(where: { $0.isStillBidding })!
        newActor.isNextActor = true
        return true
    }

    override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard let player = players.player(named: name) else {
            print("Player \(name) unexpectedly missing from bidding table \(self.name)")
            return (.tableStateInvalid, nil)
        }
        guard player.canPerform(action) else {
            return (.tableStateInvalid, nil)
        }
        
        switch action {
        case .offerWedding:
            return performWeddingOffer(forPlayer: player)
        case .increaseOrMatchGame:
            return performBidIncrease(forPlayer: player)
        case .withdrawFromAuction:
            return performWithdrawl(forPlayer: player)
        default:
            return (.tableStateInvalid, nil)
        }
    }

    private func performWeddingOffer(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard gameToOutbid.allowsWedding else {
            return (.tableStateInvalid, nil)
        }
        let newTable = WeddingTable(table: self, offerer: player)
        return (.success, newTable)
    }

    private func performBidIncrease(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard player.isNextActor, player.isStillBidding else {
            return (.tableStateInvalid, nil)
        }
        let index = players.index(of: player)
        if index < indexOfHighestBidder {
            // Player sits before the current highest bidder, so only needs to match the game
            indexOfHighestBidder = index
            if gameToOutbid == .solo {
                // Can't be outbid, so player selects game
                players.forEach { $0.isStillBidding = false }
                player.selectsGame = true
                return (.success, nil)
            }
            // TODO: Check that wedding can be offered at the correct times
            // There may be a case where a player sitting before the highest bidder
            // can't offer a wedding anymore although it should be able to
            if !gameToOutbid.allowsWedding {
                players.forEach { $0.isAllowedToOfferWedding = false }
            }
        } else {
            // Player sits after the highest bidder, so must outbid the game
            // Also the case when first starting bidding
            gameToOutbid.increase()
            indexOfHighestBidder = index
            if !gameToOutbid.allowsWedding {
                players.forEach { $0.isAllowedToOfferWedding = false }
            }
        }
        if remainingBidders == 1 {
            moveToGameSelection()
        } else {
            selectNextBidder()
        }
        return (.success, nil)
    }

    private func performWithdrawl(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard player.isStillBidding else {
            return (.tableStateInvalid, nil)
        }
        player.isStillBidding = false
        switch remainingBidders {
        case 0:
            // Nobody wants to play something, so abort the game
            // This case can only be reached when nobody has bid yet
            let table = WaitingTable(oldTableAdvancedByOne: self)
            return (.success, table)
        case 1:
            if gameToOutbid != .none {
                // Last player must play
                player.isNextActor = false
                moveToGameSelection()
                return (.success, nil)
            }
        default:
            break
        }
        selectNextBidder()
        return (.success, nil)
    }

    private func moveToGameSelection() {
        indexOfHighestBidder = players.firstIndex { $0.isStillBidding == true }!
        let highestPlayer = players[indexOfHighestBidder]
        highestPlayer.isStillBidding = false
        highestPlayer.selectsGame = true
        highestPlayer.isNextActor = true
    }

    override func cards(forPlayerAt index: Int) -> [PlayableCard] {
        players[index].cards.unplayable
    }

    override func games(forPlayerAt index: Int) -> [GameConvertible] {
        if isWaitingForGameSelection {
            let player = players[index]
            return gameToOutbid.availableGames.filter(player.canPlay)
        }
        if index <= indexOfHighestBidder {
            return gameToOutbid.availableClasses
        }
        return gameToOutbid.classesWhenOutbidding
    }

    override func gameIsSelected(byPlayerAt index: Int) -> Bool {
        players[index].selectsGame
    }
}
