package tc.oc.pgm.classes;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.Nullable;
import javax.inject.Inject;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.User;
import tc.oc.api.docs.UserId;
import tc.oc.api.users.ChangeClassRequest;
import tc.oc.api.users.UserService;
import tc.oc.commons.core.stream.BiStream;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.MapUtils;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.spawns.events.ParticipantSpawnEvent;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@ListenerScope(MatchScope.LOADED)
public class ClassMatchModule extends MatchModule implements Listener {
@Inject private BukkitUserStore userStore;
@Inject private UserService userService;
private final String category;
private final Map<String, PlayerClass> classes;
private final Set<PlayerClass> classesByName;
private final PlayerClass defaultClass;
private final Map<UserId, PlayerClass> selectedClasses = Maps.newHashMap();
private final Map<UserId, PlayerClass> lastPlayedClass = Maps.newHashMap();
public ClassMatchModule(Match match, String category, Map<String, PlayerClass> classes, PlayerClass defaultClass) {
super(match);
this.category = checkNotNull(category, "category");
this.classes = checkNotNull(classes, "classes");
this.defaultClass = checkNotNull(defaultClass, "default class");
this.classesByName = new TreeSet<>(Comparator.comparing(PlayerClass::getName));
this.classesByName.addAll(this.classes.values());
}
/**
* Gets the set of classes that are present in alphabetical order by name.
*
* @return set of classes present
*/
public Set<PlayerClass> getClasses() {
return classesByName;
}
/**
* Gets the player class by the given search term.
*
* @param search search term
* @return class where the name exactly matches the given search term
*/
public @Nullable PlayerClass getPlayerClass(String search) {
return this.classes.get(search);
}
public Optional<PlayerClass> findClass(String search) {
return MapUtils.value(classes, search);
}
/**
* Gets the class that the given player has chosen to be on next respawn,
* which is not necessarily the class that they are currently playing as.
* @param user player to look up
* @return player's class or the default class if none selected
*/
public PlayerClass selectedClass(User user) {
return selectedClasses.computeIfAbsent(user, x ->
MapUtils.value(user.classes(), category)
.flatMap(this::findClass)
.orElse(defaultClass)
);
}
/**
* Get the last class that the given player spawned as.
*/
public Optional<PlayerClass> lastPlayedClass(UserId userId) {
return MapUtils.value(lastPlayedClass, userId);
}
/**
* Get the class that given player is currently playing as
*/
public Optional<PlayerClass> playingClass(MatchPlayer player) {
return Optional.of(player)
.filter(MatchPlayer::isSpawned)
.flatMap(mp -> lastPlayedClass(mp.getPlayerId()));
}
/**
* Gets all players who currently have the given class.
*
* @param cls class of which to fetch members
* @return set of players (some of which may be offline) who have the given
* class
*/
public Set<UserId> getClassMembers(PlayerClass cls) {
return BiStream.from(selectedClasses)
.filterValues(cls::equals)
.keys()
.collect(Collectors.toImmutableSet());
}
public Set<UserId> getClassMembers(Optional<PlayerClass> cls) {
return cls.map(this::getClassMembers)
.orElseGet(ImmutableSet::of);
}
/**
* Get whether the given player can change classes.
*
* @param userId player to check
* @return true if the player can change classes, false otherwise
*/
public boolean getCanChangeClass(UserId userId) {
PlayerClass cls = this.lastPlayedClass.get(userId);
return cls == null || !cls.isSticky();
}
/**
* Sets the given player's class to the one indicated.
*
* @param userId player to set the class
* @param newClass class to set
* @return old class or default if none selected
*
* @throws IllegalStateException if the player may not change classes
*/
public PlayerClass setPlayerClass(UserId userId, PlayerClass newClass) {
checkNotNull(userId, "player id");
checkNotNull(newClass, "player class");
checkArgument(this.classes.containsValue(newClass), "class is not valid for this match");
if(!this.getCanChangeClass(userId)) {
throw new IllegalStateException("cannot change sticky class");
}
PlayerClass oldClass = this.selectedClasses.put(userId, newClass);
if(oldClass == null) oldClass = this.defaultClass;
userService.changeClass(userId, new ChangeClassRequest() {
@Override public String category() {
return category;
}
@Override public String name() {
return newClass.getName();
}
});
MatchPlayer matchPlayer = this.match.getPlayer(userId);
if(matchPlayer != null) {
this.match.getPluginManager().callEvent(new PlayerClassChangeEvent(this.match, matchPlayer, this.category, oldClass, newClass));
}
return oldClass;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerSpawn(ParticipantSpawnEvent event) {
this.lastPlayedClass.put(event.getPlayer().getPlayerId(),
selectedClass(event.getPlayer().getDocument()));
}
public void giveClassKits(MatchPlayer player) {
for(Kit kit : selectedClass(player.getDocument()).getKits()) {
player.facet(KitPlayerFacet.class).applyKit(kit, true);
}
}
}