package eu.jonahbauer.wizard.core.machine;

import eu.jonahbauer.wizard.common.machine.TimeoutContext;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.states.game.Created;
import eu.jonahbauer.wizard.core.machine.states.game.Error;
import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.util.Util;
import lombok.Getter;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public final class Game extends TimeoutContext<GameState, Game> {
    @Getter
    private final Random random;
    @Getter
    private final GameConfiguration config;
    private final Observer observer;

    private final CompletableFuture<Void> future = new CompletableFuture<>();

    public Game(GameConfiguration config, Observer observer) {
        super(new Created());
        this.random = new Random();
        this.config = config;
        this.observer = observer;
    }

    public Game(long seed, GameConfiguration config, Observer observer) {
        super(new Created());
        this.random = new Random(seed);
        this.config = config;
        this.observer = observer;
    }

    public void resume(GameState state) {
        transition(new Created(), state);
    }

    public void start(List<UUID> players) {
        execute(s -> s.start(this, players));
    }

    public void onMessage(UUID player, PlayerMessage message) {
        execute(s -> s.onMessage(this, player, message));
    }

    @Override
    protected void onTransition(GameState from, GameState to) {
        notify(new StateMessage(to != null ? Util.toSnakeCase(to.getClass().getSimpleName()) : "null"));
    }

    @Override
    protected void handleError(Throwable t) {
        // don't use usual transition procedure to prevent errors during onExit from causing stack overflow
        forceTransition(new Error(t));
        this.complete(t);
    }

    public void notify(ObserverMessage message) {
        try {
            observer.notify(message);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public void notify(UUID player, ObserverMessage message) {
        try {
            observer.notify(player, message);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public void complete(@Nullable Throwable t) {
        shutdownNow();
        if (t != null) {
            future.completeExceptionally(t);
        } else {
            future.complete(null);
        }
    }

    @Blocking
    public void await() throws InterruptedException, ExecutionException, CancellationException {
        future.get();
    }
}
