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 final List<Set<Player>> scores = new ArrayList<>();
    protected Game instance;
    private int ignoreLastPlayers = 0;
    private boolean isClosed = false;

    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 insertMultiple(Set<Player> p) {
        p.forEach(player -> this.insertResultProcessor(player, () -> {}));
        this.insertResultImplementation(p);
    }

    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 -> this.checkGameEnd())
            .addListener(RemoveEntityFromInstanceEvent.class, addEntityToInstanceEvent -> this.checkGameEnd())
            .addListener(PlayerDisconnectEvent.class, addEntityToInstanceEvent -> this.checkGameEnd());
    }

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

    protected abstract TranslatableMessage scoreMessage();

    public void insertRemainingPlayers(Set<Player> players) {
        this.insertResultImplementation(players.stream().filter(p -> !this.hasResult(p)).collect(Collectors.toSet()));
        this.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(this.isClosed) return;
        this.isClosed = true;
        new ChatMessage(Icon.STAR, true)
            .appendTranslated("score#result")
            .newLine()
            .indent()
            .numberedList(
                this.getScores()
                    .stream()
                    .map(players -> players
                        .stream()
                        .map(Player::getUsername)
                        .collect(Collectors.joining(", "))
                    )
                    .toList()
            )
            .undent()
            .newLine()
            .appendTranslated("score#thanks")
            .send(this.instance.getPlayers());

        this.instance.stop();
    }

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

    public boolean isClosed() {
        return this.isClosed;
    }

    public void close() {
        this.isClosed = true;
    }

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

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

    public int getIgnoreLastPlayers() {
        return this.ignoreLastPlayers;
    }

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