package io.github.xxyy.common.util;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;

import java.util.Map;
import java.util.UUID;

/**
 * Provides some static utility methods for dealing with Scoreboards.
 *
 * @author <a href="http://xxyy.github.io/">xxyy</a>
 * @since 9.4.14
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ScoreboardHelper {

    /**
     * Sets a fake score with a specified display name. Does not call {@link org.bukkit.Bukkit#getOfflinePlayer(String)}, but creates
     * a fake {@link io.github.xxyy.common.util.ScoreboardHelper.NamedOfflinePlayer} to save performance.
     * @param objective Objective to set
     * @param displayName Name to display
     * @param score Score to set
     */
    public static void setFakeScore(final Objective objective, final String displayName, final int score) {
        Score fakeScr = objective.getScore(new NamedOfflinePlayer(displayName));
        fakeScr.setScore(score);
    }

    /**
     * Represents a fake OfflinePlayer - This is mainly intended for use with Scoreboards.
     * All methods but {@link #getName()} throw an {@link java.lang.UnsupportedOperationException}, do nothing or return {@code null} (depends on method),
     * therefore these objects should only be passed to methods which just use {@link #getName()} (or extend and implement).
     */
    @RequiredArgsConstructor
    public static class NamedOfflinePlayer implements OfflinePlayer {
        private static final UnsupportedOperationException UNSUPPORTED_OPERATION_EXCEPTION = //Store the exception because it's always the same and stuff
                new UnsupportedOperationException("This fake OfflinePlayer was just created to return a name," +
                        " to trick Bukkit in order to save performance (Thnx, EvilSeph) - It only supports getName().");
        @Getter
        private final String name;

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

        @Override
        public UUID getUniqueId() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public boolean isBanned() {
            return false; //Ignore this, won't hurt
        }

        @Override
        public void setBanned(boolean b) {
            //Ignore this, won't hurt
        }

        @Override
        public boolean isWhitelisted() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public void setWhitelisted(boolean b) {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public Player getPlayer() {
            return null; //Return null to possibly trick implementations into believing the player is just offline
        }

        @Override
        public long getFirstPlayed() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public long getLastPlayed() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

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

        @Override
        public Location getBedSpawnLocation() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public Map<String, Object> serialize() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public boolean isOp() {
            throw UNSUPPORTED_OPERATION_EXCEPTION;
        }

        @Override
        public void setOp(boolean b) {
            //Ignore this, won't hurt
        }
    }
}

@@ -289,44 +289,34 @@
 ve;%0A
+%0A
 import 
-org.bukkit.scoreboard.Score;%0A
+java.util.HashMap;
 %0Aimp
@@ -595,16 +595,115 @@
 Helper %7B
+%0A    private static final Map%3CString, NamedOfflinePlayer%3E CACHED_OFFLINE_PLAYERS = new HashMap%3C%3E();
 %0A%0A    /*
@@ -1070,161 +1070,633 @@
    *
-/%0A    public static void setFakeScore(final Objective objective, final String displayName, final int score) %7B%0A        Score fakeScr = objective.getScore(
+ @param addToCache Whether to store the created %7B@link io.github.xxyy.common.util.ScoreboardHelper.NamedOfflinePlayer%7D in a static data structure, to%0A     *                   prevent having to create new ones - this is especially helpful for multiple Scoreboards/Objectives using the same display name%0A     *                   for all players.%0A     */%0A    public static void setFakeScore(final Objective objective, final String displayName, final int score, final boolean addToCache) %7B%0A        NamedOfflinePlayer fakePlayer = CACHED_OFFLINE_PLAYERS.get(displayName);%0A%0A        if(fakePlayer == null)%7B%0A            fakePlayer = 
 new 
@@ -1726,27 +1726,31 @@
 layName)
-)
 ;%0A
+%0A    
         
 fakeScr.
@@ -1745,15 +1745,164 @@
     
-fakeScr
+if(addToCache)%7B%0A                CACHED_OFFLINE_PLAYERS.put(displayName, fakePlayer);%0A            %7D%0A        %7D%0A        %0A        objective.getScore(fakePlayer)
 .set

Add fake player cache to ScoreboardHelper to improve performance