package org.drooms.launcher.tournament; import freemarker.ext.beans.BeansWrapper; import freemarker.template.Configuration; import freemarker.template.TemplateException; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.drooms.api.Player; import java.io.IOException; import java.io.Writer; import java.math.BigDecimal; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; public abstract class TournamentResults { static class GameResults { private final Map<Player, DescriptiveStatistics> stats = new HashMap<>(); private int games = 0; private GameResults() { // prevent outside instantiation } public void addResults(final Map<Player, Integer> result) { this.games++; result.forEach((p, points) -> this.stats.getOrDefault(p, new DescriptiveStatistics()).addValue(points)); } // FIXME better name public int getGames() { return this.games; } public BigDecimal getMedian(final Player p) { return BigDecimal.valueOf(this.getStats(p).getPercentile(50)); } private DescriptiveStatistics getStats(final Player p) { if (this.stats.containsKey(p)) { return this.stats.get(p); } else { final DescriptiveStatistics d = new DescriptiveStatistics(); d.addValue(0.0); return d; } } } private final Map<String, GameResults> results = new HashMap<>(); private final Collection<Player> players; private final String name; public TournamentResults(final String name, final Collection<Player> players) { this.name = name; this.players = Collections.unmodifiableCollection(players); } public void addResults(final String game, final Map<Player, Integer> result) { if (!this.results.containsKey(game)) { this.results.put(game, new GameResults()); } this.results.get(game).addResults(result); } private Map<String, List<Collection<Player>>> assembleGameOverview() { return Collections.unmodifiableMap(this.results.keySet().stream().collect(Collectors.toMap(Function.identity(), key -> this.evaluateGame(this.players, this.results.get(key))))); } @SuppressWarnings({ "rawtypes", "unchecked" }) private Map assembleResults() { final Map result = new HashMap(); result.put("name", this.name); result.put("players", this.players); result.put("games", this.assembleTournamentOverview()); result.put("gameScore", this.assembleGameOverview()); result.put("gameResults", this.results); result.put("results", this.evaluate()); return Collections.unmodifiableMap(result); } private Map<String, Integer> assembleTournamentOverview() { return Collections.unmodifiableMap(this.results.keySet().stream().collect(Collectors.toMap(Function.identity(), key -> this.results.get(key).getGames()))); } public Map<Long, Collection<Player>> evaluate() { final Collection<List<Collection<Player>>> gameResults = this.getGameNames().stream().map(game -> this.evaluateGame(this.players, this.results.get(game))).collect(Collectors.toList()); return this.evaluateTournament(this.players, gameResults); } protected abstract List<Collection<Player>> evaluateGame(final Collection<Player> players, final GameResults game); protected abstract SortedMap<Long, Collection<Player>> evaluateTournament(final Collection<Player> players, final Collection<List<Collection<Player>>> gameResults); public Collection<String> getGameNames() { return Collections.unmodifiableSet(this.results.keySet()); } public void write(final Writer w) throws IOException { final Configuration freemarker = new Configuration(); freemarker.setClassForTemplateLoading(TournamentResults.class, ""); freemarker.setObjectWrapper(new BeansWrapper()); freemarker.setLocale(Locale.US); freemarker.setNumberFormat("computer"); try { freemarker.getTemplate("tournament-report.html.ftl").process( this.assembleResults(), w); } catch (final TemplateException ex) { throw new IllegalStateException("Invalid template.", ex); } } }