package eu.mhsl.minenet.minigames.score;

import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import org.apache.commons.lang3.NotImplementedException;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public abstract class Score {
    private int ignoreLastPlayers = 0;
    private boolean isClosed = false;
    protected Game instance;
    private final List<Set<Player>> scores = new ArrayList<>();

    public Score() {}

    protected abstract void insertResultImplementation(Set<Player> p);

    protected void insertResultImplementation(Set<Player> p, int points) {
        throw new NotImplementedException("This Score type is not able to process points");
    }

    public void insertResult(Player p) {
        this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p)));
    }

    public void insertResult(Player p, int points) {
        this.insertResultProcessor(p, () -> this.insertResultImplementation(Set.of(p), points));
    }

    public void attachListeners() {
        this.instance.eventNode()
                .addListener(AddEntityToInstanceEvent.class, addEntityToInstanceEvent -> checkGameEnd())
                .addListener(RemoveEntityFromInstanceEvent.class, addEntityToInstanceEvent -> checkGameEnd())
                .addListener(PlayerDisconnectEvent.class, addEntityToInstanceEvent -> checkGameEnd());
    }

    protected void checkGameEnd() {
        if(this.isClosed()) return;
        if(!instance.isRunning()) return;
        if(instance.getPlayers().isEmpty()) return;
        if(resultCount() >= instance.getPlayers().size() - ignoreLastPlayers) {
            if(ignoreLastPlayers > 0) {
                insertRemainingPlayers(instance.getPlayers());
            }
            setDone();
        }
    }

    protected abstract TranslatableMessage scoreMessage();

    public void insertRemainingPlayers(Set<Player> players) {
        this.insertResultImplementation(players.stream().filter(p -> !hasResult(p)).collect(Collectors.toSet()));
        setDone();
    }

    public boolean hasResult(Player p) {
        return this.getScores().stream().anyMatch(players -> players.contains(p));
    }

    public int resultCount() {
        return this.getScores().stream().map(Set::size).reduce(Integer::sum).orElse(0);
    }

    public void setDone() {
        if(isClosed) return;
        isClosed = true;
        new ChatMessage(Icon.STAR, true)
                .appendTranslated("score#result")
                .newLine()
                .indent()
                .numberedList(
                        getScores()
                                .stream()
                                .map(players -> players
                                        .stream()
                                        .map(Player::getUsername)
                                        .collect(Collectors.joining(", "))
                                )
                                .toList()
                )
                .undent()
                .newLine()
                .appendTranslated("score#thanks")
                .send(instance.getPlayers());

        instance.stop();
    }

    private void insertResultProcessor(Player p, Runnable callback) {
        if(hasResult(p)) return;
        this.scoreMessage().send(p);
        callback.run();
        this.checkGameEnd();
    }

    public boolean isClosed() {
        return isClosed;
    }

    public void close() { isClosed = true; }

    protected void onGameEnd() {
        this.instance.stop();
    }

    public List<Set<Player>> getScores() {
        return scores;
    }

    public void setInstance(Game instance) {
        this.instance = instance;
    }

    public int getIgnoreLastPlayers() {
        return ignoreLastPlayers;
    }

    public void setIgnoreLastPlayers(int ignoreLastPlayers) {
        this.ignoreLastPlayers = ignoreLastPlayers;
    }
}
