package com.github.games647.scoreboardstats.scoreboard;

import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.EnumWrappers.ScoreboardAction;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;

import org.apache.commons.lang.builder.ToStringBuilder;

import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_DISPLAY_OBJECTIVE;
import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_OBJECTIVE;
import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_SCORE;

/**
 * Represents a sidebar objective
 */
public class Objective {

    private static final int MAX_ITEM_SIZE = 15;
    private static final int SIDEBAR_SLOT = 1;

    //A scoreboard can only hold < 16 scores
    final Map<String, Integer> scores = Maps.newHashMapWithExpectedSize(MAX_ITEM_SIZE);

    private final PlayerScoreboard scoreboard;
    private final String objectiveId;
    String displayName;

    Objective(PlayerScoreboard scoreboard, String objectiveId, String displayName) {
        this.scoreboard = scoreboard;

        this.objectiveId = objectiveId;
        this.displayName = displayName;
    }

    public boolean isShown() {
        //Prevents NPE with this ordering
        return scoreboard.getSidebarObjective().map(sidebar -> sidebar.equals(this)).orElse(false);
    }

    public boolean exists() {
        return scoreboard.getObjective(objectiveId).isPresent();
    }

    public String getId() {
        return objectiveId;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        if (this.displayName.equals(displayName)) {
            return;
        }

        this.displayName = displayName;
        sendObjectivePacket(State.UPDATE_DISPLAY_NAME);
    }

    public OptionalInt getScore(String name) {
        Integer score = scores.get(name);
        if (score == null) {
            return OptionalInt.empty();
        }

        return OptionalInt.of(score);
    }

    public int getScore(String name, int def) {
        Integer score = scores.get(name);
        if (score == null) {
            setScores(name, def);
            return def;
        }

        return score;
    }

    public boolean hasScore(String name) {
        return scores.containsKey(name);
    }

    public void setScores(String name, int value) {
        Integer oldVal = scores.put(name, value);
        if (oldVal != null && oldVal == value) {
            return;
        }

        sendScorePacket(name, value, ScoreboardAction.CHANGE);
    }

    public List<Map.Entry<String, Integer>> getScores() {
        List<Map.Entry<String, Integer>> values = Lists.newArrayListWithExpectedSize(scores.size());
        values.addAll(scores.entrySet());
        values.sort((score1, score2) -> Integer.compare(score2.getValue(), score1.getValue()));
        return values;
    }

    public void removeScore(String name) {
        scores.remove(name);
        sendScorePacket(name, 0, ScoreboardAction.REMOVE);
    }

    public void clear() {
        scores.keySet().forEach(this::removeScore);
    }

    public PlayerScoreboard getScoreboard() {
        return scoreboard;
    }

    @Override
    public int hashCode() {
        return Objects.hash(objectiveId);
    }

    @Override
    public boolean equals(Object obj) {
        //ignores also null
        return obj instanceof Objective && Objects.equals(objectiveId, ((Objective) obj).objectiveId);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    private void sendScorePacket(String name, int score, ScoreboardAction action) {
        PacketContainer packet = new PacketContainer(SCOREBOARD_SCORE);
        packet.getStrings().write(0, name);
        packet.getStrings().write(1, objectiveId);

        packet.getIntegers().write(0, score);

        packet.getScoreboardActions().write(0, action);

        scoreboard.sendPacket(packet);
    }

    void sendObjectivePacket(State state) {
        PacketContainer packet = new PacketContainer(SCOREBOARD_OBJECTIVE);
        packet.getStrings().write(0, objectiveId);

        if (state != State.REMOVE) {
            packet.getStrings().write(1, displayName);
            packet.getStrings().write(2, "integer");
        }

        scoreboard.sendPacket(packet);
    }

    void sendShowPacket() {
        PacketContainer packet = new PacketContainer(SCOREBOARD_DISPLAY_OBJECTIVE);
        packet.getStrings().write(0, objectiveId);

        packet.getIntegers().write(0, SIDEBAR_SLOT);
        scoreboard.sendPacket(packet);
    }
}