package dekk.pw.pokemate.tasks;
import com.google.common.geometry.S2LatLng;
import com.google.maps.DirectionsApi;
import com.google.maps.DirectionsApiRequest;
import com.google.maps.GeoApiContext;
import com.google.maps.model.*;
import com.pokegoapi.api.map.fort.Pokestop;
import com.pokegoapi.exceptions.LoginFailedException;
import com.pokegoapi.exceptions.RemoteServerException;
import dekk.pw.pokemate.Config;
import dekk.pw.pokemate.Context;
import dekk.pw.pokemate.Walking;
import dekk.pw.pokemate.util.Time;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Created by TimD on 7/21/2016.
* Credit: https://github.com/mjmfighter/pokemon-go-bot/blob/master/src/main/java/com/mjmfighter/pogobot/LocationWalker.java
*/
public class Navigate extends Task implements Runnable {
private final LatLng min, max;
public static final double VARIANCE = Config.getRange();
private static final List<DirectionsStep[]> routes = new ArrayList<>();
private static final List<S2LatLng> route = new ArrayList<>();
private static final Logger logger = LogManager.getLogger(Navigate.class);
private static final Object lock = new Object();
public static final NavigationType navigationType = Config.getNavigationType();
public static boolean populated;
private S2LatLng last;
private final List<String> ids = new ArrayList<>();
public Navigate(final Context context, LatLng min, LatLng max) {
super(context);
this.min = new LatLng(min.lat < max.lat ? min.lat : max.lat, min.lng < max.lng ? min.lng : max.lng);
this.max = new LatLng(min.lat > max.lat ? min.lat : max.lat, min.lng > max.lng ? min.lng : max.lng);
switch (navigationType) {
//Untested
case POKESTOPS:
if (context.getRoutesIndex() == 0)
populateRoute(context);
break;
default:
if (context.getRoutesIndex() == 0)
populateDirections(context);
break;
}
populated = true;
}
public static NavigationType getNavigationType() {
return navigationType;
}
public static List<S2LatLng> getRoute() {
return route;
}
/**
* Attempts to generate a route to all found pokestops...
*
* @param context
*/
private void populateRoute(Context context) {
Time.sleep(300);
try {
List<Pokestop> stops = context.getMap().getMapObjects().getPokestops().stream().filter(a ->
//only pokestops in our region
a.getLatitude() >= min.lat &&
a.getLatitude() <= max.lat &&
a.getLongitude() >= min.lng &&
a.getLongitude() <= max.lng).collect(Collectors.toList());
int count = stops.size();
last = S2LatLng.fromDegrees(context.getLat().doubleValue(), context.getLng().doubleValue());
while (route.size() < count - 1) {
List<Pokestop> tempStops = stops.stream().filter(a -> !ids.contains(a.getId())).sorted((a, b) -> {
S2LatLng locationA = S2LatLng.fromDegrees(a.getLatitude(), a.getLongitude());
S2LatLng locationB = S2LatLng.fromDegrees(b.getLatitude(), b.getLongitude());
Double distanceA = last.getEarthDistance(locationA);
Double distanceB = last.getEarthDistance(locationB);
return distanceA.compareTo(distanceB);
}).collect(Collectors.toList());
if (tempStops.size() == 0) {
logger.fatal("Critical Pokestop Error");
break;
}
Pokestop first = tempStops.get(0);
route.add(last = S2LatLng.fromDegrees(first.getLatitude(), first.getLongitude()));
ids.add(first.getId());
}
route.add(S2LatLng.fromDegrees(context.getLat().get(), context.getLng().get()));
} catch (RemoteServerException e) {
logger.error("Remote server error", e);
//e.printStackTrace();
} catch (LoginFailedException e) {
logger.error("Login failed.", e);
//e.printStackTrace();
} catch (NullPointerException e) {
logger.error("Null pointer error.", e);
//e.printStackTrace();
}
}
@Override
public void run() {
if (context.isWalking()) {
return;
} else if (navigationType == (NavigationType.STREETS) && context.getRoutesIndex() >= getDirections().size()) {
context.resetRoutesIndex();
} else if (navigationType == (NavigationType.POKESTOPS) && context.getRoutesIndex() >= getRoute().size()-1) {
context.resetRoutesIndex();
}
switch (navigationType) {
case POKESTOPS:
context.increaseRoutesIndex();
Walking.walk(context, route.get(context.getRoutesIndex()));
context.setConsoleString("Navigate", String.format("Navigating to waypoint %d of %d", context.getRoutesIndex(), route.size()-1));
break;
case POKEMON:
//TODO: walk dynamically to nearest pokemon
break;
default:
context.increaseRoutesIndex();
Walking.walk(context, getDirections().get(context.getRoutesIndex()));
}
}
private LatLng getNextLocation() {
Random ran = new Random();
double nextLat = min.lat + (max.lat - min.lat) * ran.nextDouble();
double nextLng = min.lng + (max.lng - min.lng) * ran.nextDouble();
return new LatLng(nextLat, nextLng);
}
private DirectionsStep[] queryDirections(LatLng start, LatLng end) {
DirectionsStep[] stepsToTake = null;
GeoApiContext ctx = new GeoApiContext().setApiKey(Config.getGoogleApiKey());
DirectionsApiRequest request = DirectionsApi.newRequest(ctx)
.origin(start)
.destination(end)
.mode(TravelMode.WALKING);
try {
DirectionsResult result = request.await();
if (result.routes.length > 0) {
DirectionsRoute directionsRoute = result.routes[0];
if (directionsRoute.legs.length > 0) {
DirectionsLeg legs = directionsRoute.legs[0];
if (legs.steps.length > 0) {
stepsToTake = legs.steps;
}
}
}
} catch (Exception e) {
e.printStackTrace();
logger.error("Remote Server Exception", e);
}
return stepsToTake;
}
private void populateDirections(Context context) {
int i = 0;
LatLng start = new LatLng(context.getLat().get(), context.getLng().get());
LatLng current = start;
LatLng next = getNextLocation();
while (i < Config.getMapPoints()) {
DirectionsStep[] steps = queryDirections(current, next);
if (steps != null) {
getDirections().add(steps);
current = next;
next = getNextLocation();
i++;
} else {
next = getNextLocation();
}
}
DirectionsStep[] steps = queryDirections(next, start);
if (steps != null) {
getDirections().add(steps);
}
}
public static List<DirectionsStep[]> getDirections() {
synchronized (lock) {
return routes;
}
}
public enum NavigationType {
STREETS, POKESTOPS, POKEMON
}
}