package eu.jonahbauer.wizard.core.machine.states.round;

import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.model.card.GameCards;
import org.jetbrains.annotations.NotNull;

import java.util.*;

import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PICK_TRUMP;

public final class DeterminingTrump extends RoundState {
    private transient UUID player;
    private transient boolean werewolf;

    public DeterminingTrump(GameData data) {
        super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS));
    }

    @Override
    public Optional<GameState> onEnter(Game game) {
        Card trumpCard = get(TRUMP_CARD);

        // handle werewolf
        for (Map.Entry<UUID, List<Card>> entry : get(HANDS).entrySet()) {
            var player = entry.getKey();
            var hand = entry.getValue();
            if (hand.contains(Card.WEREWOLF)) {
                this.player = player;
                this.werewolf = true;
                game.notify(new TrumpMessage(trumpCard, null));
                game.notify(new TrumpMessage(Card.WEREWOLF, null));
                game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
                return timeout(game);
            }
        }

        // default trump handling
        Card.Suit trumpSuit = trumpCard != null ? GameCards.get(trumpCard).getTrumpSuit() : Card.Suit.NONE;
        if (trumpSuit == null) {
            this.player = getDealer();
            game.notify(new TrumpMessage(trumpCard, null));
            game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
            return timeout(game);
        } else {
            return transition(game, trumpSuit);
        }
    }

    @Override
    public Optional<GameState> onTimeout(Game game) {
        game.notify(new TimeoutMessage());
        Card.Suit[] suits;
        if (werewolf) {
            suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW, Card.Suit.NONE};
        } else {
            suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
        }
        return transition(game, suits[game.getRandom().nextInt(suits.length)]);
    }

    @Override
    public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
        if (this.player.equals(player) && message instanceof PickTrumpMessage trumpMessage) {
            checkTrumpSuit(trumpMessage.getTrumpSuit());
            return transition(game, trumpMessage.getTrumpSuit());
        } else {
            return super.onMessage(game, player, message);
        }
    }

    private void checkTrumpSuit(Card.Suit suit) {
        if (!werewolf && suit == Card.Suit.NONE) {
            throw new IllegalArgumentException("Trump suit must not be " + Card.Suit.NONE + ".");
        } else if (suit == null) {
            throw new IllegalArgumentException("Trump suit must not be null.");
        }
    }

    private Optional<GameState> transition(Game game, @NotNull Card.Suit trumpSuit) {
        GameData data = getData().with(
                TRUMP_SUIT, trumpSuit,
                CURRENT_PLAYER, getNextPlayer(getDealer())
        );

        if (werewolf) {
            var mutableHands = new HashMap<>(get(HANDS));
            var mutableHand = new ArrayList<>(mutableHands.get(player));
            mutableHand.set(mutableHand.indexOf(Card.WEREWOLF), get(TRUMP_CARD));
            mutableHands.put(player, List.copyOf(mutableHand));
            data = data.with(HANDS, Map.copyOf(mutableHands));
            game.notify(new TrumpMessage(Card.WEREWOLF, trumpSuit));
            game.notify(player, new HandMessage(player, mutableHands.get(player)));
        } else {
            game.notify(new TrumpMessage(get(TRUMP_CARD), trumpSuit));
        }
        return transition(new Predicting(data));
    }
}
