package com.truckmuncher.app.customer;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.SphericalUtil;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.ClusterRenderer;
import com.truckmuncher.app.ApiClientFragment;
import com.truckmuncher.app.R;
import com.truckmuncher.app.data.PublicContract;
import com.truckmuncher.app.data.sql.WhereClause;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import butterknife.ButterKnife;
import butterknife.InjectView;
import static com.truckmuncher.app.data.sql.WhereClause.Operator.EQUALS;
public class CustomerMapFragment extends ApiClientFragment implements
ClusterManager.OnClusterClickListener<TruckCluster>,
ClusterManager.OnClusterItemClickListener<TruckCluster>,
TruckHeaderFragment.OnTruckHeaderClickListener,
LoaderManager.LoaderCallbacks<Cursor>,
Searchable {
private static final int REQUEST_TRUCK_DETAILS = 0;
private static final int LOADER_TRUCKS = 0;
private static final String ARG_MAP_STATE = "map_state";
private static final int REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
private static final int FASTEST_REFRESH_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_LOCATION_CHANGE = 500; // meters
@InjectView(R.id.view_pager)
ViewPager viewPager;
@InjectView(R.id.customer_map)
MapView mapView;
LatLng currentLocation;
ClusterManager<TruckCluster> clusterManager;
private Map<String, TruckCluster> activeTruckMarkers = Collections.emptyMap();
private SimpleSearchServiceHelper serviceHelper;
private TruckHeaderPagerAdapter pagerAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
serviceHelper = new SimpleSearchServiceHelper();
MapsInitializer.initialize(getActivity().getApplicationContext());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_customer_map, container, false);
ButterKnife.inject(this, view);
if (savedInstanceState != null) {
Bundle mapState = savedInstanceState.getBundle(ARG_MAP_STATE);
mapView.onCreate(mapState);
} else {
mapView.onCreate(null);
}
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
GoogleMap map = mapView.getMap();
apiClient = new GoogleApiClient.Builder(getActivity(), this, this)
.addApi(LocationServices.API)
.build();
if (map != null) {
map.getUiSettings().setZoomControlsEnabled(true);
map.setMyLocationEnabled(true);
setUpClusterer();
}
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// Change the focused truck
moveTo(pagerAdapter.getTruckId(position));
}
});
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(LOADER_TRUCKS, getArguments(), this);
}
@Override
public void onResume() {
super.onResume();
mapView.onResume();
}
@Override
public void onPause() {
super.onPause();
mapView.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mapView.onDestroy();
ButterKnife.reset(this);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Bundle mapState = new Bundle();
mapView.onSaveInstanceState(mapState);
outState.putParcelable(ARG_MAP_STATE, mapState);
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_TRUCK_DETAILS:
if (resultCode == Activity.RESULT_OK) {
String lastTruckId = data.getStringExtra(TruckDetailsActivity.ARG_ENDING_TRUCK);
viewPager.setCurrentItem(pagerAdapter.getTruckPosition(lastTruckId));
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onConnected(Bundle bundle) {
Location myLocation = LocationServices.FusedLocationApi.getLastLocation(apiClient);
if (myLocation != null) {
LatLng latLng = new LatLng(myLocation.getLatitude(), myLocation.getLongitude());
MapsInitializer.initialize(getActivity());
mapView.getMap().animateCamera(CameraUpdateFactory.newLatLng(latLng));
}
LocationRequest request = new LocationRequest()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setFastestInterval(FASTEST_REFRESH_INTERVAL)
.setInterval(REFRESH_INTERVAL);
LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, request, this);
Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(apiClient);
if (lastLocation != null) {
onLocationChanged(lastLocation);
}
}
@Override
public void onLocationChanged(Location location) {
// if the current location is null, we haven't loaded the active trucks yet.
boolean trucksNeedLoading = currentLocation == null;
LatLng oldLocation = currentLocation;
currentLocation = new LatLng(location.getLatitude(), location.getLongitude());
if (trucksNeedLoading) {
loadActiveTrucks();
getActivity().startService(GetTruckProfilesService.newIntent(getActivity(), currentLocation.latitude, currentLocation.longitude));
}
if (oldLocation == null ||
SphericalUtil.computeDistanceBetween(oldLocation, currentLocation) > MIN_LOCATION_CHANGE) {
getLoaderManager().restartLoader(LOADER_TRUCKS, null, this);
}
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
switch (i) {
case LOADER_TRUCKS:
String orderBy = null;
if (currentLocation != null) {
// We want the trucks to be ordered by their distance from the user's current location.
// Sqlite is relatively limited in its computational powers, so we'll use a modified
// Pythagorean Theorem equation to calculate the distance.
double longitudeAdjustment = Math.pow(Math.cos(Math.toRadians(currentLocation.latitude)), 2);
orderBy = String.format("((%f - latitude) * (%f - latitude) + (%f - longitude) * (%f - longitude) * %f) ASC",
currentLocation.latitude, currentLocation.latitude, currentLocation.longitude,
currentLocation.longitude, longitudeAdjustment);
}
WhereClause whereClause = new WhereClause.Builder()
.where(PublicContract.Truck.IS_SERVING, EQUALS, true)
.and()
.where(PublicContract.Truck.MATCHED_SEARCH, EQUALS, true)
.build();
return new CursorLoader(getActivity(), PublicContract.TRUCK_URI, ActiveTrucksQuery.PROJECTION, whereClause.selection, whereClause.selectionArgs, orderBy);
default:
throw new RuntimeException("Invalid loader id: " + i);
}
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
cursor.moveToPosition(-1);
activeTruckMarkers = new HashMap<>();
while (cursor.moveToNext()) {
String truckId = cursor.getString(ActiveTrucksQuery.ID);
LatLng location = new LatLng(cursor.getDouble(ActiveTrucksQuery.LATITUDE),
cursor.getDouble(ActiveTrucksQuery.LONGITUDE));
activeTruckMarkers.put(truckId, new TruckCluster(truckId, location));
}
forceClusterRender(activeTruckMarkers.values());
if (cursor.getCount() == 0) {
getActivity().findViewById(R.id.empty).setVisibility(View.VISIBLE);
viewPager.setVisibility(View.GONE);
} else {
getActivity().findViewById(R.id.empty).setVisibility(View.GONE);
viewPager.setVisibility(View.VISIBLE);
pagerAdapter = new TruckHeaderPagerAdapter(getFragmentManager(), cursor, currentLocation, this);
viewPager.setAdapter(pagerAdapter);
}
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
pagerAdapter = null;
}
@Override
public boolean onClusterClick(Cluster<TruckCluster> cluster) {
float currentZoom = mapView.getMap().getCameraPosition().zoom;
mapView.getMap().animateCamera(CameraUpdateFactory.newLatLngZoom(cluster.getPosition(), currentZoom + 1));
// true to prevent the default behavior from occurring
return true;
}
@Override
public void onTruckHeaderClick(String currentTruck) {
ArrayList<String> truckIds = pagerAdapter.getTruckIds();
startActivityForResult(TruckDetailsActivity.newIntent(getActivity(),
truckIds, currentTruck), REQUEST_TRUCK_DETAILS);
}
@Override
public boolean onClusterItemClick(final TruckCluster truckClusterItem) {
String truckId = truckClusterItem.getTruckId();
viewPager.setCurrentItem(pagerAdapter.getTruckPosition(truckId));
// false to preserve the default behavior of centering the screen on the marker
return false;
}
private void loadActiveTrucks() {
// Kick off a refresh of the vendor data
getActivity().startService(ActiveTrucksService.newIntent(getActivity(), currentLocation));
}
private void setUpClusterer() {
GoogleMap map = mapView.getMap();
// Initialize the manager with the context and the map.
clusterManager = new ClusterManager<>(getActivity(), map);
ClusterRenderer<TruckCluster> renderer = new TruckClusterRenderer<>(getActivity(), map, clusterManager);
clusterManager.setRenderer(renderer);
clusterManager.setOnClusterClickListener(this);
clusterManager.setOnClusterItemClickListener(this);
// Point the map's listeners at the listeners implemented by the cluster manager.
map.setOnCameraChangeListener(clusterManager);
map.setOnMarkerClickListener(clusterManager);
}
private void forceClusterRender(Collection<TruckCluster> markers) {
if (clusterManager != null) {
clusterManager.clearItems();
clusterManager.addItems(markers);
clusterManager.cluster();
}
}
public void moveTo(String truckId) {
TruckCluster cluster = activeTruckMarkers.get(truckId);
if (cluster != null) {
mapView.getMap().animateCamera(CameraUpdateFactory.newLatLng(cluster.getPosition()));
}
}
@Override
public void doSearch(String query) {
if (query == null || query.isEmpty()) {
serviceHelper.clearSearchQueryMatches(getActivity());
} else {
getActivity().startService(SimpleSearchService.newIntent(getActivity(), query));
}
}
public interface ActiveTrucksQuery {
String[] PROJECTION = new String[]{
PublicContract.Truck.ID,
PublicContract.Truck.LATITUDE,
PublicContract.Truck.LONGITUDE
};
int ID = 0;
int LATITUDE = 1;
int LONGITUDE = 2;
}
}