package tc.oc.pgm.ffa;

import java.util.Collections;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;

import com.google.inject.assistedinject.Assisted;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Color;
import org.bukkit.command.CommandSender;
import tc.oc.api.bukkit.users.Users;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.virtual.CompetitorDoc;
import tc.oc.commons.bukkit.chat.ComponentRenderContext;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.bukkit.util.BukkitUtils;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.NullAudience;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;

/**
 * Wraps a single {@link MatchPlayer} in a free-for-all match.
 *
 * A Tribute is created on demand for a player the first time they join a match.
 * It is initially "empty", and the player has to be added to it, in the same way
 * they are added to any party.
 *
 * If the player leaves the match, they will be removed from the Tribute, and the
 * empty Tribute will be removed from the match. As with any inactive {@link Competitor},
 * the Tribute is retained by {@link Match}, indexed by {@link #getId}, which in this
 * case is the player's ID. If the player rejoins the same match, the FFA module will
 * retrieve their existing Tribute and add them back to it, instead of creating a new one.
 *
 * Attempting to add the wrong player, or add multiple players, will throw
 * {@link UnsupportedOperationException}.
 */
public class Tribute implements Competitor {

    public interface Factory {
        Tribute create(MatchPlayer player, @Nullable ChatColor color);
    }

    public static final ChatColor DEFAULT_COLOR = ChatColor.YELLOW;

    protected final IdentityProvider identityProvider;
    protected final ComponentRenderContext renderer;

    protected final Match match;
    protected final FreeForAllMatchModule ffa;
    protected final PlayerId playerId;
    protected final ChatColor color;

    protected @Nullable MatchPlayer player;
    protected Set<MatchPlayer> players = Collections.emptySet();

    @Inject Tribute(@Assisted MatchPlayer player, @Assisted @Nullable ChatColor color, IdentityProvider identityProvider, ComponentRenderContext renderer) {
        this.identityProvider = identityProvider;
        this.renderer = renderer;
        this.match = player.getMatch();
        this.ffa = match.needMatchModule(FreeForAllMatchModule.class);
        this.playerId = player.getPlayerId();
        this.color = color != null ? color : DEFAULT_COLOR;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{match=" + getMatch() + ", name=" + getName() + "}";
    }

    public PlayerId getPlayerId() {
        return playerId;
    }

    @Override
    public Match getMatch() {
        return match;
    }

    @Override
    public String getId() {
        return playerId.player_id();
    }

    @Override
    public String getDefaultName() {
        return getName();
    }

    @Override
    public String getName() {
        return playerId.username();
    }

    @Override
    public String getName(@Nullable CommandSender viewer) {
        return renderer.render(getComponentName(), viewer).toLegacyText();
    }

    @Override
    public boolean isNamePlural() {
        return false;
    }

    @Override
    public ChatColor getColor() {
        return color;
    }

    @Override
    public Color getFullColor() {
        return BukkitUtils.colorOf(getColor());
    }

    @Override
    public String getColoredName() {
        return getColor() + getName();
    }

    @Override
    public String getColoredName(@Nullable CommandSender viewer) {
        return getColor() + getName(viewer);
    }

    @Override
    public BaseComponent getComponentName() {
        return getStyledName(NameStyle.COLOR);
    }

    @Override
    public BaseComponent getStyledName(NameStyle style) {
        return new PlayerComponent(identityProvider.currentIdentity(playerId), style);
    }

    @Override
    public BaseComponent getChatPrefix() {
        return new Component();
    }

    @Override
    public Type getType() {
        return Type.Participating;
    }

    @Override
    public boolean isParticipatingType() {
        return true;
    }

    @Override
    public boolean isParticipating() {
        return getMatch().isRunning();
    }

    @Override
    public boolean isObservingType() {
        return false;
    }

    @Override
    public boolean isObserving() {
        return !getMatch().isRunning();
    }

    @Override
    public org.bukkit.scoreboard.Team.OptionStatus getNameTagVisibility() {
        return ffa.getOptions().nameTagVisibility;
    }

    @Override
    public Set<MatchPlayer> getPlayers() {
        return players;
    }

    @Override
    public Set<PlayerId> getPastPlayers() {
        return getMatch().isCommitted() ? Collections.singleton(playerId)
                                        : Collections.emptySet();
    }

    @Override
    public void commit() {
    }

    @Override
    public @Nullable MatchPlayer getPlayer(PlayerId playerId) {
        return player != null && player.getPlayerId().equals(playerId) ? player : null;
    }

    protected void checkPlayer(MatchPlayer player) {
        if(!Users.equals(playerId, player.getBukkit())) {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public boolean addPlayerInternal(MatchPlayer player) {
        checkPlayer(player);
        boolean changed = this.player == null;
        this.player = player;
        this.players = Collections.singleton(player);
        return changed;
    }

    @Override
    public boolean removePlayerInternal(MatchPlayer player) {
        checkPlayer(player);
        boolean changed = this.player != null;
        this.player = null;
        this.players = Collections.emptySet();
        return changed;
    }

    @Override
    public boolean isAutomatic() {
        return true;
    }

    @Override
    public Audience audience() {
        return player != null ? player : NullAudience.INSTANCE;
    }

    @Override
    public CompetitorDoc getDocument() {
        return playerId;
    }
}
