package org.mtransit.android.task;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
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.ComparatorUtils;
import org.mtransit.android.commons.LocationUtils;
import org.mtransit.android.commons.MTLog;
import org.mtransit.android.commons.RuntimeUtils;
import org.mtransit.android.commons.provider.GTFSProviderContract;
import org.mtransit.android.commons.provider.POIProviderContract;
import org.mtransit.android.commons.task.MTCallable;
import org.mtransit.android.data.AgencyProperties;
import org.mtransit.android.data.DataSourceManager;
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.SearchFragment;
import android.content.Context;
import android.location.Location;
import android.text.TextUtils;
public class POISearchLoader extends MTAsyncTaskLoaderV4<ArrayList<POIManager>> {
private static final String TAG = POISearchLoader.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private ArrayList<POIManager> pois;
private String query;
private SearchFragment.TypeFilter typeFilter;
private Location userLocation;
public POISearchLoader(Context context, String query, SearchFragment.TypeFilter typeFilter, Location userLocation) {
super(context);
this.query = query;
this.typeFilter = typeFilter;
this.userLocation = userLocation;
}
@Override
public ArrayList<POIManager> loadInBackgroundMT() {
if (this.pois != null) {
return this.pois;
}
clearAllSearchTypesTasks();
this.pois = new ArrayList<POIManager>();
if (TextUtils.isEmpty(this.query)) {
return this.pois;
}
HashSet<String> favoriteUUIDs = FavoriteManager.findFavoriteUUIDs(getContext());
POISearchComparator poiSearchComparator = new POISearchComparator(favoriteUUIDs);
boolean keepAll;
ArrayList<DataSourceType> agencyTypes;
if (this.typeFilter == null || this.typeFilter.getDataSourceTypeId() == SearchFragment.TypeFilter.ALL.getDataSourceTypeId()) {
agencyTypes = DataSourceProvider.get(getContext()).getAvailableAgencyTypes();
keepAll = false;
} else {
agencyTypes = new ArrayList<DataSourceType>();
agencyTypes.add(DataSourceType.parseId(this.typeFilter.getDataSourceTypeId()));
keepAll = true;
}
ArrayList<Future<ArrayList<POIManager>>> taskList = new ArrayList<Future<ArrayList<POIManager>>>();
if (agencyTypes != null) {
for (DataSourceType agencyType : agencyTypes) {
if (!agencyType.isSearchable()) {
continue;
}
taskList.add(getFetchSearchTypeExecutor().submit(
new FindSearchTypeTask(getContext(), agencyType, this.query, keepAll, this.userLocation, poiSearchComparator)));
}
}
for (Future<ArrayList<POIManager>> future : taskList) {
try {
ArrayList<POIManager> typePOIs = future.get();
if (typePOIs != null) {
this.pois.addAll(typePOIs);
}
} catch (Exception e) {
MTLog.w(this, e, "Error while loading in background!");
}
}
clearAllSearchTypesTasks();
return this.pois;
}
private static final int CORE_POOL_SIZE = RuntimeUtils.NUMBER_OF_CORES;
private static final int MAX_POOL_SIZE = RuntimeUtils.NUMBER_OF_CORES;
private ThreadPoolExecutor fetchSearchTypeExecutor;
public ThreadPoolExecutor getFetchSearchTypeExecutor() {
if (this.fetchSearchTypeExecutor == null) {
this.fetchSearchTypeExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
}
return fetchSearchTypeExecutor;
}
private void clearAllSearchTypesTasks() {
if (this.fetchSearchTypeExecutor != null) {
this.fetchSearchTypeExecutor.shutdown();
this.fetchSearchTypeExecutor = null;
}
}
@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);
}
}
private static class FindSearchTypeTask extends MTCallable<ArrayList<POIManager>> {
private static final String TAG = POISearchLoader.class.getSimpleName() + ">" + FindSearchTypeTask.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private Context context;
private DataSourceType agencyType;
private String query;
private boolean keepAll;
private Location userLocation;
private POISearchComparator poiSearchComparator;
public FindSearchTypeTask(Context context, DataSourceType agencyType, String query, boolean keepAll, Location userLocation,
POISearchComparator poiSearchComparator) {
this.context = context;
this.agencyType = agencyType;
this.query = query;
this.keepAll = keepAll;
this.userLocation = userLocation;
this.poiSearchComparator = poiSearchComparator;
}
@Override
public ArrayList<POIManager> callMT() throws Exception {
if (TextUtils.isEmpty(this.query)) {
return null;
}
clearFetchAgencySearchTasks();
ArrayList<AgencyProperties> agencies = DataSourceProvider.get(this.context).getTypeDataSources(this.context, this.agencyType.getId());
ArrayList<Future<ArrayList<POIManager>>> taskList = new ArrayList<Future<ArrayList<POIManager>>>();
if (agencies != null) {
for (AgencyProperties agency : agencies) {
FindSearchTask task = new FindSearchTask(this.context, agency, this.query, this.userLocation);
taskList.add(getFetchAgencySearchExecutor().submit(task));
}
}
ArrayList<POIManager> typePois = new ArrayList<POIManager>();
for (Future<ArrayList<POIManager>> future : taskList) {
try {
ArrayList<POIManager> agencyPOIs = future.get();
if (agencyPOIs != null) {
typePois.addAll(agencyPOIs);
}
} catch (Exception e) {
MTLog.w(this, e, "Error while loading in background!");
}
}
clearFetchAgencySearchTasks();
LocationUtils.updateDistance(typePois, this.userLocation);
CollectionUtils.sort(typePois, this.poiSearchComparator);
if (!this.keepAll && typePois.size() > 2) {
typePois = new ArrayList<POIManager>(typePois.subList(0, 2));
}
return typePois;
}
private static final int CORE_POOL_SIZE = RuntimeUtils.NUMBER_OF_CORES;
private static final int MAX_POOL_SIZE = RuntimeUtils.NUMBER_OF_CORES;
private ThreadPoolExecutor fetchAgencySearchExecutor;
public ThreadPoolExecutor getFetchAgencySearchExecutor() {
if (this.fetchAgencySearchExecutor == null) {
this.fetchAgencySearchExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
}
return fetchAgencySearchExecutor;
}
public void clearFetchAgencySearchTasks() {
if (this.fetchAgencySearchExecutor != null) {
this.fetchAgencySearchExecutor.shutdown();
this.fetchAgencySearchExecutor = null;
}
}
}
private static class POISearchComparator implements Comparator<POIManager> {
private HashSet<String> favoriteUUIDs;
public POISearchComparator(HashSet<String> favoriteUUIDs) {
this.favoriteUUIDs = favoriteUUIDs;
}
@Override
public int compare(POIManager lhs, POIManager rhs) {
if (lhs == null && rhs == null) {
return ComparatorUtils.SAME;
} else if (lhs == null) {
return ComparatorUtils.AFTER;
} else if (rhs == null) {
return ComparatorUtils.BEFORE;
}
int lScore = lhs.poi.getScore() == null ? 0 : lhs.poi.getScore();
int rScore = lhs.poi.getScore() == null ? 0 : rhs.poi.getScore();
if (lScore > rScore) {
return ComparatorUtils.BEFORE;
} else if (lScore < rScore) {
return ComparatorUtils.AFTER;
}
boolean lfav = this.favoriteUUIDs.contains(lhs.poi.getUUID());
boolean rfav = this.favoriteUUIDs.contains(rhs.poi.getUUID());
if (lfav && !rfav) {
return ComparatorUtils.BEFORE;
} else if (!lfav && rfav) {
return ComparatorUtils.AFTER;
}
float ld = lhs.getDistance();
float rd = rhs.getDistance();
if (ld > rd) {
return ComparatorUtils.AFTER;
} else if (ld < rd) {
return ComparatorUtils.BEFORE;
}
return ComparatorUtils.SAME;
}
}
private static class FindSearchTask extends MTCallable<ArrayList<POIManager>> {
private static final String TAG = POISearchLoader.class.getSimpleName() + ">" + FindSearchTask.class.getSimpleName();
@Override
public String getLogTag() {
return TAG;
}
private Context context;
private AgencyProperties agency;
private String query;
private Location userLocation;
public FindSearchTask(Context context, AgencyProperties agency, String query, Location userLocation) {
this.context = context;
this.agency = agency;
this.query = query;
this.userLocation = userLocation;
}
@Override
public ArrayList<POIManager> callMT() throws Exception {
if (TextUtils.isEmpty(this.query)) {
return null;
}
POIProviderContract.Filter poiFilter = POIProviderContract.Filter.getNewSearchFilter(this.query);
poiFilter.addExtra(GTFSProviderContract.POI_FILTER_EXTRA_DESCENT_ONLY, true);
if (this.userLocation != null) {
poiFilter.addExtra("lat", this.userLocation.getLatitude());
poiFilter.addExtra("lng", this.userLocation.getLongitude());
}
return DataSourceManager.findPOIs(this.context, this.agency.getAuthority(), poiFilter);
}
}
}