package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris;

import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.TetrisGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game.Tetromino;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.metadata.display.BlockDisplayMeta;
import net.minestom.server.event.player.PlayerHandAnimationEvent;
import net.minestom.server.event.player.PlayerTickEvent;
import net.minestom.server.event.player.PlayerUseItemEvent;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.Random;
import java.util.WeakHashMap;

class Tetris extends StatelessGame {
    private final Map<Player, TetrisGame> tetrisGames = new WeakHashMap<>();
    private final int nextTetrominoesCount;
    private final boolean isFast;
    private final boolean hasCombat;
    private final long randomSeed;
    private boolean setTimeLimit = false;

    public Tetris(int nextTetrominoesCount, boolean isFast, boolean hasCombat) {
        super(Dimension.THE_END.key, "Tetris", new PointsWinScore());

        this.eventNode()
            .addListener(PlayerUseItemEvent.class, this::onPlayerInteract)
            .addListener(PlayerHandAnimationEvent.class, this::onPlayerAttack)
            .addListener(PlayerTickEvent.class, this::onPlayerTick);

        this.nextTetrominoesCount = nextTetrominoesCount;
        this.isFast = isFast;
        this.hasCombat = hasCombat;

        Random random = new Random();
        this.randomSeed = random.nextLong();
    }

    @Override
    protected void onStart() {
        this.getEntities().stream()
            .filter(entity -> entity.getEntityType().equals(Tetromino.getGhostEntityType()))
            .forEach(Entity::remove);

        if(this.hasCombat) {
            this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values()));
        }

        this.tetrisGames.forEach((player, tetrisGame) -> tetrisGame.start());
    }

    @Override
    protected void onStop() {
        this.tetrisGames.forEach((player, tetrisGame) -> {
            tetrisGame.loose();
            this.getScore().insertResult(player, tetrisGame.getScore());
            tetrisGame.sidebar.removeViewer(player);
        });
    }

    @Override
    protected void onPlayerLeave(Player p) {
        this.tetrisGames.get(p).sidebar.removeViewer(p);
        p.clearEffects();
        this.letPlayerLoose(p);
        p.setGameMode(GameMode.SURVIVAL);
    }

    private void onPlayerInteract(@NotNull PlayerUseItemEvent event) {
        event.setItemUseTime(0);
        this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseRight);
    }

    private void onPlayerAttack(@NotNull PlayerHandAnimationEvent event) {
        this.tetrisGames.get(event.getPlayer()).pressedButton(TetrisGame.Button.mouseLeft);
    }

    private void onPlayerTick(PlayerTickEvent event) {
        Player player = event.getPlayer();
        TetrisGame tetrisGame = this.tetrisGames.get(player);
        if(tetrisGame == null) return;
        if(tetrisGame.lost) {
            this.letPlayerLoose(player);
            return;
        }

        if(player.getGameMode() == GameMode.SPECTATOR) return;

        if(player.inputs().forward()) tetrisGame.pressedButton(TetrisGame.Button.W);
        if(player.inputs().backward()) tetrisGame.pressedButton(TetrisGame.Button.S);
        if(player.inputs().right()) tetrisGame.pressedButton(TetrisGame.Button.D);
        if(player.inputs().left()) tetrisGame.pressedButton(TetrisGame.Button.A);
        if(player.inputs().jump()) tetrisGame.pressedButton(TetrisGame.Button.space);
    }

    private void letPlayerLoose(Player player) {
        TetrisGame tetrisGame = this.tetrisGames.get(player);
        if(!this.getScore().hasResult(player)) {
            player.setGameMode(GameMode.SPECTATOR);
            player.setInvisible(true);
            if(player.getVehicle() != null) player.getVehicle().removePassenger(player);
            this.getScore().insertResult(player, tetrisGame.getScore());
        }

        boolean allGamesLost = this.tetrisGames.values().stream()
            .filter(game -> !game.lost)
            .toList()
            .isEmpty();
        if(!this.setTimeLimit && !allGamesLost) {
            this.setTimeLimit(90);
            this.setTimeLimit = true;
        }
    }

    @Override
    protected boolean onPlayerJoin(Player p) {
        p.getInventory().setItemStack(0, ItemStack.builder(Material.BIRCH_BUTTON).customName(Component.text("Controller")).build());
        p.setSprinting(false);

        if(this.tetrisGames.get(p) == null) {
            this.tetrisGames.put(p, new TetrisGame(
                this,
                this.getSpawn().sub(6, 8, 15).add(this.tetrisGames.size() * 30, 0, 0),
                Tetromino.Shape.J,
                this.nextTetrominoesCount,
                this.isFast,
                this.hasCombat,
                this.randomSeed
            ));
            this.tetrisGames.get(p).generate();
            this.tetrisGames.values().forEach(tetrisGame -> tetrisGame.updateOtherTetrisGames(this.tetrisGames.values()));
        }

        TetrisGame tetrisGame = this.tetrisGames.get(p);

        p.teleport(tetrisGame.getPlayerSpawnPosition());
        tetrisGame.sidebar.addViewer(p);

        MinecraftServer.getSchedulerManager().scheduleTask(() -> {
            Entity ghostBlock = new Entity(EntityType.BLOCK_DISPLAY);
            ((BlockDisplayMeta) ghostBlock.getEntityMeta()).setBlockState(Block.AIR);
            ghostBlock.setNoGravity(true);
            ghostBlock.setInstance(this, tetrisGame.getPlayerSpawnPosition());
            ghostBlock.addPassenger(p);
            return TaskSchedule.stop();
        }, TaskSchedule.nextTick());


        return super.onPlayerJoin(p);
    }

    @Override
    public Pos getSpawn() {
        return new Pos(0, 30, 15).withView(180, 0);
    }
}
