package eu.mhsl.minenet.minigames.instance.game;

import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.score.Score;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;

public abstract class Game extends MineNetInstance implements Spawnable {

    protected Room parentRoom;
    protected boolean isRunning = false;
    protected boolean isBeforeBeginning = true;

    protected final Random rnd = new Random(); //TODO better way than ths?

    protected final Logger logger;

    public Game(DynamicRegistry.Key<DimensionType> dimensionType) {
        super(dimensionType);

        MinecraftServer.getInstanceManager().registerInstance(this);

        logger = Logger.getLogger("Game:" + getUuid());

        eventNode()
            .addListener(PlayerMoveEvent.class, this::onPlayerMove)
            .addListener(PlayerBlockBreakEvent.class, this::onBlockBreak)
            .addListener(PlayerBlockPlaceEvent.class, this::onBlockPlace)
            .addListener(ItemDropEvent.class, this::onItemDrop);
    }

    public Game setParent(Room parentRoom) {
        this.parentRoom = parentRoom;
        return this;
    }

    public static void initialize(GameFactory factory, List<Option<?>> options, Player owner) {
        try {

            Game game = factory.manufacture(Room.getRoom(owner).orElseThrow(), options);
            game.load();
            Room.getRoom(owner).orElseThrow().moveMembersToInstance(game);

            MinecraftServer.getSchedulerManager().scheduleTask(() -> {
                game.getPlayers().forEach(player -> new ChatMessage(Icon.SCIENCE)
                    .appendStatic(factory.name().getAssembled(player).asComponent())
                    .newLine()
                    .appendStatic(factory.description().getAssembled(player).asComponent())
                    .send(player));

                return TaskSchedule.stop();
            }, TaskSchedule.seconds(3));


        } catch (Exception e) {
            new ChatMessage(Icon.ERROR).appendStatic("Instance crashed: " + e.getMessage()).send(owner);
            MinecraftServer.getSchedulerManager().scheduleNextTick(() -> Room.getRoom(owner).orElseThrow().moveMembersToRoomLobby());
            e.printStackTrace(System.err);
        }
    }

    /**
     * Load and start countdown
     */
    public void load() {
        scheduler().submitTask(() -> {
            CompletableFuture<Void> callback = new CompletableFuture<>();
            this.onLoad(callback);
//            callback.whenComplete((unused, throwable) -> this.start());
            return TaskSchedule.stop();
        }, ExecutionType.TICK_END);

    }

    protected void start() {
        isRunning = true;
        isBeforeBeginning = false;
        this.onStart();
    }

    public void stop() {
        isRunning = false;
        this.onStop();
        this.unload();
    }

    public void unload() {
        this.onUnload();

        getPlayers().forEach(Room::setOwnRoom);

        scheduler().scheduleTask(() -> {

            logger.info("stopping game instance " + this.uuid);
            getPlayers().forEach(player -> player.kick("timeout"));

            MinecraftServer.getInstanceManager().unregisterInstance(this);

        }, TaskSchedule.seconds(10), TaskSchedule.stop());
    }

    protected void onLoad(@NotNull CompletableFuture<Void> callback) {
        callback.complete(null);
    }

    protected void onStart() {}
    protected void onStop() {}
    protected void onUnload() {}

    protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {

    }

    protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
        playerBlockBreakEvent.setCancelled(true);
    }

    protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
        playerBlockPlaceEvent.setCancelled(true);
    }

    @Override
    protected void onPlayerLeave(Player p) {
        this.checkAbandoned();
    }

    protected void onItemDrop(@NotNull ItemDropEvent itemDropEvent) {
        CommonEventHandles.cancel(itemDropEvent);
    }

    protected void checkAbandoned() {
        scheduleNextTick((instance) -> {
            if(instance.getPlayers().isEmpty()) this.unload();
        });
    }

    protected void publishScore(Score score) {
        this.parentRoom.getTournament().addScore(score);
    }

    public boolean isRunning() {
        return isRunning;
    }

    public Pos getSpawn() {
        return new Pos(0,50,0);
    }
}
