package org.mtransit.android.task;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.mtransit.android.commons.CollectionUtils;
import org.mtransit.android.commons.LocationUtils;
import org.mtransit.android.commons.MTLog;
import org.mtransit.android.commons.RuntimeUtils;
import org.mtransit.android.commons.data.RouteTripStop;
import org.mtransit.android.data.AgencyProperties;
import org.mtransit.android.data.DataSourceProvider;
import org.mtransit.android.data.DataSourceType;
import org.mtransit.android.data.POIManager;
import org.mtransit.android.provider.FavoriteManager;
import org.mtransit.android.ui.fragment.HomeFragment;
import android.app.Activity;
import android.content.Context;
public class HomePOILoader extends MTAsyncTaskLoaderV4<ArrayList<POIManager>> {
private static final String TAG = HomePOILoader.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private static final int NB_MAX_BY_TYPE = 2;
private static final int NB_MAX_BY_TYPE_ONE_TYPE = 6;
private static final int NB_MAX_BY_TYPE_TWO_TYPES = 4;
private int nbMaxByType = NB_MAX_BY_TYPE;
private double lat;
private double lng;
private float accuracyInMeters;
private ArrayList<POIManager> pois;
private WeakReference<HomeFragment> homeFragmentWR;
public HomePOILoader(HomeFragment homeFragment, Context context, double lat, double lng, float accuracyInMeters) {
super(context);
this.lat = lat;
this.lng = lng;
this.accuracyInMeters = accuracyInMeters;
this.homeFragmentWR = new WeakReference<HomeFragment>(homeFragment);
}
@Override
public ArrayList<POIManager> loadInBackgroundMT() {
if (this.pois != null) {
return this.pois;
}
this.pois = new ArrayList<POIManager>();
HashSet<String> favoriteUUIDs = FavoriteManager.findFavoriteUUIDs(getContext());
ArrayList<DataSourceType> availableAgencyTypes = DataSourceProvider.get(getContext()).getAvailableAgencyTypes();
if (availableAgencyTypes != null) {
if (availableAgencyTypes.size() <= 2) {
this.nbMaxByType = NB_MAX_BY_TYPE_ONE_TYPE;
} else if (availableAgencyTypes.size() <= 3) {
this.nbMaxByType = NB_MAX_BY_TYPE_TWO_TYPES;
}
float minDistanceInMeters = LocationUtils.getAroundCoveredDistanceInMeters(this.lat, this.lng, LocationUtils.MIN_AROUND_DIFF);
if (minDistanceInMeters < LocationUtils.MIN_NEARBY_LIST_COVERAGE_IN_METERS) {
minDistanceInMeters = LocationUtils.MIN_NEARBY_LIST_COVERAGE_IN_METERS;
}
if (minDistanceInMeters < this.accuracyInMeters) {
minDistanceInMeters = this.accuracyInMeters;
}
for (DataSourceType type : availableAgencyTypes) {
if (!type.isHomeScreen()) {
continue;
}
ArrayList<POIManager> typePOIs = findAllNearby(getContext(), type, this.lat, this.lng, minDistanceInMeters, this.nbMaxByType);
filterTypePOIs(favoriteUUIDs, typePOIs, minDistanceInMeters);
CollectionUtils.sort(typePOIs, POIManager.POI_ALPHA_COMPARATOR);
deliverPartialResult(typePOIs);
this.pois.addAll(typePOIs);
}
}
return this.pois;
}
private void deliverPartialResult(final ArrayList<POIManager> typePOIs) {
HomeFragment homeFragment = this.homeFragmentWR == null ? null : this.homeFragmentWR.get();
if (homeFragment != null) {
Activity activity = homeFragment.getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
HomeFragment homeFragment = HomePOILoader.this.homeFragmentWR == null ? null : HomePOILoader.this.homeFragmentWR.get();
if (homeFragment != null) {
homeFragment.onLoadPartial(typePOIs);
}
}
});
}
}
}
private void filterTypePOIs(HashSet<String> favoriteUUIDs, ArrayList<POIManager> typePOIs, float minDistanceInMeters) {
Iterator<POIManager> it = typePOIs.iterator();
int nbKept = 0;
float lastKeptDistance = -1;
HashSet<String> routeTripKept = new HashSet<String>();
while (it.hasNext()) {
POIManager poim = it.next();
if (!favoriteUUIDs.contains(poim.poi.getUUID())) {
if (poim.poi instanceof RouteTripStop) {
RouteTripStop rts = (RouteTripStop) poim.poi;
String routeTripId = rts.getRoute().getId() + "-" + rts.getTrip().getId();
if (routeTripKept.contains(routeTripId)) {
it.remove();
continue;
}
} else if (nbKept >= this.nbMaxByType && lastKeptDistance != poim.getDistance()) {
it.remove();
continue;
}
}
if (nbKept >= this.nbMaxByType && lastKeptDistance != poim.getDistance() && poim.getDistance() > minDistanceInMeters) {
it.remove();
continue;
}
if (poim.poi instanceof RouteTripStop) {
RouteTripStop rts = (RouteTripStop) poim.poi;
String routeTripId = rts.getRoute().getId() + "-" + rts.getTrip().getId();
routeTripKept.add(routeTripId);
}
lastKeptDistance = poim.getDistance();
nbKept++;
}
}
private static ArrayList<POIManager> findAllNearby(Context context, DataSourceType type, double typeLat, double typeLng, float typeMinCoverageInMeters,
int nbMaxByType) {
ArrayList<POIManager> typePOIs;
LocationUtils.AroundDiff typeAd = LocationUtils.getNewDefaultAroundDiff();
Double lastTypeAroundDiff = null;
int typeMaxSize = LocationUtils.MAX_NEARBY_LIST;
while (true) {
Collection<AgencyProperties> typeAgencies = DataSourceProvider.get(context).getTypeDataSources(context, type.getId());
typePOIs = findNearby(context, typeLat, typeLng, typeAd, lastTypeAroundDiff, typeMaxSize, typeMinCoverageInMeters, typeAgencies);
if (LocationUtils.searchComplete(typeLat, typeLng, typeAd.aroundDiff)) {
break;
}
if (CollectionUtils.getSize(typePOIs) > nbMaxByType
&& LocationUtils.getAroundCoveredDistanceInMeters(typeLat, typeLng, typeAd.aroundDiff) >= typeMinCoverageInMeters) {
break;
}
if (CollectionUtils.getSize(typePOIs) == 0) {
lastTypeAroundDiff = typeAd.aroundDiff;
} else {
lastTypeAroundDiff = null;
}
LocationUtils.incAroundDiff(typeAd);
}
return typePOIs;
}
private static ArrayList<POIManager> findNearby(Context context, double typeLat, double typeLng, LocationUtils.AroundDiff typeAd,
Double lastTypeAroundDiff, int typeMaxSize, float typeMinCoverageInMeters, Collection<AgencyProperties> typeAgencies) {
ArrayList<POIManager> typePOIs = new ArrayList<POIManager>();
NearbyPOIListLoader.filterAgencies(typeAgencies, typeLat, typeLng, typeAd, lastTypeAroundDiff);
if (typeAgencies == null || typeAgencies.size() == 0) {
return typePOIs;
}
ThreadPoolExecutor executor = new ThreadPoolExecutor(RuntimeUtils.NUMBER_OF_CORES, RuntimeUtils.NUMBER_OF_CORES, 1, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(typeAgencies.size()));
ArrayList<Future<ArrayList<POIManager>>> taskList = new ArrayList<Future<ArrayList<POIManager>>>();
for (AgencyProperties agency : typeAgencies) {
FindNearbyAgencyPOIsTask task = new FindNearbyAgencyPOIsTask(context, agency.getAuthority(), typeLat, typeLng, typeAd.aroundDiff, true, true,
typeMinCoverageInMeters, typeMaxSize);
taskList.add(executor.submit(task));
}
for (Future<ArrayList<POIManager>> future : taskList) {
try {
ArrayList<POIManager> agencyNearbyStops = future.get();
if (agencyNearbyStops != null) {
typePOIs.addAll(agencyNearbyStops);
}
} catch (Exception e) {
MTLog.w(TAG, e, "Error while loading in background!");
}
}
executor.shutdown();
LocationUtils.removeTooMuchWhenNotInCoverage(typePOIs, typeMinCoverageInMeters, typeMaxSize);
return typePOIs;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
if (this.pois != null) {
deliverResult(this.pois);
}
if (this.pois == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
public void deliverResult(ArrayList<POIManager> data) {
this.pois = data;
if (isStarted()) {
super.deliverResult(data);
}
}
}