
//**************************************************
function OlkitDatabaseSearchEngine(options_) {
	var $ = window.jQuery;

	function log() {
		if (typeof(window.console) != 'undefined' && null != window.console && null != window.console.log) {
			try { window.console.log.apply(window.console,arguments); }
            	catch(e) {};
		};
	};
	
	var defaultOptions = {
		url: './search'
		,relMediaPath: 'test_media/'
	};
	
	var options = $.extend({},defaultOptions,options_);
	
	return {
		getRelMediaPath: function(mediaFile) {
			if( mediaFile ) {
				return options.relMediaPath+mediaFile;
			};
			return options.relMediaPath;
		}
		
		,getHoverSound: function(placeId, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/getHoverMedia'
				,data: {
					id:placeId
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
    				if( result && result.media && result.media.length > 0 ) {
    					var media = result.media[0];
						if( media && media.hover_audio) {
							callback(media.hover_audio);
	    				};
    				};
				}
			});
		}
	
		,searchForContributions: function(searchString, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/searchContributions'
				,data: {
					content:searchString
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
					if( result.contributions ) {
						callback(result.contributions);
					};
				}
			});
		}
		
		,searchForPlaceNames: function(searchString, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/searchFeatures'
				,data: {
					content:searchString
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
					if( result.features ) {
						callback(result.features);
					};
				}
			});
		}
		
		,findGeometryCentroidFromPlaceId: function(placeId, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/findGeometryCentroid'
				,data: {
					type:'place_id'
					,id:placeId
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
					if( result && result.features ) {
						callback(result.features);
					};
				}
			});
		}
		
		,findGeometryCentroidFromId: function(id, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/findGeometryCentroid'
				,data: {
					type:'id'
					,id:id
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
					if( result && result.features ) {
						callback(result.features);
					};
				}
			});
		}
		
		,getAudioMediaFromPlaceId: function(placeId, callback) {
			$.ajax({
				type: 'POST'
				,url: options.url + '/getAudioMedia'
				,data: {
					id:placeId
				}
				,dataType: 'json'
				,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
				,async: true
				,success: function(result) {
					if( result && result.media ) {
						callback(result.media);
					};
				}
			});
		}
	};
};

//**************************************************
function OlkitContributionDb(options_) {
	var $ = window.jQuery;

	function log() {
		if (typeof(window.console) != 'undefined' && null != window.console && null != window.console.log) {
			try { window.console.log.apply(window.console,arguments); }
            	catch(e) {};
		};
	};
	
	var defaultOptions = {
		url: './contributions'
	};
	
	var options = $.extend({},defaultOptions,options_);
	
	return {
		getContributionsFromPlaceId: function(placeId, callback) {
			$.getJSON(
				'./contributions/fromName?name=' + encodeURIComponent(placeId)
				,function(result) {
					var contributions = result.contributions;
					if( contributions ) {
						callback(contributions);
					};
				}
			);
		}
	};
};

