package com.thebluealliance.androidclient.helpers;
import com.thebluealliance.androidclient.R;
import com.thebluealliance.androidclient.TbaLogger;
import com.thebluealliance.androidclient.config.AppConfig;
import android.content.Context;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.JsonReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import okhttp3.CacheControl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Encapsulation of special-cased stuff for showing pit locations at 2016 Champs
*/
public final class PitLocationHelper {
private PitLocationHelper() {
// unused
}
private static final String LAST_UPDATED_PREF_KEY = "cmp_pit_locations_update_time";
private static final String PIT_LOCATIONS_FILENAME = "2016_pit_locations.json";
private static final String SHOW_PIT_KEY = "show_pit_location_for_team";
private static final String SHOW_PIT_EVENTS = "show_pit_location_events";
private static final String PIT_URL_KEY = "champs_pit_url";
private static final String PIT_LAST_UPDATE_KEY = "champs_pit_last_update";
private static Map<String, TeamPitLocation> sTeamLocationCache = new HashMap<>();
public static boolean shouldShowPitLocation(AppConfig config) {
return config.getBoolean(SHOW_PIT_KEY);
}
public static boolean shouldShowPitLocationAtEvent(AppConfig config, String eventKey) {
String eventKeys = config.getString(SHOW_PIT_EVENTS);
return shouldShowPitLocation(config)
&& !TextUtils.isEmpty(eventKeys)
&& eventKeys.contains(eventKey);
}
public static @Nullable TeamPitLocation getPitLocation(Context context, String teamKey) {
// First, check cache
if (sTeamLocationCache.containsKey(teamKey)) {
return sTeamLocationCache.get(teamKey);
}
// If that fails, try to load it into the cache
if (readTeamLocationFromJson(context, teamKey) != null) {
return sTeamLocationCache.get(teamKey);
}
// Didn't find anything
return null;
}
public static void updateFromRemoteUrl(Context context, String newContent, long updateTimeSeconds) {
// Write to the file
try {
File locationFile = getLocationsFile(context);
if (!locationFile.exists()) {
locationFile.createNewFile();
}
BufferedWriter writer = new BufferedWriter(new FileWriter(locationFile));
writer.write(newContent);
writer.flush();
writer.close();
// Note the update time, but only if it was successful
PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(LAST_UPDATED_PREF_KEY, updateTimeSeconds).commit();
// Wipe the in-memory cache of teams so that they are lazily re-loaded
sTeamLocationCache.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean shouldUpdateFromRemoteUrl(Context context, AppConfig config) {
long lastUpdateTime = PreferenceManager.getDefaultSharedPreferences(context).getLong(LAST_UPDATED_PREF_KEY, -1);
long remoteUpdateTime = config.getLong(PIT_LAST_UPDATE_KEY, 0);
String url = config.getString(PIT_URL_KEY);
// TODO better URL validation
boolean validUrl = !TextUtils.isEmpty(url)
&& (url.startsWith("http://")
|| url.startsWith("https://"));
return shouldShowPitLocation(config)
&& validUrl
&& (lastUpdateTime == -1 || remoteUpdateTime > lastUpdateTime);
}
public static void updateRemoteDataIfNeeded(Context context,
AppConfig appConfig,
OkHttpClient httpClient) {
String remoteUrl = appConfig.getString(PIT_URL_KEY);
long lastUpdateTime = appConfig.getLong(PIT_LAST_UPDATE_KEY, 0);
if (PitLocationHelper.shouldUpdateFromRemoteUrl(context.getApplicationContext(), appConfig)
&& !TextUtils.isEmpty(remoteUrl)) {
try {
Request request = new Request.Builder()
.url(remoteUrl)
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
okhttp3.Response champsPitLocation = httpClient.newCall(request).execute();
String responseString = champsPitLocation.body().string();
PitLocationHelper.updateFromRemoteUrl(context.getApplicationContext(),
responseString,
lastUpdateTime);
} catch (Exception e) {
TbaLogger.w("Unable to update champs pit locations", e);
}
}
}
private static void populateLocationsFileFromPackagedResourceIfNeeded(Context context) {
File locationsFile = getLocationsFile(context);
if (locationsFile.exists()) {
// Don't overwrite!
return;
}
InputStream input = null;
OutputStream output = null;
try {
locationsFile.createNewFile();
input = context.getResources().openRawResource(R.raw.pit_addresses_2016);
output = new FileOutputStream(locationsFile);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buf)) > 0) {
output.write(buf, 0, bytesRead);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
input.close();
output.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Finds a specified team in the pit location json file and caches it if found.
*
* The file should be in the following format:
*
* {"locations": { "frc111": {"div": "Tesla", "addr": "Q16"}, "frc254": {...}, ...} }
*
* In the future, the root object may contain additional properties.
*
* @param teamKey the team key to search for in the locations dict (frcXXXX)
* @return the location object if one was found
*/
private static TeamPitLocation readTeamLocationFromJson(Context context, String teamKey) {
// Load the resource into our file if we haven't done so yet
populateLocationsFileFromPackagedResourceIfNeeded(context);
JsonReader reader = null;
try {
reader = new JsonReader(new FileReader(getLocationsFile(context)));
reader.beginObject();
while (reader.hasNext()) {
String section = reader.nextName();
if (!section.equals("locations")) {
reader.skipValue();
continue;
}
reader.beginObject();
while(reader.hasNext()) {
String currentTeamKey = reader.nextName();
TbaLogger.d("reading team: " + currentTeamKey);
if (!currentTeamKey.equals(teamKey)) {
reader.skipValue();
} else {
// We found the team!
reader.beginObject();
String location = null, division = null;
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("div")) {
division = reader.nextString();
} else if (name.equals("loc")) {
location = reader.nextString();
} else {
reader.skipValue();
}
}
if (location != null && division != null) {
TeamPitLocation loc = new TeamPitLocation(division, location);
sTeamLocationCache.put(currentTeamKey, loc);
return loc;
}
}
}
reader.endObject();
}
reader.endObject();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private static File getLocationsFile(Context context) {
return new File(context.getFilesDir().getPath() + File.pathSeparator + PIT_LOCATIONS_FILENAME);
}
public static class TeamPitLocation {
private String division, location;
public TeamPitLocation(String division, String location) {
this.division = division;
this.location = location;
}
public String getDivision() {
return division;
}
public String getLocation() {
return location;
}
public String getAddressString() {
return String.format("%s (%s)", location, division);
}
}
}