import Foundation
import WebSocketKit

/**
 Specifies the number of cards of the called suit that a player must have
 to be allowed to play any card of the suit instead of having to play the ace.
 */
private let numberOfCardsToProtectAce = 4

final class PlayingPlayer: CardHoldingPlayer {

    var canStillRaise = true

    var isCalledWithAce: Card?

    var didPlayCalledAce = false

    var playedCard: Card?

    var leadsGame: Bool

    var numberOfRaises = 0

    /// All tricks won by the player in this game
    var wonTricks: [Trick] = []

    init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) {
        leadsGame = leads
        super.init(player: player)
        if let ace = ace, cards.contains(ace) {
            isCalledWithAce = ace
        } else {
            isCalledWithAce = nil
        }
    }

    /// The players has been called in a call game
    var isCallee: Bool {
        isCalledWithAce != nil
    }

    private var isUnknownCallee: Bool {
        isCallee && !didPlayCalledAce
    }

    override var actions: [PlayerAction] {
        guard canStillRaise else {
            return []
        }
        guard !isUnknownCallee else {
            // Player belongs to caller, but other side has raised
            return leadsGame ? [.doubleDuringGame] : []
        }
        guard !leadsGame else {
            return []
        }
        return [.doubleDuringGame]
    }

    func play(card: Card) {
        playedCard = card
        canStillRaise = false
        cards = cards.filter { $0 != card }
        if card == isCalledWithAce {
            leadsGame.toggle()
            didPlayCalledAce = true
        }
    }

    func switchLead() {
        leadsGame.toggle()
    }

    func sortCards(for game: GameType) {
        cards = cards.sortedCards(forGame: game)
    }

    func canPlay(card: Card, for trick: Trick, in game: GameType) -> Bool {
        playableCards(for: trick, in: game).contains { $0.card == card && $0.isPlayable }
    }

    func playableCards(for trick: Trick, in game: GameType) -> [PlayableCard] {
        guard isNextActor else {
            return cards.unplayable
        }
        guard cards.count > 1 else {
            // Last card can always be played
            return cards.playable
        }
        guard let firstCard = trick.first else {
            return playableCardsForStarter(game: game)
        }

        let sorter = game.sortingType

        guard sorter.isTrump(firstCard) else {
            return playableCardsFollowing(suit: firstCard.suit, game: game)
        }
        guard !sorter.hasTrump(in: cards) else {
            // Must follow with trump
            return cards.map { $0.playable(sorter.isTrump($0)) }
        }
        // Can play any card if not in calling game
        guard let suit = game.calledSuit else {
            return cards.playable
        }
        // Can play any card, except the called ace
        let ace = Card(suit, .ass)
        return cards.map { $0.playable($0 != ace) }
    }

    private func playableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) -> [PlayableCard] {
        let sorter = game.sortingType
        let suitCards = sorter.cards(with: playedSuit, in: cards)

        func followSuit() -> [PlayableCard] {
            cards.map { $0.playable(!sorter.isTrump($0) && $0.suit == playedSuit) }
        }

        guard let calledSuit = game.calledSuit else {
            return suitCards.isEmpty ? cards.playable : followSuit()
        }
        let ace = Card(calledSuit, .ass)
        guard !suitCards.isEmpty else {
            // Exclude called ace, all others allowed
            return cards.map { $0.playable($0 != ace) }
        }
        guard calledSuit == playedSuit else {
            // Must follow suit (called ace not present)
            return followSuit()
        }

        // The called suit is played, must commit ace
        guard cards.contains(ace) else {
            // Must follow suit
            return followSuit()
        }
        // Must play ace
        return cards.map { $0.playable($0 == ace) }
    }

    private func playableCardsForStarter(game: GameType) -> [PlayableCard] {
        guard let suit = game.calledSuit else {
            return cards.playable
        }
        let ace = Card(suit, .ass)
        // Check if called ace exists, to prohibit other cards of the same suit
        guard cards.contains(ace) else {
            return cards.playable
        }
        // Jodeln
        if cards.count == numberOfCardsPerPlayer,
           cards.suitCount(suit, in: game) >= numberOfCardsToProtectAce {
            return cards.playable
        }

        // Only ace allowed for the called suit
        return cards.map { $0.playable($0.suit != suit || $0.symbol.isTrumpOrAce) }
    }

    var currentPoints: Int {
        wonTricks.map { $0.points }.reduce(0, +)
    }

    override var states: [PlayerState] {
        var states = super.states
        if didPlayCalledAce {
            states.append(.isCalled)
        }
        if leadsGame {
            states.append(.leadsGame)
        }
        if numberOfRaises > 0 {
            states.append(.didRaise)
        }
        return states
    }

    override var info: PlayerInfo {
        var info = super.info
        info.playedCard = playedCard?.id
        return info
    }
}
