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

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.core.machine.states.InvalidDataException;
import eu.jonahbauer.wizard.core.machine.states.round.ChangingPrediction;
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
import eu.jonahbauer.wizard.common.messages.observer.TrickMessage;
import eu.jonahbauer.wizard.core.model.card.*;
import eu.jonahbauer.wizard.core.util.Pair;

import java.util.*;

import static eu.jonahbauer.wizard.core.machine.states.GameData.*;

public final class FinishingTrick extends TrickState {

    public FinishingTrick(GameData data) {
        super(data.require(STACK));
        checkData(data);
    }

    @Override
    public void onEnter(Game game) {
        var stack = get(STACK);
        var cards = stack.stream().map(Pair::second).toList();

        var winner = CardUtils.getWinner(stack, get(TRUMP_SUIT));
        game.notify(new TrickMessage(winner, cards));

        GameData data = getData();

        boolean bomb = cards.contains(Card.BOMB);
        boolean juggler = cards.contains(Card.JUGGLER_BLUE)
                          || cards.contains(Card.JUGGLER_RED)
                          || cards.contains(Card.JUGGLER_GREEN)
                          || cards.contains(Card.JUGGLER_YELLOW);
        boolean cloud = cards.contains(Card.CLOUD_BLUE)
                        || cards.contains(Card.CLOUD_RED)
                        || cards.contains(Card.CLOUD_GREEN)
                        || cards.contains(Card.CLOUD_YELLOW);
        boolean hasNextTrick = get(TRICK) < get(ROUND);

        if (!bomb) {
            // trick is not counted when a bomb is present
            var tricks = new HashMap<>(get(TRICKS));
            tricks.merge(winner, 1, Integer::sum);
            data = data.with(TRICKS, Map.copyOf(tricks));

            // mark "clouded player"
            if (cloud) {
                data = data.with(CLOUDED_PLAYER, winner);
            }
        }

        data = data.with(CURRENT_PLAYER, winner);

        if (!hasNextTrick) {
            if (data.has(CLOUDED_PLAYER)) {
                transition(game, new ChangingPrediction(data));
            } else {
                transition(game, new FinishingRound(data));
            }
        } else if (juggler) {
            transition(game, new Juggling(data));
        } else {
            transition(game, new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
        }
    }

    private static void checkData(GameData data) {
        // every player has played exactly one card
        int stackSize = data.get(STACK).size();
        int playerCount = data.get(PLAYERS).size();
        if (stackSize != playerCount) {
            throw new InvalidDataException("Unexpected stack size: " + stackSize + " (expected " + playerCount + ").");
        }

        // every player has round - trick cards left on his hand
        int expectedSize = data.get(ROUND) - data.get(TRICK);
        for (List<Card> hand : data.get(HANDS).values()) {
            if (hand.size() != expectedSize) {
                throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + ").");
            }
        }
    }
}
