/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

var lastGpsUpdateTimestamp = 0;

var userPosition = {
    coords: {
        latitude: 0.0,
        longitude: 0.0,
        accuracy: 999999
    },
    updated: 0
};

// Preload last known location while GPS warms up
if (getStorage("user_latitude") != null && getStorage("user_longitude") != null) {
    userPosition.coords.latitude = getStorage("user_latitude");
    userPosition.coords.longitude = getStorage("user_longitude");
}

// Request the user's IP geolocation as a poor substitute for an actual location
// Should improve UX for weather tool at least
$.ajax({
    url: SETTINGS.geoipapi,
    dataType: 'json',
    timeout: 10 * 1000,
    success: function (resp) {
        if (resp.status == "OK" && userPosition.coords.accuracy > 99999) {
            userPosition.coords.latitude = resp.location.latitude;
            userPosition.coords.longitude = resp.location.longitude;
            userPosition.coords.accuracy = 99999;
            userPosition.updated = time();
        }
    }
});

var geoerrorcount = 0;

var mapLocationControlStarted = false;

function setupPositionWatcher() {
    if ("geolocation" in navigator) {
        navigator.geolocation.watchPosition(function (position) {
            userPosition.coords = position.coords;
            userPosition.updated = time();
            setStorage("user_latitude", userPosition.coords.latitude);
            setStorage("user_longitude", userPosition.coords.longitude);
            if (mapLocationControlStarted) {
                // Don't refresh at an interval less than ten seconds
                var currentTimestamp = Math.floor(Date.now() / 1000);
                if (lastGpsUpdateTimestamp < (currentTimestamp - 10)) {
                    updateDistances(position.coords.latitude, position.coords.longitude);

                    var alertinterval = getStorage("alertinterval");
                    if (alertinterval == null) {
                        alertinterval = 30;
                    } else {
                        alertinterval = alertinterval * 1;
                    }

                    lastGpsUpdateTimestamp = currentTimestamp;
                    for (var i = 0; i < packages.length; i++) {
                        if (packages[i].distance * 1 < getStorage("alertradius") * 1) {

                            if (packages[i].lastAlert > currentTimestamp - alertinterval) {
                                continue;
                            }

                            if (getUndeliveredCount(packages[i]) == 0) {
                                continue;
                            }

                            try {
                                if (map != null) {
                                    map.updatePackageLayer(packages);
                                }
                                loadPackageList();
                            } catch (ex) {
                                // It'll show up sooner or later anyways
                            }

                            playSound("alert");
                            packages[i].lastAlert = currentTimestamp;
                        }
                    }
                }
            } else {
                if (map != null) {
                    map.startLocateControl();
                    mapLocationControlStarted = true;
                }
            }
        }, function (err) {
            if (typeof error == "function") {
                error(err.message);
            }
        }, {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0
        });
    } else {
        geoerrorcount++;
        console.log("Warn", "Geolocation error #" + geoerrorcount + ": ", error);
        // Stop showing error toasts if they're happening a lot
        if (geoerrorcount <= 3) {
            app.toast.show({
                text: '<i class="fas fa-compass"></i> ' + error,
                position: "bottom",
                destroyOnClose: true,
                closeTimeout: 1000 * 3
            });
        }
    }
}

/**
 * Calculate distance between two GPS points using Vincenty Formula.
 *
 * From Aman Singh https://stackoverflow.com/q/30536869
 *
 * @param {type} lat1
 * @param {type} lon1
 * @param {type} lat2
 * @param {type} lon2
 * @returns {Number} distance in meters
 */
function getDistance(lat1, lon1, lat2, lon2) {

    var toRad = function (value) {
        return value * Math.PI / 180;
    }

    var a = 6378137, b = 6356752.314245, f = 1 / 298.257223563;
    var L = toRad(lon2 - lon1);
    var U1 = Math.atan((1 - f) * Math.tan(toRad(lat1)));
    var U2 = Math.atan((1 - f) * Math.tan(toRad(lat2)));
    var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
    var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

    var lambda = L, lambdaP, iterLimit = 100;
    do
    {
        var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
        var sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
        if (sinSigma == 0)
            return 0;

        var cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
        var sigma = Math.atan2(sinSigma, cosSigma);
        var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
        var cosSqAlpha = 1 - sinAlpha * sinAlpha;
        var cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
        if (isNaN(cos2SigmaM))
            cos2SigmaM = 0;
        var C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
        lambdaP = lambda;
        lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
    } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);

    if (iterLimit == 0)
        return NaN

    var uSq = cosSqAlpha * (a * a - b * b) / (b * b);
    var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
    var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
    var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
    var s = b * A * (sigma - deltaSigma);
    return s;
}

/**
 * Return a formatted string with units corresponding to a number of meters.
 * Respects user "units" setting ("metric" or "imperial").
 * @param number meters
 * @param bool space Add a space between number and units.  Default true.
 * @returns string "1000 ft", "2 mi", "3 km",
 */
function getDisplayDistance(meters, space) {
    if (typeof space == 'undefined') {
        space = true;
    }
    var units = getStorage("units");

    if (units == null) {
        units = "metric";
    }

    var number = Math.round(meters);
    var label = "m";

    if (units == "imperial") {
        // Convert to feet
        number = Math.round(number * 3.28084);
        label = "ft";
        if (number >= 1320) { // 0.25 miles
            number = (number / 5280);
            if (number < 10) {
                number = number.toFixed(2);
            } else {
                number = Math.round(number);
            }
            label = "mi";
        }
    } else {
        if (number >= 1000) {
            number = (number / 1000);
            if (number < 16) {
                number = number.toFixed(1);
            } else {
                number = Math.round(number);
            }
            label = "km";
        }
    }

    if (space) {
        return number + " " + label;
    }
    return number + "" + label;
}