package mil.nga.giat.mage.map.marker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.MarkerManager;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import org.ocpsoft.prettytime.PrettyTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import mil.nga.giat.mage.R;
import mil.nga.giat.mage.profile.ProfileActivity;
import mil.nga.giat.mage.profile.ProfileFragment;
import mil.nga.giat.mage.sdk.datastore.location.Location;
import mil.nga.giat.mage.sdk.datastore.location.LocationHelper;
import mil.nga.giat.mage.sdk.datastore.location.LocationProperty;
import mil.nga.giat.mage.sdk.datastore.user.User;
import mil.nga.giat.mage.sdk.datastore.user.UserHelper;
import mil.nga.giat.mage.sdk.datastore.user.UserLocal;
import mil.nga.giat.mage.sdk.exceptions.UserException;
import mil.nga.giat.mage.sdk.fetch.DownloadImageTask;
public class LocationMarkerCollection implements PointCollection<Location>, OnMarkerClickListener, OnInfoWindowClickListener {
private static final String LOG_NAME = LocationMarkerCollection.class.getName();
protected GoogleMap map;
protected Context context;
protected Date latestLocationDate = new Date(0);
protected Long clickedAccuracyCircleLocationId;
protected Circle clickedAccuracyCircle;
protected InfoWindowAdapter infoWindowAdapter = new LocationInfoWindowAdapter();
protected boolean visible = true;
protected Map<Long, Long> userIdToLocationId = new ConcurrentHashMap<Long, Long>();
protected Map<Long, Marker> locationIdToMarker = new ConcurrentHashMap<Long, Marker>();
protected Map<String, Location> markerIdToLocation = new ConcurrentHashMap<String, Location>();
protected MarkerManager.Collection markerCollection;
public LocationMarkerCollection(Context context, GoogleMap map) {
this.context = context;
this.map = map;
MarkerManager markerManager = new MarkerManager(map);
markerCollection = markerManager.newCollection();
}
@Override
public void add(Location l) {
final Geometry g = l.getGeometry();
if (g != null) {
// one user has one location
Long locId = userIdToLocationId.get(l.getUser().getId());
if(locId != null) {
if(locationIdToMarker.get(locId) != null) {
Location oldLoc = markerIdToLocation.get(locationIdToMarker.get(locId).getId());
if(oldLoc.getTimestamp().before(l.getTimestamp())) {
remove(oldLoc);
} else {
removeOldMarkers();
return;
}
}
}
// If I got an observation that I already have in my list
// remove it from the map and clean-up my collections
remove(l);
Point point = g.getCentroid();
LatLng latLng = new LatLng(point.getY(), point.getX());
MarkerOptions options = new MarkerOptions().position(latLng).visible(visible).icon(LocationBitmapFactory.bitmapDescriptor(context, l, l.getUser()));
Marker marker = markerCollection.addMarker(options);
userIdToLocationId.put(l.getUser().getId(), l.getId());
locationIdToMarker.put(l.getId(), marker);
markerIdToLocation.put(marker.getId(), l);
if (l.getTimestamp().after(latestLocationDate)) {
latestLocationDate = l.getTimestamp();
}
removeOldMarkers();
}
}
@Override
public void addAll(Collection<Location> locations) {
for (Location l : locations) {
add(l);
}
}
// TODO: this should preserve latestLocationDate
@Override
public void remove(Location l) {
Marker marker = locationIdToMarker.remove(l.getId());
if (marker != null) {
markerIdToLocation.remove(marker.getId());
markerCollection.remove(marker);
marker.remove();
}
}
@Override
public void onInfoWindowClick(Marker marker) {
Location l = markerIdToLocation.get(marker.getId());
if (l == null) {
return;
}
Intent profileView = new Intent(context, ProfileActivity.class);
profileView.putExtra(ProfileFragment.USER_ID, l.getUser().getRemoteId());
context.startActivity(profileView);
}
@Override
public boolean onMarkerClick(Marker marker) {
Location l = markerIdToLocation.get(marker.getId());
if (l == null) {
return false;
}
final Geometry g = l.getGeometry();
if (g != null) {
Point point = g.getCentroid();
LatLng latLng = new LatLng(point.getY(), point.getX());
LocationProperty accuracyProperty = l.getPropertiesMap().get("accuracy");
if (accuracyProperty != null && !accuracyProperty.getValue().toString().trim().isEmpty()) {
try {
Float accuracy = Float.valueOf(accuracyProperty.getValue().toString());
if (clickedAccuracyCircle != null) {
clickedAccuracyCircle.remove();
}
clickedAccuracyCircle = map.addCircle(new CircleOptions().center(latLng).radius(accuracy).fillColor(0x1D43b0ff).strokeColor(0x620069cc).strokeWidth(1.0f));
clickedAccuracyCircleLocationId = l.getId();
} catch (NumberFormatException nfe) {
Log.e(LOG_NAME, "Problem adding accuracy circle to the map.", nfe);
}
}
}
map.setInfoWindowAdapter(infoWindowAdapter);
// make sure to set the Anchor after this call as well, because the size of the icon might have changed
marker.setIcon(LocationBitmapFactory.bitmapDescriptor(context, l, l.getUser()));
marker.setAnchor(0.5f, 1.0f);
marker.showInfoWindow();
return true;
}
public boolean offMarkerClick() {
if (clickedAccuracyCircle != null) {
clickedAccuracyCircle.remove();
clickedAccuracyCircle = null;
}
return true;
}
@Override
public void refreshMarkerIcons() {
for (Marker m : markerCollection.getMarkers()) {
Location tl = markerIdToLocation.get(m.getId());
if (tl != null) {
boolean showWindow = m.isInfoWindowShown();
try {
// make sure to set the Anchor after this call as well, because the size of the icon might have changed
m.setIcon(LocationBitmapFactory.bitmapDescriptor(context, tl, UserHelper.getInstance(context).read(tl.getUser().getId())));
m.setAnchor(0.5f, 1.0f);
} catch (UserException ue) {
Log.e(LOG_NAME, "Error refreshing the icon for user: " + tl.getUser().getId(), ue);
}
if (showWindow) {
m.showInfoWindow();
}
}
}
}
@Override
public void clear() {
clickedAccuracyCircle = null;
locationIdToMarker.clear();
markerIdToLocation.clear();
markerCollection.clear();
latestLocationDate = new Date(0);
}
@Override
public void onCameraChange(CameraPosition cameraPosition) {
// Don't care about this, I am not clustered
}
@Override
public void setVisibility(boolean visible) {
if (this.visible == visible)
return;
this.visible = visible;
for (Marker m : locationIdToMarker.values()) {
m.setVisible(visible);
}
if (clickedAccuracyCircle != null) {
clickedAccuracyCircle.setVisible(visible);
}
}
@Override
public boolean isVisible() {
return this.visible;
}
@Override
public Date getLatestDate() {
return latestLocationDate;
}
/**
* Used to remove markers for locations that have been removed from the local datastore.
*/
public void removeOldMarkers() {
LocationHelper lh = LocationHelper.getInstance(context.getApplicationContext());
Set<Long> locationIds = locationIdToMarker.keySet();
for (Long locationId : locationIds) {
Location locationExists = new Location();
locationExists.setId(locationId);
if (!lh.exists(locationExists)) {
Marker marker = locationIdToMarker.remove(locationId);
if (marker != null) {
markerIdToLocation.remove(marker.getId());
marker.remove();
}
if (clickedAccuracyCircleLocationId != null && clickedAccuracyCircleLocationId.equals(locationId)) {
if (clickedAccuracyCircle != null) {
clickedAccuracyCircle.remove();
clickedAccuracyCircle = null;
}
}
}
}
}
private class LocationInfoWindowAdapter implements InfoWindowAdapter {
private final Map<Marker, Drawable> avatars = new HashMap<>();
@Override
public View getInfoContents(final Marker marker) {
final Location location = markerIdToLocation.get(marker.getId());
if (location == null) {
return null;
}
User user = location.getUser();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.people_info_window, null);
final ImageView avatarView = (ImageView) v.findViewById(R.id.avatarImageView);
UserLocal userLocal = user.getUserLocal();
if (userLocal.getLocalAvatarPath() != null) {
final Drawable avatar = avatars.get(marker);
if (avatar == null) {
Glide.with(context)
.load(userLocal.getLocalAvatarPath())
.asBitmap()
.dontAnimate()
.centerCrop()
.into(new BitmapImageViewTarget(avatarView) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(context.getResources(), resource);
circularBitmapDrawable.setCircular(true);
avatars.put(marker, circularBitmapDrawable);
marker.showInfoWindow();
}
@Override public void onLoadCleared(Drawable placeholder) {
avatars.remove(marker);
}
});
} else {
avatarView.setImageDrawable(avatar);
}
} else if (user.getAvatarUrl() != null) {
new DownloadImageTask(context, Collections.singletonList(user), DownloadImageTask.ImageType.AVATAR, false).execute();
}
TextView name = (TextView) v.findViewById(R.id.name);
name.setText(user.getDisplayName());
TextView date = (TextView) v.findViewById(R.id.date);
date.setText(new PrettyTime().format(location.getTimestamp()));
return v;
}
@Override
public View getInfoWindow(Marker marker) {
return null; // Use default info window
}
}
}