package org.deke.risk.riskahead;

import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.core.app.ActivityCompat;
import androidx.loader.app.LoaderManager;
import androidx.core.content.ContextCompat;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.appcompat.widget.SearchView;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RatingBar;
import android.widget.TextView;

import com.google.firebase.crashlytics.FirebaseCrashlytics;

import com.android.volley.Request;
import com.android.volley.toolbox.StringRequest;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.heatmaps.HeatmapTileProvider;
import com.google.maps.android.heatmaps.WeightedLatLng;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;

import org.deke.risk.riskahead.helper.IncidentReport;
import org.deke.risk.riskahead.helper.AppConfig;
import org.deke.risk.riskahead.helper.AppController;
import org.deke.risk.riskahead.helper.BaseActivity;
import org.deke.risk.riskahead.helper.OwnIconRendered;
import org.deke.risk.riskahead.helper.PlaceProvider;
import org.deke.risk.riskahead.helper.SessionManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * (C) Copyright 2016 RiskAhead (http://www.riskahead.net)
 * Developer & Contributor: Dennis Thießen (dennis.thiessen@riskahead.net)
 * Date of Release: 10.02.2016
 */
public class MapsActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor>,
        ClusterManager.OnClusterItemInfoWindowClickListener<IncidentReport>, ClusterManager.OnClusterClickListener<IncidentReport>, ClusterManager.OnClusterItemClickListener<IncidentReport>, OnMapReadyCallback {

    private static final String TAG = MapsActivity.class.getSimpleName();

    private static final int DEFAULT_ZOOM_LEVEL = 14;
    private static final int THRESHOLD_ZOOM_LEVEL = 10;
    private static final int MY_PERMISSIONS_REQUEST_LOCATION = 12;

    private Double PRELOAD_PERCENTAGE = 0.3;
    private int MIN_ZOOM_LEVEL = 9;

    private float current_zoom_level;

    private GoogleMap mMap;

    private ClusterManager<IncidentReport> mClusterManager;
    private TileOverlay mOverlay;
    private HeatmapTileProvider mProvider;

    private Marker mMarker;
    private FloatingActionButton addButton;
    private Marker markerShowingInfoWindow;
    private IncidentReport clickedClusterItem;

    private LocationManager locationManager;
    private LocationListener locationListener;
    private Location location;

    private static final int MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters
    private static final int MIN_TIME_BW_UPDATES = 1000 * 60; // 1 minute

    private LatLng myLocation;
    private LatLng markedLocation;
    private LatLng curNortheastBounds;
    private LatLng curSouthwestBounds;

    private boolean markersShown = false;
    private boolean showAdd = true;

    private int pbCounter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userHasToBeLoggedIn();

        if (session.getSubsPremium()) {
            MIN_ZOOM_LEVEL = 7;
        } else {
            MIN_ZOOM_LEVEL = 9;
        }

        MapFragment mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.frag_maps_map);
        mapFragment.getMapAsync(this);

        findViewById(R.id.fab_reportwf_map).setVisibility(View.INVISIBLE);
    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        initMap();

        handleIntent(getIntent());
        initClustering();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        System.gc();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.d(TAG, "Getting permission results...");
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_LOCATION: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Permission was granted by user");
                    enableMyLocationButton();
                    gotoMyLocation();

                } else {
                    Log.d(TAG, "Permission was not granted by user");

                    String sDefSystemLanguage = Locale.getDefault().getLanguage();

                    switch (sDefSystemLanguage) {
                        case "en":
                            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(51.507351, -0.127758), DEFAULT_ZOOM_LEVEL));
                            break;
                        case "de":
                            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(52.518594, 13.376188), DEFAULT_ZOOM_LEVEL));
                            break;
                        default:
                            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(19.410704, -99.132385), DEFAULT_ZOOM_LEVEL));
                            break;
                    }
                }
            }
        }
    }

    private void initMap() {
        Log.d(TAG, "Init Map");
        mMap.getUiSettings().setZoomControlsEnabled(true);
        mMap.setMinZoomPreference(MIN_ZOOM_LEVEL);
        mMap.setOnMyLocationButtonClickListener(() -> {
            gotoMyLocation();
            return false;
        });

        // Retrieve height of action bar and modify map padding
        TypedValue tv = new TypedValue();
        if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true))
        {
            int actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
            mMap.setPadding(0, actionBarHeight,0,0);
        }

        enableMyLocationButton();

        mMap.setOnMapClickListener(point -> {
            if (mMarker != null) {
                mMarker.remove();
            }

            markedLocation = point;
            mMarker = mMap.addMarker(new MarkerOptions().position(point));

            if (current_zoom_level > DEFAULT_ZOOM_LEVEL) {
                mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(point, current_zoom_level));
            } else {
                mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(point, DEFAULT_ZOOM_LEVEL));
            }

            addButton = findViewById(R.id.fab_reportwf_map);
            addButton.setVisibility(View.VISIBLE);

            if (getIntent().hasExtra(AppConfig.EXTRA_MAP_TYPE)) {
                if (getIntent().getStringExtra(AppConfig.EXTRA_MAP_TYPE).equals(AppConfig.INTENT_MAP_GETINCLOC)) {
                    addButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(getApplicationContext(), R.color.md_green_400)));
                    showAdd = false;
                    Drawable identityIcon = new IconicsDrawable(getApplicationContext())
                            .icon(GoogleMaterial.Icon.gmd_done)
                            .color(ContextCompat.getColor(getApplicationContext(), R.color.white))
                            .sizeDp(24);

                    addButton.setImageDrawable(identityIcon);

                    addButton.setOnClickListener(v -> gotoReportActivity(markedLocation.latitude + ":" + markedLocation.longitude));
                } else if (getIntent().getStringExtra(AppConfig.EXTRA_MAP_TYPE).equals(AppConfig.INTENT_MAP_GETNOTIFY)) {
                    addButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(getApplicationContext(), R.color.md_green_400)));
                    showAdd = false;
                    Drawable identityIcon = new IconicsDrawable(getApplicationContext())
                            .icon(GoogleMaterial.Icon.gmd_done)
                            .color(ContextCompat.getColor(getApplicationContext(), R.color.white))
                            .sizeDp(24);

                    addButton.setImageDrawable(identityIcon);

                    addButton.setOnClickListener(v -> {
                        session.setManualLocation(Double.doubleToRawLongBits(markedLocation.latitude), Double.doubleToRawLongBits(markedLocation.longitude));
                        Log.d(TAG, "MARKED LOCATION: " + markedLocation.latitude + " " + markedLocation.longitude);
                        gotoSettingsActivity();
                    });
                }
            } else {
                addButton.setOnClickListener(v -> {
                    Log.d(TAG, "MARKED LOCATION: " + markedLocation.latitude + " " + markedLocation.longitude);
                    showAdd = false;
                    gotoReportActivity(markedLocation.latitude + ":" + markedLocation.longitude);
                });
            }

        });
    }

    public GoogleMap.OnCameraIdleListener getCameraChangeListener() {
        return () -> {
            mClusterManager.onCameraIdle();
            CameraPosition position = mMap.getCameraPosition();
            current_zoom_level = position.zoom;

            if (position.zoom > 13) {
                PRELOAD_PERCENTAGE = 0.7;
            } else {
                PRELOAD_PERCENTAGE = 0.3;
            }

            LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;

            Log.d("OnCameraChangeListener", "Position: " + bounds.getCenter() + " Zoom: " + position.zoom + " Bounds: NE:" + bounds.northeast + " SW: " + bounds.southwest);
            Log.d("OnCameraChangeListener", "Saved Position - Bounds: NE:" + curNortheastBounds + " SW: " + curSouthwestBounds);

            boolean isOutOfBounds = false;

            double distanceLat = Math.abs(bounds.northeast.latitude - bounds.southwest.latitude);
            double distanceLon = Math.abs(bounds.northeast.longitude - bounds.southwest.longitude);

            double southwestLat, southwestLng;
            double northeastLat, northeastLng;

            if (bounds.southwest.latitude < bounds.northeast.latitude) {
                southwestLat = bounds.southwest.latitude - distanceLat * PRELOAD_PERCENTAGE;
                northeastLat = bounds.northeast.latitude + distanceLat * PRELOAD_PERCENTAGE;

                if ((curSouthwestBounds != null) && ((bounds.southwest.latitude < curSouthwestBounds.latitude) || (bounds.northeast.latitude > curNortheastBounds.latitude))) {
                    isOutOfBounds = true;
                }
            } else {
                southwestLat = bounds.southwest.latitude + distanceLat * PRELOAD_PERCENTAGE;
                northeastLat = bounds.northeast.latitude - distanceLat * PRELOAD_PERCENTAGE;

                if ((curSouthwestBounds != null) && ((bounds.southwest.latitude > curSouthwestBounds.latitude) || (bounds.northeast.latitude < curNortheastBounds.latitude))) {
                    isOutOfBounds = true;
                }
            }

            if (bounds.southwest.longitude < bounds.northeast.longitude) {
                southwestLng = bounds.southwest.longitude - distanceLon * PRELOAD_PERCENTAGE;
                northeastLng = bounds.northeast.longitude + distanceLon * PRELOAD_PERCENTAGE;

                if ((curSouthwestBounds != null) && ((bounds.southwest.longitude < curSouthwestBounds.longitude) || (bounds.northeast.longitude > curNortheastBounds.longitude))) {
                    isOutOfBounds = true;
                }
            } else {
                southwestLng = bounds.southwest.longitude + distanceLon * PRELOAD_PERCENTAGE;
                northeastLng = bounds.northeast.longitude - distanceLon * PRELOAD_PERCENTAGE;

                if ((curSouthwestBounds != null) && ((bounds.southwest.longitude > curSouthwestBounds.longitude) || (bounds.northeast.longitude < curNortheastBounds.longitude))) {
                    isOutOfBounds = true;
                }
            }

            if ((curSouthwestBounds == null) || isOutOfBounds) {
                curNortheastBounds = new LatLng(northeastLat, northeastLng);
                curSouthwestBounds = new LatLng(southwestLat, southwestLng);

                Log.d("OnCameraChangeListener", "isOutOfBounds is true => New Calculated - Bounds: NE:" + curNortheastBounds + " SW: " + curSouthwestBounds);

                if (position.zoom >= THRESHOLD_ZOOM_LEVEL) {
                    refreshIncidentHeatMap(curNortheastBounds, curSouthwestBounds);
                    refreshIncidentMarkers(curNortheastBounds, curSouthwestBounds);
                    markersShown = true;
                } else if (position.zoom < THRESHOLD_ZOOM_LEVEL) {
                    mClusterManager.clearItems();
                    refreshIncidentHeatMap(curNortheastBounds, curSouthwestBounds);
                    markersShown = false;
                }
            } else if ((position.zoom >= THRESHOLD_ZOOM_LEVEL) && !markersShown) {
                curNortheastBounds = new LatLng(northeastLat, northeastLng);
                curSouthwestBounds = new LatLng(southwestLat, southwestLng);

                Log.d("OnCameraChangeListener", "zoom threshold passed => New Calculated - Bounds: NE:" + curNortheastBounds + " SW: " + curSouthwestBounds);

                refreshIncidentHeatMap(curNortheastBounds, curSouthwestBounds);
                refreshIncidentMarkers(curNortheastBounds, curSouthwestBounds);
                markersShown = true;
            } else if ((position.zoom < THRESHOLD_ZOOM_LEVEL) && !markersShown) {
                mClusterManager.clearItems();
                markersShown = false;
            }
        };
    }

    private void refreshIncidentMarkers(LatLng northeast, LatLng southwest) {
        showProgress();

        String tag_string_req = "getincidentsfrombound";
        StringRequest strReq = getStringRequestGetAllIncidentsFromBound(northeast, southwest);
        AppController.getInstance().addToRequestQueue(strReq, tag_string_req);
    }

    private void addIncidentMarkersOnMap(JSONArray incidents) {
        mClusterManager.clearItems();

        for (int i = 0; i < incidents.length(); i++) {
            try {
                mClusterManager.addItem(new IncidentReport(incidents.getJSONObject(i)));
            } catch (JSONException e) {
                FirebaseCrashlytics.getInstance().log("JSONException caught: " + e);
            }
        }

        mClusterManager.cluster();
    }

    private void refreshIncidentHeatMap(LatLng northeast, LatLng southwest) {
        showProgress();

        String tag_string_req = "getincidentsforheatmapfrombound";
        StringRequest strReq = getStringRequestGetAllIncidentsForHeatMapFromBound(northeast, southwest);
        AppController.getInstance().addToRequestQueue(strReq, tag_string_req);
    }

    private void addIncidentMarkersOnHeatMap(JSONArray incidents) {
        ArrayList<WeightedLatLng> heatMapPositions = new ArrayList<>();

        for (int i = 0; i < incidents.length(); i++) {
            try {
                heatMapPositions.add(new WeightedLatLng(new LatLng(incidents.getJSONObject(i).getDouble("latitude"), incidents.getJSONObject(i).getDouble("longitude")), 1.0));
            } catch (JSONException e) {
                FirebaseCrashlytics.getInstance().log("JSONException caught: " + e);
            }
        }

        if (mProvider == null) {
            mProvider = new HeatmapTileProvider.Builder()
                    .radius(50)
                    .weightedData(heatMapPositions)
                    .opacity(0.7)
                    .build();

            mOverlay = mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider));
        } else {
            mProvider.setWeightedData(heatMapPositions);
            mOverlay.clearTileCache();
        }
    }

    private StringRequest getStringRequestGetAllIncidentsForHeatMapFromBound(final LatLng northeast, final LatLng southwest) {
        return new StringRequest(Request.Method.POST, AppConfig.REST_GET_INCIDENTS_FROMBOUND_FORHEATMAP, response -> {
            Log.d(TAG, "Map HeatMap positions response: " + response);
            hideProgress();
            try {
                JSONObject jObj = new JSONObject(response);
                boolean error = jObj.getBoolean("error");

                if (!error) {
                    JSONArray incidents = jObj.getJSONArray("msg");
                    Log.d(TAG, "HeatMap positions response length: " + incidents.length());
                    addIncidentMarkersOnHeatMap(incidents);
                } else {
                    String errorMsg = jObj.getString("error_msg");

                    int loglevel =  errorMsg.contains("Could not retrieve incidents from bound") ? Log.INFO : Log.ERROR;
                    FirebaseCrashlytics.getInstance().log("Error getting map heat map positions (server returned error): " + errorMsg);
                }
            } catch (JSONException e) {
                FirebaseCrashlytics.getInstance().log("JSONException caught: " + e);
            }

        }, error -> {
            hideProgress();
            FirebaseCrashlytics.getInstance().log("Error getting map positions: " + error.getMessage());
            handleVolleyErrorResponse();
        }) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("uid", user.get(SessionManager.KEY_UID));
                params.put("token", user.get(SessionManager.TOKEN));
                params.put("nelat", Double.toString(northeast.latitude));
                params.put("nelng", Double.toString(northeast.longitude));
                params.put("swlat", Double.toString(southwest.latitude));
                params.put("swlng", Double.toString(southwest.longitude));

                return params;
            }
        };
    }

    private StringRequest getStringRequestGetAllIncidentsFromBound(final LatLng northeast, final LatLng southwest) {
        return new StringRequest(Request.Method.POST, AppConfig.REST_GET_INCIDENTS_FROMBOUND, response -> {
            Log.d(TAG, "Map positions response: " + response);
            hideProgress();
            try {
                JSONObject jObj = new JSONObject(response);
                boolean error = jObj.getBoolean("error");

                if (!error) {
                    JSONArray incidents = jObj.getJSONArray("msg");
                    Log.d(TAG, "Map positions response length: " + incidents.length());
                    addIncidentMarkersOnMap(incidents);
                } else {
                    String errorMsg = jObj.getString("error_msg");
                    FirebaseCrashlytics.getInstance().log("Error getting map positions (server returned error): " + errorMsg);
                }
            } catch (JSONException e) {
                FirebaseCrashlytics.getInstance().log("JSONException caught: " + e);
            }

        }, error -> {
            hideProgress();
            FirebaseCrashlytics.getInstance().log("Error getting map positions: " + error.getMessage());
            showMessage(getString(R.string.errormsg_couldnotretrieve));
        }) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("uid", user.get(SessionManager.KEY_UID));
                params.put("token", user.get(SessionManager.TOKEN));
                params.put("nelat", Double.toString(northeast.latitude));
                params.put("nelng", Double.toString(northeast.longitude));
                params.put("swlat", Double.toString(southwest.latitude));
                params.put("swlng", Double.toString(southwest.longitude));

                return params;
            }
        };
    }

    private void initClustering() {
        mClusterManager = new ClusterManager<>(this, mMap);
        mMap.setOnCameraIdleListener(getCameraChangeListener());

        mClusterManager.setRenderer(new OwnIconRendered(MapsActivity.this, mMap, mClusterManager));

        mMap.setOnMarkerClickListener(mClusterManager);
        mMap.setOnInfoWindowClickListener(mClusterManager);
        mMap.setInfoWindowAdapter(mClusterManager.getMarkerManager());

        mClusterManager.setOnClusterItemInfoWindowClickListener(this);

        mClusterManager.setOnClusterClickListener(this);
        mClusterManager.setOnClusterItemClickListener(this);
        mClusterManager.setOnClusterItemClickListener(item -> {
            clickedClusterItem = item;
            return false;
        });

        mClusterManager.getMarkerCollection().setOnInfoWindowAdapter(
                new MyCustomAdapterForItems());
    }

    private void loadInfoWindowInformation(View infoView, String incidentID) {
        showProgress();

        String tag_string_req = "getincidentscoreforinfowindow";
        StringRequest strReq = getStringRequestGetIncidentScoreForInfoWindow(infoView, incidentID);
        AppController.getInstance().addToRequestQueue(strReq, tag_string_req);
    }

    private StringRequest getStringRequestGetIncidentScoreForInfoWindow(final View infoView, final String incidentID) {
        return new StringRequest(Request.Method.POST, AppConfig.REST_GET_INCIDENT, response -> {
            Log.d(TAG, "Map positions response: " + response);
            hideProgress();
            try {
                JSONObject jObj = new JSONObject(response);
                boolean error = jObj.getBoolean("error");

                if (!error) {
                    JSONObject incident = jObj.getJSONObject("msg");
                    clickedClusterItem = new IncidentReport(incident);

                    TextView tvUsername = infoView.findViewById(R.id.txt_infowindow_fromuser);
                    RatingBar rbScore = infoView.findViewById(R.id.rb_infowindow_rating);

                    tvUsername.setText(String.format("%s %s", getString(R.string.lbl_mapsinfowindow_author), clickedClusterItem.getFromUsername()));
                    rbScore.setRating(clickedClusterItem.getScoreStars());

                    if (markerShowingInfoWindow != null && markerShowingInfoWindow.isInfoWindowShown()) {
                        markerShowingInfoWindow.showInfoWindow();
                        markerShowingInfoWindow = null;
                    }
                } else {
                    String errorMsg = jObj.getString("error_msg");
                    Log.i(TAG, "Error getting incident details (server returned error): " + errorMsg);
                    showMessage(getResources().getString(R.string.txt_errormsg_getinfowindow));
                }
            } catch (JSONException e) {
                FirebaseCrashlytics.getInstance().log("JSONException caught: " + e);
            }

        }, error -> {
            hideProgress();
            FirebaseCrashlytics.getInstance().log("Error getting incident details: " + error.getMessage());
            handleVolleyErrorResponse();
        }) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("uid", user.get(SessionManager.KEY_UID));
                params.put("token", user.get(SessionManager.TOKEN));
                params.put("incidentid", incidentID);

                return params;
            }
        };
    }

    private class MyCustomAdapterForItems implements GoogleMap.InfoWindowAdapter {
        private final View myContentsView;

        MyCustomAdapterForItems() {
            myContentsView = View.inflate(getApplicationContext(), R.layout.map_info_window, null);
        }

        @Override
        public View getInfoContents(Marker marker) {
            markerShowingInfoWindow = marker;
            return null;
        }

        @Override
        public View getInfoWindow(Marker marker) {
            markerShowingInfoWindow = marker;
            LinearLayout layCategoryColor = myContentsView.findViewById(R.id.lay_infowindow_color);

            TextView tvCategory = myContentsView.findViewById(R.id.txt_infowindow_category);
            TextView tvSubcategory = myContentsView.findViewById(R.id.txt_infowindow_subcategory);
            TextView tvVictim = myContentsView.findViewById(R.id.txt_infowindow_victim);
            TextView tvSuspect = myContentsView.findViewById(R.id.txt_infowindow_subject);
            TextView tvTime = myContentsView.findViewById(R.id.txt_infowindow_time);


            if (clickedClusterItem != null) {
                loadInfoWindowInformation(myContentsView, Integer.toString(clickedClusterItem.getId()));

                layCategoryColor.setBackgroundColor(clickedClusterItem.getCategoryColor());

                tvCategory.setText(clickedClusterItem.getIncidentCategoryName(getApplicationContext()));
                tvSubcategory.setText(clickedClusterItem.getIncidentSubCategoryName(getApplicationContext()));

                tvSuspect.setText(String.format("%s %s", getString(R.string.lbl_mapsinfowindow_suspect), clickedClusterItem.getSuspectString(getApplicationContext())));
                tvVictim.setText(String.format("%s %s", getString(R.string.lbl_mapsinfowindow_victim), clickedClusterItem.getVictimString(getApplicationContext())));
                tvTime.setText(String.format("%s %s", getString(R.string.lbl_mapsinfowindow_time), clickedClusterItem.getTimeString(getApplicationContext())));
            }
            return myContentsView;
        }
    }

    private void handleIntent(Intent intent) {
        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            doSearch(query);
        } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
            getPlace(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
        } else {
            String intentMessage = intent.getStringExtra(EXTRA_MESSAGE);

            if (intentMessage != null && intentMessage.contains(":")) {
                String[] position = intentMessage.split(":");

                String latitude = position[0];
                String longitude = position[1];

                LatLng point = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude));
                mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(point, DEFAULT_ZOOM_LEVEL));
                if (getIntent().hasExtra(AppConfig.EXTRA_MAP_TYPE))
                    mMarker = mMap.addMarker(new MarkerOptions().position(point));
            } else {
                LatLng myPosition = session.getLocation();

                if ((myPosition != null) && (myPosition.latitude != 0.0) && (myPosition.longitude != 0.0)) {
                    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(myPosition.latitude, myPosition.longitude), DEFAULT_ZOOM_LEVEL));
                } else {
                    gotoMyLocation();
                }
            }
        }
    }

    @Override
    public boolean onClusterItemClick(IncidentReport incidentReport) {
        return false;
    }

    @Override
    public boolean onClusterClick(Cluster<IncidentReport> cluster) {
        return false;
    }

    @Override
    public void onClusterItemInfoWindowClick(IncidentReport incidentReport) {
        gotoViewReportActivity(incidentReport.getId());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        handleIntent(intent);
    }

    private void doSearch(String query) {
        Bundle data = new Bundle();
        data.putString("query", query);
        LoaderManager.getInstance(this).restartLoader(0, data, this);
    }

    private void getPlace(String query) {
        Bundle data = new Bundle();
        data.putString("query", query);
        LoaderManager.getInstance(this).restartLoader(1, data, this);
    }

    private void showLocation(Cursor c) {
        MarkerOptions markerOptions;
        LatLng position = null;

        while (c.moveToNext()) {
            markerOptions = new MarkerOptions();
            position = new LatLng(Double.parseDouble(c.getString(1)), Double.parseDouble(c.getString(2)));
            markerOptions.position(position);
            markerOptions.title(c.getString(0));
        }

        if (position != null) {
            CameraUpdate cameraPosition = CameraUpdateFactory.newLatLng(position);
            mMap.animateCamera(cameraPosition);
        }
    }

    @Override
    public void onLoaderReset(@NonNull Loader<Cursor> arg0) {
    }

    @Override
    @NonNull
    public Loader<Cursor> onCreateLoader(int arg0, Bundle query) {
        CursorLoader cLoader;

        if (arg0 == 0)
            cLoader = new CursorLoader(getBaseContext(), PlaceProvider.SEARCH_URI, null, null, new String[]{query.getString("query")}, null);
        else
            cLoader = new CursorLoader(getBaseContext(), PlaceProvider.DETAILS_URI, null, null, new String[]{query.getString("query")}, null);

        return cLoader;
    }

    private class MyLocationListener implements LocationListener {

        @Override
        public void onLocationChanged(Location loc) {
            if (loc.getLongitude() != 0.0 && loc.getLatitude() != 0.0) {
                Log.d(TAG, "Save GPS as new location...");
                if (session.getLocation() == null || session.getLocation().latitude == 0.0 || session.getLocation().longitude == 0.0) {
                    session.setLocation(Double.doubleToRawLongBits(loc.getLatitude()), Double.doubleToRawLongBits(loc.getLongitude()));
                } else {
                    session.setLocation(Double.doubleToRawLongBits(loc.getLatitude()), Double.doubleToRawLongBits(loc.getLongitude()));
                }

                session.setNotificationServiceReady(true);
            }
        }

        @Override
        public void onProviderDisabled(String provider) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }
    }

    private boolean isLocationEnabled() {
        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
                locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    }

    public void gotoMyLocation() {
        //check for permission
        Log.d(TAG, "Permission for GPS not granted yet - ask for permission now");
        if (ActivityCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION);
        } else {
            Log.d(TAG, "Permission for GPS granted - get position");
            locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
            locationListener = new MyLocationListener();

            try {
                if (!isLocationEnabled()) {
                    showMessage("Please activate GPS in your android settings.");
                    Log.d(TAG, "Locationservice not enabled");
                } else {
                    Log.d(TAG, "Locationservice enabled - check type");

                    // if GPS Enabled get lat/long using GPS Services
                    if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                        Log.d(TAG, "Locationservice enabled - GPS Provider Found");
                        if (location == null) {
                            locationManager.requestLocationUpdates(
                                    LocationManager.GPS_PROVIDER,
                                    MIN_TIME_BW_UPDATES,
                                    MIN_DISTANCE_CHANGE_FOR_UPDATES, locationListener);
                            Log.d(TAG, "GPS location from GPS Service activated");
                            if (locationManager != null) {
                                location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                                if (location != null) {
                                    myLocation = new LatLng(location.getLatitude(), location.getLongitude());
                                }
                            }
                        }
                    } else if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                        // get location from Network Provider
                        Log.d(TAG, "Locationservice enabled - Network Provider Found");
                        locationManager.requestLocationUpdates(
                                LocationManager.NETWORK_PROVIDER,
                                MIN_TIME_BW_UPDATES,
                                MIN_DISTANCE_CHANGE_FOR_UPDATES, locationListener);
                        Log.d(TAG, "GPS location from Network Provider activated");
                        if (locationManager != null) {
                            location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                            if (location != null) {
                                myLocation = new LatLng(location.getLatitude(), location.getLongitude());
                            }
                        }
                    }

                    mMap.moveCamera(CameraUpdateFactory.newLatLng(myLocation));
                }
            } catch (Exception e) {
                FirebaseCrashlytics.getInstance().log("Exception caught: " + e);
            }
        }
    }

    private void enableMyLocationButton() {
        try{
            mMap.setMyLocationEnabled(true);
        }catch(SecurityException se){
            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION);
        }catch(AssertionError ae){
            FirebaseCrashlytics.getInstance().log("Could not find mapFragment view: " + ae);
        }
    }

    @Override
    public void onLoadFinished(@NonNull Loader<Cursor> arg0, Cursor c) {
        showLocation(c);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_maps, menu);
        getMenuInflater().inflate(R.menu.menu_common, menu);

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        if(searchManager != null){
            SearchableInfo searchInfo = searchManager.getSearchableInfo(getComponentName());
            SearchView searchView = (SearchView) menu.findItem(R.id.location_search).getActionView();
            searchView.setSearchableInfo(searchInfo);

            EditText txtSearch = searchView.findViewById(androidx.appcompat.R.id.search_src_text);
            txtSearch.setHint(getResources().getString(R.string.search_hint));
            txtSearch.setHintTextColor(Color.DKGRAY);
            txtSearch.setTextColor(Color.BLACK);
        }else{
            FirebaseCrashlytics.getInstance().log("Could not get searchManager");
        }

        return true;
    }

    @Override
    protected void onPause() {
        Log.d(TAG,"onPause executed: LM: "+(locationManager != null)+" LL: "+(locationListener!=null));

        if(locationManager != null && locationListener != null) {
            locationManager.removeUpdates(locationListener);
            locationListener = null;
            locationManager = null;
        }

        super.onPause();
        if (showAdd) showAd();
    }

    @Override
    protected int getLayoutResourceId() {
        return R.layout.activity_maps;
    }

    @Override
    protected String getActivityName() {
        return getResources().getString(R.string.title_activity_maps);
    }

    private void showProgress() {
        findViewById(R.id.pb_map_progress).setVisibility(View.VISIBLE);
        pbCounter++;
    }

    @Override
    protected void onResume() {
        super.onResume();
        navDrawer.setSelection(mapAvtivityID, false);
        showAdd = true;
    }

    private void hideProgress() {
        pbCounter--;
        if (pbCounter == 0) {
            findViewById(R.id.pb_map_progress).setVisibility(View.GONE);
        }
    }
}