//**************************************************
function OlkitAttributeFormManagerSidePanel(options_) {
	var $ = window.jQuery;

	function log() {
		if (typeof(window.console) != 'undefined' && null != window.console && null != window.console.log) {
			try { window.console.log.apply(window.console,arguments); }
            	catch(e) {};
		};
	};
	
	var defaultOptions = {
		tableName: 'names'
		,geomName: 'the_geom'
		,panelName: 'side'
		,selectAudioFn: function(feature_,cbFn){ alert('Feature not supported'); }
		,onFeatureInsertedFn: function(fid,feature){}
		,onFeatureUpdatedFn: function(fid,feature){}
		,onFeatureDeletedFn: function(fid,feature){}
		,onCancelFn: function(){}
		,onCloseFn: function(){}
	};
	
	var options = $.extend({},defaultOptions,options_);

	var cancelled = true;
	var editedFeature = null;
	var editedLayer = null;
	var originalGeometry = null;
	var originalStyle = null;
	var attributeDialog = null;
	var dbWebForm = null;

    function showAttributeForm(feature_) {
    	editedFeature = feature_;
		editedLayer = editedFeature.layer;
    	cancelled = false;

		originalGeometry = editedFeature.geometry.clone();
		originalStyle = editedFeature.style;

		editedLayer.events.register('featuremodified', editedFeature, featureModified);

    	var attributeForm = null;
    	
		attributeDialog = $('#'+options.panelName);
		attributeDialog.empty();

		var dialogHeader = $('<p class="olkitAttrFormFeatureInstructions">Enter or edit the place attribute values:</p>');
		attributeDialog.append( dialogHeader );

		var attributeForm = $('<div class="olkitAttrFormFeatureAttributes"></div>');
		attributeDialog.append( attributeForm );

		var formButtons = $('<div class="olkitAttrFormButtons"></div>');
		installCancelButton();
		attributeDialog.append( formButtons );
		
        var dbWebFormOptions = {
        	tableName: options.tableName
        	,installButtons: function(buttons) {
        		formButtons.empty();
        		if( buttons.Save ) { installSaveButton( buttons.Save ) };
        		if( buttons['Delete'] ) { installDeleteButton( buttons['Delete'] ) };
        		installCancelButton();
        	}
        	,fieldOpts: {
				'layer': {
					defaultValue: 1
					,choices: [
						{value:0,label:'Admin'}
						,{value:1,label:'Public'}
					]
				}
				,'hover_audio': {
					defaultValue: ''
					,select: function(cbFn){options.selectAudioFn(feature_,cbFn);}
				}
			}
        	,onError: onError
        };
        if( editedFeature.attributes.place_id ) {
        	// Editing existing point
			var ids = editedFeature.fid ? editedFeature.fid.split('.') : [];
			if( ids.length > 1 ) {
				// Use id, if possible
	        	dbWebFormOptions.whereClauses = [
	 	        		$.NUNALIIT_DBWEB.formatWhereClause('id',
	 	        											$.NUNALIIT_DBWEB.whereComparison_eq,
	 	        											ids[1])
	 	        	];
			} else {
	        	dbWebFormOptions.whereClauses = [
 	        		$.NUNALIIT_DBWEB.formatWhereClause('place_id',
 	        											$.NUNALIIT_DBWEB.whereComparison_eq,
 	        											editedFeature.attributes.place_id)
 	        	];
			};
        } else {
        	// Adding a new point, provide geometries from map
        	dbWebFormOptions.data = $.extend({},editedFeature.attributes);
        	
        	var geom = convertFeatureGeometryForDb(editedFeature);
        	dbWebFormOptions.data[options.geomName] = ''+geom;
        };
        dbWebForm = attributeForm.dbWebForm(dbWebFormOptions);
    	
    	function installSaveButton(cbFn) {
			var button = $('<input type="button" value="Save"/>');
			button.click(function(evt){
				cbFn({
					onSuccess:onSaved
					,onError: function(){alert('Error occurred while saving data');}
				});
				discardAttributeForm();
			});
			formButtons.append(button);
    	};
    	
    	function installDeleteButton(cbFn) {
			var button = $('<input type="button" value="Delete"/>');
			button.click(function(evt){
		  		if( confirm('Do you really want to delete this feature?') ) {
    				cbFn({
    					onSuccess:onDeleted
    					,onError: function(){alert('Error occurred while deleting data');}
    				});
					discardAttributeForm();
		  		};
			});
			formButtons.append(button);
    	};
    	
    	function installCancelButton() {
			var button = $('<input type="button" value="Cancel"/>');
			button.click(function(evt){
				cancelAttributeForm();
			});
			formButtons.append(button);
    	};
    	
    	function onSaved(savedData) {
			if( feature_.attributes.place_id ) {
				// This is an update
				var fid = feature_.fid;
				options.onFeatureUpdatedFn(fid,feature_);
			} else {
				// This is an insert
				var fid = options.tableName+'.'+savedData.id;
				options.onFeatureInsertedFn(fid,feature_);
			};
    	};
    	
    	function onDeleted() {
			var fid = feature_.fid;
			options.onFeatureDeletedFn(fid,feature_);
    	};
    	
    	function onError(error) {
			attributeDialog.empty();

			var errorDiv = $('<div class="olkitAttrFormError"></div>');
			attributeDialog.append( errorDiv );

			formButtons.empty();
			installCancelButton();
			attributeDialog.append( formButtons );
			
			// Print error
			var ul = $('<ul></ul>');
			errorDiv.append(ul);
			
			currentError = error;
			while(currentError) {
				var li = $('<li>'+currentError.message+'</li>');
				ul.append(li);
				
				currentError = currentError.cause;
			};
    	};
	};

	// Restores feature geometry before discarding the form
	function cancelAttributeForm() {
		if( cancelled ) return;
		cancelled = true;
	
		options.onCancelFn(editedFeature);

		// Reinstate the previous geometry		
		if( editedFeature && originalGeometry ) {
			// Erase feature before changing geometry
			if( editedFeature.layer ) {
				editedFeature.layer.eraseFeatures([editedFeature]);
			};
			editedFeature.geometry = originalGeometry;
		};
		
		discardAttributeForm();
	};

	function discardAttributeForm() {
		if( null == attributeDialog ) {
			return;
		};
		
		attributeDialog.empty();
		attributeDialog = null;
		dbWebForm = null;

		if( editedFeature ) {
			editedLayer.events.unregister('featuremodified', editedFeature, featureModified);
			
			// Reinstate the original style
			if( null !== editedFeature.layer ) { // test for deletion
				editedFeature.style = originalStyle;
				editedFeature.layer.drawFeature(editedFeature);
			};
		};

		options.onCloseFn(editedFeature);
		
		editedFeature = null;
		editedLayer = null;
		originalGeometry = null;
		originalStyle = null;
	};
	
    function convertFeatureGeometryForDb(feature) {
		var proj = feature.layer.map.projection;
		var internalSrsName = proj.getCode();
		if( internalSrsName != 'EPSG:4326' ) {
			// Need to convert
			var geom = editedFeature.geometry.clone();
			var dbProj = new OpenLayers.Projection('EPSG:4326');
			geom.transform(proj,dbProj);
			return geom;
		};
		return feature.geometry;
    };
	
    function featureModified(evt_) {
    	var feature = evt_.feature;
    	var geom = convertFeatureGeometryForDb(feature);
    	
    	if( dbWebForm ) {
    		dbWebForm.updateData(options.geomName,''+geom);
    	};
    };
	
	return {
		showAttributeForm: showAttributeForm
		,cancelAttributeForm:  cancelAttributeForm
	};
};

