package cgeo.geocaching.models;
import cgeo.geocaching.enumerations.CoordinatesType;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.WaypointType;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.GeopointParser;
import cgeo.geocaching.maps.mapsforge.v6.caches.GeoitemRef;
import cgeo.geocaching.storage.DataStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
public class Waypoint implements IWaypoint {
public static final String PREFIX_OWN = "OWN";
private static final int ORDER_UNDEFINED = -2;
private int id = -1;
private String geocode = "geocode";
private WaypointType waypointType = WaypointType.WAYPOINT;
private String prefix = "";
private String lookup = "";
private String name = "";
private Geopoint coords = null;
private String note = "";
private String userNote = "";
private int cachedOrder = ORDER_UNDEFINED;
private boolean own = false;
private boolean visited = false;
private boolean originalCoordsEmpty = false;
/**
* require name and type for every waypoint
*
*/
public Waypoint(final String name, final WaypointType type, final boolean own) {
this.name = name;
this.waypointType = type;
this.own = own;
}
/**
* copy constructor
*
*/
public Waypoint(final Waypoint other) {
merge(other);
this.waypointType = other.waypointType;
id = -1;
}
public void merge(final Waypoint old) {
if (StringUtils.isBlank(prefix)) {
setPrefix(old.prefix);
}
if (StringUtils.isBlank(lookup)) {
lookup = old.lookup;
}
if (StringUtils.isBlank(name)) {
name = old.name;
}
if (coords == null) {
coords = old.coords;
}
if (StringUtils.isBlank(note)) {
note = old.note;
}
if (StringUtils.isBlank(userNote)) {
userNote = old.userNote;
}
if (StringUtils.equals(note, userNote)) {
userNote = "";
}
if (id < 0) {
id = old.id;
}
visited = old.visited;
}
public static void mergeWayPoints(final List<Waypoint> newPoints, final List<Waypoint> oldPoints, final boolean forceMerge) {
// Build a map of new waypoints for faster subsequent lookups
final Map<String, Waypoint> newPrefixes = new HashMap<>(newPoints.size());
for (final Waypoint waypoint : newPoints) {
newPrefixes.put(waypoint.getPrefix(), waypoint);
}
// Copy user modified details of the old waypoints over the new ones
for (final Waypoint oldWaypoint : oldPoints) {
final String prefix = oldWaypoint.getPrefix();
if (newPrefixes.containsKey(prefix)) {
newPrefixes.get(prefix).merge(oldWaypoint);
} else if (oldWaypoint.isUserDefined() || forceMerge) {
newPoints.add(oldWaypoint);
}
}
}
public boolean isUserDefined() {
return own || waypointType == WaypointType.OWN;
}
public void setUserDefined() {
own = true;
setPrefix(PREFIX_OWN);
}
private int order() {
if (cachedOrder == ORDER_UNDEFINED) {
cachedOrder = waypointType.order;
}
return cachedOrder;
}
@NonNull
public String getPrefix() {
return prefix;
}
public void setPrefix(@NonNull final String prefix) {
this.prefix = prefix;
cachedOrder = ORDER_UNDEFINED;
}
@NonNull
public String getUrl() {
return "https://www.geocaching.com/seek/cache_details.aspx?wp=" + geocode;
}
@Override
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
@Override
public String getGeocode() {
return geocode;
}
public void setGeocode(final String geocode) {
this.geocode = StringUtils.upperCase(geocode);
}
@Override
public WaypointType getWaypointType() {
return waypointType;
}
public String getLookup() {
return lookup;
}
public void setLookup(final String lookup) {
this.lookup = lookup;
}
@Override
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Nullable
@Override
public Geopoint getCoords() {
return coords;
}
public void setCoords(final Geopoint coords) {
this.coords = coords;
}
public String getNote() {
return note;
}
public void setNote(final String note) {
this.note = note;
}
@Override
public String toString() {
return name + " " + waypointType.getL10n();
}
/**
* Checks whether a given waypoint is a final and has coordinates
*
* @return True - waypoint is final and has coordinates, False - otherwise
*/
public boolean isFinalWithCoords() {
return waypointType == WaypointType.FINAL && coords != null;
}
@Override
public CoordinatesType getCoordType() {
return CoordinatesType.WAYPOINT;
}
public void setVisited(final boolean visited) {
this.visited = visited;
}
public boolean isVisited() {
return visited;
}
public int getStaticMapsHashcode() {
long hash = 0;
if (coords != null) {
hash = coords.hashCode();
}
hash ^= waypointType.markerId;
return (int) hash;
}
/**
* Sort waypoints by their probable order (e.g. parking first, final last).
*/
public static final Comparator<? super Waypoint> WAYPOINT_COMPARATOR = new Comparator<Waypoint>() {
@Override
public int compare(final Waypoint left, final Waypoint right) {
return left.order() - right.order();
}
};
/**
* Delegates the creation of the waypoint-id for gpx-export to the waypoint
*
*/
public String getGpxId() {
String gpxId = prefix;
if (StringUtils.isNotBlank(geocode)) {
final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB);
if (cache != null) {
gpxId = cache.getWaypointGpxId(prefix);
}
}
return gpxId;
}
/**
* Detect coordinates in the given text and converts them to user defined waypoints.
* Works by rule of thumb.
*
* @param text Text to parse for waypoints
* @param namePrefix Prefix of the name of the waypoint
* @return a collection of found waypoints
*/
public static Collection<Waypoint> parseWaypoints(@NonNull final String text, @NonNull final String namePrefix) {
final List<Waypoint> waypoints = new LinkedList<>();
final Collection<ImmutablePair<Geopoint, Integer>> matches = GeopointParser.parseAll(text);
int count = 1;
for (final ImmutablePair<Geopoint, Integer> match : matches) {
final Geopoint point = match.getLeft();
final Integer start = match.getRight();
final String name = namePrefix + " " + count;
final String potentialWaypointType = text.substring(Math.max(0, start - 15));
final Waypoint waypoint = new Waypoint(name, parseWaypointType(potentialWaypointType), true);
waypoint.setCoords(point);
waypoints.add(waypoint);
count++;
}
return waypoints;
}
/**
* Detect waypoint types in the personal note text. It works by rule of thumb only.
*/
private static WaypointType parseWaypointType(final String input) {
final String lowerInput = StringUtils.substring(input, 0, 20).toLowerCase(Locale.getDefault());
for (final WaypointType wpType : WaypointType.values()) {
if (lowerInput.contains(wpType.getL10n().toLowerCase(Locale.getDefault()))) {
return wpType;
}
if (lowerInput.contains(wpType.id)) {
return wpType;
}
if (lowerInput.contains(wpType.name().toLowerCase(Locale.US))) {
return wpType;
}
}
return WaypointType.WAYPOINT;
}
public GeoitemRef getGeoitemRef() {
return new GeoitemRef(getGpxId(), getCoordType(), getGeocode(), getId(), getName(), getWaypointType().markerId);
}
public String getUserNote() {
return userNote;
}
public void setUserNote(final String userNote) {
this.userNote = userNote;
}
public boolean isOriginalCoordsEmpty() {
return originalCoordsEmpty;
}
public void setOriginalCoordsEmpty(final boolean originalCoordsEmpty) {
this.originalCoordsEmpty = originalCoordsEmpty;
}
}