package app.data.service; import app.data.chesscom.ChessComArchive; import app.data.chesscom.ChessComArchiveList; import app.data.chesscom.ChessComGame; import app.data.entity.Game; import app.data.entity.GameInfo; import app.data.entity.Match; import app.data.entity.Player; import app.utils.ChessComUtils; import app.utils.HttpUtils; import com.google.gson.Gson; import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class ChessComService { // TODO: make everything nullsafe private static final Gson gson = new Gson(); private static final String ARCHIVES_ENDPOINT = "https://api.chess.com/pub/player/{username}/games/archives"; public ChessComService() { } @NonNull public List getLatestGamesBetweenPlayers(@NonNull Match match, int minAmountOfGames, int maxAmountOfMonths) { List list = new ArrayList<>(); for (String archiveUrl : getLatestArchiveUrls(match.getPlayer1(), maxAmountOfMonths)) { list.addAll(getChessComGames(archiveUrl).stream() .sorted(Comparator.comparingLong(ChessComGame::getEndTime).reversed()) .filter(chessComGame -> ChessComUtils.isGameBetweenPlayers(chessComGame, match)) .filter(ChessComUtils::hasValidTimeControl) .map(chessComGame -> createGame(chessComGame, match)) .collect(Collectors.toList())); if (list.size() >= minAmountOfGames) { break; } } return list; } // TODO: find exactly two games of each time control private List getChessComGames(String archiveUrl) { List list = new ArrayList<>(); getChessComArchive(archiveUrl).ifPresent(chessComArchive -> list.addAll(chessComArchive.getGames())); return list; } private Optional getChessComArchive(String archiveUrl) { String unpreparedJson = HttpUtils.getJson(archiveUrl); if (unpreparedJson == null) { return Optional.empty(); } String preparedJson = ChessComUtils.getPreparedArchiveJson(unpreparedJson); return Optional.of(gson.fromJson(preparedJson, ChessComArchive.class)); } private List getLatestArchiveUrls(@NonNull Player player, int maxAmountOfMonths) { List archiveUrls = getArchiveUrls(player); if (archiveUrls.size() >= maxAmountOfMonths) { archiveUrls = archiveUrls.subList(0, maxAmountOfMonths); } return archiveUrls; } private List getArchiveUrls(@NonNull Player player) { String endPoint = ARCHIVES_ENDPOINT.replace("{username}", ChessComUtils.getUsernameForUrl(player)); List archiveUrls = gson.fromJson(HttpUtils.getJson(endPoint), ChessComArchiveList.class).getArchives(); archiveUrls.sort(Collections.reverseOrder()); return archiveUrls; } @NonNull private Game createGame(@NonNull ChessComGame chessComGame, @NonNull Match match) { Game game = new Game(); GameInfo gameInfo = new GameInfo(); game.setMatch(match); game.setPlayer1IsWhite(chessComGame.getWhite().getUsername().equals(match.getPlayer1().getNickname())); game.setResult(ChessComUtils.getResult(chessComGame)); game.setGameInfo(gameInfo); gameInfo.setChessComId(chessComGame.getUrl()); gameInfo.setTimeControl(chessComGame.getTimeControl()); gameInfo.setGame(game); return game; } public Optional getChessComGame(@NonNull Game game) { return getChessComGame(game, game.getMatch().getPlayer1()) .or(() -> getChessComGame(game, game.getMatch().getPlayer2())); } private Optional getChessComGame(@NonNull Game game, Player player) { if (!(game.getMatch().getPlayer1().equals(player) || game.getMatch().getPlayer2().equals(player))) { throw new IllegalArgumentException("Player must be participating in Game!"); } for (String archiveUrl : getArchiveUrls(player)) { Optional chessComArchive = getChessComArchive(archiveUrl); if (chessComArchive.isEmpty()) { continue; } List chessComGames = chessComArchive.get().getGames(); for (ChessComGame chessComGame : chessComGames) { if (chessComGame.getUrl().equals(game.getGameInfo().getChessComId())) { return Optional.of(chessComGame); } } } return Optional.empty(); } }