/**
 * @requires AtlasLayerSwitcher
 */
/**
 * Class: AtlasMap
 * This class creates an preconfigured map OpenLayers.Map for the ReefAtlas.
 * It allows slaveMaps {AtlasMap} to be attached to it. This allow multiple
 * maps to have their pan and zoom linked together.
 *
 * Inherits from:
 *  - <OpenLayers.Map>
 */
var AtlasMap = OpenLayers.Class(OpenLayers.Map, {
	
	/**
	 * Overwrite the default Z indexing to allow for more layers. Each layer
	 * is incremented in z-index by 5.
     * Constant: Z_INDEX_BASE
     * {Object} Base z-indexes for different classes of thing 
     */
    Z_INDEX_BASE: {
        BaseLayer: 100,
        Overlay: 500,
        Feature: 5500,
        Popup: 6000,
        Control: 7000
    },
	
	/** 
     * APIProperty: defaultWfsUrl
     * {string URL} This address is used as the default address for WFS requests.
	 * To override this for a given layer, set the wfsUrl attribute in the layerData.
     */
	defaultWfsUrl: '/geoserver/wms',
	
	/**
	 * Property: slaveMaps
	 * [{AtlasMap}] array of maps that this map controls the pan and zoom of.
	 */
	slaveMaps : [],
	
	/**
	 * Property: popup
	 * {OpenLayers.Popup.FramedCloud} Used to save the displayed popup so we can 
	 * automatically remove it so there is only one popup at a time.
	 */
	popup: null,
	
	/**
	 * True while the map is being loaded with layers. Use this attribute to 
	 * prevent excessive redraws during the initialisation of the map.
	 */
	loading : false,
	
	/**
	 * Displays the specified error message instead of the map. Use this for
	 * serious errors. This does not affect execution flow.
	 */
	dispError: function(title, message) {
		this.div.innerHTML='<h1>'+title+'</h1>'+message;
	},
	
	/**
     * Constructor: AtlasMap
	 *   Creates a map in the specified div and configured with the specified layers. 
	 *   If this map is to be a master map then the MouseToolBar should added to allow 
	 *   full map control. This is to work around a bug in OpenLayers.
	 *
	 *   masterMap.addControl(new OpenLayers.Control.MouseToolbar());
	 *
	 * Parameters: 
	 *     div -		{string} html div to place the map into
	 *     layerData -	array of objects describing the layers to load. 
						see manualLayers/README.txt 		
     */
    initialize: function(div, layerData) {
		this.loading = true;		
		// Default controls for the map.
		var controls = [
			new OpenLayers.Control.PanZoomBar(),
			// Don't add the MouseToolbar as there is a bug in OpenLayers that
			// prevents removing it properly for a slavemap.
			//new OpenLayers.Control.MouseToolbar(),
			new OpenLayers.Control.ScaleLine(),
			new OpenLayers.Control.MousePosition(),
			new OpenLayers.Control.KeyboardDefaults(),
			//new OpenLayers.Control.Attribution()
			new OpenLayers.Control.AtlasLegend(),
			new OpenLayers.Control.AtlasLayerSwitcher({'ascending':false})
		];
		// Designed for the GeoWebCache
		var mapOptions = {
			maxResolution: 0.703125,
			controls: controls,
			numZoomLevels: 12,
			projection: new OpenLayers.Projection('EPSG:4326'),
			maxExtent: new OpenLayers.Bounds(-180.0, -90.0, 180.0, 90.0)
		};
		// Hack in a new event type. It must be added before the initialize
		// so that the event is added before controls attempt to register
		// with it.
		ATLAS.Util.pushIfUnique(OpenLayers.Map.prototype.EVENT_TYPES, 'finishedLoading');
		
		OpenLayers.Map.prototype.initialize.apply(this, [div, mapOptions]);	
		this._addAtlasLayers(layerData);
		this._registerFeatureRequest();
		this.loading = false;
		// Let controls know that we have finished loading all the data layers.
		this.events.triggerEvent("finishedLoading");
	},
	

	/**
	 * Function: _addAtlasLayers
	 *   Add all the layers available in the reef atlas to this map.
	 * Parameters:
	 *   layerData - Description of all layers to add, see manualLayers/README.txt.
	 */ 
	_addAtlasLayers: function (layerData) {
		
		for(var i = 0; i < layerData.length; i++) {
			var layer = layerData[i];
			var extraOptions = {
				isBaseLayer : layer.baseLayer, 
				wrapDateLine : true,
				//transitionEffect: 'resize',		// zoom the existing image to give
												// immediate zooming while the images
												// are loading
				buffer: 0	// buffer of images outside the viewport to preload.
							// keep it small to minimise the total download, but
							// have some so panning is still smooth.
			};
			
			// Check: Should layer.query and layer.queryLayers be moved into 
			// atlasData attribute, rather than being attached to the extraOptions?
			if (layer.query) {
				extraOptions.query = layer.query;
			}
			
			if (layer.queryLayers) {
				extraOptions.queryLayers = layer.queryLayers;
			} else {
				extraOptions.queryLayers = layer.layerName;
			}
			var olLayer;
			if (layer.layerType === 'WMS') {
				olLayer = new OpenLayers.Layer.WMS(
					layer.title, 
					layer.wmsUrl,
					{layers: layer.layerName,
					 format : layer.jpeg ? 'image/jpeg' : 'image/png',
					 transparent : !layer.baseLayer
					}, extraOptions);
					
				// The visibility of the base layer must be set prior to adding 
				// the layer to the map, otherwise it does not appear. Surely
				// this is a bug in OpenLayers.
				olLayer.setVisibility(layer.visible);					
				this.addLayer(olLayer);
				
			} else if (layer.layerType === 'KML') {
				olLayer = new OpenLayers.Layer.Vector(layer.title, {
					projection: this.displayProjection,
					strategies: [new OpenLayers.Strategy.Fixed()],
					protocol: new OpenLayers.Protocol.HTTP({
						url: layer.kmlUrl,
						format: new OpenLayers.Format.KML({
							extractStyles: true,
							extractAttributes: true
						})
					})
				});
				
						
				olLayer.select = new OpenLayers.Control.SelectFeature(olLayer);
				this.addControl(olLayer.select);
					
				olLayer.events.on({
					"featureselected": function(event) {
						var feature = event.feature;
						// Since KML is user-generated, do naive protection against
						// Javascript.
						var content = "<h2>"+feature.attributes.name + "</h2>" + 
							feature.attributes.description;
						if (content.search("<script") != -1) {
							content = "Content contained Javascript! Escaped content below.<br />" + 
								content.replace(/</g, "&lt;");
						}
						popup = new OpenLayers.Popup.FramedCloud("chicken", 
												 feature.geometry.getBounds().getCenterLonLat(),
												 new OpenLayers.Size(450,250),
												 content,
												 null, true, 
												 // bind the this to be the layer, not the popup
												 OpenLayers.Function.bind(
													function(evt) {
														this.select.unselectAll();
													}, 
													this)
												);
						feature.popup = popup;
						this.map.addPopup(popup);
					},
					"featureunselected": function (event) {
						var feature = event.feature;
						if(feature.popup) {
							this.map.removePopup(feature.popup);
							feature.popup.destroy();
							delete feature.popup;
						}
					},
					"visibilitychanged": function(event) {
						//alert("visibility: "+this.getVisibility());
						if (this.getVisibility()) {
							this.select.activate();
						} else {
							this.select.deactivate();
						}
					}
				});
				olLayer.setVisibility(layer.visible);
				
				// The layer must be added to the map before the feature selector for
				// layer can be activated, otherwise the selector is ineffective.
				this.addLayer(olLayer);
				
				// If the layer is already visible (due to being turned on in a map
				// link) then make sure the point selection still works.
				// Without this the point popups don't work for visible KML
				// layers in a map link.
				if (layer.visible) {
					// Note: select must be added to the map before calling activate,
					// other wise it crashes.
					// Note: that activating a layer raises its Z index above normal
					// layers.
					olLayer.select.activate();
				}
				

			} else {
				this.dispError("Layer configuration error",
					"unknown layerType ("+layer.layerType+") for layer: "+layer.layerName);
			}

			// Attach the layer data unique to the Atlas to the layer object. 
			// Use a single property to limit possible collisions with existing 
			// and future properties
			olLayer.atlasData = layer;
		}
	},
	
	/**
	 * Function: _registerFeatureRequest
	 * 
	 *   Register with the map so that each time the map is Ctrl clicked a WMS feature
	 *   request is made for each of the visible layers.
	 *   This is currently a hack as all requests go back to the Reef Atlas GeoServer.
	 *   In the long run not all layers will reside in the Reef Atlas GeoServer.
	 */
	_registerFeatureRequest: function () {
		this.events.register('click', this, function (e) {
			// Only activate with a shift click. This is to get around the bug that
			// doubleclicks seem to cause two clicks, a drag also causes a click,
			// and clicking on one of the mouseToolbar controls also generates
			// a click event.
			if (e.ctrlKey) {
				// Find the list of layers registered for WCS or WFS requests.
				
				// HACK: Place the query against the first available valid query layer.
				// i.e. send the query to this layer. This only works if all the
				// queries are going to the same server.
				var layerToQueryHack;
				
				var visibleLayers = [];
				for(var i = 0; i < this.layers.length; i++) {
					var layer = this.layers[i];
					// We reply on the query attribute we added when creating the layers
					if (layer.getVisibility() && (layer.query)) {
						// concat does not work for some reason, so lets do it manually
						if (ATLAS.Util.isArray(layer.queryLayers)) {
							for (var j = 0; j < layer.queryLayers.length; j++) {
								visibleLayers.push(layer.queryLayers[j]);
							}
						} else {
							visibleLayers.push(layer.queryLayers);
						}
						if (typeof layerToQueryHack === 'undefined') {
							layerToQueryHack = layer;
						}
					}
				}
				// Hack
				//layerToQueryHack = this.layers[this.layers.length-1];		// Should be a WMS layer corresponding to the GeoServer
				
				var url = layerToQueryHack.getFullRequestString (
					{
						REQUEST: "GetFeatureInfo",
						EXCEPTIONS: "application/vnd.ogc.se_xml",
						BBOX: this.getExtent().toBBOX(),
						X: e.xy.x,
						Y: e.xy.y,					
						INFO_FORMAT: 'text/html',
						QUERY_LAYERS: visibleLayers.join(),		// Send one request for all layers
						FEATURE_COUNT: 4,
						WIDTH: this.size.w,
						HEIGHT: this.size.h
					}, this.defaultWfsUrl);

				OpenLayers.loadURL(	url, '', this, function(response) {
					// Remove any old popup if any
					if (this.popup !== null) {
						this.removePopup(this.popup);
					}
					//document.getElementById('info').innerHTML = '<div class="featureInfoWrapper">'+response.responseText+'</div>';
					this.popup = new OpenLayers.Popup.FramedCloud("chicken", 
									 this.getLonLatFromViewPortPx(e.xy),
									 new OpenLayers.Size(100,100),
									 '<div class="featureInfoWrapper">'+response.responseText+'</div>',
									 null, true, null);
					this.addPopup(this.popup);
					
					
				});
			}
			
		}); 
	},

	/**
	 * APIMethod: addSlaveMap
	 * Add a slave map to be controlled by this map. Pan and Zoom operations are
	 * replicated in the slave map. This also disables the PanZoomBar, the MouseToolBar
	 * and KeyboardDefaults of the slave map so that the controls are total dependant
	 * on the master map.
	 *
	 * Note: due to a bug in OpenLayers the removal of the MouseToolBar, and
	 * KeyboardDefaults only partially work. For this reason slaveMaps should
	 * never have these controls added to them.
	 *
	 * Parameters:
	 *   slaveMap - {AtlasMap} Map to become a slave of this map.
	 */
	addSlaveMap: function(slaveMap) {
		//Disable the mouse controls for the slaveMap
		var toRemove = [ 
			"OpenLayers.Control.PanZoomBar",
			"OpenLayers.Control.MouseToolbar",
			"OpenLayers.Control.KeyboardDefaults"
		];
		for ( var i = 0; i < toRemove.length; i++) {
			var controls = slaveMap.getControlsByClass(toRemove[i]);
			
			for ( var j = 0; j < controls.length; j++) {
				slaveMap.removeControl(controls[j]);
				controls[j].deactivate();
			}
		}
		// Save the slave so it will receive pan and zoom events
		this.slaveMaps.push(slaveMap);
		
		// Make sure our master map is registered to copy pan and zoom
		// events to the slave maps
		this.events.register('moveend', this, function (event) {
			// Make sure that the slave maps have the same view 
			// position as the master map
			for(var i = 0; i < this.slaveMaps.length; i++) {
				//this.slaveMaps[i].panTo(this.getCenter());
				// Set the zoom first so that if the slaves have no extent set
				// it will still work. panTo will not work if no zoom level
				// has been set.
				this.slaveMaps[i].zoomTo(this.getZoom());
				this.slaveMaps[i].panTo(this.getCenter());
				
			}
		});
				
	}
});

