(function() { 

	var _mu = m2.util;
	var _isE = m2.util.isEmptyString;
	
	/**
	 * @namespace 
	 * This namespace contains common functions for formatting MapQuest site URLs.
	 * 
	 * @static
	 */
	m2.URL = {
	    
	    /**
	     * Path for cdn resources.
	     */
	    CDN_PATH : "/cdn",
	    
	    /**
	     * Path for requesting a map.
	     */
	    MAP_PATH : "/maps",
	    
	    /**
	     * Path for requesting directions.
	     */
	    DIRECTIONS_PATH : "/maps",
	    
	    /**
	     * Path for requesting the print page.
	     */
	    PRINT_PATH : "/print",
	    
	    /**
	     * Path for requesting the settings page.
	     * @type String
	     */
	    SETTINGS_PATH : "/settings",
	    
	    /**
	     * Path for requesting weather widget content.
	     */
	    WEATHER_WIDGET_PATH : "/widget/weather",
	    
	    /**
	     * Path for requesting the directions form.
	     */
	    DIRECTIONS_FORM_PATH : "/directions",
	    
	    /**
	     * LocalQuest url.  Set at runtime.
	     */
	    LOCAL_URL : "",
        
	    /**
	     * Hash for setting print map parameters.
	     */
	    PRINT_HASH: "#a/mapsprint/m:${type}:${zoom}:${centerLatitude}:${centerLongitude}:${traffic}::/r:${referrer}/e",
	    
	    /**
	     * Gets an absolute url for the given relative url using the current window host/port information.
	     * 
	     * @param {Object} url  relative url
	     * @return the absolute url
	     */
	    getWindowUrl : function(url) {
	        var wl = window.location;
	        return wl.protocol + "//" + wl.hostname + (wl.port ? ":" + wl.port : "") + url;
	    },
	    
        /**
         * Gets a url for requesting a map with the given location.
         * 
         * @param {Object} location       location
         * @param {boolean} isSingleLine  whether or not a single line address was used
         * @return {String} the url
         */
        getMapUrl : function(location, isSingleLine) {        
            var params = [];
            
            // Determine the query parameters.  Order based on SEO requirements.
            var hasName = !_isE(location.name);
            
            if (hasName) {
                params.push("name=" + encodeURIComponent(location.name))
            }
            
            // Only submit a single line address if it needs to be geocoded.  This ensures places contain
            // the proper address info on the result page.
            var isGeocoded = (!_isE(location.latitude) && !_isE(location.longitude));
    
            if (isSingleLine && !isGeocoded) {

                // Single line address (e.g., 2 box).  Always include the address input field to
                // allow the back end to determine that a 2 box form was submitted.
                params.push("addressInput=" + encodeURIComponent(location.addressInput));
            } else {
                // Multiple value address (e.g., 5 box).
                if (!_isE(location.city)) {
    				params.push("city=" + encodeURIComponent(_mu.capitalize(location.city)));
    			}
                if (!_isE(location.state)) {
    				params.push("state=" + encodeURIComponent(location.state.toUpperCase()));
    			}
                if (!_isE(location.addressLine1)) {
    				params.push("address=" + encodeURIComponent(_mu.capitalize(location.addressLine1)));
    			}
                if (!_isE(location.postalCode)) {
    				params.push("zipcode=" + encodeURIComponent(location.postalCode));
    			}
            }            
            if (!hasName && !_isE(location.query)) {
    			params.push("cat=" + encodeURIComponent(location.query));
            }
                
            if (!_isE(location.country)) {
    			params.push("country=" + encodeURIComponent(location.country));
    		}
            if (isGeocoded) {
    			params.push("latitude=" + encodeURIComponent(location.latitude));
    			params.push("longitude=" + encodeURIComponent(location.longitude));
            }
            if (!_isE(location.geocodeQuality)) {
    			params.push("geocode=" + encodeURIComponent(location.geocodeQuality.toUpperCase()));
    		}
        		
        	if (!_isE(location.id)) {
        		params.push("id=" + encodeURIComponent(location.id));
        	}
            
            // Format the url.
            var url = this.getWindowUrl(this.MAP_PATH);
            
            if (params.length > 0) {
                url += "?" + params.join("&").replace(/%20/g,"+");
            } else {
                // No location parameters.  Use same fallback as original map form.
                url += "/US/";
            }
            
            return url;
        },
        
        /**
         * Gets a url for requesting directions with the given locations.
         * 
         * @param {Object} start          starting location
         * @param {Object} end            ending location
         * @param {boolean} isSingleLine  whether or not a single line address was used
         * @param {Object} options        route options
         * @return the url
         */
        getDirectionsUrl : function(start, end, isSingleLine, options) {    
            var locations = [start, end];    
            var params = [];
            var locationCount = locations.length;
            var location;
            var ind;
            var hasName;
            var isGeocoded;
            
            // Determine the query parameters.  Order based on SEO requirements.
            for (var i = 0; i < locationCount; ++i) {
                ind = i + 1;
                location = locations[i];            
    
                hasName = !_isE(location.name);
                
                if (hasName) {
                    params.push(ind + "qn=" + encodeURIComponent(location.query))
                }
                
                // Only submit a single line address if it needs to be geocoded.  This ensures places contain
                // the proper address info on the result page.
                isGeocoded = (!_isE(location.latitude) && !_isE(location.longitude));
    
                if (isSingleLine && !isGeocoded) {
                    // Single line address (e.g., 2 box).  Always include the address input field to
                    // allow the back end to determine that a 2 box form was submitted.
                    params.push(ind + "ai=" + encodeURIComponent(location.addressInput));
                } else {
                    // Multiple value address (e.g., 5 box).
                    if (!_isE(location.city)) {
    					params.push(ind + "c=" + encodeURIComponent(_mu.capitalize(location.city)));
    				}
                    if (!_isE(location.state)) {
    					params.push(ind + "s=" + encodeURIComponent(location.state.toUpperCase()));
    				}
                    if (!_isE(location.addressLine1)) {
    					params.push(ind + "a=" + encodeURIComponent(_mu.capitalize(location.addressLine1)));
    				}
                    if (!_isE(location.postalCode)) {
    					params.push(ind + "z=" + encodeURIComponent(location.postalCode));
    				}
                }
    
                if (!hasName && !_isE(location.query)) {
    				params.push(ind + "pn=" + encodeURIComponent(location.query));
    			}
                
                if (!_isE(location.country)) {
        			params.push(ind + "y=" + encodeURIComponent(location.country));
        		}
                if (isGeocoded) {
        			params.push(ind + "l=" + encodeURIComponent(location.latitude));
        			params.push(ind + "g=" + encodeURIComponent(location.longitude));
                }
                if (!_isE(location.geocodeQuality)) {
        			params.push(ind + "v=" + encodeURIComponent(location.geocodeQuality.toUpperCase()));
        		}
        		
        		if (!_isE(location.id)) {
        			params.push(ind + "id=" + encodeURIComponent(location.id));
        		}
            }
            
            if (options) {
                if (!_isE(options.avoidHighways)) {
    				params.push("aoh=1");
    			}
                if (!_isE(options.avoidTollRoads)) {
    				params.push("aot=1");
    			}
                if (!_isE(options.avoidSeasonal)) {
    				params.push("aos=1");
    			}
                if (!_isE(options.type) && (options.type == "shortest")) {
    				params.push("r=s");
    			}
            }
            
            // Format the url.
            var url = this.getWindowUrl(this.DIRECTIONS_PATH);
            
            if (params.length > 0) {
                url += "?" + params.join("&").replace(/%20/g,"+");
            }
    
            return url;
        },
	
	    /**
	     * Gets a url for requesting the directions form with the given locations.
	     * 
	     * @param {Object} start          starting location
	     * @param {Object} end            ending location
	     * @return the url
	     */
	    getDirectionsFormUrl : function(start, end) {    
	        var locations = [start, end];    
	        var params = [];
	        var locationCount = locations.length;
	        var location;
	        var ind;
	        var formatParam;
            var isGeocoded;
	        
	        // Determine the query parameters.
	        for (var i = 0; i < locationCount; ++i) {
	            location = locations[i];
	            
	            if (!location) {
	                continue;
	            }
	            
	            ind = i + 1;
                
	            if (!_isE(location.name)) {
					params.push(ind + "pn=" + encodeURIComponent(location.name));
				}
	
	            // Always pass separate fields to allow for both 2 box and 5 box form population.
                isGeocoded = (!_isE(location.latitude) && !_isE(location.longitude));
	            
	            if (!_isE(location.addressLine1)) {
					params.push(ind + "a=" + encodeURIComponent(_mu.capitalize(location.addressLine1)));
				}
	            if (!_isE(location.city)) {
					params.push(ind + "c=" + encodeURIComponent(_mu.capitalize(location.city)));
				}
	            if (!_isE(location.state)) {
					params.push(ind + "s=" + encodeURIComponent(location.state.toUpperCase()));
				}
	            if (!_isE(location.postalCode)) {
					params.push(ind + "z=" + encodeURIComponent(location.postalCode.toUpperCase()));
				}
                
                if (!_isE(location.country)) {
        			params.push(ind + "y=" + encodeURIComponent(location.country));
        		}
                if (isGeocoded) {
        			params.push(ind + "l=" + encodeURIComponent(location.latitude));
        			params.push(ind + "g=" + encodeURIComponent(location.longitude));
                }
                if (!_isE(location.geocodeQuality)) {
        			params.push(ind + "v=" + encodeURIComponent(location.geocodeQuality.toUpperCase()));
        		}
        		if (!_isE(location.id)) {
        			params.push(ind + "id=" + encodeURIComponent(location.id));
        		}
        		
	        }
	
	        return this.getWindowUrl(this.DIRECTIONS_FORM_PATH) + ((params.length > 0) ?  "?" + params.join("&") : "");
	    },
		
	    /**
	     * Gets a url for requesting a printable map with the given location.
	     * 
	     * @param {Object} model    model
	     */
	    getPrintMapUrl : function(model) {
	        var location = model.locations[0].location;
	        var params = [];
	        
	        // Determine the location query parameters.
	        if (!_isE(location.city)) {
				params.push("city=" + encodeURIComponent(location.city));
			}
	        if (!_isE(location.state)) {
				params.push("state=" + encodeURIComponent(location.state));
			}
	        if (!_isE(location.addressLine1)) {
				params.push("address=" + encodeURIComponent(location.addressLine1));
			}
	        if (!_isE(location.postalCode)) {
				params.push("zipcode=" + encodeURIComponent(location.postalCode));
			}
			if (!_isE(location.country)) {
				params.push("country=" + encodeURIComponent(location.country.toUpperCase()));
			}
			if (!_isE(location.latitude)) {
				params.push("latitude=" + encodeURIComponent(location.latitude));
			}
			if (!_isE(location.longitude)) {
				params.push("longitude=" + encodeURIComponent(location.longitude));
			}
			if (!_isE(location.geocodeQuality)) {
				params.push("geocode=" + encodeURIComponent(location.geocodeQuality.toUpperCase()));
			}
	        
	        // Determine the map parameters.
	        var traffic = model.trafficEnabled ? "1" : "";
	        var referrer = '';
	        
	        switch (s_pageName) {
	            case 'home.form.map':
	                break;
	            case 'map.form.map':
	                referrer = 'maps';
	                break;
	            case 'dir.form.map':
	                referrer = 'directions';
	                break;
	            default:
	                // Assume map results.
	                referrer = 'mapresults';
	                break;
	        }
	        
	        var hash = this.PRINT_HASH;
	        hash = hash.replace(/\${type}/, model.viewType);
	        hash = hash.replace(/\${zoom}/, model.zoomLevel);
	        hash = hash.replace(/\${centerLatitude}/, model.centerLatitude);
	        hash = hash.replace(/\${centerLongitude}/, model.centerLongitude);
	        hash = hash.replace(/\${type}/, model.viewType);
	        hash = hash.replace(/\${traffic}/, traffic);
	        hash = hash.replace(/\${referrer}/, referrer);
	        
	        // Format the url.
	        var url = this.getWindowUrl(this.PRINT_PATH);
	        
	        if (params.length > 0) {
	            url += "?" + params.join("&").replace(/%20/g,"+");
	        }
	        
	        url += hash;
	        
	        return url;
	    },
	
	    /**
	     * Gets a url for requesting the weather widget content.
	     * 
	     * @param {Object} location    location
	     */
	    getWeatherWidgetUrl : function(location) {
	        var params = [];
	        
	        // Determine the location query parameters.
	        if (!_isE(location.city)) {
				params.push("city=" + encodeURIComponent(location.city));
			}
	        if (!_isE(location.state)) {
				params.push("state=" + encodeURIComponent(location.state));
			}
	        if (!_isE(location.postalCode)) {
				params.push("zipcode=" + encodeURIComponent(location.postalCode));
			}
	        if (!_isE(location.country)) {
				params.push("country=" + encodeURIComponent(location.country));
			}
	        
	        params.push("latitude=" + location.latitude);
	        params.push("longitude=" + location.longitude);
	        
	        // Format the url.  Uses a path as url will be added when executing the ftl request.
	        var url = this.WEATHER_WIDGET_PATH;
	        
	        if (params.length > 0) {
	            url += "?" + params.join("&").replace(/%20/g,"+");
	        }
	        
	        return url;
	    },
	
	    /**
	     * Gets a url for requesting an OnStar dialog with the given location.
	     * 
	     * @param {Object}  model           location
	     * @param {String}  typeOfRedirect  
	     * @return {String}                 the url
	     */
	    getSendToRedirectUrl : function(model,typeOfRedirect) {
	        var location = model.locations[0].location;
	        var params = [];
	
	        // Determine location parameters.
	        params.push("city=" + (!_isE(location.city) ? encodeURIComponent(location.city) : ""));
	        params.push("state=" + (!_isE(location.state) ? encodeURIComponent(location.state) : ""));
	        params.push("address=" + (!_isE(location.addressLine1) ? encodeURIComponent(location.addressLine1) : ""));
	        params.push("zipcode=" + (!_isE(location.postalCode) ? encodeURIComponent(location.postalCode) : ""));
	        params.push("country=" + (!_isE(location.country) ? encodeURIComponent(location.country.toUpperCase()) : ""));
	        params.push("latitude=" + (!_isE(location.latitude) ? encodeURIComponent(location.latitude) : ""));
	        params.push("longitude=" + (!_isE(location.longitude) ? encodeURIComponent(location.longitude) : ""));
	        // TODO: why level???
	        params.push("level=" + (!_isE(location.geocodeQuality) ? encodeURIComponent(location.geocodeQuality) : ""));
			
			// Format the url.
	        var url = this.getWindowUrl(this.MAP_PATH);
	        url += "?" + params.join("&").replace(/%20/g,"+");
	        url += "&" + typeOfRedirect + "=1"
	
	        return url;
	    },

	    /**
	     * Gets a url for requesting an OnStar dialog with the given location.
	     * 
	     * @param {Object}  model           location
	     * @param {String}  typeOfRedirect  
	     * @return {String}                 the url
	     */
	    getSendToRedirectUrlRecentLocation : function(location,typeOfRedirect) {
	       
	        var params = [];
	
	        // Determine location parameters.
	        params.push("city=" + (!_isE(location.city) ? encodeURIComponent(location.city) : ""));
	        params.push("state=" + (!_isE(location.state) ? encodeURIComponent(location.state) : ""));
	        params.push("address=" + (!_isE(location.addressLine1) ? encodeURIComponent(location.addressLine1) : ""));
	        params.push("zipcode=" + (!_isE(location.postalCode) ? encodeURIComponent(location.postalCode) : ""));
	        params.push("country=" + (!_isE(location.country) ? encodeURIComponent(location.country.toUpperCase()) : ""));
	        params.push("latitude=" + (!_isE(location.latitude) ? encodeURIComponent(location.latitude) : ""));
	        params.push("longitude=" + (!_isE(location.longitude) ? encodeURIComponent(location.longitude) : ""));
	        // TODO: why level???
	        params.push("level=" + (!_isE(location.geocodeQuality) ? encodeURIComponent(location.geocodeQuality) : ""));
			
			// Format the url.
	        var url = this.getWindowUrl(this.MAP_PATH);
	        url += "?" + params.join("&").replace(/%20/g,"+");
	        url += "&" + typeOfRedirect + "=1"
	
	        return url;
	    },

        /**
         * Gets a url for the settings page.
         */
        getSettingsUrl : function() {
			return this.getWindowUrl(this.SETTINGS_PATH);
        },
	    
	    /**
	     * Gets a url for adding a location.
	     * 
	     * @return the url
	     */
	    getAddLocationUrl : function() {
            return this.getSettingsUrl();
	    },
	    
	    /**
	     * Gets a url for editing a location.
	     * 
	     * @return the url
	     */
	    getEditLocationUrl : function() {
            return this.getSettingsUrl();
	    },
	    
	    /**
	     * Create the URL for deep linking to LocalQuest
	     * @param {object} location location
	     */
	    getLocalUrl: function (location) {
	    	var url = this.LOCAL_URL+'?';
	    	if(location.city) url += '&city=' + location.city;
	    	if(location.state) url += '&state=' + location.state;
	    	return url;
	    },
        
	    /**
	     * Gets a map model based on the current url.
	     */
	    getMapModel : function() {
	        var hash = window.location.hash;
	        
	        if (!hash) {
	            return null;
	        }    
	        
	        var sections = hash.split('/');
	        var sectionCount = sections.length;
	        var section;
	        var values;
	        var model = null;
	        
	        for (var i = 0; i < sectionCount; ++i) {
	            section = sections[i];
	            values = section.split(':');
	            
	            if (values[0] == 'm') {
	                model = { };
					if (values[1] != "") { model.viewType = values[1]; }
					model.zoomLevel = parseInt(values[2]);
					if (values[3] != "") { model.centerLatitude = parseFloat(values[3]); }
					if (values[4] != "") { model.centerLongitude = parseFloat(values[4]); }
					if (values[5] == "1") { model.trafficEnabled = true; }
	            }
	        } 
	        
	        return model;
	    }
	};
})();