//**************************************************
function olkitMapAndControls(options_) {
	var $ = window.jQuery;

	function log() {
		if (typeof(window.console) != 'undefined' && null != window.console && null != window.console.log) {
			try { window.console.log.apply(window.console,arguments); }
            	catch(e) {};
		};
	};
	
	var defaultOptions = {
		mapCoordinateSpecifications: {
			/*
			 * The set of (internally) consistent defaults for describing the map display.  Note that the default
			 * map display is set up for Google backgrounds and uses Google's spherical Mercator (900913).  So this
			 * is a description of the map coordinate space for Google's projection BUT specified in WGS84 lat long
			 * coordinate space.  It is transformed as needed to the actual target coordinate space.
			 */
			srsName: 'EPSG:4326'
			,maxExtent: [-180,-85.05,180,85.05] // Max extent provided by Google imagery
			,initialBounds: [-180,-90,180,90]
			
			/*
			 * Use this coordinate space for map controls (such as the mouse position tracker)?  By default,
			 * OpenLayers will use the map projection but, if specified, this reprojects back to a different
			 * projection for user displays (this is supposed to be LESS confusing).
			 *
			 * Default is TRUE because the original atlases used this behaviour to display Google map backgrounds
			 * (using the default mapDisplay.srsName projection below which is in meter units) with the controls 
			 * configured to display NAD83 lat lon (EPSG:4326).  Perfectly reasonable if the user is used to
			 * working with their data in lat/long (as evidenced by the boundng box, etc. being specified in
			 * lat/long).
			 */ 
			,useForMapControls: true
		}
		,mapDisplay: {
			srsName: 'EPSG:900913' // the projection the map is actually displayed in.
			,backgroundMapLayersFn: null // @return array of OpenLayer layers
			,maxResolution: 156543.0339 // if not using Google proj, compute as maxExtent / expected map display size (in pixels).
			,units: 'm' // map units
		}
		,addPointsOnly: false
		,placeDisplay: {} //place info panel display options.
		,saveFeature: {} // save feature details
		,layerInfo: { // feature layer access details.
			 /*
			  * sourceSrsName: default source projection of WFS feature data - Geoserver can reproject but cannot
			  * handle a bbox param on a GetFeature in reprojected coordinates.  This is used to tell the atlas 
			  * what coordinates to use when request the vector layer features.
			  */
			sourceSrsName: 'EPSG:4326'
			,useHoverSound : true
			,featurePopupHtmlFn: null
			,styleMapFn: null
		}
		,sidePanelName: 'side'
		,toggleClick: true
		,toggleClickFn: function(){ $('#'+options.sidePanelName).empty(); } // what to do with side display when unselected
	};
	
	var options = $.extend(true, {}, defaultOptions, options_);

	var dbSearchEngine = OlkitDatabaseSearchEngine( (options_ ? options_.dbSearchEngine : null) );

	var contributionDb = OlkitContributionDb( (options_ ? options_.contributionDb : null) );
	
	var map, names;
	var html = "";

	// Figure typename and schema
	options.layerInfo.typename = options.layerInfo.featurePrefix + ':' + options.layerInfo.featureType;
	options.layerInfo.schema = options.layerInfo.wfsUrl 
		+ '?service=WFS&version=' + options.layerInfo.wfsVersion 
		+ '&request=DescribeFeatureType&typeName=' + options.layerInfo.typename;
		
	// Feature Attribute Form
	var attributeFormManagerOptions = {
		selectAudioFn: selectAudioMedia
		,onFeatureInsertedFn: onAttributeFormInserted
		,onFeatureUpdatedFn: onAttributeFormUpdated
		,onFeatureDeletedFn: onAttributeFormDeleted
		,onCancelFn: onAttributeFormCancelled
		,onCloseFn: onAttributeFormClosed
	};
	var attributeFormManagerFn = OlkitAttributeFormManagerSidePanel;
	attributeFormManagerOptions.panelName = options.sidePanelName;
	$.extend(attributeFormManagerOptions, options.saveFeature ? options.saveFeature : {} );
	var attributeFormManager = attributeFormManagerFn( attributeFormManagerOptions );
	
	/*
	 * Generate the default styles.  Standard styling supprts highlighting
	 * of added features waiting to be promoted for general visibility.
	 */
	function generateDefaultFeatureLayerStyleMap() {
		var clickedStyle = new OpenLayers.Style({
			fillColor: "#ffffff",
			strokeColor: "#ff2200",
			fillOpacity: 0.1,
			strokeOpacity: 1,
			strokeWidth: 3,
			strokeLinecap: "round", //[butt | round | square]
			strokeDashstyle: "solid", //[dot | dash | dashdot | longdash | longdashdot | solid]
			pointRadius: 8,
			pointerEvents: "visiblePainted"
		});
		var selectedStyle = new OpenLayers.Style({
			fillColor: "#0000ff",
			fillOpacity: 0.4,
			strokeColor: "#0000ff",
			strokeOpacity: 1,
			strokeWidth: 1,
			strokeLinecap: "round", //[butt | round | square]
			strokeDashstyle: "solid", //[dot | dash | dashdot | longdash | longdashdot | solid]
			pointRadius: 6,
			pointerEvents: "visiblePainted",
			cursor: "pointer"
		});
		var defaultStyle = new OpenLayers.Style({
			fillColor: "#ee9900",
			fillOpacity: 0.4,
			strokeColor: "#ee9999",
			strokeOpacity: 1,
			strokeWidth: 1,
			strokeLinecap: "round", //[butt | round | square]
			strokeDashstyle: "solid", //[dot | dash | dashdot | longdash | longdashdot | solid]
			pointRadius: 6,
			pointerEvents: "visiblePainted"
		});
		defaultStyle.addRules([
			new OpenLayers.Rule({
			    symbolizer: { fillColor: '#ee9900' }
			    ,filter: new OpenLayers.Filter.Comparison({
			        type: OpenLayers.Filter.Comparison.EQUAL_TO
			        ,property: 'layer'
			        ,value: 0
			    })
			})
			,new OpenLayers.Rule({
			    symbolizer: { fillColor: '#ffffff' }
				,elseFilter: true
			})
		]);
		return(new OpenLayers.StyleMap({ 'default': defaultStyle, 'clicked': clickedStyle, 'select': selectedStyle}));
	}
	
	/*
	 * Determine whether or not an optional set of styles (and optional
	 * rules) have been provided in the options. If so, invoke the 
	 * passed option function to construct the styleMap.  If not, use the
	 * default function to do the same.
	 * @param void
	 * @return OpenLayers.StyleMap defining styles for display states 'default',
	 *         'clicked', and 'select'.
	 */    
	function defineFeatureLayerStyleMap() {
		var styleMap = null;
		if (null != options.layerInfo.styleMapFn) {
			styleMap = options.layerInfo.styleMapFn();
		} else {
			styleMap = generateDefaultFeatureLayerStyleMap();
		}
		return(styleMap);
	}
	var featureLayerStyleMap = defineFeatureLayerStyleMap();
	
	// Install callback
	if( $.progress && $.progress.addProgressTracker) {
		function progressOnStopCb(keyInfo,options) {
			if( keyInfo && keyInfo.data && keyInfo.data.place_id ) {
				if (_olkit_clickedPlaceInfo.getPlaceId() == keyInfo.data.place_id) {
					_olkit_clickedPlaceInfo.loadAndRenderContributions();
				}
			}
		};
	
		$.progress.addProgressTracker({
				onStart: function(){} 
				,onUpdate: function(){} 
				,onComplete: progressOnStopCb
				,onRemove: function(){}
			});
	} else {
			log('Progress module not found. Activity tracking will not be available.'); 
	};
	
	function insertSound(surl) {
		if (surl) {
			$('#dhtmlsound').html('<embed src="'+dbSearchEngine.getRelMediaPath(surl)+'" hidden="true" autostart="true" loop="false"/>');
		} else {
			$('#dhtmlsound').empty();
		}
	}
	
	function initAndDisplayClickedPlaceInfo(feature) {
		if (null === _olkit_clickedPlaceInfo) {
			var placeInfoOptions = {
				displayDiv               : options.sidePanelName
				,attribTableDiv          : "side_attrib"   /* this div is created dynamically */
				,attribTableClassBase    : "attrib"        /* modified as needed to create columns - see olkit_PlaceInfo */
				,contribTableDiv         : "side_contrib"  /* this div is created dynamically */
				,contribTableClassBase   : "contrib" /* modified as needed to create columns - see olkit_PlaceInfo */
			};
			if( options.placeDisplay ) {
				$.extend(placeInfoOptions, options.placeDisplay);
			}
			_olkit_clickedPlaceInfo = olkit_PlaceInfo(
				feature
				,placeInfoOptions
				,dbSearchEngine
				,contributionDb
			);
		} else {
			_olkit_clickedPlaceInfo.setFeatureReinitDisplay(feature);
		}
		_olkit_clickedPlaceInfo.loadAndRenderContributions();
		_olkit_searchPanelHandler.deactivated();
	}
	
	
	function markFeatureAsClicked(place_id) {
		for (var i=0; i<names.features.length; i++) {
			if (place_id == names.features[i].attributes.place_id) {
				startClicked(names.features[i]);

				clickedHighlight();
				clickedPlaceInfo();
			};
		};
	};
	
	var getLayerFeatureFromFid = function(layer,fid) {
		
		if( layer && layer.features ) {
			var loop;
			var features = layer.features;
			for(loop=0;loop<features.length;++loop) {
				if( features[loop].fid && features[loop].fid == fid ) {
					return features[loop];
				}
			}
		}
		
		return null;
	}
	
	var reloadFeature = function(fid,options_) {
		var defaultOptions = {
			onReloaded: function(feature){}
		};
		var reloadOptions = $.extend({},defaultOptions,options_);
		
		var protocol = new OpenLayers.Protocol.HTTP({
			url: options.layerInfo.wfsUrl
			,params: {
				typename: options.layerInfo.typename
				,service: "WFS"
				,version: options.layerInfo.wfsVersion
				,request: "GetFeature"
				,srsName: options.mapDisplay.srsName
				,featureId: [fid]
				,outputformat: 'json'
			}
			,format: new OpenLayers.Format.GeoJSON()
			,callback: handleRead
		});
		protocol.read();

		// When receiving a response, update the real feature if
		// it already exists, or add a new one
		function handleRead(resp) {
			if( resp.code === OpenLayers.Protocol.Response.SUCCESS ) {
				var loop;
				for(loop=0; loop<resp.features.length; ++loop) {
					// Read in feature
					var loadedFeature = resp.features[loop];
					var feature = getLayerFeatureFromFid(names,loadedFeature.fid);
					if( feature ) {
						names.destroyFeatures([feature]);
					} 
					
					// Create new feature and add to layer
					// If in edit mode, first disable editAttribute widget
					if( currentMode === modes.EDIT ) {
						editModeAddFeatureEnabled = false;
						
						names.addFeatures(loadedFeature);
						
						editModeAddFeatureEnabled = true;
					} else {
						names.addFeatures(loadedFeature);
					}
					reloadOptions.onReloaded(loadedFeature);
					
					// Update side panel if required
					if( _olkit_clickedPlaceInfo && _olkit_clickedPlaceInfo.getFid() == loadedFeature.fid) {
						_olkit_clickedPlaceInfo.setFeatureReinitDisplay(loadedFeature);
					}
				}
			} else {
				alert('Error while obtaining a new feature.\n'
					 +'Map might not display up-to-date information.\n'
					 +'You might need to refresh your page.');
			}
		}
	}
	
	var removeFeature = function(fid) {

		var feature = getLayerFeatureFromFid(names,fid);
		if( feature ) {
			names.destroyFeatures(feature);
		} else {
			// Nothing to do
		}
	}
	
    // === HOVER AND CLICK START ========================================================

    var hoverInfo = {
		feature: null
		,endFn: []
	};
	var clickedInfo = {
		feature: null
		,endFn: []
	}

	function revertToDefaultStyle(feature){
		if (feature.style) {
			delete feature.style;
			feature.renderIntent = 'default';
		};
		names.redraw();
	}
	
   	function startClicked(feature) {
   		var clickedAgain = (null != clickedInfo && clickedInfo.feature == feature);
		endClicked();
		if (options.toggleClick && clickedAgain) {
			revertToDefaultStyle(feature);
			options.toggleClickFn();
		} else {
			clickedInfo.feature = feature;
		};
	};
	
	function endClicked() {
		if( clickedInfo && clickedInfo.feature ) {
			if( clickedInfo.endFn ) {
				for(var loop=0; loop<clickedInfo.endFn.length; ++loop) {
					//try{
					clickedInfo.endFn[loop](); 
					//} catch(e){};
				};
			};
			clickedInfo.endFn = [];
			clickedInfo.feature = null;
		};
	};
	
	function clickedHighlight() {
		var feature = clickedInfo.feature;
		if (null == feature) {
			return;
		}
		
		clickedInfo.feature.style = featureLayerStyleMap.styles["clicked"].defaultStyle;
		names.redraw();
		
		clickedInfo.endFn.push(function(){revertToDefaultStyle(feature);});
	};
	
	function clickedPlaceInfo() {
		if (null != clickedInfo && null != clickedInfo.feature) {
			initAndDisplayClickedPlaceInfo(clickedInfo.feature); // async
		}
	};
	
	function startHover(feature) {
		endHover();
		hoverInfo.feature = feature;
	};
	
	function endHover() {
		if( hoverInfo && hoverInfo.feature ) {
			if( hoverInfo.endFn ) {
				for(var loop=0; loop<hoverInfo.endFn.length; ++loop) {
					//try{
					hoverInfo.endFn[loop](); 
					//} catch(e){};
				};
			};
			hoverInfo.endFn = [];
			hoverInfo.feature = null;
		};
	};

	function hoverFeatureSound() {
		var loadHoverSound = 
			(null != hoverInfo.feature.attributes.place_id &&
			 true == options.layerInfo.useHoverSound);
		if (loadHoverSound) {
			dbSearchEngine.getHoverSound(hoverInfo.feature.attributes.place_id, insertSound);
			hoverInfo.endFn.push(function(){
				insertSound(null);
			});
		};
	}

	function defaultPopupHtml(attrs) {
		var resArray = [];
		resArray.push('Name: '+  (attrs.placename?attrs.placename:'') + '<br/>');
		resArray.push('Meaning: '+  (attrs.meaning?attrs.meaning:'') + '<br/>');
		resArray.push('Entity: '+  (attrs.entity?attrs.entity:'') + '<br/>');
		return(resArray);	
	}
	
	function hoverFeaturePopup() {
    	var attrs = hoverInfo.feature.attributes;
		var feature_popup_html =
			(null != options.layerInfo.featurePopupHtmlFn ?
				options.layerInfo.featurePopupHtmlFn(attrs) :
				defaultPopupHtml(attrs));
    	var popup_lonlat = hoverInfo.feature.geometry.getBounds().getCenterLonLat();
    	var popup = new OpenLayers.Popup.AnchoredBubble("chicken",
    												popup_lonlat,
    												null,
    												feature_popup_html.join(''),
    												{
    													size: new OpenLayers.Size(10,10),
    													offset: new OpenLayers.Pixel(-5,-5)
    												},
    												false,
    												onPopupClose);
    	popup.autoSize = true;
    	popup.panMapIfOutOfView = true;
		popup.setOpacity("80");
		destroyCurrentPopup();
		map._gcrcPopup = popup;
		map.addPopup(popup);
		
		hoverInfo.endFn.push(destroyCurrentPopup);
		
		function destroyCurrentPopup() {
			if( map._gcrcPopup ) {
				map.removePopup(map._gcrcPopup);
				map._gcrcPopup.destroy();
				map._gcrcPopup = null;
			};
		};
		
		function onPopupClose(evt) {
	    }
	};

	function installClickHandlerForHoverFeature(cbFn) {
		var feature = hoverInfo.feature;
		
		feature.layer.events.registerPriority('mouseup', feature, cbFn);
		hoverInfo.endFn.push(function(){
			feature.layer.events.unregister('mouseup', feature, cbFn);
		});
	}
	
    // === LOGIN STUFF START ========================================================
	
	var isLoggedIn = function() {
    	if( $.NUNALIIT_AUTH ) {
    		// The auth module is present, check if user logged in
			var authContext = $.NUNALIIT_AUTH.getAuthContext();
    		if( authContext && authContext.logged ) {
    			return true;
    		};

    		return false;
    	} else {
    		// Authentication module is not loaded
    		return false;
    	};
	};
	
	var isUser = function() {
		if( !isLoggedIn() ) return false;
		
		var user = $.NUNALIIT_AUTH.getUser();
		if( user.anonymous ) return false;
		
		return true;
	};
	
	var isAdmin = function() {
		if( !isLoggedIn() ) return false;
		
		var user = $.NUNALIIT_AUTH.getUser();
		if( user.admin ) return true;
		
		return false;
	};
	
	var isAnonymous = function() {
		if( !isLoggedIn() ) return false;
		
		var user = $.NUNALIIT_AUTH.getUser();
		if( user.anonymous ) return true;
		
		return false;
	};
    
    var loginStateChanged = function(currentUser) {
    	if( currentUser ) {
    		var aElem = $('<a class="loginLink" href="javascript:Logout">Logout</a>');
    		aElem.click(function(){
    			if( $.NUNALIIT_AUTH ) $.NUNALIIT_AUTH.logout();
    			return false;
    		});
    		$('#login').empty();
    		$('#login').append(aElem);
    		if( isUser() ) {
    			showMapInteractionSwitch();
    		}
    	} else {
    		var aElem = $('<a class="loginLink" href="javascript:Login">Admin Login</a>');
    		aElem.click(function(){
    			if( $.NUNALIIT_AUTH ) $.NUNALIIT_AUTH.login();
    			return false;
    		});
    		$('#login').empty();
    		$('#login').append(aElem);
    		hideMapInteractionSwitch();
			switchMapMode(modes.NAVIGATE);
    	};
    };

    // Install listener and update login link
    if( $.NUNALIIT_AUTH ) {
    	// Initialize
		$.NUNALIIT_AUTH.init({
			listeners: loginStateChanged
		});
    };
    	
    // === LOGIN STUFF END ========================================================
	
    // === MAP MODES START ========================================================
    
	var navigationControls
	    ,editControls
	    ,editFeatureControls
	    ;
    
	var modes = {
		NAVIGATE: {
			name        : "NAVIGATE"
			,buttonValue : "Add or Edit a Map Feature"
		}
		,EDIT: {
			name        : "EDIT"
			,buttonValue : "Cancel Feature Editing"
		}
		,EDIT_FEATURE: {
			name        : "EDIT_FEATURE"
			,buttonValue : "Cancel Feature Editing"
		}
	};
	var currentMode = modes.NAVIGATE;
	
	var mapInteractionName = "map_interaction";
	var mapInteractionDivName = mapInteractionName + "_div";
 	var mapInteractionButton = null;
 	
 	function createMapInteractionSwitch() {
 		mapInteractionButton = $('<input type="button" value="'+modes.NAVIGATE.buttonValue+'"/>'); 
 		mapInteractionButton.click( function(evt) { 
			if( currentMode === modes.NAVIGATE ) {
				switchToEditMode();
				
			} else if( currentMode === modes.EDIT ) {
				switchMapMode(modes.NAVIGATE);
				
			} else if( currentMode === modes.EDIT_FEATURE ) {
				cancelEditFeatureMode();
			}
		});
		$("#"+mapInteractionDivName)
			.empty()
			.append(mapInteractionButton);
	};
	
 	function hideMapInteractionSwitch() {
 		mapInteractionButton.hide();
	};
	
	function showMapInteractionSwitch() {
 		mapInteractionButton.show();
	};
	
	function activateControl(control) {
		if( control ) control.activate();
	};
	
	function deactivateControl(control) {
		if( control ) control.deactivate();
	};
			
    function switchMapMode(mode) {
    	if( currentMode === mode ) {
    		// nothing to do
    		return;
    	};
    	
    	// Remove current mode
    	if( currentMode === modes.EDIT ) {
    		deactivateControl( editControls.addPoints );
    		deactivateControl( editControls.toolbar );
    		deactivateControl( editControls.modifyFeature );
    		deactivateControl( editControls.highlightFeature );
            names.events.unregister('featureadded', null, editModeAddFeatureCallback);
            names.events.unregister('beforefeaturesadded', null, convertToMultiGeometry);
            
    	} else if( currentMode === modes.EDIT_FEATURE ) {
    		deactivateControl( editFeatureControls.modifyFeatureGeometry );
            
    	} else if( currentMode === modes.NAVIGATE ) {
    		if( navigationControls.selectFeature ) {
    			navigationControls.selectFeature.unselectAll();
    		};
    		endClicked();
    		deactivateControl( navigationControls.selectFeature );
    	};

    	// Apply new mode
        currentMode = mode;
        mapInteractionButton.val(currentMode.buttonValue);
    	if( currentMode === modes.EDIT ) {
            names.events.register('featureadded', null, editModeAddFeatureCallback);
            names.events.register('beforefeaturesadded', null, convertToMultiGeometry);
    		activateControl( editControls.addPoints );
    		activateControl( editControls.toolbar );
    		activateControl( editControls.modifyFeature );
    		activateControl( editControls.highlightFeature );
            
    	} else if( currentMode === modes.EDIT_FEATURE ) {
    		activateControl( editFeatureControls.modifyFeatureGeometry );
            
    	} else if( currentMode === modes.NAVIGATE ) {
    		activateControl( navigationControls.selectFeature );
    	};
    };
    
    function switchToEditMode() {
    	if( $.NUNALIIT_AUTH ) {
    		var logInRequired = true;
    		
    		// The auth module is present, check if user logged in
			var user = $.NUNALIIT_AUTH.getUser();
    		if( user ) {
    			// User is not allowed to be anonymous
    			if( !user.anonymous ) {
    				logInRequired = false;
    			};
    		};
    		
    		if( logInRequired ) {
    			// User is not logged in
    			$.NUNALIIT_AUTH.login({
    				prompt: '<p>You must log in as a registered user to add a point to the map.</p>'
    				,anonymousLoginAllowed: false
    				,onSuccess: switchToEditMode
    			});
    		} else {
    			// Already logged in, just switch
    	    	switchMapMode(modes.EDIT);
    		};
    	} else {
    		alert("Authentication module not installed.");
    	};
    };
    
    function switchToEditFeatureMode(feature) {
    	switchMapMode(modes.EDIT_FEATURE);

		attributeFormManager.showAttributeForm(feature);
    	
    	if( editFeatureControls.modifyFeatureGeometry ) {
    		// There is a bug where selecting feature right away does
    		// not draw the vertices, etc.
			setTimeout(function(){
		   		editFeatureControls.modifyFeatureGeometry.beforeSelectFeature(feature);
		   		editFeatureControls.modifyFeatureGeometry.selectFeature(feature);
			},1);
    	};
    };
    
    function cancelEditFeatureMode() {
   		attributeFormManager.cancelAttributeForm();
    };
    
    // === NAVIGATION MODE ========================================================


	function onNavigateHover(feature) {
		startHover(feature);
		
		hoverFeatureSound();
		hoverFeaturePopup();
		installClickHandlerForHoverFeature(onNavigateClick);
	};
	
	function onNavigateClick(evt) {
		var feature = hoverInfo.feature;
		
		startClicked(feature);

		clickedHighlight();
		clickedPlaceInfo();
	};
    
    // === EDIT MODE ========================================================

	var editModeAddFeatureEnabled = true;
	function editModeAddFeatureCallback(evt) {
    	if( editModeAddFeatureEnabled ) {
	    	var feature = evt.feature;
    		switchToEditFeatureMode(feature);
    	};
	};
	
    function onEditHover(feature) {
		startHover(feature);
		
		hoverFeatureSound();
		hoverFeaturePopup();
		installClickHandlerForHoverFeature(onEditClick);
    };
    
	function onEditClick(evt) {
		var feature = hoverInfo.feature;

		OpenLayers.Event.stop(evt);
		feature.layer.events.unregister('mouseup', feature, onEditClick);
		
		endHover();
		startClicked(feature);

		clickedHighlight();
		
		var editAllowed = true;
		if( typeof feature.attributes.layer != 'undefined' ) {
			if( '0' == feature.attributes.layer && !isAdmin() ) {
				editAllowed = false;
				alert('This feature can be edited only by an administrator');
			};
		};
		
		if( editAllowed ) {
			switchToEditFeatureMode(feature);
		};
	};
    
    function convertToMultiGeometry(evt) {
		for (var i=0; i<evt.features.length; ++i) {
			if( evt.features[i].geometry.CLASS_NAME === OpenLayers.Geometry.Point.prototype.CLASS_NAME ) {
				evt.features[i].geometry = new OpenLayers.Geometry.MultiPoint([evt.features[i].geometry]);
				
			} else if( evt.features[i].geometry.CLASS_NAME === OpenLayers.Geometry.LineString.prototype.CLASS_NAME ) {
				evt.features[i].geometry = new OpenLayers.Geometry.MultiLineString([evt.features[i].geometry]);
				
			} else if( evt.features[i].geometry.CLASS_NAME === OpenLayers.Geometry.Polygon.prototype.CLASS_NAME ) {
				evt.features[i].geometry = new OpenLayers.Geometry.MultiPolygon([evt.features[i].geometry]);
			};
		};
    };
    
    var createFirstContribution = function(feature) {
		if( feature.attributes && feature.attributes.place_id ) {
			if( NUNALIIT_CONTRIBUTIONS ) {
				// NUNALIIT_CONTRIBUTIONS module is needed
				NUNALIIT_CONTRIBUTIONS.addContribution({
					title: 'Added point'
					,notes: ''
					,data: {
						place_id: feature.attributes.place_id
					}
				});
			};
    	};
    };
    
    // ======= EDIT_FEATURE MODE =======================================================

    function onAttributeFormClosed(editedFeature) {
		if( editedFeature && editedFeature.state === OpenLayers.State.INSERT ) {
			names.destroyFeatures(editedFeature);
		};
		
		switchMapMode(modes.NAVIGATE);
    }

	function onAttributeFormCancelled(editedFeature) {
		switchMapMode(modes.NAVIGATE);
	};
    
	function onAttributeFormInserted(fid, feature) {
		// This is an insert
		fidAdded(fid);
		reloadFeature(fid,{
			onReloaded: function(feature) {
				createFirstContribution(feature);
			}
		});
	};
    
	function onAttributeFormUpdated(fid, feature) {
		// This is an update
		var fid = feature.fid;
		fidUpdated(fid);
		reloadFeature(fid);
	};
	
	function onAttributeFormDeleted(fid, feature) {
		fidDeleted(fid);
	};
    
	function selectAudioMedia(feature, onSelectCallback) {
		var placeId = null;
		if( feature && feature.attributes && feature.attributes.place_id ) {
			placeId = feature.attributes.place_id;
		};
		
		if( placeId ) {
			insertSound();
			
			var selectWindow = $('<div class="selectMedia jqmWindow" style="z-index:3005"></div>');
			$(document.body).append(selectWindow);
			
			var head = $('<h1>Select a hover audio file</h2>');
			selectWindow.append(head);
			
			var listElem = $('<div></div>');
			selectWindow.append(listElem);
			
			// Buttons
			var cancelButton = $('<input type="button" value="Cancel"/>');
			cancelButton.click(function(){
				selectWindow.jqmHide();
			});
			selectWindow.append(cancelButton);
			
			selectWindow.jqm({onHide:function(h){
				// Remove overlay
				if(h.o) h.o.remove();
				// Remove window
				h.w.remove();	
			}});
			selectWindow.jqmShow();
			
			dbSearchEngine.getAudioMediaFromPlaceId(placeId,function(mediaArray){
				if( 0 == mediaArray.length ) {
					listElem.html('<span>There are no audio files available</span>');
				} else {
					var tableElem = $('<table class="mediaSelection"></table>');
					listElem.append(tableElem);
					
					for(var loop=0; loop<mediaArray.length; ++loop) {
						var media = mediaArray[loop];
						addMedia(tableElem, media);
					};
				};
			});
		} else {
			alert('No media to select');
		};
		
		function addMedia(tableElem, media) {
			var trElem = $('<tr></tr>');

			var tdElem = $('<td></td>');
			
			if( media.title ) {
				var html = $('<span>'+'title: '+media.title+'</span>');
				tdElem.append(html);
				tdElem.append( $('<br/>') );
			};
			
			if( media.mimetype ) {
				var html = $('<span>'+'MIME type: '+media.mimetype+'</span>');
				tdElem.append(html);
				tdElem.append( $('<br/>') );
			};
			
			if( media.filename ) {
				var html = $('<span>'+'file: '+media.filename+'</span>');
				tdElem.append(html);
				tdElem.append( $('<br/>') );
			};
			
			trElem.hover(function(){
				var value = media.filename?media.filename:'';
				insertSound(value);
			},function(){
				insertSound();
			});
			trElem.click(function(){
				var value = media.filename?media.filename:'';
				insertSound();
				onSelectCallback(value);
				selectWindow.jqmHide();
				return false;
			});

			trElem.append(tdElem);

			tableElem.append(trElem);
		};
	};
	
    // === EDIT MODE STUFF END ======================================================================

    // === COMETD MODE STUFF START ========================================================
    
    var cometEnabled = true;
    var fidChannel = '/fid';
    var contributionChannel = '/contribution';
    
    function initCometChannels() {
    	if( cometEnabled && $.cometd ) {
			$.cometd.init('./cometd');
			$.cometd.subscribe(fidChannel,fidHandler);
			$.cometd.subscribe(contributionChannel,contributionHandler);
    	}
    }
    
	function fidHandler(msg) {
		log('fidHandler',msg);
		if( msg.data && msg.data.type && msg.data.fid ) {
			if( msg.data.type == 'added' ) {
				reloadFeature(msg.data.fid);
				
			} else if( msg.data.type == 'updated' ) {
				reloadFeature(msg.data.fid);
				
			} else if( msg.data.type == 'deleted' ) {
				removeFeature(msg.data.fid);
			}
		}
	}	
    	
	function fidAdded(fid) {
		if( cometEnabled && $.cometd ) {
			var msg = {
				fid: fid
				,type: 'added'
			};
			$.cometd.publish(fidChannel,msg);
		}
	}
	
	function fidUpdated(fid) {
		if( cometEnabled && $.cometd ) {
			var msg = {
				fid: fid
				,type: 'updated'
			};
			$.cometd.publish(fidChannel,msg);
		}
	}
	
	function fidDeleted(fid) {
		if( cometEnabled && $.cometd ) {
			var msg = {
				fid: fid
				,type: 'deleted'
			};
			$.cometd.publish(fidChannel,msg);
		}
	}

	function contributionHandler(msg) {
		log('contributionHandler',msg);
		if( msg.data ) {
			var data = msg.data;
			if( data.place_id && _olkit_clickedPlaceInfo && _olkit_clickedPlaceInfo.getPlaceId() == data.place_id ) {
				_olkit_clickedPlaceInfo.loadAndRenderContributions();
			}
		}
	}	
	
    // === COMETD MODE STUFF END ========================================================

	return {
		init: function () {
		
    		createMapInteractionSwitch();

			var mapProjection = new OpenLayers.Projection(options.mapDisplay.srsName);
			var userCoordProjection = new OpenLayers.Projection(options.mapCoordinateSpecifications.srsName);
			
			// Convert max extent from map coord specification space to map display projection
			var maxExt = new OpenLayers.Bounds(
					options.mapCoordinateSpecifications.maxExtent[0]
					,options.mapCoordinateSpecifications.maxExtent[1]
					,options.mapCoordinateSpecifications.maxExtent[2]
					,options.mapCoordinateSpecifications.maxExtent[3]
					);
			if (userCoordProjection.getCode() != mapProjection.getCode()) {
				maxExt.transform(userCoordProjection, mapProjection);
			}
//log('maxExt',maxExt,'userCoordProjection',userCoordProjection.getCode(),'mapProjection',mapProjection.getCode());

			map = new OpenLayers.Map('map', {
				projection: mapProjection,
				displayProjection: (options.mapCoordinateSpecifications.useForMapControls ? userCoordProjection : mapProjection),
				units: options.mapDisplay.units,
				maxResolution: options.mapDisplay.maxResolution,
				maxExtent: maxExt
				,theme: null // Let host page control loading of appropriate CSS style sheet
			});

			// Create names layer
			var namesSourceProjection = new OpenLayers.Projection(options.layerInfo.sourceSrsName);
			if( options.layerInfo.useFixedStrategy ) {
				// Compute bbox string in the source coordinate space of the vector layer
				var vecSourceExtent = new OpenLayers.Bounds(
					options.mapCoordinateSpecifications.maxExtent[0]
					,options.mapCoordinateSpecifications.maxExtent[1]
					,options.mapCoordinateSpecifications.maxExtent[2]
					,options.mapCoordinateSpecifications.maxExtent[3]);
//log('vecSourceExtent',vecSourceExtent,'userCoordProjection',userCoordProjection.getCode(),
//	'namesSourceProjection',namesSourceProjection.getCode());
				if (userCoordProjection.getCode() != namesSourceProjection.getCode()) {
					/*
					 * if the user coordinate space is different from the source projection of the vector layer
					 * then project the max extent bounding box back to the source projection of the vector layer.
					 * The WFS request can reproject the data but it cannot handle a bbox request in the reprojected
					 * coordinate space.
					 */
					vecSourceExtent.transform(userCoordProjection, namesSourceProjection);
				}
				var bbox = vecSourceExtent.toBBOX();
//log('bbox',bbox);

				names = new OpenLayers.Layer.Vector(options.layerInfo.name, {
					strategies: [ new OpenLayers.Strategy.Fixed() ]
					,protocol: new OpenLayers.Protocol.HTTP({
						url: options.layerInfo.wfsUrl,
						params: {
							typename: options.layerInfo.typename,
							service: "WFS",
							version: options.layerInfo.wfsVersion,
							request: "GetFeature",
							srsName: mapProjection.getCode(),
							bbox: bbox,
							outputformat: 'json'
						},
						format: new OpenLayers.Format.GeoJSON()
					})
					,styleMap: featureLayerStyleMap
				});
				
			} else {
				// BBOX Strategy
				names = new OpenLayers.Layer.Vector(options.layerInfo.name, {
					strategies: [ new OpenLayers.Strategy.BBOX() ]
					,projection: namesSourceProjection
					,protocol: new OpenLayers.Protocol.HTTP({
						url: options.layerInfo.wfsUrl
						,params: {
							typename: options.layerInfo.typename
							,service: "WFS"
							,version: options.layerInfo.wfsVersion
							,request: "GetFeature"
							,outputformat: 'json'
						}
						,format: new OpenLayers.Format.GeoJSON()
					})
					,styleMap: featureLayerStyleMap
				});
			};

			function genBackgroundMapLayers() {
				var bg = null;
				if (null != options.mapDisplay.backgroundMapLayersFn) {
					bg = options.mapDisplay.backgroundMapLayersFn();
				} else {
					var bg = [
						new OpenLayers.Layer.Google(
							"Google Satellite",
							{
								type: G_SATELLITE_MAP
								,'sphericalMercator': true
							})
						,new OpenLayers.Layer.Google(
							"Google Physical",
							{
								type: G_PHYSICAL_MAP
								,'sphericalMercator': true
							})
						,new OpenLayers.Layer.Google(
							"Google Hybrid",
							{
								type: G_HYBRID_MAP
								,'sphericalMercator': true
							})
					]
				}
				return(bg);
			}
			map.addLayers(genBackgroundMapLayers().concat([names]));
	
			map.addControl(new OpenLayers.Control.MousePosition({
				displayProjection: (options.mapCoordinateSpecifications.useForMapControls ? userCoordProjection : mapProjection),
			}));
			map.addControl(new OpenLayers.Control.LayerSwitcher());
			//map.addControl(new OpenLayers.Control.DragPan());
	
			// Create initial bounds in the map's projection
			var initialZoomBounds = new OpenLayers.Bounds(
				options.mapCoordinateSpecifications.initialBounds[0]
				,options.mapCoordinateSpecifications.initialBounds[1]
				,options.mapCoordinateSpecifications.initialBounds[2]
				,options.mapCoordinateSpecifications.initialBounds[3]
			);
			if (map.displayProjection.getCode() != map.getProjectionObject().getCode()) {
				initialZoomBounds.transform(map.displayProjection, map.getProjectionObject());
			}
			map.zoomToExtent(initialZoomBounds);
log('map',map);

			// Draw controls
			navigationControls = {
			};
			editControls = {
			};
			editFeatureControls = {
			};
			
			// Select names
			navigationControls.selectFeature = new OpenLayers.Control.SelectFeature(
				names
				,{
					hover: true
					,onSelect: onNavigateHover
					,onUnselect: endHover
				}
			);
			map.addControl(navigationControls.selectFeature);
			navigationControls.selectFeature.activate();
			
        	// Select adding of new features
        	if( options.addPointsOnly ) {
        		// Maps with points only
        		editControls.addPoints = new OpenLayers.Control.DrawFeature(names,OpenLayers.Handler.Point);
        		map.addControl(editControls.addPoints);
        	} else {
        		// Maps with all geometry types
        		editControls.toolbar = new OpenLayers.Control.EditingToolbar(names);
        		map.addControl(editControls.toolbar);
        		editControls.toolbar.deactivate();
            	
            	// Work around for bug in EditingToolbar (http://trac.openlayers.org/ticket/2182)
        		editControls.toolbar.defaultControl = editControls.toolbar.controls[0];
        	};

    		// During editing, have a tool to modify a geometry
    		editFeatureControls.modifyFeatureGeometry = new OpenLayers.Control.ModifyFeature(names, {'displayClass': 'olControlMoveFeature'});
    		editFeatureControls.modifyFeatureGeometry.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
			map.addControl(editFeatureControls.modifyFeatureGeometry);
			editFeatureControls.modifyFeatureGeometry.deactivate();

    		// During editing, highlight moused over feature
    		editControls.highlightFeature = new OpenLayers.Control.SelectFeature(
				names
				,{
					hover: true
					,onSelect: onEditHover
					,onUnselect: endHover
				}
			);
			map.addControl(editControls.highlightFeature);
			editControls.highlightFeature.deactivate();

			// install searchPanel Handler
			_olkit_searchPanelHandler = olkit_SearchPanel(
				{
					panelDivName          : options.sidePanelName
					,textInputDivName      : "searchInput"
					,initialSearchText     : "search the atlas"
					,contribTableClassBase : "contrib"
					,contribTableDiv       : "side_contrib"
					,contribMediaClick     : _olkit_sp_mediaClick
					,mapReference          : map
					,markFeatureAsClicked  : markFeatureAsClicked
					,sourceSrsName         : options.layerInfo.sourceSrsName
					,mapDisplaySrsName     : options.mapDisplay.srsName
				}
				,dbSearchEngine
			);
			
			initCometChannels();
		}

    	// Returns true if a user is logged in
    	,isLoggedIn: isLoggedIn
    	
    	// Returns true if currently logged in as a regular user
    	,isUser: isUser
    	
    	// Returns true if currently logged in as an administrative user
    	,isAdminUser: isAdmin
    	
    	// Returns true if currently logged in as an anonymous user
    	,isAnonymousUser: isAnonymous
    	
    	,redefineFeatureLayerStylesAndRules : function() {
    		featureLayerStyleMap = defineFeatureLayerStyleMap();
    		endClicked();
    		names.styleMap = featureLayerStyleMap;
    		names.redraw();
    	}
	}
};

