package cgeo.geocaching.apps;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.WaypointType;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.utils.Log;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import locus.api.android.ActionDisplay;
import locus.api.android.ActionDisplayPoints;
import locus.api.android.objects.PackWaypoints;
import locus.api.android.utils.LocusUtils;
import locus.api.android.utils.exceptions.RequiredVersionMissingException;
import locus.api.objects.extra.Location;
import locus.api.objects.extra.Waypoint;
import locus.api.objects.geocaching.GeocachingData;
import locus.api.objects.geocaching.GeocachingWaypoint;
/**
* for the Locus API:
*
* @see <a href="http://docs.locusmap.eu/doku.php?id=manual:advanced:locus_api:installation">Locus API</a>
* @see <a href="https://bitbucket.org/asamm/locus-api-android-sample/src/1fad202e6166239b6e424f03bac79f0000f8eb6d/src/main/java/menion/android/locus/api/utils/SampleCalls.java?at=default&fileviewer=file-view-default">Sample Calls</a>
*/
public abstract class AbstractLocusApp extends AbstractApp {
@SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION")
protected AbstractLocusApp(@NonNull final String text, @NonNull final String intent) {
super(text, intent);
}
@Override
public boolean isInstalled() {
try {
return LocusUtils.getActiveVersion(CgeoApplication.getInstance()) != null;
} catch (final Exception ignored) {
Log.w("Couldn't get active Locus version", ignored);
}
return false;
}
/**
* Display a list of caches / waypoints in Locus
*
* @param objectsToShow
* which caches/waypoints to show
* @param withCacheWaypoints
* Whether to give waypoints of caches to Locus or not
*/
protected static boolean showInLocus(final List<?> objectsToShow, final boolean withCacheWaypoints, final boolean export,
final Context context) {
if (objectsToShow == null || objectsToShow.isEmpty()) {
return false;
}
final boolean withCacheDetails = objectsToShow.size() < 200;
final PackWaypoints pd = new PackWaypoints("c:geo");
for (final Object o : objectsToShow) {
Waypoint p = null;
// get icon and Point
if (o instanceof Geocache) {
p = getCachePoint((Geocache) o, withCacheWaypoints, withCacheDetails);
} else if (o instanceof cgeo.geocaching.models.Waypoint) {
p = getWaypointPoint((cgeo.geocaching.models.Waypoint) o);
}
if (p != null) {
pd.addWaypoint(p);
}
}
if (pd.getWaypoints().isEmpty()) {
return false;
}
if (pd.getWaypoints().size() <= 1000) {
try {
ActionDisplayPoints.sendPack(context, pd, export ? ActionDisplay.ExtraAction.IMPORT : ActionDisplay.ExtraAction.CENTER);
} catch (final RequiredVersionMissingException e) {
Log.e("AbstractLocusApp.showInLocus: problem in sendPack", e);
return false;
}
} else {
final File externalDir = Environment.getExternalStorageDirectory();
if (externalDir == null || !externalDir.exists()) {
Log.w("AbstractLocusApp.showInLocus: problem with obtain of External dir");
return false;
}
String filePath = externalDir.getAbsolutePath();
if (!filePath.endsWith("/")) {
filePath += "/";
}
filePath += "Android/data/menion.android.locus.api/files/showIn.locus";
final ArrayList<PackWaypoints> data = new ArrayList<>();
data.add(pd);
try {
ActionDisplayPoints.sendPacksFile(context, data, filePath, export ? ActionDisplay.ExtraAction.IMPORT : ActionDisplay.ExtraAction.CENTER);
} catch (final RequiredVersionMissingException e) {
Log.e("AbstractLocusApp.showInLocus: problem in sendPacksFile", e);
return false;
}
}
return true;
}
/**
* This method constructs a {@code Point} for displaying in Locus
*
* @param withWaypoints
* whether to give waypoints to Locus or not
* @param withCacheDetails
* whether to give cache details (description, hint) to Locus or not
* should be false for all if more than 200 Caches are transferred
* @return null, when the {@code Point} could not be constructed
*/
@Nullable
private static Waypoint getCachePoint(final Geocache cache, final boolean withWaypoints, final boolean withCacheDetails) {
if (cache == null || cache.getCoords() == null) {
return null;
}
// create one simple point with location
final Location loc = new Location("cgeo");
loc.setLatitude(cache.getCoords().getLatitude());
loc.setLongitude(cache.getCoords().getLongitude());
final Waypoint wpt = new Waypoint(cache.getName(), loc);
// generate new data
final GeocachingData gcData = new GeocachingData();
// fill data with variables
gcData.setCacheID(cache.getGeocode());
gcData.setName(cache.getName());
// rest is optional so fill as you want - should work
gcData.setOwner(cache.getOwnerUserId());
gcData.setPlacedBy(cache.getOwnerDisplayName());
if (cache.getDifficulty() > 0) {
gcData.setDifficulty(cache.getDifficulty());
}
if (cache.getTerrain() > 0) {
gcData.setTerrain(cache.getTerrain());
}
final int container = toLocusSize(cache.getSize());
if (container != NO_LOCUS_ID) {
gcData.setContainer(container);
}
gcData.setAvailable(!cache.isDisabled());
gcData.setArchived(cache.isArchived());
gcData.setPremiumOnly(cache.isPremiumMembersOnly());
final Date hiddenDate = cache.getHiddenDate();
if (hiddenDate != null) {
gcData.setDateHidden(hiddenDate.getTime());
}
final int type = toLocusType(cache.getType());
if (type != NO_LOCUS_ID) {
gcData.setType(type);
}
gcData.setFound(cache.isFound());
if (withWaypoints && cache.hasWaypoints()) {
for (final cgeo.geocaching.models.Waypoint waypoint : cache.getWaypoints()) {
if (waypoint == null) {
continue;
}
final GeocachingWaypoint gcWpt = new GeocachingWaypoint();
gcWpt.setCode(waypoint.getLookup());
gcWpt.setName(waypoint.getName());
gcWpt.setDesc(waypoint.getNote());
gcWpt.setType(GeocachingWaypoint.CACHE_WAYPOINT_TYPE_PARKING);
final String locusWpId = toLocusWaypoint(waypoint.getWaypointType());
if (locusWpId != null) {
gcWpt.setType(locusWpId);
}
final Geopoint waypointCoords = waypoint.getCoords();
if (waypointCoords != null) {
gcWpt.setLon(waypointCoords.getLongitude());
gcWpt.setLat(waypointCoords.getLatitude());
}
gcData.waypoints.add(gcWpt);
}
}
// Other properties of caches. When there are many caches to be displayed
// in Locus, using these properties can lead to Exceptions in Locus.
// Should not be used if caches count > 200
if (withCacheDetails) {
gcData.setDescriptions(
cache.getShortDescription(), true,
cache.getDescription(), true);
gcData.setEncodedHints(cache.getHint());
}
// set data and return point
wpt.gcData = gcData;
return wpt;
}
/**
* This method constructs a {@code Point} for displaying in Locus
*
* @return null, when the {@code Point} could not be constructed
*/
@Nullable
private static Waypoint getWaypointPoint(final cgeo.geocaching.models.Waypoint waypoint) {
if (waypoint == null) {
return null;
}
final Geopoint coordinates = waypoint.getCoords();
if (coordinates == null) {
return null;
}
// create one simple point with location
final Location loc = new Location("cgeo");
loc.setLatitude(coordinates.getLatitude());
loc.setLongitude(coordinates.getLongitude());
final Waypoint p = new Waypoint(waypoint.getName(), loc);
//TODO: not supported by new Locus API (or I haven't found it yet, pstorch)
//p.setDescription("<a href=\"" + waypoint.getUrl() + "\">" + waypoint.getGeocode() + "</a>");
return p;
}
private static final int NO_LOCUS_ID = -1;
private static int toLocusType(final CacheType ct) {
switch (ct) {
case TRADITIONAL:
return GeocachingData.CACHE_TYPE_TRADITIONAL;
case MULTI:
return GeocachingData.CACHE_TYPE_MULTI;
case MYSTERY:
return GeocachingData.CACHE_TYPE_MYSTERY;
case LETTERBOX:
return GeocachingData.CACHE_TYPE_LETTERBOX;
case EVENT:
return GeocachingData.CACHE_TYPE_EVENT;
case MEGA_EVENT:
return GeocachingData.CACHE_TYPE_MEGA_EVENT;
case GIGA_EVENT:
return GeocachingData.CACHE_TYPE_GIGA_EVENT;
case EARTH:
return GeocachingData.CACHE_TYPE_EARTH;
case CITO:
return GeocachingData.CACHE_TYPE_CACHE_IN_TRASH_OUT;
case WEBCAM:
return GeocachingData.CACHE_TYPE_WEBCAM;
case VIRTUAL:
return GeocachingData.CACHE_TYPE_VIRTUAL;
case WHERIGO:
return GeocachingData.CACHE_TYPE_WHERIGO;
case LOSTANDFOUND:
return GeocachingData.CACHE_TYPE_LF_EVENT;
case PROJECT_APE:
return GeocachingData.CACHE_TYPE_PROJECT_APE;
case GPS_EXHIBIT:
return GeocachingData.CACHE_TYPE_GPS_ADVENTURE;
default:
return NO_LOCUS_ID;
}
}
private static int toLocusSize(final CacheSize cs) {
switch (cs) {
case MICRO:
return GeocachingData.CACHE_SIZE_MICRO;
case SMALL:
return GeocachingData.CACHE_SIZE_SMALL;
case REGULAR:
return GeocachingData.CACHE_SIZE_REGULAR;
case LARGE:
return GeocachingData.CACHE_SIZE_LARGE;
case NOT_CHOSEN:
return GeocachingData.CACHE_SIZE_NOT_CHOSEN;
case OTHER:
return GeocachingData.CACHE_SIZE_OTHER;
default:
return NO_LOCUS_ID;
}
}
@Nullable
private static String toLocusWaypoint(final WaypointType wt) {
switch (wt) {
case FINAL:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_FINAL;
case OWN:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_PHYSICAL_STAGE;
case PARKING:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_PARKING;
case PUZZLE:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_VIRTUAL_STAGE;
case STAGE:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_PHYSICAL_STAGE;
case TRAILHEAD:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_TRAILHEAD;
case WAYPOINT:
return GeocachingWaypoint.CACHE_WAYPOINT_TYPE_REFERENCE;
default:
return null;
}
}
}