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

import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

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

public final class Juggling extends TrickState {
    private final transient Map<UUID, Card> juggledCards = new ConcurrentHashMap<>();

    public Juggling(GameData data) {
        super(data);
    }

    @Override
    public Optional<GameState> onEnter(Game game) {
        game.notify(new UserInputMessage(null, JUGGLE_CARD, getTimeout(game, true)));
        return timeout(game);
    }

    @Override
    public Optional<GameState> onTimeout(Game game) {
        game.notify(new TimeoutMessage());
        for (UUID player : get(PLAYERS)) {
            juggledCards.computeIfAbsent(player, p -> {
                var hand = get(HANDS).get(p);
                return hand.get(game.getRandom().nextInt(hand.size()));
            });
        }
        return juggle(game);
    }

    @Override
    public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
        if (get(PLAYERS).contains(player) && message instanceof JuggleMessage juggleMessage) {
            Card card = juggleMessage.getCard();

            if (!get(HANDS).get(player).contains(card)) {
                throw new IllegalArgumentException("You do not have this card on your hand.");
            }

            juggledCards.put(player, card);
            if (juggledCards.size() == get(PLAYERS).size()) {
                return juggle(game);
            } else {
                return Optional.empty();
            }
        } else {
            return super.onMessage(game, player, message);
        }
    }

    private Optional<GameState> juggle(Game game) {
        Map<UUID, Card> newCards = new HashMap<>();
        juggledCards.forEach((player, card) -> newCards.put(getNextPlayer(player), card));

        var mutableHands = new HashMap<>(get(HANDS));

        for (UUID player : get(PLAYERS)) {
            var mutableHand = new ArrayList<>(mutableHands.get(player));
            var oldCard = juggledCards.get(player);
            var newCard = newCards.get(player);
            mutableHand.set(mutableHand.indexOf(oldCard), newCard);
            var immutableHand = List.copyOf(mutableHand);
            mutableHands.put(player, immutableHand);
            game.notify(player, new HandMessage(player, immutableHand));
        }

        var data = getData().with(
                HANDS, Map.copyOf(mutableHands),
                TRICK, get(TRICK) + 1
        );

        return transition(new StartingTrick(data));
    }
}
