package mil.nga.mapcache.view.map.grid;

import android.app.Activity;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import mil.nga.geopackage.BoundingBox;
import mil.nga.mapcache.utils.ThreadUtils;

/**
 * Abstract class to an object that creates grid overlays to display on top of the map.
 */
public abstract class GridCreator {

    /**
     * Contains all the grid objects to display on map.
     */
    private GridModel gridModel;

    /**
     * The map to add the overlays to.
     */
    private GoogleMap map;

    /**
     * Used to run back on the UI thread.
     */
    private Activity activity;

    /**
     * The polylines that are currently displayed on the map.
     */
    private List<Polyline> mapPolylines = new ArrayList<>();

    /**
     * Creates the polylines that can be added to the map.
     */
    private PolylineGridCreator lineCreator;

    /**
     * The observer on the model.
     */
    private Observer observer = (observer, o) -> modelUpdate(observer, o);

    /**
     * Indicates if this creator has been destroyed so no more map updates occur.
     */
    private boolean isDestroyed = false;

    /**
     * Constructor
     *
     * @param model    This will contain all of the grid object to display on the map.
     * @param map      The map to add the overlays to.
     * @param activity Used to run back on the UI thread.
     */
    public GridCreator(GridModel model, GoogleMap map, Activity activity) {
        this.gridModel = model;
        this.map = map;
        this.activity = activity;
        this.lineCreator = new PolylineGridCreator(gridModel);
        this.gridModel.addObserver(observer);
    }

    public void destroy() {
        isDestroyed = true;
        this.gridModel.deleteObserver(observer);
        removeGrids();
    }

    /**
     * Creates a grid overlay in the background and displays it on the map.
     *
     * @param bounds The bounds of the overlay to create.
     */
    public void createGridForMap(BoundingBox bounds) {
        ThreadUtils.getInstance().runBackground(() -> {
            Grid[] grids = createGrid(bounds);
            this.gridModel.setGrids(grids);

            // Now spawn the label maker and polyline creator
            ThreadUtils.getInstance().runBackground(() -> {
                createLabels();
            });
            createPolylines();
        });
    }

    /**
     * Removes the grids from the map.
     */
    private void removeGrids() {
        removePolylines();
    }

    /**
     * Removes the polylines from the map.
     */
    private void removePolylines() {
        for (Polyline polyline : mapPolylines) {
            polyline.remove();
        }
        mapPolylines.clear();
    }

    /**
     * Creates the grid object based on whatever grid this class is.
     *
     * @param bounds The bounding box of the grid.
     * @return The grids.
     */
    protected abstract Grid[] createGrid(BoundingBox bounds);

    /**
     * Creates the polylines based on what is populated for the grids within the model.
     */
    private void createPolylines() {
        lineCreator.createPolylines();
    }

    /**
     * Creates the labels based on what is populated for the grids within the model.
     */
    private void createLabels() {

    }

    /**
     * Called any time the model is updated.
     *
     * @param observer The gridModel
     * @param o        The property that changed
     */
    private void modelUpdate(Observable observer, Object o) {
        if (GridModel.POLYLINES_PROPERTY.equals(o)) {
            this.activity.runOnUiThread(() -> updatePolylines());
        } else if (GridModel.LABELS_PROPERTY.equals(o)) {
            this.activity.runOnUiThread(() -> updateLabels());
        }
    }

    /**
     * Updates the polylines on the map.
     */
    private void updatePolylines() {
        removePolylines();
        if(!isDestroyed) {
            for (PolylineOptions polylineOptions : this.gridModel.getPolylines()) {
                mapPolylines.add(map.addPolyline(polylineOptions));
            }
        }
    }

    /**
     * Updates the labels on the map.
     */
    private void updateLabels() {

    }
}

@@ -169,16 +169,121 @@
 riptor;%0A
+import com.google.android.gms.maps.model.Marker;%0Aimport com.google.android.gms.maps.model.MarkerOptions;%0A
 import c
@@ -325,16 +325,16 @@
 lyline;%0A
-
 import c
@@ -1248,16 +1248,241 @@
 eator;%0A%0A
+    /**%0A     * The labels that are currently displayed on the map.%0A     */%0A    private List%3CMarker%3E mapLabels = new ArrayList%3C%3E();%0A%0A    /**%0A     * Creates the labels for the grids.%0A     */%0A    private LabelMaker labelMaker;%0A%0A
     /**%0A
@@ -2259,24 +2259,97 @@
 er);%0A    %7D%0A%0A
+    /**%0A     * Removes any resources that may be left out there.%0A     */%0A
     public v
@@ -3158,32 +3158,56 @@
 ovePolylines();%0A
+        removeLabels();%0A
     %7D%0A%0A    /**%0A 
@@ -3412,32 +3412,237 @@
 clear();%0A    %7D%0A%0A
+    /**%0A     * Removes the labels from the map.%0A     */%0A    private void removeLabels() %7B%0A        for (Marker marker : mapLabels) %7B%0A            marker.remove();%0A        %7D%0A        mapLabels.clear();%0A    %7D%0A%0A
     /**%0A     * C
@@ -4175,25 +4175,59 @@
 eLabels() %7B%0A
+        labelMaker.createLabels();
 %0A
-
     %7D%0A%0A    /
@@ -4826,16 +4826,17 @@
       if
+ 
 (!isDest
@@ -5115,16 +5115,213 @@
 els() %7B%0A
+        removeLabels();%0A        if (!isDestroyed) %7B%0A            for (MarkerOptions label : this.gridModel.getLabels()) %7B%0A                mapLabels.add(map.addMarker(label));%0A            %7D%0A        %7D
 %0A    %7D%0A%7D

Add labels to the grids on the map