/*
 * corvusgps.mapping.js
 *
 * Copyright (c) CorvusGPS.com, 2015. All rights reserved.
 */

var corvusGpsMapping = new function () {
    "use strict";
    var self = this;
    self.pushpinGenerator = new PushpinGenerator();
    self.isMapReady = false;

    /* region Leaflet items */
    self.map = null;
    self.startingLocation = new L.LatLng(37.7833, -122.4167);
    self.startingZoom = 13;
    self.polylineLayer = null;
    self.geoJsonLayer = null;
    /* endregion */

    /* region data */
    self.markers = [];
    self.route = [];
    self.i = 0;
    self.isDevice = true;
    self.isSingleResult = true;
    self.activePopups = {};
    self.isMapClearing = false;
    self.isNewItemSelected = false;
    /* endregion */

    /**
     * Gets the last map coordinates from HTML5 local storage (if they're available).
     */
    function restoreSavedMapState() {
        var lastMapStateJson = window.localStorage.getItem("lastMapState");
        if (typeof (lastMapStateJson) !== "undefined" && lastMapStateJson != null && lastMapStateJson.length > 0) {
            var lastMapState = JSON.parse(lastMapStateJson);
            self.startingLocation = lastMapState.center;
            self.startingZoom = lastMapState.zoom;
        }

        window.localStorage.removeItem("lastMapState");
    }

    /**
     * Enables multiple popups for Leaflet.
     * Solution found @ https://gist.github.com/mpmckenna8/9395643
     */
    function enableMultipleLeafletPopups() {
        /* Leaflet hack begins */
        L.Map = L.Map.extend({
            openPopup: function (popup) {
                // this.closePopup(); // just comment this out
                this._popup = popup;
                return this.addLayer(popup).fire(
                    "popupopen",
                    { popup: this._popup });
            }
        }); /* end of hack */
    }

    /**
     * Initializes the Leaflet map control and displays it in the specified element.
     * 
     * @param {string} mapElementId The element in which the Leaflet map will be displayed.
     */
    function initializeLeaflet(mapElementId) {
        self.pushpinGenerator = new PushpinGenerator();
        restoreSavedMapState();
        enableMultipleLeafletPopups();
        self.map = L.map(mapElementId, {
            center: self.startingLocation,
            zoom: self.startingZoom,
            attributionControl: false
        });

        var mapboxTiles = L.tileLayer("https://api.mapbox.com/v4/corvusgps.l6flccne/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiY29ydnVzZ3BzIiwiYSI6InB1dm1odkEifQ.dGBQFh8qqYaeffyTv6zuTA");
        self.map.addLayer(mapboxTiles);
        self.map.on("popupopen", function (e) {
            self.activePopups[e.popup._source.number.toString()] = true;
        });
        self.map.on("popupclose", function (e) {
            if (!self.isMapClearing) {
                var id = e.popup._source.number;
                self.activePopups[id.toString()] = false;
            }
        });
        self.polylineLayer = new L.LayerGroup();
        self.geoJsonLayer = L.geoJson(
            null,
            {
                onEachFeature: function (feature, layer) {
                    var content = feature.properties;
                    if (content) {
                        var popupData;
                        if (self.isDevice) {
                            popupData = new Date(content.date * 1000).toLocaleString();
                            layer.bindPopup(popupData, { autoPan: false, closeButton: true }).openPopup();
                            if (self.isNewItemSelected) {
                                self.activePopups[layer.getPopup()._source.number.toString()] = true;
                            }
                        } else {
                            popupData =
                                "<a href='#close' style='display: block;'>"
                                + "<span>"
                                + jQuery.parseJSON(content.name)
                                + "<br/>"
                                + (new Date(content.date * 1000).toLocaleString())
                                + "</span>"
                                + "</a>";
                            layer.bindPopup(
                                popupData, { autoPan: false, closeButton: true }).openPopup();
                            if (self.isNewItemSelected) {
                                self.activePopups[layer.getPopup()._source.number.toString()] = true;
                            }
                        }
                    }
                },
                pointToLayer: function (feature, latlng) {
                    feature.properties.number = self.i;
                    self.route[self.i] = latlng;
                    self.markers[self.i] = self.pushpinGenerator.get_pushpin(feature, latlng);
                    return self.markers[self.i++];
                }
            });
        self.isMapReady = true;
    }

    /**
     * Updates the Leaflet map's GeoJSON layer with the specified GeoJSON data.
     * 
     * @param {string} geoJson The specified GeoJSON data which will be added to the map.
     * @param {boolean} isPanToRequired Indicates whether the map will be panned to center of the received geo data.
     */
    function updateMapWithData(geoJson, isPanToRequired) {
        if (typeof (isPanToRequired) === "undefined") {
            isPanToRequired = true;
        }

        try {
            var isMapValid = false;
            self.geoJsonLayer.addTo(self.map);
            self.geoJsonLayer.addData(geoJson);
            var view;
            if (typeof (geoJson["extra"]["last_latitude"]) !== "undefined" && typeof (geoJson["extra"]["last_longitude"]) !== "undefined") {
                // this schema is usually working for a single device
                view = new L.LatLng(geoJson["extra"]["last_latitude"], geoJson["extra"]["last_longitude"]);
            }

            if (typeof view !== "undefined" && view.length !== 0) {
                // this is for single device results
                isMapValid = true;
                if (self.isDevice && !self.isSingleResult) {
                    // if there is an interval result, draw a line between the items
                    self.polylineLayer.addTo(self.map);
                    self.polyline = new L.Polyline(self.route, { color: 'red', weight: 3 });
                    self.polylineLayer.addLayer(self.polyline);
                }

                if (isPanToRequired) {
                    self.map.panTo(view);
                }
            } else {
                // this is for group results
                var bounds = self.geoJsonLayer.getBounds();
                if (bounds.isValid(bounds)) {
                    isMapValid = true;
                    if (isPanToRequired) {
                        self.map.whenReady(function () {
                            window.setTimeout(function () {
                                self.map.fitBounds(bounds, { padding: [50, 50] });
                            }.bind(this), 200);
                        }, this);
                    }
                }
            }

            if (isMapValid) {
                // displays the previously opened pushpins
                for (var popupId in self.activePopups) {
                    if (self.activePopups.hasOwnProperty(popupId)) {
                        if (self.activePopups[popupId] === true) {
                            if (self.markers.hasOwnProperty(popupId)) {
                                self.markers[popupId].openPopup();
                            } else {
                                self.activePopups[popupId] = false;
                            }
                        }
                    }
                }
            }

            self.isNewItemSelected = false;
        } catch (e) {
            console.log("Error while updating the map with GeoJSON data - " + e.toString());
        }
    }

    /**
     * Clears the contents of the Leaflet map's additional (e.g. GeoJSON, PolyLine) layers.
     */
    function clearAdditionalMapLayers() {
        self.isMapClearing = true;
        self.i = 0;

        // removes the previous content
        if (self.map.hasLayer(self.geoJsonLayer)) {
            self.map.removeLayer(self.geoJsonLayer);
        }

        if (self.map.hasLayer(self.polylineLayer)) {
            self.map.removeLayer(self.polylineLayer);
        }

        self.geoJsonLayer.clearLayers();
        self.polylineLayer.clearLayers();
        self.isMapClearing = false;
    }

    /**
     * Clears the list of active popups.
     */
    function clearPopups() {
        for (var popupId in self.activePopups) {
            if (self.activePopups.hasOwnProperty(popupId)) {
                delete self.activePopups[popupId];
            }
        }
    }

    /**
     * Gets a value indicating whether the Leaflet map has been initialized.
     * 
     * @returns {boolean} A value indicating whether the map has been initialized.
     */
    this.getIsMapReady = function () {
        return self.isMapReady;
    };

    /**
     * Gets the Leaflet map reference.
     * 
     * @returns {Object} The Leaflet map reference.
     */
    this.getMap = function () {
        return self.map;
    };

    /*
     * Gets the GeoJSON layer reference of the Leaflet map.
     * 
     * @returns {Object} The GeoJSON layer reference of the Leaflet map.
     */
    this.getGeoJsonLayer = function () {
        return self.geoJsonLayer;
    };

    /**
     * Initializes the Leaflet map control and displays it in the specified element.
     * 
     * @param {string} mapElementId The element in which the Leaflet map will be displayed.
     */
    this.initializeLeaflet = function (mapElementId) {
        initializeLeaflet(mapElementId);
    };

    /**
     * Updates the Leaflet map's GeoJSON layer with the specified GeoJSON data.
     * 
     * @param {string} geoJson The specified GeoJSON data which will be added to the map.
     * @param {boolean} isPanToRequired Indicates whether the map will be panned to center of the received geo data.
     */
    this.updateMapWithData = function (geoJson, isPanToRequired) {
        updateMapWithData(geoJson, isPanToRequired);
    };

    /**
     * Clears the contents of the Leaflet map's additional (e.g. GeoJSON) layers.
     */
    this.clearAdditionalMapLayers = function () {
        clearAdditionalMapLayers();
    }

    /**
     * Clears all the popups from the Leaflet map.
     */
    this.clearPopups = function () {
        clearPopups();
    }

    /**
     * Gets the current coordinates of the Leaflet map.
     * 
     * @returns {LatLng} The current coordinates of the Leaflet map.
     */
    this.getCurrentCenter = function () {
        return self.map.getCenter();
    }

    /**
     * Gets the current zoom level of the Leaflet map.
     * 
     * @returns {double} The current zoom level of the Leaflet map.
     */
    this.getCurrentZoomLevel = function () {
        return self.map.getZoom();
    }

    /**
     * Sets a value indicating whether a new item (device or group) has been selected from the GUI.
     * 
     * @param {boolean} isNewItemSelected A value indicating whether a new item has been selected.
     */
    this.setIsNewItemSelected = function (isNewItemSelected) {
        self.isNewItemSelected = isNewItemSelected;
    }

    /**
     * Sets a value indicating whether a device or a group has been selected from the GUI.
     * 
     * @param {boolean} isDevice A value indicating whether a device has been selected.
     */
    this.setIsDevice = function (isDevice) {
        self.currentPopupId = -1;
        self.isDevice = isDevice;
    }

    /**
     * Sets a value indicating whether single or multiple result mode hase been selected from the GUI.
     * 
     * @param {boolean} isSingleResult A value indicating whether the single result mode has been selected.
     */
    this.setIsSingleResult = function (isSingleResult) {
        self.currentPopupId = -1;
        self.isSingleResult = isSingleResult;
    }
};