package com.faforever.client.player;
import com.faforever.client.chat.PlayerInfoBean;
import com.faforever.client.chat.SocialStatus;
import com.faforever.client.chat.avatar.AvatarBean;
import com.faforever.client.chat.avatar.event.AvatarChangedEvent;
import com.faforever.client.game.GameInfoBean;
import com.faforever.client.game.GameService;
import com.faforever.client.game.GameStatus;
import com.faforever.client.player.event.FriendJoinedGameEvent;
import com.faforever.client.remote.FafService;
import com.faforever.client.remote.domain.Player;
import com.faforever.client.remote.domain.PlayersMessage;
import com.faforever.client.remote.domain.SocialMessage;
import com.faforever.client.user.UserService;
import com.faforever.client.user.event.LoginSuccessEvent;
import com.faforever.client.util.Assert;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import org.jetbrains.annotations.NotNull;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static com.faforever.client.chat.SocialStatus.FOE;
import static com.faforever.client.chat.SocialStatus.FRIEND;
import static com.faforever.client.chat.SocialStatus.OTHER;
import static com.faforever.client.chat.SocialStatus.SELF;
public class PlayerServiceImpl implements PlayerService {
private final ObservableMap<String, PlayerInfoBean> playersByName;
private final ObservableMap<Integer, PlayerInfoBean> playersById;
private final List<Integer> foeList;
private final List<Integer> friendList;
private final ObjectProperty<PlayerInfoBean> currentPlayer;
@Resource
FafService fafService;
@Resource
UserService userService;
@Resource
GameService gameService;
@Resource
EventBus eventBus;
/**
* Maps game IDs to status change listeners.
*/
private Map<Integer, InvalidationListener> statusChangeListeners;
public PlayerServiceImpl() {
playersByName = FXCollections.observableHashMap();
playersById = FXCollections.observableHashMap();
friendList = new ArrayList<>();
foeList = new ArrayList<>();
currentPlayer = new SimpleObjectProperty<>();
statusChangeListeners = new HashMap<>();
}
@PostConstruct
void postConstruct() {
eventBus.register(this);
fafService.addOnMessageListener(PlayersMessage.class, this::onPlayersInfo);
fafService.addOnMessageListener(SocialMessage.class, this::onFoeList);
gameService.addOnGameInfoBeansChangeListener(listChange -> {
while (listChange.next()) {
for (GameInfoBean gameInfoBean : listChange.getRemoved()) {
updateGameStateForPlayers(gameInfoBean);
gameInfoBean.statusProperty().removeListener(statusChangeListeners.remove(gameInfoBean.getUid()));
}
for (GameInfoBean gameInfoBean : listChange.getAddedSubList()) {
updateGameStateForPlayers(gameInfoBean);
InvalidationListener statusChangeListener = statusChange -> updateGameStateForPlayers(gameInfoBean);
statusChangeListeners.put(gameInfoBean.getUid(), statusChangeListener);
gameInfoBean.statusProperty().addListener(new WeakInvalidationListener(statusChangeListener));
}
}
});
}
@Subscribe
public void onLoginSuccess(LoginSuccessEvent event) {
synchronized (currentPlayer) {
currentPlayer.set(createAndGetPlayerForUsername(event.getUsername()));
}
}
@Subscribe
public void onAvatarChanged(AvatarChangedEvent event) {
synchronized (currentPlayer) {
PlayerInfoBean player = currentPlayer.get();
AvatarBean avatar = event.getAvatar();
if (avatar == null) {
player.setAvatarTooltip(null);
player.setAvatarUrl(null);
} else {
player.setAvatarTooltip(avatar.getDescription());
player.setAvatarUrl(Objects.toString(avatar.getUrl(), null));
}
}
}
private void updateGameStateForPlayers(GameInfoBean gameInfoBean) {
ObservableMap<String, List<String>> teams = gameInfoBean.getTeams();
synchronized (teams) {
teams.forEach((team, players) -> updateGameStateForPlayer(players, gameInfoBean));
}
}
//FIXME ugly fix until host can be resolved from gamestate
private void updateGameStateForPlayer(List<String> players, GameInfoBean gameInfoBean) {
for (String player : players) {
PlayerInfoBean playerInfoBean = getPlayerForUsername(player);
if (playerInfoBean == null) {
continue;
}
playerInfoBean.setGameUid(gameInfoBean.getUid());
updatePlayerGameStatus(playerInfoBean, GameStatus.fromGameState(gameInfoBean.getStatus()));
}
if (GameStatus.fromGameState(gameInfoBean.getStatus()) == GameStatus.LOBBY) {
PlayerInfoBean host = getPlayerForUsername(gameInfoBean.getHost());
updatePlayerGameStatus(host, GameStatus.HOST);
}
}
private void updatePlayerGameStatus(PlayerInfoBean playerInfoBean, GameStatus gameStatus) {
if (playerInfoBean != null && playerInfoBean.getGameStatus() != gameStatus) {
//FIXME until api, host is set twice or ugly code, I chose to set twice
playerInfoBean.setGameStatus(gameStatus);
if (playerInfoBean.getSocialStatus() == FRIEND && (gameStatus == GameStatus.HOST || gameStatus == GameStatus.LOBBY)) {
eventBus.post(new FriendJoinedGameEvent(playerInfoBean));
}
}
}
@Override
public PlayerInfoBean getPlayerForUsername(String username) {
return playersByName.get(username);
}
@Override
public PlayerInfoBean createAndGetPlayerForUsername(@NotNull String username) {
Assert.checkNullArgument(username, "username must not be null");
synchronized (playersByName) {
if (!playersByName.containsKey(username)) {
PlayerInfoBean player = new PlayerInfoBean(username);
player.idProperty().addListener((observable, oldValue, newValue) -> {
synchronized (playersById) {
playersById.remove(oldValue.intValue());
playersById.put(newValue.intValue(), player);
}
});
playersByName.put(username, player);
}
}
return playersByName.get(username);
}
@Override
public Set<String> getPlayerNames() {
return playersByName.keySet();
}
@Override
public void addFriend(PlayerInfoBean player) {
playersByName.get(player.getUsername()).setSocialStatus(FRIEND);
friendList.add(player.getId());
foeList.remove((Integer) player.getId());
fafService.addFriend(player);
}
@Override
public void removeFriend(PlayerInfoBean player) {
playersByName.get(player.getUsername()).setSocialStatus(OTHER);
friendList.remove((Integer) player.getId());
fafService.removeFriend(player);
}
@Override
public void addFoe(PlayerInfoBean player) {
playersByName.get(player.getUsername()).setSocialStatus(FOE);
foeList.add(player.getId());
friendList.remove((Integer) player.getId());
fafService.addFoe(player);
}
@Override
public void removeFoe(PlayerInfoBean player) {
playersByName.get(player.getUsername()).setSocialStatus(OTHER);
foeList.remove((Integer) player.getId());
fafService.removeFoe(player);
}
@Override
public PlayerInfoBean getCurrentPlayer() {
synchronized (currentPlayer) {
if (currentPlayer.get() == null) {
throw new IllegalStateException("Current player has not yet been set");
}
return currentPlayer.get();
}
}
@Override
public ReadOnlyObjectProperty<PlayerInfoBean> currentPlayerProperty() {
return currentPlayer;
}
private void onPlayersInfo(PlayersMessage playersMessage) {
playersMessage.getPlayers().forEach(this::onPlayerInfo);
}
private void onFoeList(SocialMessage socialMessage) {
onFoeList(socialMessage.getFoes());
onFriendList(socialMessage.getFriends());
}
private void onFoeList(List<Integer> foes) {
updateSocialList(foeList, foes, FOE);
}
private void onFriendList(List<Integer> friends) {
updateSocialList(friendList, friends, FRIEND);
}
private void updateSocialList(List<Integer> socialList, List<Integer> newValues, SocialStatus socialStatus) {
socialList.clear();
socialList.addAll(newValues);
synchronized (playersById) {
for (Integer userId : socialList) {
PlayerInfoBean playerInfoBean = playersById.get(userId);
if (playerInfoBean != null) {
playerInfoBean.setSocialStatus(socialStatus);
}
}
}
}
private void onPlayerInfo(Player player) {
if (player.getLogin().equalsIgnoreCase(userService.getUsername())) {
PlayerInfoBean playerInfoBean = getCurrentPlayer();
playerInfoBean.updateFromPlayerInfo(player);
playerInfoBean.setSocialStatus(SELF);
} else {
PlayerInfoBean playerInfoBean = createAndGetPlayerForUsername(player.getLogin());
if (friendList.contains(player.getId())) {
playerInfoBean.setSocialStatus(FRIEND);
} else if (foeList.contains(player.getId())) {
playerInfoBean.setSocialStatus(FOE);
} else {
playerInfoBean.setSocialStatus(OTHER);
}
playerInfoBean.updateFromPlayerInfo(player);
}
}
}