/**
 * @author peter.goulborn
 * Requires OpenLayers, Prototype, atExtendOpenLayers.js
 *
 * $Revision: 51 $
 * $Author: Peter.goulborn $ 
 * 
 * This is a class to provide an Openlayers map for an iSharemaps server.
 */

if (!Astun) var Astun = {};
if (!Astun.iSharemaps) Astun.iSharemaps = {};
if (!OpenLayers || !Prototype || !Astun.iSharemaps.Common) throw new Error('Dependent library not found');  // Needs to be nicer, possibly iSharemaps-wide library error?


(function() {
	
	// local properties & objects
	
	// expose OLMap class
	Astun.iSharemaps.OLMap = Class.create({
		'initialize': function  ( elementId , dataurl , rasterurl , options ) {
			/*
			 * ARGUMENTS
			 * ---------
			 * elementId -> id of the map element
			 * dataurl -> iShareMaps map page URL string
			 * rasterurl -> URL string for raster map 
			 * options: {Object} optional settings for wrapper and all maps 
			 *		created by it.
			 *	callbacks: {Object} map method container object.
			 *		{ click, doubleclick, hoverPause, hoverMove }
			 * ---------
			 * 
			 * NOTES
			 * -----
			 * Assumes base layer remains the iSharemaps layer.
			 * -----
			 */
			this.dataurl = dataurl;
			this.rasterurl = rasterurl;
			this.mapElement = $(elementId);
			this.locationZoom = 1000; // width of map in metres, hardcoded as no setting to govern this  
			var clickCallbacks = 	Object.extend({}, {
				'click': options.callbacks.click, 
				'dblclick': options.callbacks.doubleclick
			});
			var hoverCallbacks = 	Object.extend({}, {
				'pause': options.callbacks.hoverPause || function(){}, 
				'move': options.callbacks.hoverMove || function(){}
			});
			this.currentLocation = {};
			this.query={};
			this.query.popups = $A();
			this.installedControls = {};
			this.installedControls.nav = new OpenLayers.Control.Navigation();	
			this.installedControls.lonlat = new OpenLayers.Control.MousePosition();	
			this.installedControls.keys = new OpenLayers.Control.KeyboardDefaults();  
			this.installedControls.attrib = new OpenLayers.Control.Attribution(); 
			this.installedControls.pzb = new OpenLayers.Control.PanZoomBar(); 
			this.installedControls.scale = new OpenLayers.Control.ScaleLine();
			this.installedControls.hover = new OpenLayers.Control.Hover({'delay': 2000}, hoverCallbacks);
			//this.installedControls.hover = new OpenLayers.Control.Hover( {}, hoverCallbacks);
			this.installedControls.click = new OpenLayers.Control.Click({'single': false, pixelTolerance: 1}, clickCallbacks); 
			this.timers = {};
			this.timers.CurrentWait = 0 ;
			this.date='';
			
			this.defaultMapControls = [ 
				this.installedControls.nav,  
				this.installedControls.lonlat,	
				this.installedControls.keys, 
				this.installedControls.attrib,
				this.installedControls.pzb,
				this.installedControls.scale,
				this.installedControls.hover,
				this.installedControls.click
			];
			this.arThemePopup=[];
			
			var mapResize = function(evt){
					this.map.updateSize();
			}
			
			
			
			Event.observe(this.mapElement, 'astun:mapResize', mapResize.bindAsEventListener(this));
			
		},
		'addControl': function(controlName, control) {
			this.map.addControl(control);
			this.installedControls[controlName] = control;
			this.installedControls[controlName].activate();
		},
		'removeControl': function(controlName) {
			var removed = false;
			var control = this.installedControls[controlName];
			if (!!control) {
				removed = this.installedControls[controlName].deactivate();
				this.map.removeControl(control);
			} 
			return removed;
			
		},
		'setControl': function(controlName, control) {
			this.removeControl(controlName);
			return this.addControl(controlName,control);
		}
		,
		'loadMap': function (source, boundingBox, options) {
			/* 
			 * source: {String} iSharemaps map source name 
			 * boundingBox: {Array} [ left, bottom, right, top ]
			 * options: {Object} container for optional settings:
			 * 		initialView: {Object} ISM view (easting, northing, zoom}.
			 * 		forceInitial: {Boolean} Whether initial view is always used.
			 *		rasterType: {String} Raster layer class (e.g. 'TileCache').
			 *		map{Object}: Optional object with {<OpenLayers.Map>} 
			 *			properties to tag onto the map. 'maxExtent and 
			 *			'controls' properties will be
			 *			overwritten.		
			 *		data {Object}: Optional object with 
			 *			{<OpenLayers.Layer.iShareMaps>} properties to tag onto
			 *			the layer.
			 *		raster {Object}: Optional object with 
			 *			{<OpenLayers.Layer>} properties to tag onto	the layer.
			 *			Currently expected to be one of:
			 *				<OpenLayers.Layer.WMS>    
			 *				<OpenLayers.Layer.TileCache>    
			 *		common: {Object} settings common to both data and raster 
			 *			layers
			 */
			var resScaleProperties = $A(['scales','resolutions','maxResolution','minScale']);
			if (!options) {
				var options = {};
			}
			var rasterType = (options.rasterType) ? options.rasterType : 'WMS';
			this.mapSource = source;
			var bounds = new OpenLayers.Bounds.fromArray(boundingBox);	
			var resBounds =  new OpenLayers.Bounds(boundingBox[0]-(bounds.getWidth()/2),boundingBox[1]-(bounds.getHeight()/2),boundingBox[2]+(bounds.getWidth()/2),boundingBox[3]+(bounds.getHeight()/2));
			var defaultMapOptions = {
				'numZoomLevels': 9,
				'restrictedExtent': resBounds,
				'projection': 'EPSG:27700',
				'units': 'm'
			};
			var mapOptions = (!!options.map) ? Object.extend( defaultMapOptions, options.map ) : defaultMapOptions;
			
			var resScaleSet = false;
			for (var p = 0; p < resScaleProperties.length; p++) {
				resScaleSet = !!mapOptions[resScaleProperties[p]];
				if (resScaleSet) {
					break;
				}
			}
			
			if (!resScaleSet) {
				mapOptions.maxresolution = 50; //units per pixel
			}
			
			this.map = new OpenLayers.Map(
				this.mapElement, 
				Object.extend (
					mapOptions,					
					{
						'maxExtent': bounds,
						'controls': this.defaultMapControls
					}
				)
			
			);
			this.map.wrapper = this; // this is needed but ideally shouldn't be necessary...
			
			
			for (var c = 0; c < this.map.controls.length; c++) {
				this.map.controls[c].activate();
			}		
			
			var defaultLayerOptions =  {
				'projection': this.map.projection,
				'units': this.map.units,
				'maxExtent': this.map.maxExtent
			}
			while (resScaleProperties.length) {		
				var property = resScaleProperties.pop();	
				var value = this.map[property];
				if (value) {
					defaultLayerOptions[property] = value;
				}
			}
				
			
			var ismOptions = Object.extend({},defaultLayerOptions);
			Object.extend(
				ismOptions, 
				{
					'singleTile': true,
					'ratio': 1.4,
					'alpha':true
				}
			);
			/*
			var ismDataLayer = new OpenLayers.Layer.iShareMaps(
				'iSharemaps Data Layer', 
				this.dataurl, 
				{
					'mapsource': source,
					'transparent': true
				}, 
				(options.data) ? Object.extend(ismOptions, options.data) : ismOptions
			);
			
			
			
			this.map.addLayer(ismDataLayer);
			*/
			
			
			
			var rasterOptions = Object.extend({},defaultLayerOptions)
			Object.extend(
				rasterOptions, 				 
				{
					'buffer': 1,
					'transitionEffect': 'resize'
				}
			);
			var rasterLayerName = 'basic';
			switch (rasterType) {
				case 'WMS':
					this.rasterLayer = new OpenLayers.Layer.WMS(
						'iShareMaps Raster Layer',
						this.rasterurl,
						{
							'layers': rasterLayerName
						},
						(options.raster) ? Object.extend(rasterOptions, options.raster) : rasterOptions
					);
					break;
				case 'TileCache':
					this.rasterLayer = new OpenLayers.Layer.TileCache(
						'iShareMaps Raster Layer',
			            this.rasterurl,
			            rasterLayerName,
						(options.raster) ? Object.extend(rasterOptions, options.raster) : rasterOptions
					);
					break;
				default:
					throw ('Invalid raster layer type specified');
					break;
			}
			
			
			this.map.addLayer(this.rasterLayer);  
			//this.ismDataLayer = ismDataLayer
			//this.markerLayer = new OpenLayers.Layer.Markers( 'Markers', {'maxResolution': this.map.maxResolution, 'maxExtent': this.map.maxExtent} );
			//this.map.markerLayer = this.markerLayer; //map.markerLayer is deprecated please use markerlayer instead.
			//this.map.addLayer(this.markerLayer);
			
			/*
			this.setISMLayers((options.data && options.data.defaultLayers) ? options.data.defaultLayers : []);
			if ( !this.ismDataLayer.params.layers.length) {
				this.ismDataLayer.visibility = false;
			}
			*/
			
			
			var setView = function(mapView){
				if (options.forceInitial || !mapView) {
					var view = options.initialView;
				}
				else {
					var view = mapView.evalJSON();
				}
				
				if (view) {
					this.draw(view);
				}
				else {
					this.map.zoomToMaxExtent();
				}
			}
			if (!options.deferDraw) {
				this.mapElement.fire('astun:loadSetting', {setting: 'mapView', loadFunction: setView.bind(this)});
			}
			
		
			this.map.events.register("moveend", this, function() {
				// Create iSharemaps 'view' from centre point plus image width and resolution
				// saving current view
				/*var en = this.map.getCenter();
				var imageSize = this.ismDataLayer.getImageSize();
				var view = {
					'easting': en.lon,
					'northing': en.lat, 
					'zoom' : imageSize.w * this.ismDataLayer.getResolution()
				};
				var json = Object.toJSON(view);
				this.mapElement.fire('astun:saveSetting', {setting: 'mapView', value: '' + json});
				*/
				// FT call panner
				this.layerControl.thematicInfo.oLayer.panThematic();
				var chkRaster = $('chkRaster');
					if (chkRaster) {
					chkRaster.checked=true;
					}
            });
			this.map.events.register("zoomend", this, function() {
				// FT call new function zoomThematic (like toggleThematic)
		        this.layerControl.thematicInfo.oLayer.zoomThematic();
				var chkRaster = $('chkRaster');
					if (chkRaster) {
					chkRaster.checked=true;
					}
            });
            /*
			
			ismDataLayer.events.register("loadstart", ismDataLayer, function() {
                this.map.wrapper.mapElement.fire("astun:layerStart", {layer: this} );
            });
			ismDataLayer.events.register("loadend", ismDataLayer, function() {
                this.map.wrapper.mapElement.fire("astun:layerEnd", {layer: this} );
            });*/
			this.mapElement.fire('astun:mapLoaded', this);
			
		},
		'setISMLayers': function ( layers ) {
			/*
			 * layers -> iSharemaps layer names array
			 */
			this.ismDataLayer.params.layers = $A(layers);
		},
		'removeISMLayer': function  ( layer ) {
			/*
			 * layer -> layer name string
			 */
			this.ismDataLayer.params.layers = this.ismDataLayer.params.layers.without(layer);
		},
		'addISMLayer': function  ( layer ) {
			/*
			 * layer -> layer name string
			 */
			this.ismDataLayer.params.layers.push(layer);
		}, 
		'createMarker': function ( position, icon ) {
		/**
		* Function: createMarker 
		* Draws a marker on the marker layer on the map 
		*
		* Parameters:
		* position - {object} OpenLayers.LonLat object
		* icon - OpenLayers.Icon object (defaults to default OpenLayers icon)	
		*
		* Returns:
		* (object} - OpenLayers.Marker object
		*/
			var marker = new OpenLayers.Marker ( position, icon || OpenLayers.Marker.defaultIcon());
			this.map.markerLayer.addMarker(marker);
			//set marker layer to be top one
			this.map.markerLayer.setZIndex(this.map.Z_INDEX_BASE['Popup'] - 1); 
			
			return marker;	
			 
		},
		'deleteMarker': function ( marker ) {
		/**
		* Function: deleteMarker 
		* Removes a specific marker from the display and destroys the object 
		*
		* Parameters:
		* marker - {object} OpenLayers.Marker object
		*
		* Returns:
		*/
			if (marker) {
				this.map.markerLayer.removeMarker(marker);
				marker.destroy();
				marker = null;
			}
		},
		'clearPopups': function() {
		/**
		* Function: clearPopups 
		* Removes removes all popups from the display and destroy them 
		*
		* Parameters:
		*
		* Returns:
		*/
			$('atGeomPanel').show();
			if ( this.map.popups.length > 0 ) {
				while ( this.map.popups.length)
				{
					var popup = this.map.popups.pop();
					this.map.removePopup(popup);
					popup.destroy();
					popup = null;
				}
			}
		},
		'draw': function  ( view )	{
		/**
		* Function: draw 
		* Draws or redraw the map.
		* Recenters if coordinates are specified,
		* rezooms if iSharemaps zoom level is specified, 
		* redraws iSharemaps layer, so any other changes to parameters take effect.
		*
		* Parameters:
		* view - {object) iSharemaps view object: { easting, northing, zoom }
		* Returns:
		*/
			if (!!view) {
				var newCentre = OpenLayers.Util.extend(this.map.getCenter() || new OpenLayers.LonLat(), {'lon': view.easting - 0.0, 'lat': view.northing - 0.0	});
				var imageSize = this.map.getCurrentSize();
				var currentResolution = this.map.getResolution();
				if (view.zoom && view.zoom != imageSize.w * currentResolution) {
					var resolution = view.zoom / imageSize.w;
				}
				else {
					var resolution = currentResolution;
				}
				this.map.setCenter(
					newCentre, 
					this.map.getZoomForResolution(resolution),
					false,
					true
				);
			} 
			else 
			{
				window.olmap = this;
				
				if ( this.timers.CurrentWait )
					clearTimeout(this.timers.CurrentWait);
					
				this.timers.CurrentWait = setTimeout("window.olmap.ismDataLayer.redraw();",300 );
				/*
				window.olmap = this;
				 var timeOutVar = setTimeout("window.olmap.ismDataLayer.redraw()",500 );
				
				//this.ismDataLayer.redraw();		
				*/		
			}
		
		},
		'getGeoJSONData' : function( params, callbackFunction ) {
		/**
		* Function: getGeoJSONData 
		* Makes a Ajax call with supplied params to return GeoJSON data
		*
		* Parameters:
		* params - {object) params object as detailed below
		* callbackFunction - {object) function object to be called on sucess
		*
		* Returns:
		*/
			var defaultParams = {
				'MapSource': this.mapSource,
				'RequestType': 'GeoJSON',
				'ServiceAction': '',
				'ActiveTool': '',
				'ActiveLayer': '',
				'mapid': -1,
				'axuid': new Date().valueOf()
			};	
			var parameters = Object.extend(defaultParams, params);
			var thisPointer = this ;							
							
			this.mapElement.fire('astun:dataLoadBegin', {});
			var successFunc = function(transport) 
			{
				//check if return string contains a valid collection
				var strResponse = transport.responseText ; 
				
				if ( strResponse.search(/FeatureCollection/) > 0 )
				{
					var results = transport.responseText.evalJSON();							
					callbackFunction(results, thisPointer ) ; 
				}
				else
				{						
					callbackFunction({'unexpectedResponse': transport.responseText}, thisPointer ) ; 
				}
				
				this.mapElement.fire('astun:dataLoadComplete', {});
	
			};	 
			new Ajax.Request 
			(this.dataurl, 
				{
					method: 'get',
					parameters: parameters,
					onFailure: function(transport) 
					{
						alert ("Error: Failed to get geometry data!");
						//window.alert("FAILURE: Could not access iShareMaps search page!");	
					},
					onSuccess : successFunc.bindAsEventListener(this)			
				}
			); //Ajax request 
		},
		
		'getMapMultiInfo': function(en, pxTolerance, queryType , callbackFunction)
		{
		/**
		* Function: getMapMultiInfo 
		* Returns collection of underlying map features on mouse-click and mouse hoverPause
		*
		* Parameters:
		* en - {object) OpenLayers.LonLat object
		* pxTolerance - {number} the pixel tolerance for selection of features, ignore those beyond
		* queryType - {string) the nature of query which will determine features to be returned
		* callbackFunction - {object} Reference to the function to be called on success
		*
		* Returns:
		*/
			return; // not used so exit
			this.mapElement.fire('astun:dataLoadBegin', {});
			switch ( queryType) 
				{ // switch
					case 'info':
						{ //info
							var params = 
							{
									//'MapSource': 'Elmbridge/Faults',
									'MapSource': this.mapSource,
									'RequestType': 'GeoJSON',
									'ServiceAction': 'GetMultiInfoFromPoint',
									'ActiveTool': 'MultiInfo',
									'ActiveLayer': '', //can be left blank?
									//'Layers': 'faults',
									'Layers': this.ismDataLayer.params.layers, //always use current layers
									'Easting':	en.lon,
									'Northing':	en.lat,
									'mapid': -1,
									'axuid': new Date().valueOf(),
									//'tolerance': this.map.getResolution() * 24 
									'tolerance': this.map.getResolution() * pxTolerance 
							};
							var thisPointer = this ;
							var successFunc = function (transport){
								try 
								{
									var strResponse = transport.responseText ; 
									if ( strResponse.search(/FeatureCollection/) > 0 )
									{
										var results = transport.responseText.evalJSON();
										callbackFunction(results, thisPointer) ;
									}
									else
									{
										callbackFunction({'unexpectedResponse': transport.responseText}, thisPointer ) ; 
									}
								}
								catch (parsingError) 
								{
									if (transport.status != 0) { // i.e. if not fail because interrupted
										alert ("Error: Invalid iShareMaps search page!");
									}
								}
								finally {
									this.mapElement.fire('astun:dataLoadComplete', {});
								}
								
							}
							new Ajax.Request
							(this.ismDataLayer.url, 
								{
									method: 'get',
									parameters: params,
									onFailure: function(transport) 
									{
										alert ("AJAX Error: Failed to get iShareMaps search page!");
									},
									onSuccess : successFunc.bindAsEventListener(this)		
								}
							); //Ajax request for infoClick and showMapToolTip
							break ;
						} //info									
			
				} // switch			
		}, //getMapMultiInfo
		'findMyNearest': function (location, layername, maxResults, distance, callbackFunction) {
			/*
			 * Finds nearest items on map as GeoJSON collection and calls supplied function with the results
			 * location {<OpenLayers.LonLat>} - point around which to call query
			 * layername - name defined in map source
			 * maxResults - most results to return
			 * distance - furthest results can be returned from
			 * callbackFunction - function to call on successful query, should take two arguments (response, pointer to this map wrapper)
			 */		
			this.currentPosition = location;			
			this.getGeoJSONData( {
				'ServiceAction': 'ShowMyClosest',
				'ActiveTool': 'MultiInfo',
				'ActiveLayer': layername,
				'Distance':  distance ,
				'MaxResults': maxResults,
				'Easting':	location.lon,
				'Northing':	location.lat
			},
			callbackFunction);
		
		}, //findMyNearest
		'findMy': function (location, layername, callbackFunction) {
			/*
			 * Finds nearest items on map as GeoJSON collection and calls supplied function with the results
			 * location {<OpenLayers.LonLat>} - point around which to call query
			 * layername - name defined in map source
			 * maxResults - most results to return
			 * distance - furthest results can be returned from
			 * callbackFunction - function to call on successful query, should take two arguments (response, pointer to this map wrapper)
			 */		
			this.currentPosition = location;			
			this.getGeoJSONData( {
				'ServiceAction': 'ShowService',
				'ActiveTool': 'MultiInfo',
				'ActiveLayer': layername,
				'Easting':	location.lon,
				'Northing':	location.lat
			},
			callbackFunction);
		
		},

		'clearThemePopups': function() {
		/**
		* Function: clearThemePopups 
		* Removes removes all Theme Popups from the display and destroy them 
		*
		* Parameters:
		*
		* Returns:
		*/
			if ( this.arThemePopup.length > 0 ) {
				while ( this.arThemePopup.length)
				{
					var popup = this.arThemePopup.pop();
					this.map.removePopup(popup);
					popup.destroy();
					popup = null;
				}
			}
		},
		
		
		'showLocation': function(en)
		{
		/**
		* Function: showLocation 
		* Draws a marker to correspond to a specific position
		* Identify polygon containing point on thematic layer and change it's border to yellow  
		*
		* Parameters:
		* en - {object) OpenLayers.LonLat object
		*
		* Returns:
		*/
			
			//delete any previous marker and create new one
			this.deleteMarkers();
			var myURL = location.href.substring(0,location.href.lastIndexOf("/")+1);
			myURL = myURL + 'images/addressiconsmall.gif' ;
			var size = new OpenLayers.Size(14,22);
			var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
			var icon = new OpenLayers.Icon(myURL,size,offset);
			this.createMarker(en,icon);
			
			//check if anythematic layer present, get feature containing point and set home ward
			var useThematicLayer = !!this.thematicLayer;
			var featureName = null;
			if ( useThematicLayer && this.map.thematic.homeFeature)
			{
				featureName = this.map.thematic.homeFeature.current.attributes.name;											
			}//	if thematic layer present
			
			
		},
		

		'deleteMarkers': function()
		{
		/**
		* Function: deleteMarkers 
		* Deletes all markers currently visible and destroys them from marker layer
		*
		* Parameters:
		*
		* Returns:
		*/
		
			if (this.markerLayer){
				for (var i=0; i<this.markerLayer.markers.length; i++)
				{
					this.deleteMarker(this.markerLayer.markers[i]);
				}
			}
		},

		//sfeatureName: name of polygon feature on layer		
		'zoomToFeature': function(sFeatureName)
		{
		/**
		* Function: zoomToFeature 
		* Zoom-in on polygon with the specified name to occupy the best maximum map view region
		*
		* Parameters:
		* sFeatureName - {string} matching polygon.attributes.name
		*
		* Returns:
		*/
			if (!this.thematicLayer) {
				this.thematicLayer = $A(this.map.layers).find(function(layer){
					return layer.CLASS_NAME == 'OpenLayers.Layer.Vector';
				});
			}
			var oFeature = null;
			if ( !this.thematicLayer )
				return ;

			for (var i=0; i< this.thematicLayer.features.length; i++)
			{
				if ( this.thematicLayer.features[i].attributes.name == sFeatureName )
				{
					oFeature = this.thematicLayer.features[i];
					break ;
				}
			}
			if (!oFeature)
				return

			var boundingBox = oFeature.geometry.getBounds();
			this.map.zoomToExtent(boundingBox,false);
		},
		'searchISMLayer': function( layer, field, value, callbackFunction ) {
			this.getGeoJSONData( 
				{
					'ServiceAction': 'SearchLayer',
					'ActiveTool': 'MultiInfo',
					'ActiveLayer': layer,
					'SearchLayer': layer,
					'SearchField': field,
					'SearchValue': value,
					'MapSource': this.mapsource
				},
				function( response, mapWrapper ) {
					var view = null;
					if(response.unexpectedResponse) {
						alert('No results found for layer search.\n\nSearchLayer: '+astun.mypages.queryParameters.SearchLayer+'\nSearchField: '+astun.mypages.queryParameters.SearchField+'\nSearchValue: '+astun.mypages.queryParameters.SearchValue);
					}
					else {
						var featureCollectionArray = response;
						var featureCol = featureCollectionArray[0];	
											
						var feature = featureCol.features.shift(); 
											
						var lonLat = new OpenLayers.LonLat(feature.geometry.coordinates[0][0],feature.geometry.coordinates[0][1]);
						var zoom = mapWrapper.locationZoom;
						
						// if third coordinate different then dealing with bounding box, extend to that too
						if ((feature.geometry.coordinates[0][0] !== feature.geometry.coordinates[2][0]) || (feature.geometry.coordinates[0][1] !== feature.geometry.coordinates[2][1])) {
							var topRight = new OpenLayers.LonLat(feature.geometry.coordinates[2][0],feature.geometry.coordinates[2][1]);
							var featureBounds = new OpenLayers.Bounds();
							featureBounds.extend(lonLat);
							featureBounds.extend(topRight);
							featureBounds = featureBounds.scale(2.0); // some padding around boundary as fudge to try and make sure appropriate zoom level is used
							zoom = featureBounds.getWidth();
							lonLat = featureBounds.getCenterLonLat();
							
						}
						view = {'easting': lonLat.lon, 'northing': lonLat.lat, 'zoom': zoom};
						callbackFunction( view );
					}
				}
			);
			
		
		}
		
	});	
	
})();

