/*namespace 'googleObjects' contains extensions to the Google maps API classes */

var googleObjects; // acts as namespace for everything in this file

if (!googleObjects) {
    googleObjects = {};
}

// namespace created in anonymous function so only the namespace itself is added to global namespace
(function () {

    if (typeof GMap !== 'undefined') {

        /* EXTENSIONS TO GMap2: */   
         
        /* Set center and zoom level of map so that it contains and is centered around 'bounds'. 
        
           If zooming in (e.g. from lvl 3 to lvl 5), you should call this again in the zoom handler to correct for projection distortion. (The trouble stems from only being able to do pixel-latlng translations for the current zoom level.)
        */
        GMap2.prototype.centerAndZoomOnBounds = function (bounds) {
            
            var map = this;
            
            function getNewCenter(bounds, newZoom) {
                var regionNE = bounds.getNorthEast();
                var regionSW = bounds.getSouthWest();
                
                
                var newLng = bounds.getCenter().lng();
                
                var projection = G_NORMAL_MAP.getProjection();
                var topY = projection.fromLatLngToPixel(regionNE, newZoom).y;
                var bottomY = projection.fromLatLngToPixel(regionSW, newZoom).y;
                var midPointY = new GPoint(0, (topY + bottomY) / 2);
                var newLat = projection.fromPixelToLatLng(midPointY, newZoom).lat();
                
                return new GLatLng(newLat, newLng);
            }
            
            var oldZoom = this.getZoom(); 
            var newZoom = this.getBoundsZoomLevel(bounds);
            
            if (newZoom < 1) {
                newZoom = 1;
            }
            
            if (newZoom === 1) { // limit outtermost zoom to 1 because zoom 0 is ugly
                this.setCenter(new GLatLng(0, 179.5), 1);  // KLUDGE ALERT: setting to 180 would make the map animation frames not show up for the world region because of the way the double divs work; close enough solution for now
            } else {
                
                var newCenter = getNewCenter(bounds, newZoom);
                
                if (newZoom === oldZoom) {
                    this.panTo(newCenter);
                } else {
                    this.setCenter(newCenter, newZoom);
                } 
            }
            
            return newZoom;

        };
        
        
        /*
         */
        GMap2.prototype.centerAndZoom = function (lat, lng, zoom) {
            this.setCenter(new GLatLng(lat, lng), zoom);
        };
        
        
        /*
         */
        GMap2.prototype.getRegionsIntersectingView = function(zoom, resizeFactor) {

            if (zoom) {
                var candidateRegions = SWMap.world.regionsByZoom[zoom];
            } else {
                candidateRegions = SWMap.world.regions;
            }    
            
            var mapBounds = this.getBounds();
            
            if (resizeFactor) {
                mapBounds = mapBounds.resizeByFactor(resizeFactor);
            }
            
            //TODO should have paramater to expand bounds so as to include regions near the view
            
            var intersectingRegions = [];
            
            for (var i in candidateRegions) {
                var region = candidateRegions[i];
                if (mapBounds.intersects(region.bounds)) {
                    intersectingRegions.push(region);
                }
            }
            
            return intersectingRegions;
        };
        
        
        /*
         */
        GMap2.prototype.isBoundsInsideWorld = function(bounds) {
            var zoom = this.getZoom();
            if (zoom < 1) {
                return false; 
            } else {
                            
                var N = bounds.getNorthEast().lat();
                var S = bounds.getSouthWest().lat();
                
                if (N > 85 || S < -85) {
                    return false;
                }
            }            
            return true;
        };
            
        
        /* 
            Should be called upon zoomend and moveend. When grey area above top of the world or 
            bottom of the world is visible, keepViewOffEdgeOfWorld pans the map up or down so these 
            areas are not visible. (Doesn't do anything at zoom level 0 because it's impossible at level 0 to have only 
            map areas in view.)
        */
        GMap2.prototype.keepViewOffEdgeOfWorld = function() {
            
            // zoom level 1 always shows lat 85 OR lat -85 OR both, so can't keep view on the world
            var zoom = this.getZoom();
            if (zoom < 1) {
                return; 
            } else {
               
                var center = this.getCenter();
                var lng = center.lng();
                
                var bounds = this.getBounds();
                var NE = bounds.getNorthEast();
                var SW = bounds.getSouthWest();
                
                var NWorld = new GLatLng(85, lng);  // 85 is the max lat of the visible map
                var SWorld = new GLatLng(-85, lng);
                
                var NMap = new GLatLng(NE.lat(), lng); // center north of map view
                var SMap = new GLatLng(SW.lat(), lng); // center south of map view
                                
                if (NMap.lat() > 85) {
                    // distance in px between NMap and NWorld
                    var NDist = Math.abs(this.fromLatLngToDivPixel(NMap).y - this.fromLatLngToDivPixel(NWorld).y);
                    
                    NDist += 2;  // an extra margin of 2 pixesl (to make sure the view doesn't hang over the edge); without it, in rare cases you get an infinite loop of calls to keepViewOffEdgeOfMap for some reason I can't discern (somehow the map must be getting moved slightly off the edge)
                    
                    this.panBy(new GSize(0, -NDist)); // pan down by NDist
                }
                
                if (SMap.lat() < -85) {
                    // distance in px between SMap and SWorld
                    var SDist = Math.abs(this.fromLatLngToDivPixel(SMap).y - this.fromLatLngToDivPixel(SWorld).y);
                    
                    SDist += 2; // an extra margin of 2 pixesl (to make sure the view doesn't hang over the edge); without it, in rare cases you get an infinite loop of calls to keepViewOffEdgeOfMap for some reason I can't discern (somehow the map must be getting moved slightly off the edge)
                    
                    this.panBy(new GSize(0, SDist)); // pan up by SDist
                }
            }
        };
        
                
        
        /* EXTENSIONS TO GLatLngBounds: */
        
         
        /*
            returns new bounds that is centered on same point but resized by factor (e.g. factor of 3 is three times as wide and tall; 0.2 is 1/5 as wide and tall)
        */
        GLatLngBounds.prototype.resizeByFactor = function (factor) {
            
            // find height and width of this bounds
            var SW = this.getSouthWest();
            var NE = this.getNorthEast();
            
            var S = SW.lat();
            var W = SW.lng();
            var N = NE.lat();
            var E = NE.lng();
            
            N += 180; // ensure both N and E are positive
            S += 180;
            
            var height = N - S;
            
            // ensure difference is the short one (and not the long one that goes around the world)
            
            if (W > E) {
                E += 360;
            }       
                            
            var width = Math.abs(E - W);
            
            // figure vert and horizontal distance from center for resized bounds
            
            var vertDist = (height / 2) * factor;
            var horizDist = (width / 2) * factor;
            
            
            // make new bounds
            
            var center = this.getCenter();
            
            var centerLat = center.lat();
            var centerLng = center.lng();
            
            S = centerLat - vertDist;
            W = centerLng - horizDist;
            N = centerLat + vertDist;
            E = centerLng + horizDist;
            
            SW = new GLatLng(S, W, false);
            NE = new GLatLng(N, E, false);
            
            return new GLatLngBounds(SW, NE);
        };
        

        /* Convenience. Return the coords of the bounds as an object with members N S E and W */
        GLatLngBounds.prototype.getCoords = function () {
           
            coords = {};
            
            var NE = this.getNorthEast();
            var SW = this.getSouthWest();
            
            coords.N = NE.lat();
            coords.E = NE.lng();
            coords.S = SW.lat();
            coords.W = SW.lng();
            
            return coords;
            
        };
        
        
        /**/
        GLatLngBounds.prototype.containsMarker = function (marker) {
            return this.containsLatLng(marker.getPoint());
        };
        
            
        /* return a random GLatLng location within the bounds */
        GLatLngBounds.prototype.randomLatLng = function () {
            var SW = this.getSouthWest();
            var NE = this.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);
        };
        
        /* EXTENSIONS TO GLatLng: */
        
        
        /*  */
        GLatLng.prototype.getTightestContainingRegion = function (maxZoomLevel, minZoomLevel) {
            
            var map = SWMap.map;
            maxZoomLevel = maxZoomLevel || map.getZoom();
            minZoomLevel = minZoomLevel || 1;
            var regionsByZoom = SWMap.world.regionsByZoom;
            var possibleMatches = [];

            // search from current zoom level up to level 2
            for (var i = maxZoomLevel; i > minZoomLevel; i--) {
                
                var regions = regionsByZoom[i];

                // find all the matches in the current zoom level
                for (var j = 0; j < regions.length; j++) {
                    if (regions[j].bounds.containsLatLng(this)) {
                        possibleMatches.push(regions[j]);
                    }
                }
                
                // if one or more matches at this level, don't search the remaining levels
                if (possibleMatches.length > 0) {
                    break;
                }
            }

            if (possibleMatches.length === 0) {
                return regionsByZoom[1][0];  // the world region is the only containing region (world may not have been found as a possible match in the case that the bounds extends above or below the N and S limits)
            } else if (possibleMatches.length === 1) {
                return possibleMatches[0]; // sole member of possibleMatches is the tightest region
            } else { // more than one possible match
                return this.getClosestRegionByCenter(possibleMatches); // the possible match with the closest center is the region in which the bounds is most centered
            }
        };
        
        
        /* Find closest region to this one (measured by distance to center point in terms of degrees) */
        GLatLng.prototype.getClosestRegionByCenter = function (regions) {
            var closest = regions[0];
            var closestDist = this.distanceFrom(regions[0].bounds.getCenter());
            
            for (var i = 1; i < regions.length; i++) {
                var dist = this.distanceFrom(regions[i].bounds.getCenter());
                if (dist < closestDist) {
                    closest = regions[i];
                    closestDist = dist;
                }   
            }
            
            return closest;
        };

    }
    
})();