package cgeo.geocaching.connector.ec;
import cgeo.geocaching.connector.LogResult;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags.SaveFlag;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.files.GPX10Parser;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.Viewport;
import cgeo.geocaching.log.LogType;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.storage.DataStore;
import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.SynchronizedDateFormat;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import com.fasterxml.jackson.databind.JsonNode;
import io.reactivex.Single;
import io.reactivex.functions.Function;
import okhttp3.Response;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
final class ECApi {
@NonNull
private static final String API_HOST = "https://extremcaching.com/exports/";
@NonNull
private static final ECLogin ecLogin = ECLogin.getInstance();
@NonNull
private static final SynchronizedDateFormat LOG_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US);
private ECApi() {
// utility class with static methods
}
static String getIdFromGeocode(final String geocode) {
return StringUtils.removeStartIgnoreCase(geocode, "EC");
}
@Nullable
static Geocache searchByGeoCode(final String geocode) {
final Parameters params = new Parameters("id", getIdFromGeocode(geocode));
try {
final Response response = apiRequest("gpx.php", params).blockingGet();
final Collection<Geocache> caches = importCachesFromGPXResponse(response);
if (CollectionUtils.isNotEmpty(caches)) {
return caches.iterator().next();
}
return null;
} catch (final Exception ignored) {
return null;
}
}
@NonNull
static Collection<Geocache> searchByBBox(final Viewport viewport) {
if (viewport.getLatitudeSpan() == 0 || viewport.getLongitudeSpan() == 0) {
return Collections.emptyList();
}
final Parameters params = new Parameters("fnc", "bbox");
params.add("lat1", String.valueOf(viewport.getLatitudeMin()));
params.add("lat2", String.valueOf(viewport.getLatitudeMax()));
params.add("lon1", String.valueOf(viewport.getLongitudeMin()));
params.add("lon2", String.valueOf(viewport.getLongitudeMax()));
try {
final Response response = apiRequest(params).blockingGet();
return importCachesFromJSON(response);
} catch (final Exception ignored) {
return Collections.emptyList();
}
}
@NonNull
static Collection<Geocache> searchByCenter(final Geopoint center) {
final Parameters params = new Parameters("fnc", "center");
params.add("distance", "20");
params.add("lat", String.valueOf(center.getLatitude()));
params.add("lon", String.valueOf(center.getLongitude()));
try {
final Response response = apiRequest(params).blockingGet();
return importCachesFromJSON(response);
} catch (final Exception ignored) {
return Collections.emptyList();
}
}
@NonNull
static LogResult postLog(@NonNull final Geocache cache, @NonNull final LogType logType, @NonNull final Calendar date, @NonNull final String log) {
final Parameters params = new Parameters("cache_id", cache.getGeocode());
params.add("type", logType.type);
params.add("log", log);
params.add("date", LOG_DATE_FORMAT.format(date.getTime()));
params.add("sid", ecLogin.getSessionId());
final String uri = API_HOST + "log.php";
try {
final Response response = Network.postRequest(uri, params).blockingGet();
if (response.code() == 403 && ecLogin.login() == StatusCode.NO_ERROR) {
apiRequest(uri, params, true);
}
if (response.code() != 200) {
return new LogResult(StatusCode.LOG_POST_ERROR, "");
}
final String data = Network.getResponseData(response, false);
if (!StringUtils.isBlank(data) && StringUtils.contains(data, "success")) {
if (logType == LogType.FOUND_IT || logType == LogType.ATTENDED) {
ecLogin.setActualCachesFound(ecLogin.getActualCachesFound() + 1);
}
final String uid = StringUtils.remove(data, "success:");
return new LogResult(StatusCode.NO_ERROR, uid);
}
} catch (final Exception ignored) {
// Response is already logged
}
return new LogResult(StatusCode.LOG_POST_ERROR, "");
}
@NonNull
private static Single<Response> apiRequest(final Parameters params) {
return apiRequest("api.php", params);
}
@NonNull
private static Single<Response> apiRequest(final String uri, final Parameters params) {
return apiRequest(uri, params, false);
}
@NonNull
private static Single<Response> apiRequest(final String uri, final Parameters params, final boolean isRetry) {
// add session and cgeo marker on every request
if (!isRetry) {
params.add("cgeo", "1");
params.add("sid", ecLogin.getSessionId());
}
final Single<Response> response = Network.getRequest(API_HOST + uri, params);
// retry at most one time
return response.flatMap(new Function<Response, Single<Response>>() {
@Override
public Single<Response> apply(final Response response) {
if (!isRetry && response.code() == 403 && ecLogin.login() == StatusCode.NO_ERROR) {
return apiRequest(uri, params, true);
}
return Single.just(response);
}
});
}
@NonNull
private static Collection<Geocache> importCachesFromGPXResponse(final Response response) {
try {
return new GPX10Parser(StoredList.TEMPORARY_LIST.id).parse(response.body().byteStream(), null);
} catch (final Exception e) {
Log.e("Error importing gpx from extremcaching.com", e);
return Collections.emptyList();
} finally {
response.close();
}
}
@NonNull
private static List<Geocache> importCachesFromJSON(final Response response) {
try {
final JsonNode json = JsonUtils.reader.readTree(Network.getResponseData(response));
if (!json.isArray()) {
return Collections.emptyList();
}
final List<Geocache> caches = new ArrayList<>(json.size());
for (final JsonNode node : json) {
final Geocache cache = parseCache(node);
if (cache != null) {
caches.add(cache);
}
}
return caches;
} catch (final Exception e) {
Log.w("importCachesFromJSON", e);
return Collections.emptyList();
}
}
@Nullable
private static Geocache parseCache(final JsonNode response) {
try {
final Geocache cache = new Geocache();
cache.setReliableLatLon(true);
cache.setGeocode("EC" + response.get("cache_id").asText());
cache.setName(response.get("title").asText());
cache.setCoords(new Geopoint(response.get("lat").asText(), response.get("lon").asText()));
cache.setType(getCacheType(response.get("type").asText()));
cache.setDifficulty((float) response.get("difficulty").asDouble());
cache.setTerrain((float) response.get("terrain").asDouble());
cache.setSize(CacheSize.getById(response.get("size").asText()));
cache.setFound(response.get("found").asInt() == 1);
DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE));
return cache;
} catch (final NullPointerException e) {
Log.e("ECApi.parseCache", e);
return null;
}
}
@NonNull
private static CacheType getCacheType(final String cacheType) {
if (cacheType.equalsIgnoreCase("Tradi")) {
return CacheType.TRADITIONAL;
}
if (cacheType.equalsIgnoreCase("Multi")) {
return CacheType.MULTI;
}
if (cacheType.equalsIgnoreCase("Event")) {
return CacheType.EVENT;
}
if (cacheType.equalsIgnoreCase("Mystery")) {
return CacheType.MYSTERY;
}
return CacheType.UNKNOWN;
}
}