package com.jasonrobinson.racer.async;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.text.TextUtils;
import com.jasonrobinson.racer.async.LadderAsyncTask.LadderParams;
import com.jasonrobinson.racer.async.LadderAsyncTask.LadderResult;
import com.jasonrobinson.racer.enumeration.PoeClass;
import com.jasonrobinson.racer.model.Ladder;
import com.jasonrobinson.racer.model.Ladder.Entry;
import com.jasonrobinson.racer.model.WatchType;
import com.jasonrobinson.racer.network.RaceClient;
import com.jasonrobinson.racer.util.LadderUtils;
import java.net.SocketTimeoutException;
import java.util.Locale;
import retrofit.RetrofitError;
public abstract class LadderAsyncTask extends AsyncTask<LadderParams, Void, LadderResult> {
public static final int LIMIT_PER_REQUEST = 200;
// Character names are unique across all leagues (I think?), eliminating the
// possibility of a name conflict between races
private static LruCache<String, Integer> sCharacterRankCache = new LruCache<>(10);
@Override
protected LadderResult doInBackground(LadderParams... params) {
LadderResult result = new LadderResult();
LadderParams ladderParams = params[0];
String id = ladderParams.id;
int start = ladderParams.start;
int count = ladderParams.count;
PoeClass poeClass = ladderParams.poeClass;
String character = ladderParams.name;
WatchType type = ladderParams.type;
PoeClass characterPoeClass = ladderParams.characterPoeClass;
RaceClient client = new RaceClient();
try {
Ladder ladder;
if (poeClass == null) {
ladder = fetchLadder(client, id, start, count);
} else {
ladder = fetchLadderForClass(client, id, start, count, poeClass);
}
if (isCancelled()) {
result.ladder = ladder;
return result;
}
// Character Watcher
Entry characterEntry = null;
boolean poeClassEqual = poeClass != null && characterPoeClass != null && poeClass == characterPoeClass;
if ((poeClassEqual || characterPoeClass == null || poeClass == null) && !TextUtils.isEmpty(character)) {
characterEntry = LadderUtils.findEntry(ladder.getEntries(), character, type);
if (characterEntry == null) {
characterEntry = fetchEntry(client, id, character, type, ladder.getTotal(), poeClassEqual || characterPoeClass == null ? poeClass : null);
}
}
if (isCancelled()) {
result.ladder = ladder;
return result;
}
// Filter by class
if (poeClass != null) {
LadderUtils.filterEntriesByClass(ladder.getEntries(), poeClass);
LadderUtils.addClassRanksToEntries(ladder.getEntries());
// Prune extra entries
int size = ladder.getEntries().size();
for (int i = size - 1; i >= count; i--) {
ladder.getEntries().remove(i);
}
}
if (characterEntry != null) {
ladder.getEntries().add(0, characterEntry);
}
result.ladder = ladder;
return result;
} catch (SocketTimeoutException e) {
result.socketException = e;
return result;
} catch (RetrofitError e) {
result.retrofitError = e;
return result;
}
}
/**
* This fetches enough ladder ranks to ensure the class count is greater
* than or equal to <code>count</code>. The ladder returned still contains
* every class.
*/
private Ladder fetchLadderForClass(RaceClient client, String id, int start, int count, PoeClass poeClass) throws SocketTimeoutException {
Ladder ladder = null;
int classCount = 0;
do {
Ladder nextLadder = fetchLadder(client, id, start, LIMIT_PER_REQUEST);
classCount += LadderUtils.getClassCount(nextLadder.getEntries(), poeClass);
if (ladder == null) {
ladder = nextLadder;
} else {
ladder.getEntries().addAll(nextLadder.getEntries());
}
start += LIMIT_PER_REQUEST;
if (isCancelled()) {
break;
}
}
while (classCount < count && start < ladder.getTotal());
return ladder;
}
private Ladder fetchLadder(RaceClient client, String id, int start, int count) throws SocketTimeoutException {
Ladder ladder = null;
int end = start + count;
for (int offset = start; offset < end; offset += LIMIT_PER_REQUEST) {
Ladder nextLadder = client.fetchLadder(id, offset, LIMIT_PER_REQUEST);
if (ladder == null) {
ladder = nextLadder;
continue;
}
ladder.getEntries().addAll(nextLadder.getEntries());
if (isCancelled()) {
break;
}
}
// Prune extra entries
if (ladder != null) {
int size = ladder.getEntries().size();
for (int i = size - 1; i >= count; i--) {
ladder.getEntries().remove(i);
}
}
return ladder;
}
/**
* ONLY supply the PoeClass if you want the class rank.
*/
private Entry fetchEntry(RaceClient client, String id, String name, WatchType type, int totalRanks, PoeClass poeClass) throws SocketTimeoutException {
Integer characterRank = sCharacterRankCache.get(name.toLowerCase(Locale.US));
if (characterRank == null) {
characterRank = 0;
}
Entry entry;
if (characterRank == 0 || poeClass != null) {
entry = fetchEntryLinear(client, id, name, type, totalRanks, poeClass);
} else {
entry = fetchEntryBinary(client, id, name, type, characterRank, totalRanks);
}
if (entry != null) {
sCharacterRankCache.put(name, entry.getRank());
}
return entry;
}
private Entry fetchEntryLinear(RaceClient client, String id, String name, WatchType type, int totalRanks, PoeClass poeClass) throws SocketTimeoutException {
boolean findClassRank = poeClass != null;
int classRankCount = 0;
for (int offset = 0; offset < totalRanks; offset += LIMIT_PER_REQUEST) {
Ladder nextLadder = client.fetchLadder(id, offset, LIMIT_PER_REQUEST);
Entry entry = LadderUtils.findEntry(nextLadder.getEntries(), name, type);
if (entry != null) {
if (findClassRank) {
LadderUtils.addClassRankToEntry(nextLadder.getEntries(), entry, classRankCount);
}
return entry;
}
if (findClassRank) {
classRankCount += LadderUtils.getClassCount(nextLadder.getEntries(), poeClass);
}
if (isCancelled()) {
break;
}
}
return null;
}
private Entry fetchEntryBinary(RaceClient client, String id, String name, WatchType type, int rank, int totalRanks) throws SocketTimeoutException {
int startOffset = rank - LIMIT_PER_REQUEST / 2;
int totalQueries = (int) Math.ceil((double) totalRanks / LIMIT_PER_REQUEST);
for (int i = 0; i < totalQueries; i++) {
int offset;
if (i % 2 == 0) {
offset = startOffset + i * LIMIT_PER_REQUEST;
} else {
offset = (startOffset - 1) - i * LIMIT_PER_REQUEST;
}
if (offset < 0 || offset > totalRanks) {
continue;
}
Ladder nextLadder = client.fetchLadder(id, offset, LIMIT_PER_REQUEST);
Entry entry = LadderUtils.findEntry(nextLadder.getEntries(), name, type);
if (entry != null) {
return entry;
}
if (isCancelled()) {
break;
}
}
return null;
}
public static class LadderParams {
private String id;
private int start;
private int count;
private PoeClass poeClass;
private String name;
private WatchType type;
private PoeClass characterPoeClass;
public LadderParams(String id, int start, int count, PoeClass poeClass, String name, WatchType type, PoeClass characterPoeClass) {
this.id = id;
this.start = start;
this.count = count;
this.poeClass = poeClass;
this.name = name;
this.type = type;
this.characterPoeClass = characterPoeClass;
}
}
public static class LadderResult {
public SocketTimeoutException socketException;
public RetrofitError retrofitError;
public Ladder ladder;
private LadderResult() {
}
}
}