import Foundation

final class PlayingTable: AbstractTable<PlayingPlayer> {

    let game: GameType

    let leadingTrumps: Int

    var indexOfTrickStarter = 0

    var didDoubleInCurrentRound = false

    var hasCompletedTrick: Bool {
        !players.contains { $0.playedCard == nil }
    }

    var nextTrick: [Card] {
        hasCompletedTrick ? [] : currentTrick
    }

    var currentTrick: [Card] {
        players.rotated(toStartAt: indexOfTrickStarter).compactMap { $0.playedCard }
    }

    var completedTrick: Trick? {
        let trick = currentTrick
        guard trick.count == maximumPlayersPerTable else {
            return nil
        }
        return trick
    }

    var allCardsPlayed: Bool {
        !players.contains { !$0.cards.isEmpty }
    }

    var totalNumberOfDoubles: Int {
        players.map {
            $0.numberOfRaises + ($0.didDouble ? 1 : 0)
        }.reduce(0,+)
    }

    override var playedGame: GameType? {
        game
    }

    convenience init(table: BiddingTable, game: GameType, playedBy player: BiddingPlayer) {
        let calledAce = game.calledSuit?.ace
        let players = table.players.map {
            PlayingPlayer(player: $0, leads: $0 == player, calledAce: calledAce)
        }
        self.init(table: table, players: players, game: game)
    }

    convenience init(wedding table: WeddingTable, offeredBy offerer: WeddingPlayer, acceptedBy player: WeddingPlayer) {
        let players = table.players.map {
            PlayingPlayer(player: $0, leads: $0 == player || $0 == offerer, calledAce: nil)
        }
        self.init(table: table, players: players, game: .hochzeit)
    }

    private init(table: ManageableTable, players: [PlayingPlayer], game: GameType) {
        self.game = game
        let selectorCards = players.filter { $0.leadsGame || $0.isCallee }.map { $0.cards }.reduce([], +)
        let otherCards = players.filter { !$0.leadsGame && !$0.isCallee }.map { $0.cards }.reduce([], +)
        let selectorTrumps = game.sortingType.consecutiveTrumps(selectorCards)
        let otherTrumps = game.sortingType.consecutiveTrumps(otherCards)
        self.leadingTrumps = max(selectorTrumps, otherTrumps)
        super.init(table: table, players: players)
        players.forEach { $0.sortCards(for: game) }
        players.first!.isNextActor = true
    }

    override func cardStackPosition(ofPlayerAt index: Int) -> Int {
        (4 + index - indexOfTrickStarter) % 4
    }

    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 playing table \(self.name)")
            return (.tableStateInvalid, nil)
        }
        guard action == .doubleDuringGame else {
            print("Player \(name) wants to perform action \(action) on playing table")
            return (.tableStateInvalid, nil)
        }
        guard player.canPerform(.doubleDuringGame) else {
            print("Player \(name) is not allowed to raise")
            return (.tableStateInvalid, nil)
        }
        player.numberOfRaises += 1
        players.forEach { $0.switchLead() }
        self.didDoubleInCurrentRound = true
        return (.success, nil)
    }

    override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
        guard let player = players.player(named: name) else {
            print("Player \(name) unexpectedly missing from playing table \(self.name)")
            return (.tableStateInvalid, nil)
        }
        guard player.isNextActor else {
            print("Player \(name) wants to play card but is not active")
            return (.tableStateInvalid, nil)
        }
        guard player.canPlay(card: card, for: nextTrick, in: game) else {
            return (.tableStateInvalid, nil)
        }
        if hasCompletedTrick {
            players.forEach { $0.playedCard = nil }
            indexOfTrickStarter = players.index(of: player)
        }
        player.play(card: card)
        if let completedTrick = completedTrick {
            return didFinish(trick: completedTrick, in: game)
        } else {
            let next = players.next(after: player)
            next.isNextActor = true
            player.isNextActor = false
            return (.success, nil)
        }
    }

    override func cards(forPlayerAt index: Int) -> [PlayableCard] {
        players[index].playableCards(for: nextTrick, in: game)
    }

    private func didFinish(trick: Trick, in game: GameType) -> (result: PlayerActionResult, table: ManageableTable?) {
        let index = trick.highCardIndex(forGame: game)
        let winner = players[(indexOfTrickStarter + index) % 4]
        players.forEach {
            $0.isNextActor = false
            $0.canStillRaise = didDoubleInCurrentRound
        }
        winner.wonTricks.append(trick)
        winner.isNextActor = true

        if game == .bettel && winner.selectsGame {
            // A bettel is lost if a single trick is won by the game selector
            return finishedGame()
        }

        didDoubleInCurrentRound = false
        if allCardsPlayed {
            return finishedGame()
        }
        return (.success, nil)
    }

    private func finishedGame() -> (result: PlayerActionResult, table: ManageableTable?) {
        let table = FinishedTable(table: self)
        return (.success, table)
    }
}
