package models;
import actions.User;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import formatters.ScoreFormatter;
import javax.annotation.Nullable;
import javax.persistence.Transient;
import java.math.BigDecimal;
import java.util.*;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static java.math.RoundingMode.HALF_UP;
public class Ranking {
public Difficulty difficulty;
public Mode mode;
public List<Score> scores;
@Transient
public String difficultyName;
@Transient
public String modeName;
public boolean general;
Ranking() {
}
public Ranking(Collection<Score> scores) {
this.scores = new ArrayList<Score>();
int rank = 1;
for (Score score : scores) {
if (score.isVip()) {
if (score.rank == null) {
score.updateRank(rank);
score.update();
}
this.scores.add(score);
rank++;
}
}
}
public Ranking(Collection<Score> scores, Difficulty difficulty) {
this(scores);
this.difficulty = difficulty;
this.difficultyName = difficulty.name;
}
public Ranking(Collection<Score> scores, Mode mode) {
this(scores);
this.mode = mode;
this.modeName = mode.name;
}
public Ranking(Collection<Score> scores, Difficulty difficulty, Mode mode) {
this(scores);
this.difficulty = difficulty;
this.difficultyName = difficulty.name;
this.mode = mode;
this.modeName = mode.name;
}
public String joinedPlayerCountPerSplittedScore() {
TreeSet<BigDecimal> scores = new TreeSet<BigDecimal>(scoresWithAverageScore());
List<Integer> playerPerCategories = new ArrayList<Integer>();
for (BigDecimal category : getSplittedScores()) {
playerPerCategories.add(scores.tailSet(category).size());
}
return Joiner.on(",").join(playerPerCategories);
}
public String joinedSplittedScores() {
return Joiner.on(",").join(getSplittedScores());
}
public int averageScoreIndex() {
BigDecimal averageScore = averageScoreAsBigDecimal();
return getSplittedScores().indexOf(averageScore);
}
public int playerScoreIndex() {
Player player = User.current();
if (player.isAuthenticated()) {
for (Score score : scores) {
if (score.isPlayedBy(player)) {
return getSplittedScores().indexOf(score.value);
}
}
}
return Integer.MAX_VALUE;
}
private List<BigDecimal> scoresWithAverageScore() {
List<BigDecimal> scores = newArrayList(transform(this.scores, new Function<Score, BigDecimal>() {
@Nullable
@Override
public BigDecimal apply(@Nullable Score score) {
return score.value;
}
}));
scores.add(averageScoreAsBigDecimal());
scores.add(geomAverageScoreAsBigDecimal());
Collections.sort(scores);
return scores;
}
private List<BigDecimal> getSplittedScores() {
TreeSet<BigDecimal> scoresMaps = new TreeSet<BigDecimal>(scoresWithAverageScore());
List<BigDecimal> scoreCategories = new ArrayList<BigDecimal>();
BigDecimal min = scoresMaps.first();
BigDecimal max = scoresMaps.last();
if (min.equals(max)) {
min = BigDecimal.ZERO;
}
BigDecimal step = (max.subtract(min)).divide(BigDecimal.valueOf(scoresMaps.size()), HALF_UP);
scoreCategories.add(min);
for (int i = 1; i < (scoresMaps.size() - 1); i++) {
scoreCategories.add(scoreCategories.get(i - 1).add(step));
}
scoreCategories.add(max);
TreeSet<BigDecimal> longs = new TreeSet<BigDecimal>(scoreCategories);
longs.add(averageScoreAsBigDecimal());
longs.add(geomAverageScoreAsBigDecimal());
Player current = User.current();
for (Score score : scores) {
if (score.isPlayedBy(current)) {
longs.add(score.value);
break;
}
}
return new ArrayList<BigDecimal>(longs);
}
public String averageScore() {
return ScoreFormatter.format(averageScoreAsBigDecimal());
}
public String geomAverageScore() {
return ScoreFormatter.format(geomAverageScoreAsBigDecimal());
}
public int geomAverageScoreIndex() {
BigDecimal averageScore = geomAverageScoreAsBigDecimal();
return getSplittedScores().indexOf(averageScore);
}
private BigDecimal averageScoreAsBigDecimal() {
BigDecimal sum = BigDecimal.ZERO;
for (Score score : scores) {
sum = sum.add(score.value);
}
if (sum.longValue() == 0L) {
return BigDecimal.ZERO;
}
return sum.divide(BigDecimal.valueOf(scores.size()), HALF_UP);
}
private BigDecimal geomAverageScoreAsBigDecimal() {
BigDecimal GM_log = BigDecimal.ZERO;
for (Score score : scores) {
if (score.value.equals(BigDecimal.ZERO)) {
return BigDecimal.ZERO;
}
try {
GM_log = GM_log.add(BigDecimal.valueOf(Math.log(score.value.doubleValue())));
}catch(Exception e){
// invalid score
}
}
BigDecimal divisor = BigDecimal.valueOf(scores.size());
BigDecimal divide = GM_log.divide(divisor, HALF_UP);
double a = divide.doubleValue();
return BigDecimal.valueOf((long)Math.exp(a));
}
public String uniqueKey() {
String difficultyId = "";
if (difficulty != null) {
difficultyId = difficulty.id.toString();
}
String modeId = "";
if (mode != null) {
modeId = mode.id.toString();
}
return difficultyId + "_" + modeId;
}
public JsonNode json() {
ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
if (difficulty != null) {
node.set("difficulty", difficulty.json());
}
if (mode != null) {
node.set("mode", mode.json());
}
ArrayNode scores = new ArrayNode(JsonNodeFactory.instance);
for (Score score : this.scores) {
scores.add(score.json());
}
node.set("scores", scores);
return node;
}
public boolean isNotEmpty() {
return !this.scores.isEmpty();
}
}