/**
 * @namespace WPGMZA
 * @module MapSettings
 * @requires WPGMZA
 */
jQuery(function($) {
	
	/**
	 * Handles map settings, parsing them from the data-settings attribute on the maps HTML element.
	 * NB: This will be split into GoogleMapSettings and OLMapSettings in the future.
	 * @class WPGMZA.MapSettings
	 * @constructor WPGMZA.MapSettings
	 */
	WPGMZA.MapSettings = function(element)
	{
		var self = this;
		var str = element.getAttribute("data-settings");
		var json;
		
		try{
			json = JSON.parse(str);
		}catch(e) {
			
			str = str.replace(/\\%/g, "%");
			str = str.replace(/\\\\"/g, '\\"');
			
			try{
				json = JSON.parse(str);
			}catch(e) {
				json = {};
				console.warn("Failed to parse map settings JSON");
			}
			
		}
		
		WPGMZA.assertInstanceOf(this, "MapSettings");


		
		function addSettings(input) {
			if(!input)
				return;
			
			for(var key in input) {
				if(key == "other_settings")
					continue; // Ignore other_settings
				
				var value = input[key];
				
				if(String(value).match(/^-?\d+$/))
					value = parseInt(value);
					
				self[key] = value;
			}
		}
		
		addSettings(WPGMZA.settings);
		
		addSettings(json);
		
		if(json && json.other_settings)
			addSettings(json.other_settings);

	}
	
	/**
	 * Returns settings on this object converted to OpenLayers view options
	 * @method
	 * @memberof WPGMZA.MapSettings
	 * @return {object} The map settings, in a format understood by OpenLayers
	 */
	WPGMZA.MapSettings.prototype.toOLViewOptions = function()
	{
		var self = this;
		var options = {
			center: ol.proj.fromLonLat([-119.4179, 36.7783]),
			zoom: 4
		};
		
		function empty(name)
		{
			if(typeof self[name] == "object")
				return false;
			
			return !self[name] || !self[name].length;
		}
		
		// Start location
		if(typeof this.start_location == "string")
		{
			var coords = this.start_location.replace(/^\(|\)$/g, "").split(",");
			if(WPGMZA.isLatLngString(this.start_location))
				options.center = ol.proj.fromLonLat([
					parseFloat(coords[1]),
					parseFloat(coords[0])
				]);
			else
				console.warn("Invalid start location");
		}
		
		if(this.center)
		{
			options.center = ol.proj.fromLonLat([
				parseFloat(this.center.lng),
				parseFloat(this.center.lat)
			]);
		}
		
		if(!empty("map_start_lat") && !empty("map_start_lng"))
		{
			options.center = ol.proj.fromLonLat([
				parseFloat(this.map_start_lng),
				parseFloat(this.map_start_lat)
			]);
		}
		
		// Start zoom
		if(this.zoom){
			options.zoom = parseInt(this.zoom);
		}
		
		if(this.start_zoom){
			options.zoom = parseInt(this.start_zoom);
		}

		if(this.map_start_zoom){
			options.zoom = parseInt(this.map_start_zoom);
		}
		
		// Zoom limits
		// TODO: This matches the Google code, so some of these could be potentially put on a parent class
		if(this.map_min_zoom && this.map_max_zoom)
		{
			options.minZoom = Math.min(this.map_min_zoom, this.map_max_zoom);
			options.maxZoom = Math.max(this.map_min_zoom, this.map_max_zoom);
		}

		switch(parseInt(this.type)) {
			case 2:
				options.mapType = 'satellite';
				break;
			case 3:
				options.mapType = 'hybrid';
				break;
			case 4:
				options.mapType = 'terrain';
				break;
			default:
				options.mapType = 'roadmap';
				break;
		}
		
		return options;
	}

	/**
	 * Returns settings on this object converted to Leaflet view options
	 * @method
	 * @memberof WPGMZA.MapSettings
	 * @return {object} The map settings, in a format understood by OpenLayers
	 */
	WPGMZA.MapSettings.prototype.toLeafletViewOptions = function()
	{
		var self = this;
		var options = {
			center: L.latLng({lat: 36.7783, lng: -119.4179}),
			zoom: 4
		};
		
		function empty(name) {
			if(typeof self[name] == "object")
				return false;
			
			return !self[name] || (typeof self[name] === 'string' ? !self[name].length : !(self[name].toString().length));
		}
		
		// Start location
		if(typeof this.start_location == "string") {
			var coords = this.start_location.replace(/^\(|\)$/g, "").split(",");
			if(WPGMZA.isLatLngString(this.start_location))
				options.center = L.latLng({
					lat: parseFloat(coords[0]),
					lng: parseFloat(coords[1])
				});
			else
				console.warn("Invalid start location");
		}
		
		if(this.center) {
			options.center = L.latLng({
					lat: this.center.lat,
					lng: this.center.lng
			});
			
		}
		
		if(!empty("map_start_lat") && !empty("map_start_lng")) {
			options.center = L.latLng({
					lat: this.map_start_lat,
					lng: this.map_start_lng
			});
		}
		
		// Start zoom
		if(this.zoom){
			options.zoom = parseInt(this.zoom);
		}
		
		if(this.start_zoom){
			options.zoom = parseInt(this.start_zoom);
		}

		if(this.map_start_zoom || this.map_start_zoom == 0){
			options.zoom = parseInt(this.map_start_zoom);
		}
		
		// Zoom limits
		// TODO: This matches the Google code, so some of these could be potentially put on a parent class
		if(this.map_min_zoom && this.map_max_zoom) {
			options.minZoom = Math.min(this.map_min_zoom, this.map_max_zoom);
			options.maxZoom = Math.max(this.map_min_zoom, this.map_max_zoom);
		}

		// Leaflet's EPSG3857 projection breaks marker positioning below zoom 3 due to
		// world-copy wrapping. Enforce a safe floor for standard tile maps only —
		// custom tile mode uses L.CRS.Simple which does not have this constraint.
		if(!this.customTileMode) {
			const safeMinZoom = 3;
			if(!options.minZoom || options.minZoom < safeMinZoom) {
				options.minZoom = safeMinZoom;
			}
			if(options.zoom < options.minZoom) {
				options.zoom = options.minZoom;
			}
		}

		function isSettingDisabled(value) {
			if(value === "yes")
				return true;

			return (value ? true : false);
		}

		options.zoomControl = !isSettingDisabled(this.wpgmza_settings_map_zoom);
        options.dragging = !isSettingDisabled(this.wpgmza_settings_map_draggable);
        options.doubleClickZoom	= !isSettingDisabled(this.wpgmza_settings_map_clickzoom);
        options.scrollWheelZoom	= !isSettingDisabled(this.wpgmza_settings_map_scroll);

		options.closePopupOnClick = this.close_infowindow_on_map_click ? true : false;
		
		switch(parseInt(this.type)) {
			case 2:
				options.mapType = 'satellite';
				break;
			case 3:
				options.mapType = 'hybrid';
				break;
			case 4:
				options.mapType = 'terrain';
				break;
			default:
				options.mapType = 'roadmap';
				break;
		}
		
		return options;
	}
	
	/**
	 * Returns settings on this object converted to Google's MapOptions spec.
	 * @method
	 * @memberof WPGMZA.MapSettings
	 * @return {object} The map settings, in the format specified by google.maps.MapOptions
	 */
	WPGMZA.MapSettings.prototype.toGoogleMapsOptions = function()
	{
		var self = this;
		var latLngCoords = (this.start_location && this.start_location.length ? this.start_location.split(",") : [36.7783, -119.4179]);
		
		function empty(name)
		{
			if(typeof self[name] == "object")
				return false;
			
			return !self[name] || !self[name].length;
		}
		
		function formatCoord(coord)
		{
			if(WPGMZA.isNumeric(coord))
				return coord;
			return parseFloat( String(coord).replace(/[\(\)\s]/, "") );
		}
		
		var latLng = new google.maps.LatLng(
			formatCoord(latLngCoords[0]),
			formatCoord(latLngCoords[1])
		);
		
		var zoom = (this.start_zoom ? parseInt(this.start_zoom) : 4);
		
		if(!this.start_zoom && this.zoom){
			zoom = parseInt( this.zoom );
		}

		if(this.map_start_zoom){
			zoom = parseInt(this.map_start_zoom);
		}
		
		var options = {
			zoom:			zoom,
			center:			latLng
		};
		
		if(!empty("center"))
			options.center = new google.maps.LatLng({
				lat: parseFloat(this.center.lat),
				lng: parseFloat(this.center.lng)
			});
		
		if(!empty("map_start_lat") && !empty("map_start_lng"))
		{
			// NB: map_start_lat and map_start_lng are the "real" values. Not sure where start_location comes from
			options.center = new google.maps.LatLng({
				lat: parseFloat(this.map_start_lat),
				lng: parseFloat(this.map_start_lng)
			});
		}
		
		if(this.map_min_zoom && this.map_max_zoom)
		{
			options.minZoom = Math.min(this.map_min_zoom, this.map_max_zoom);
			options.maxZoom = Math.max(this.map_min_zoom, this.map_max_zoom);
		}
		
		// NB: Handles legacy checkboxes as well as new, standard controls
		function isSettingDisabled(value)
		{
			if(value === "yes")
				return true;
			
			return (value ? true : false);
		}
		
		// These settings are all inverted because the checkbox being set means "disabled"
		options.zoomControl				= !isSettingDisabled(this.wpgmza_settings_map_zoom);
		options.cameraControl			= !isSettingDisabled(this.wpgmza_settings_map_camera_control);
        options.panControl				= !isSettingDisabled(this.wpgmza_settings_map_pan);
        options.mapTypeControl			= !isSettingDisabled(this.wpgmza_settings_map_type);
        options.streetViewControl		= !isSettingDisabled(this.wpgmza_settings_map_streetview);
        options.fullscreenControl		= !isSettingDisabled(this.wpgmza_settings_map_full_screen_control);
        
        options.draggable				= !isSettingDisabled(this.wpgmza_settings_map_draggable);
        options.disableDoubleClickZoom	= isSettingDisabled(this.wpgmza_settings_map_clickzoom);

        if(isSettingDisabled(this.wpgmza_settings_map_tilt_controls)){
        	options.rotateControl = false;
        	options.tilt = 0;
        }
		
		// NB: This setting is handled differently as setting scrollwheel to true breaks gestureHandling
		if(this.wpgmza_settings_map_scroll)
			options.scrollwheel			= false;
		
		if(this.wpgmza_force_greedy_gestures == "greedy" 
			|| this.wpgmza_force_greedy_gestures == "yes"
			|| this.wpgmza_force_greedy_gestures == true)
		{
			options.gestureHandling = "greedy";
			
			// Setting this at all will break gesture handling. Make sure we delete it when using greedy gesture handling
			if(!this.wpgmza_settings_map_scroll && "scrollwheel" in options)
				delete options.scrollwheel;
		}
		else
			options.gestureHandling = "cooperative";
		
		switch(parseInt(this.type))
		{
			case 2:
				options.mapTypeId = google.maps.MapTypeId.SATELLITE;
				break;
			
			case 3:
				options.mapTypeId = google.maps.MapTypeId.HYBRID;
				break;
			
			case 4:
				options.mapTypeId = google.maps.MapTypeId.TERRAIN;
				break;
				
			default:
				options.mapTypeId = google.maps.MapTypeId.ROADMAP;
				break;
		}

		if(WPGMZA.settings && WPGMZA.settings.googleMarkerMode && WPGMZA.settings.googleMarkerMode === WPGMZA.GoogleMarker.MARKER_MODE_ADVANCED){
			options.mapId = `wpgmza_map_${this.id}`;
		}
		
		if(this.wpgmza_theme_data && this.wpgmza_theme_data.length){
			if(WPGMZA.settings && WPGMZA.settings.googleMarkerMode && WPGMZA.settings.googleMarkerMode !== WPGMZA.GoogleMarker.MARKER_MODE_ADVANCED){
				options.styles = WPGMZA.GoogleMap.parseThemeData(this.wpgmza_theme_data);
			}
		}


		
		return options;
	}

	/**
	 * Configure tile server
	 * 
	 * This is specifically for engines that utilize either and XYZ or vector tile layer system (Not Google Maps)
	 * 
	 * This hydrates the content with global definition, manages inline replacements, building paramaters and 
	 * eventually will be used for local map setting overrides as well
	 * 
	 * @param string url The URL being processed
	 * @param object options The options to apply while configuring
	 * 
	 * @return object
	 */
	WPGMZA.MapSettings.prototype.configureTileServer = function(url, options){
		if(url){
            const config = {
                url : url,
                params : {},
                replacements : [],
                authentication : {
                    name : 'apikey',
                    token : false
                },
                options : {},
                type : 'xyz'
            };

            /* We could add localized map options here as well */
            const settings = WPGMZA.settings ? WPGMZA.settings : {};

            /* Check for a custom override */
            if(config.url === 'custom_override'){
                /* Custom override detected */
                if(settings.tile_server_url_override && settings.tile_server_url_override.trim() !== ""){
                    config.url = settings.tile_server_url_override.trim();
                }
				
				if(settings.tile_server_key_field_name_override && settings.tile_server_key_field_name_override.trim() !== ""){
					config.authentication.name = settings.tile_server_key_field_name_override.trim();
				}

				if(settings.tile_server_type_override && settings.tile_server_type_override.trim() !== ""){
					config.type = settings.tile_server_type_override.trim();
				}
            }

            if(options){
                if(options.authentication && options.authentication.trim().length > 0){
                    config.authentication.token = options.authentication.trim();
                }

                if(options.preferServerGlobals){
                    /* Engine prefers {s} server flag */
                    config.replacements.push({key : "{a-c}", value : "{s}"});
                } else {
					config.replacements.push({key : "{s}", value : "{a-c}"});
				}
            }

            /* Hydrate the server definition */
            config.definition = WPGMZA.tileServer && WPGMZA.tileServer instanceof Object ? WPGMZA.tileServer : false;
			if(options.map && options.map.settings){
				if(options.map.settings.tile_server_override && options.map.settings.tile_server_override_config){
					/* We have a full override for this specific map */
					if(options.map.settings.tile_server_override_config.provider === config.definition.provider){
						/* Providers do match, we can go ahead */
						config.definition = options.map.settings.tile_server_override_config;
						config.url = config.definition.url;
					}
				}
			}

            if(config.definition){
				if(config.definition.multi){
					/* Multi layer - Complex*/
					if(config.definition.layers && config.definition.layers.length){
						/* Check the active map selection layer type */
						let selectedLayerType = 'roadmap'; 

						if(options.map && options.map.getMapType){
							selectedLayerType = options.map.getMapType();
						}

						let layerLookup = [];
						let activeLayer = config.definition.layers[0] ? config.definition.layers[0] : false;
						/* Check that the selection type is available in the layer list */
						for(let layerIndex in config.definition.layers){
							let layer = config.definition.layers[layerIndex];
							if(layer.selection_type === selectedLayerType){
								/* Match */
								activeLayer = layer;
							}

							layerLookup[layerIndex] = layer.selection_type;
						}

						if(activeLayer && activeLayer.config && activeLayer.config instanceof Object){
							/* We have matched a layer for use - replace the hydration override */
							config.definition = activeLayer.config;

							/* Replace the local config drivers as well */
							if(config.definition.url !== config.url){
								config.url = config.definition.url;
							}

							/* Flag multi-layer support on the map settings - This will allow us to generate controls */
							this._multilayerTileserver = {
								layers : layerLookup,
								active : selectedLayerType
							};

							config.layers = layerLookup;
						} else {
							console.warn(`📍 WP Go Maps: Could not identify default map layer`);
						}
					} else {
						console.warn(`📍 WP Go Maps: You are using a multi-layer tile server, but no layers were found. Please check your configuration.`);
					}
				} 

				/* Definition - Attributions */
				if(config.definition.attribution){
					let attribution = config.definition.attribution;
					let attrReplace = {
						YEAR : new Date().getFullYear()
					};

					for(let replaceK in attrReplace){
						attribution = attribution.replace(`{{${replaceK}}}`, attrReplace[replaceK]);
					}

					config.options.attributions = attribution;
					config.options.attribution = attribution;
				}

				/* Definition - Check auth name  */
				if(config.definition.key_field_name && config.definition.slug !== 'custom'){
					config.authentication.name = config.definition.key_field_name;
				} 

				/* Definition - Check type change */
				if(config.definition.type && config.definition.type !== config.type && config.definition.slug !== 'custom'){
					config.type = config.definition.type;
				}

				/* Definition - Params */
				if(config.definition.params){
					for(let paramName in config.definition.params){
						config.params[paramName] = config.definition.params[paramName];
					}
				}
            }

			/* Check for dependency layers */
			if(config.definition.dependencies){
				config.dependencies = [];
				for(let depIndex in config.definition.dependencies){
					let dependency = config.definition.dependencies[depIndex];
					if(dependency && dependency.provider && config.definition.provider){
						if(dependency.provider === config.definition.provider){
							/* Dependency providers match, so we can work with it */
							config.dependencies.push({
								url : dependency.url,
								params : dependency.params ? dependency.params : {}
							});
						}
					}
				}
			}

            /* Append the authentication */
            if(config.authentication.token){
                if(config.url.indexOf('{' + config.authentication.name + '}') !== -1){
                    /* It's an inline auth token */
                    config.replacements.push({key : "{" + config.authentication.name + "}", value : config.authentication.token});
                } else {
                    /* Append as a param */
                    config.params[config.authentication.name] = config.authentication.token;
                }
		    }

            /* Check for an alias - Aliases are only for storage system */
            if(config.url.indexOf("{alias:") !== -1){
                /* This is a direct replacement */
                config.url = config.url.replace(/{alias:.*?}/, '');
            }

			/* Handle dependency layers */
			if(config.dependencies){
				for(let depIndex in config.dependencies){
					let dependencyUrl = config.dependencies[depIndex].url;
					let dependencyParams = config.dependencies[depIndex].params;

					dependencyUrl = dependencyUrl.replace(/{alias:.*?}/, '');
					if(dependencyUrl.indexOf('{' + config.authentication.name + '}') !== -1){
						dependencyUrl = dependencyUrl.replace('{' + config.authentication.name + '}', config.authentication.token);
					} else {
						/* Append as a param */
						dependencyParams[config.authentication.name] = config.authentication.token;
					}

					let compiledDepParams = [];
					for(let paramName in dependencyParams){
						compiledDepParams.push(`${paramName}=${dependencyParams[paramName]}`);
					}

					config.dependencies[depIndex].compiledParams = compiledDepParams.join("&");
					if(config.dependencies[depIndex].compiledParams){
						dependencyUrl = `${dependencyUrl}?${config.dependencies[depIndex].compiledParams}`;
					}

					config.dependencies[depIndex].url = dependencyUrl;
				}
			}

            /* Compile the URL */
            config.compiledParams = [];
            for(let paramName in config.params){
				config.compiledParams.push(`${paramName}=${config.params[paramName]}`);
            }

			config.compiledParams = config.compiledParams.join("&");
			if(config.compiledParams){
				config.url = `${config.url}?${config.compiledParams}`;
			}

            /* Perform replacements */
            for(let replacementKey in config.replacements){
				const replacement = config.replacements[replacementKey];
                config.url = config.url.replace(replacement.key, replacement.value);
            }

			/* Check if this is a custom image instead */
			if(this.custom_tile_enabled){
				config.custom = {
					width : 1024,
					height : 1024
				};

				if(this.custom_tile_image_width){
					config.custom.width = parseInt(this.custom_tile_image_width);
				}

				if(this.custom_tile_image_height){
					config.custom.height = parseInt(this.custom_tile_image_height);
				}

				config.custom.dimensions = null;
				try { 
					if(window.devicePixelRatio && window.devicePixelRatio != 1){
						config.custom.dimensions = [config.custom.width, config.custom.height];
					}
				} catch (ex){
					/* Do nothing */
				}

				if(this.custom_tile_image){
					config.type = 'custom';
					config.url = this.custom_tile_image;

					if(this.custom_tile_image_attribution){
						config.custom.attribution = this.custom_tile_image_attribution;
					}
				}
			}

            return config;
        }
        return false;
	}

	WPGMZA.MapSettings.prototype.hasMutlilayerTileServer = function() {
		return this._multilayerTileserver ? this._multilayerTileserver : false;
	}
});
