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

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.Dimension;
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.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.*;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;

import java.util.*;

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 boolean setTimeLimit = false;
    private final long randomSeed;

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

        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();
            getScore().insertResult(player, tetrisGame.getScore());
            tetrisGame.sidebar.removeViewer(player);
        });
    }

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

    @Override
    protected void onPlayerMove(@NotNull PlayerMoveEvent event) {
        Player player = event.getPlayer();
        Pos currentPosition = event.getNewPosition();

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

        if(tetrisGame == null) {
            event.setCancelled(true);
            return;
        }
        if(tetrisGame.lost) 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);

        event.setNewPosition(tetrisGame.getPlayerSpawnPosition().withView(currentPosition));
        player.setSprinting(false);
    }

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

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

    protected void onPlayerTick(PlayerTickEvent event) {
        Player player = event.getPlayer();
        TetrisGame tetrisGame = this.tetrisGames.get(player);
        if(tetrisGame == null) return;
        if(tetrisGame.lost && player.getGameMode() != GameMode.SPECTATOR) {
            letPlayerLoose(player);
        }
    }

    private void letPlayerLoose(Player player) {
        TetrisGame tetrisGame = this.tetrisGames.get(player);
        player.setGameMode(GameMode.SPECTATOR);
        player.setInvisible(true);
        getScore().insertResult(player, tetrisGame.getScore());

        boolean allGamesLost = this.tetrisGames.values().stream()
            .filter(game -> !game.lost)
            .toList()
            .isEmpty();
        if(!setTimeLimit && !allGamesLost) {
            this.setTimeLimit(90);
            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,
                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);

        return super.onPlayerJoin(p);
    }

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