/*namespace 'mapGeneral' contains general functionality related to maps*/

var mapGeneral; // acts as namespace for everything in this file

if (!mapGeneral) {
    mapGeneral = {};
}

// namespace created in anonymous function so only the namespace itself is added to global namespace
(function() {
    /* globals used in various files */
    mapGeneral.FIRST_ZOOM_LEVEL_WITHOUT_WIND = 10;

    /**/
    mapGeneral.averageOfPoints = function(points) {
        var sumLats = 0;
        var sumNormalizedLngs = 0;

        var numPoints = points.length;
        for (var i = 0; i < numPoints; i++) {
            var point = points[i];

            sumLats += point.lat;
            sumNormalizedLngs += (point.lng + 180);
        }

        var avgLat = sumLats / numPoints;
        var avgLng = (sumNormalizedLngs / numPoints) - 180;

        return new GLatLng(avgLat, avgLng);
    };


    /**/
    mapGeneral.boundsAroundPoint = function(lat, lng, margin) {
        var N = lat + margin;
        var S = lat - margin;

        var E = lng + margin;
        var W = lng - margin;

        return mapGeneral.newBounds(S, W, N, E);
    };


    /* 
    returns smallest bounds that encloses these points
    TODO hasn't been tested 
    */
    mapGeneral.boundsOfPoints = function(points) {
        var lat = points[0].lat;

        var N = lat;
        var S = lat;

        var numPoints = points.length;
        for (var i = 0; i < numPoints; i++) {
            var point = points[i];

            lat = point.lat;

            if (lat > N) {
                N = lat;
            } else if (lat < S) {
                S = lat;
            }
        }


        var E = points[numPoints].lng;
        var W = points[0].lng;

        var diffEW = 360 - (E - W);  // initial diff crosses itl date line

        points.sort(function(a, b) {
            return a.lng - b.lng;
        });

        for (i = 0, j = 1; i < numPoints - 1; i++, j++) {
            var tempE = points[j].lng;
            var tempW = points[i].lng;

            var diff = tempE - tempW;
            if (diff > diffEW) {
                E = tempE;
                W = tempW;
                diffEW = diff;
            }
        }

        return mapGeneral.newBounds(S, W, N, E);
    };


    /* same as mapGeneral.boundsOfPoints, except returned bounds won't ever cross 
    itl date line and so may not be the smallest containing bounds of these points 
    */
    mapGeneral.boundsOfPoints_naive = function(points) {
        var point = points[0];

        var lat = point.lat;
        var lng = point.lng;

        var N = lat;
        var S = lat;

        var E = lng;
        var W = lng;

        var numPoints = points.length;

        for (var i = 0; i < numPoints; i++) {
            var point = points[i];

            lat = point.lat;

            if (lat > N) {
                N = lat;
            } else if (lat < S) {
                S = lat;
            }

            lng = point.lng;

            if (lng > E) {
                E = lng;
            } else if (lng < W) {
                W = lng;
            }
        }

        return mapGeneral.newBounds(S, W, N, E);
    };


    /* given a GLatLngBounds, return a random GLatLng location within that bounds */
    mapGeneral.randomLatLng = function(bounds) {
        var SW = bounds.getSouthWest();
        var NE = bounds.getNorthEast();

        var height = NE.lat() - SW.lat();

        if (NE.lng() > SW.lng()) {
            var width = NE.lng() - SW.lng();
        } else {
            width = 360 - (SW.lng() - NE.lng());
        }

        var lat = (Math.random() * height) + SW.lat();
        var lng = (Math.random() * width) + SW.lng();

        return new GLatLng(lat, lng);
    };



    /* return a random GLatLngBounds */
    mapGeneral.randomBounds = function() {

        var width = Math.random() * 360;
        var height = Math.random() * 170;

        var S = -85 + Math.random() * (170 - height);
        var N = S + height;

        var W = -180 + Math.random() * 360;
        var E = W + width;

        return mapGeneral.newBounds(S, W, N, E);

    };


    /*  convenience to make a new GLatLngBounds with less typing */
    var newBounds = mapGeneral.newBounds = function(S, W, N, E) {
        return new GLatLngBounds(new GLatLng(S, W), new GLatLng(N, E));
    };

    if (typeof GMap !== 'undefined') {
        mapGeneral.BOUNDS_OF_WHOLE_WORLD = mapGeneral.newBounds(-90, -180, 90, 180);
    }


    mapGeneral.ORDINALS = {
        N: 'N',
        NE: 'NE',
        E: 'E',
        SE: 'SE',
        S: 'S',
        SW: 'SW',
        W: 'W',
        NW: 'NW'
    };


    /*   */
    mapGeneral.vectorToOrdinal = function(vector /* [lngDist, latDist] */) {

        var ORDINALS = mapGeneral.ORDINALS;

        var lngDist = vector[0];
        var latDist = vector[1];

        if (latDist === 0 && lngDist === 0) {
            return null;
        }

        var radians = Math.atan2(latDist, lngDist);

        var PI = Math.PI;

        var direction;

        if (radians >= 0) {
            if (radians < (PI / 8)) {
                direction = ORDINALS.E;
            } else if (radians < (3 * PI / 8)) {
                direction = ORDINALS.NE;
            } else if (radians < (5 * PI / 8)) {
                direction = ORDINALS.N;
            } else if (radians < (7 * PI / 8)) {
                direction = ORDINALS.NW;
            } else {
                direction = ORDINALS.W;
            }
        } else {
            if (radians > (-PI / 8)) {
                direction = ORDINALS.E;
            } else if (radians > (-3 * PI / 8)) {
                direction = ORDINALS.SE;
            } else if (radians > (-5 * PI / 8)) {
                direction = ORDINALS.S;
            } else if (radians > (-7 * PI / 8)) {
                direction = ORDINALS.SW;
            } else {
                direction = ORDINALS.W;
            }
        }

        return direction;
    };


    ////////////CLASS LoadingMessage
    /*

    */
    mapGeneral.LoadingMessage = function() {
        this.messageImg = document.getElementById('loadingMessage');
    };

    mapGeneral.LoadingMessage.prototype.constructor = mapGeneral.LoadingMessage;

    mapGeneral.LoadingMessage.prototype.show = function() {
        this.messageImg.style.display = 'block';
    };

    mapGeneral.LoadingMessage.prototype.hide = function() {
        this.messageImg.style.display = 'none';
    };
    ////////////END CLASS LoadingMessage

    /*  */

    var mapTypesByShortName;

    mapGeneral.getMapTypeByShortName = function(shortName) {
        if (!mapTypesByShortName) {
            var types = SWMap.map.getMapTypes();
            mapTypesByShortName = {};
            for (var i in types) {
                var type = types[i];
                mapTypesByShortName[type.getName(true)] = type;
            }
        }

        return mapTypesByShortName[shortName];
    };

    /* */
    mapGeneral.zoomIn = function(map) {
        var currentZoom = map.getZoom();
        mapGeneral.setZoom(map, currentZoom + 1, currentZoom);
    };

    /**/
    mapGeneral.zoomOut = function(map) {
        var currentZoom = map.getZoom();
        mapGeneral.setZoom(map, currentZoom - 1, currentZoom);
    };

    /* */
    mapGeneral.setZoom = function(map, newZoom) {
        var info = SWMap.info;

        if (newZoom < info.minZoom) {
            newZoom = info.minZoom;
        }
        if (newZoom > info.maxZoom) {
            newZoom = info.maxZoom;
        }

        map.setZoom(newZoom);
    };

    /* don't call with type "off"; use SWMap.animationControl.hideAnimation() instead */
    mapGeneral.centerAndZoomOnRegionAnimation = function(region, type, frame, play) {
        var map = SWMap.map;
        var region = region || map.getCenter().getTightestContainingRegion();
        var oldZoom = map.getZoom();
        var newZoom = region.zoom;
        var oldCenter = map.getCenter();
        var newCenter = region.center;

        SWMap.animationControl.setAnimation(region, type, frame);

        if (oldZoom === newZoom) {
            if (oldCenter === newCenter) {
                SWMap.historyControl.addState(); // position isn't changing but state is
                if (play) {
                    // need delay or else you get ugly race condition
                    window.setTimeout(
                        function() {
                            SWMap.animationControl.play(false);
                        },
                        0
                    );
                }
            }
            else if (type !== 'off') { // shouldn't pan to 'off'
                var moveHandler = (play) ? 'ON_PLAY_AFTER_PAN_AFTER_SETTING_ANIMATION' : 'ON_PAN_AFTER_SETTING_ANIMATION';
                handlers.setMoveEndHandler(moveHandler); // we have already set anim, so prevent moveend from doing it again
                map.panTo(newCenter);
            }
        } else {
            var zoomHandler = (play) ? 'ON_PLAY_AFTER_ZOOM_TO_REGION' : 'ON_ZOOM_TO_REGION';
            handlers.setZoomEndHandler(zoomHandler);
            map.setCenter(newCenter, newZoom);
            SWMap.regionTrail.flashCurrentRegion();
        }
    };
})();

