/*
 * Javascript component for consuming MGB map related functionalities
 * http://www.myGeoBook.com
 */
(function() {

	// declared for speed and name replacing outside the script
	var window = this,
	// declared for speed and name replacing outside the script
	undefined,
	mapServiceBaseUrl = 'http://mgbmap.mygeobook.com/MgbMap/mapService.svc/';

	// private helper functions start
	var MAP_PROFILE_DISPLAY_MODE = 1;
	var MAP_SEARCH_DISPLAY_MODE = 2;
	var MAP_POI_BOOKMARKS_DISPLAY_MODE = 3;

	/*
 	// Creates a public class or namespece, by iteratively creating all its elements.
 	// No part of the namespace is overriden if it already exists
 	// Parameters:
 	// name - the full name of the namespace, class or function to register
 	// localObject (optional) - the object to register under the supplied name.
 	// 	If you just want to register a namespace, the localObject parameter must not be defined.
 	// 	If you want to register a class or function, you must specify its full name (including namespaces) in the name parameter, and
 	// 	its value in the localObject parameter
 	*/
	function registerNamespaceOrClass(name, localObject) {
		var elements = name.split('.'), crtObject = window;
		for(var i=0; i<elements.length; i++) {
			if((i == elements.length - 1) && localObject) {
				crtObject[elements[i]] = localObject;
				break;
			}
			if(!crtObject[elements[i]]) {
				crtObject[elements[i]] = {};
			}

			crtObject = crtObject[elements[i]];
		}
	}

	var cbSeed = (new Date()).getTime();
	/*
 	// Makes a remote call using the script tag method (used in order to enable cross domain communication).
 	// Parameters:
 	// url - the url for the script to load. The caller must not explicitly specify the name of the javascript callback method, since the name for
 	// 	it will be automatically supplied. Instead, the caller must use the '?' charcter for the callback name. For example:
 	// 		http://myDomain.com/myJsEnabledApi/myMethod.svc?myParam1=hello&myPram2=world&callback=?
 	// callback (optional) - function that can be specified by the caller and that will be called after the script call is
 	// 	finished. The function will be called with two parameters: first parameter will be the result from remote call and the second
 	// 	the callbackParams object that was passed by the caller when he made the call to makeScriptCall.
 	// callbackParams (optional) - check the description for the callback parameter
 	*/
	function makeScriptCall(url, callback, callbackParams) {
		var cbFunc = 'MgbFunc' + cbSeed++;
		url = url.replace('=?', '=' + cbFunc);

		var head = document.getElementsByTagName("head")[0];
		var script = document.createElement("script");
		script.src = url;

		if(callback) {
			window[cbFunc] = function(callResult) {
				callback(callResult, callbackParams);
				// perform clean up
				window[cbFunc] = undefined;
				try {
					delete window[cbFunc];
				}
				catch(e) {
				} // removes the cbFunc index from window. The item itself was made available for garbage collection with the window[cbFunc] = undefined statement
				if ( head )
					head.removeChild( script );
			}
		}

		head.appendChild(script);
	}

	/*
 	// Counts the properties on an object. Use it to find the actual length on a sparse array.
 	// Not the most performant or elegant method, but this is what Javascript has to offer.
 	*/
	function countObject(obj) {
		var cnt = 0;
		for(var prop in obj) {
			if(obj.hasOwnProperty(prop)) {
				cnt++;
			}
		}

		return cnt;
	}

	// private helper functions end

	// MgbPopupManager class start
	function MgbPopupManager() {
		// nameSeed to be used with ids in order to ensure their unicity
		var nameSeed = (new Date()).getTime().toString();

		// create dom elements
		var mgbPopupContainer = document.createElement('div');
		mgbPopupContainer.className = 'mgb-popup-container mgb-left-beak-popup';
		var mgbPopupShadow = document.createElement('div');
		mgbPopupShadow.className = 'mgb-popup-shadow';
		var mgbPopupBodyTop = document.createElement('div');
		mgbPopupBodyTop.className = 'mgb-popup-body-top';
		var mgbPopupBodyContent = document.createElement('div');
		mgbPopupBodyContent.className = 'mgb-popup-body-content';
		mgbPopupBodyContent.id = 'mgbPopupBodyContent_' + nameSeed;
		var mgbPopupBodyBottom = document.createElement('div');
		mgbPopupBodyBottom.className = 'mgb-popup-body-bottom';
		var mgbPopupBeak = document.createElement('div');
		mgbPopupBeak.className = 'mgb-popup-beak';

		// add dom elements to the document
		document.body.appendChild(mgbPopupContainer);
		mgbPopupContainer.appendChild(mgbPopupShadow);
		mgbPopupShadow.appendChild(mgbPopupBodyTop);
		mgbPopupShadow.appendChild(mgbPopupBodyContent);
		mgbPopupShadow.appendChild(mgbPopupBodyBottom);
		mgbPopupContainer.appendChild(mgbPopupBeak);

		// create jQuery objects and set ui properties
		var jqPopupContainer = jQuery(mgbPopupContainer);
		var jqBeak = jQuery(mgbPopupBeak);
		jqPopupContainer.mouseover(popupMouseOver);
		jqPopupContainer.mouseout(popupMouseOut);

		// private fields
		var self = this;
		var _hide = false;
		var _hideDelay = 250;
		var _containerCssClasses = {
			beakLeft: 'mgb-popup-container mgb-left-beak-popup',
			beakRight: 'mgb-popup-container mgb-right-beak-popup'
		};
		var _onDisplayPopup;

		function popupMouseOver() {
			_hide = false;
		}

		function popupMouseOut() {
			self.hidePopup();
		}

		this.displayPopup = function(mapObject, pushpinElementId, htmlContent, poiId, pushpinClassName) {
			var pushPinSelector = pushpinElementId ? '#' + pushpinElementId : '.' + pushpinClassName;  
			/*if(pushpinElementId)
				pushPinSelector = '#' + pushpinElementId;
			if(pushpinClassName)
				pushPinSelector = '.' + pushpinClassName;*/
				
			var jqPushpin = jQuery(pushPinSelector);
			jqPopupContainer.css('visibility', 'hidden');
			mgbPopupBodyContent.innerHTML = htmlContent;
			if(poiId && _onDisplayPopup) {
				var jqSelector = '#' + mgbPopupBodyContent.id.toString() + ' a';
				jQuery(jqSelector).click( function() {
					_onDisplayPopup(poiId, this.href);
				});
			}

			var px = 'px';
			var pushpinOffset = jqPushpin.offset(), pushpinHeight = jqPushpin.outerHeight(), pushpinWidth = jqPushpin.outerWidth();
			var popupHeight = jqPopupContainer.outerHeight(), popupWidth = jqPopupContainer.outerWidth();
			var beakHeight = jqBeak.height();
			var jqWindow = jQuery(window), windowTop = jqWindow.scrollTop(), windowLeft = jqWindow.scrollLeft(),
			windowHeight = jqWindow.height(), windowWidth = jqWindow.width();

			if(!pushpinOffset)
				return;

			mgbPopupContainer.className = _containerCssClasses.beakLeft;
			var popupLeft = pushpinOffset.left + pushpinWidth;
			var popupTop = pushpinOffset.top + pushpinHeight / 2 - popupHeight / 2;
			var beakTop = popupHeight / 2 -  beakHeight / 2;
			if(popupLeft + popupWidth > windowLeft + windowWidth) {
				mgbPopupContainer.className = _containerCssClasses.beakRight;
				popupLeft = pushpinOffset.left - popupWidth;
			}
			if(popupTop + popupHeight > windowTop + windowHeight) {
				popupTop = windowTop + windowHeight - popupHeight;
				beakTop = pushpinOffset.top + pushpinHeight / 2 - popupTop - beakHeight / 2;
			}
			if(popupTop < windowTop) {
				popupTop = windowTop;
				beakTop = pushpinOffset.top + pushpinHeight / 2 - popupTop - beakHeight / 2;
			}

			mgbPopupContainer.style.left = popupLeft + px;
			mgbPopupContainer.style.top = popupTop + px;
			mgbPopupBeak.style.top = beakTop + px;
			jqPopupContainer.css('visibility', 'visible');
			_hide = false;
		}
		this.hidePopup = function() {
			_hide = true;
			setTimeout( function() {
				if(!_hide)
					return;
				jqPopupContainer.css('visibility', 'hidden');
			}, _hideDelay);
		}
		this.getOnDisplayPopup = function() {
			return _onDisplayPopup;
		}
		this.setOnDisplayPopup = function(value) {
			_onDisplayPopup = value;
		}
	}

	_popupManagerInstance = null;
	function getPopupManagerInstace() {
		if (_popupManagerInstance === null) {
			_popupManagerInstance = new MgbPopupManager();
		}

		return _popupManagerInstance;
	}
	// MgbPopupManager class end

	// MgbService class start
	registerNamespaceOrClass('MgbService', MgbService);
	/*
 	// Creates a Mgb Map Service JS client.
 	// Parameters:
 	// mapObject - the map object on which the MGB data will be rendered
 	// mapSupplier - a string with the name of the map supplier. Currently accepted values: 'Microsoft'.
 	*/
	function MgbService (mapObject, mapSupplier) {
		var self = this,
		_mapSessionKey,
		_poiEditFunc,
		_poiDeleteFunc,
		_poiDetailsFunc,
		_poiRatingsFunc,
		_eventsTable = {},
		_profiles = {},
		_mapWrapper,
		_autoZoomAndCenter = true,
		_searchEntireMap = true,
		_poiDescriptionXsltUrl = null,
		_poiDescriptionXslt = null,
		_instanceGlobalName = '__mgbServiceInstance' + cbSeed++;

		// instance global name registered so that global events can be registered to it.
		// use the dispose method when you no longer need this class and you want to unregister the global instance
		registerNamespaceOrClass(_instanceGlobalName, this);

		function profileIdsToJson(profileIds) {
			if (!profileIds) {
				return '[]';
			}

			var result = '[';
			for (var crtId in profileIds) {
				if(profileIds.hasOwnProperty(crtId)) {
					result += crtId + ',';
				}
			}
			result = result.substr(0, result.length);
			result += ']';

			return result;
		}

		this.dispose = function() {
			window[_instanceGlobalName] = undefined;
		}
		// internal methods start
		this.getProfileUrl = function(id, north, west, south, east, includeTitle) {
			var result = mapServiceBaseUrl + 'jsclient/profile?profileId=' + id;
			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			if (north !== undefined && north !== null) {
				result += '&north=' + north;
				result += '&west=' + west;
				result += '&south=' + south;
				result += '&east=' + east;
			}
			result += '&callback=?';

			return result;
		}
		this.getSearchUrl = function(searchString, profileIds, north, west, south, east) {
			var result = mapServiceBaseUrl + 'jsclient/search?searchString=' + encodeURIComponent(searchString) +
			'&profileIds=' + profileIdsToJson(profileIds);

			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			if (north !== undefined && north !== null) {
				result += '&north=' + north;
				result += '&west=' + west;
				result += '&south=' + south;
				result += '&east=' + east;
			}
			result += '&callback=?';

			return result;
		}
		this.getPoiBookmarksUrl = function(north, west, south, east) {
			var result = mapServiceBaseUrl + 'jsclient/poiBookmarks?';

			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			if (north !== undefined && north !== null) {
				result += '&north=' + north;
				result += '&west=' + west;
				result += '&south=' + south;
				result += '&east=' + east;
			}
			result += '&callback=?';

			return result;
		}
		this.getPoiDetailsUrl = function(id) {
			var result = mapServiceBaseUrl + 'jsclient/poiDetails?poiId=' + id;
			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			if(_poiEditFunc)
				result += '&editFunc=' + _poiEditFunc;
			if(_poiDeleteFunc)
				result += '&deleteFunc=' + _poiDeleteFunc;
			if(_poiDetailsFunc)
				result += '&detailsFunc=' + _instanceGlobalName + '.onPoiDetailsClick';
			if(_poiRatingsFunc)
				result += '&ratingsFunc=' + _poiRatingsFunc;
			result += '&crtUrl=' + encodeURIComponent(window.location.href);
			result += '&crtPageReferrer=' + document.referrer;
			result += '&zoomFunc=' + _instanceGlobalName + '.zoomToLocation';
			result += '&callback=?';

			return result;
		}
		this.getPoiXmlDetailsUrl = function(id) {
			var result = mapServiceBaseUrl + 'jsclient/xmlPoiDetails?poiId=' + id;
			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			result += '&crtUrl=' + encodeURIComponent(window.location.href);
			result += '&crtPageReferrer=' + document.referrer;
			result += '&callback=?';

			return result;
		}
		this.addPoiTitlesRequestToUrl = function(url) {
			var result = url + '&extPoiDetails=true';

			return result;
		}
		this.getStatisticsDetailsClickedUrl = function(poiId) {
			var result = mapServiceBaseUrl + 'jsClient/statisticsDetailsClicked?poiId=' + poiId;
			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			result += '&crtUrl=' + encodeURIComponent(window.location.href);
			result += '&crtPageReferrer=' + document.referrer;

			return result;
		}
		this.getStatisticsPopupLinkClickedUrl = function(poiId, clickedUrl) {
			var result = mapServiceBaseUrl + 'jsClient/statisticsPopupLinkCliked?poiId=' + poiId;
			if(_mapSessionKey)
				result += '&sessionKey=' + _mapSessionKey;
			result += '&crtUrl=' + encodeURIComponent(window.location.href);
			result += '&crtPageReferrer=' + document.referrer;
			result += '&clickedUrl=' + encodeURIComponent(clickedUrl);

			return result;
		}
		this.loadPoiDescription = function(poiId, poiLat, poiLon, callback) {
			if(_poiDescriptionXslt === null) {
				var url = this.getPoiDetailsUrl(poiId.toString());
				makeScriptCall(url, function(responseData) {
					if(callback) {
						callback(responseData);
					}
				});
			}
			else {
				var url = this.getPoiXmlDetailsUrl(poiId.toString());
				makeScriptCall(url, function(xmlString) {
					var transformationParams = {
						zoomFunc: _instanceGlobalName + '.zoomToLocation(' + poiLat.toString() + ', ' + poiLon.toString() + ')',
						detailsFunc: _poiDetailsFunc ? _instanceGlobalName + '.onPoiDetailsClick' + '(' + poiId.toString() + ')' : '',
						deleteFunc: _poiDeleteFunc ? _poiDeleteFunc + '(' + poiId.toString() + ')' : '',
						editFunc: _poiEditFunc ? _poiEditFunc + '(' + poiId.toString() + ')' : ''
					}

					jQuery.transform({xmlstr: xmlString, xslstr: _poiDescriptionXslt, xslParams: transformationParams,
						success: function(transformResult) {
							if(callback) {
								callback(transformResult);
							}
						}
					});
				});
			}
		}
		// internal methods end

		this.getMapSessionKey = function() {
			return _mapSessionKey;
		}
		this.setMapSessionKey = function(val) {
			_mapSessionKey = val;
		}
		this.getPoiEditFunc = function() {
			return _poiEditFunc;
		}
		this.setPoiEditFunc = function(val) {
			_poiEditFunc = val;
		}
		this.getPoiDeleteFunc = function() {
			return _poiDeleteFunc;
		}
		this.setPoiDeleteFunc = function(val) {
			_poiDeleteFunc = val;
		}
		this.getPoiDetailsFunc = function() {
			return _poiDetailsFunc;
		}
		this.setPoiDetailsFunc = function(val) {
			_poiDetailsFunc = val;
		}
		this.getPoiRatingsFunc = function() {
			return _poiRatingsFunc;
		}
		this.setPoiRatingsFunc = function(val) {
			_poiRatingsFunc = val;
		}
		this.getPoiDescriptionXsltUrl = function() {
			return _poiDescriptionXsltUrl;
		}
		this.setPoiDescriptionXsltUrl = function(val) {
			_poiDescriptionXsltUrl = val;
			jQuery.ajax({type: "GET", dataType: "html", async: false, url: _poiDescriptionXsltUrl, success: function(returnData) {
					_poiDescriptionXslt = returnData;
				}});
		}
		this.getPoiDescriptionXslt = function() {
			return _poiDescriptionXslt;
		}
		this.setPoiDescriptionXslt = function(val) {
			_poiDescriptionXslt = val;
		}
		this.attachEvent = function(eventName, handler) {
			var ev = _eventsTable[eventName];
			if(!ev) {
				ev = [];
				_eventsTable[eventName] = ev;
			}

			// check if the event already exists
			for(var i=0; i<ev.length; i++)
				if(ev[i] == handler)
					return true;

			ev.push(handler);
		}
		this.detachEvent = function(eventName, handler) {
			var ev = _eventsTable[eventName];
			if(!ev)
				return true;

			for(var i=0; i<ev.length; i++)
				if(ev[i] == handler)
					ev.splice(i, 1);
		}
		this.getProfiles = function() {
			return _profiles;
		}
		this.getDisplayedProfilesCount = function() {
			return countObject(_profiles);
		}
		this.showProfile = function (id) {
			_profiles[id] = id;
			_mapWrapper.showProfile(id);
		}
		this.hideProfile = function(id) {
			_mapWrapper.hideProfile(id);
			try {
				delete _profiles[id];
			}
			catch(e) {
			}
		}
		// used to zoom and center on a POI. the default zoom is being used
		this.zoomToLocation = function(lat, lon) {
			var defaultZoom = _mapWrapper.getDefaultZoom();
			var crtZoom = _mapWrapper.getCrtZoomLevel();
			var newZoom = defaultZoom > crtZoom ? defaultZoom : crtZoom;
			_mapWrapper.setCrtCenterAndZoom(lat, lon, newZoom);
		}
		this.zoomAndCenterToDefault = function() {
			var defaultCenter = _mapWrapper.getDefaultCenter();
			var defaultZoom = _mapWrapper.getDefaultZoom();
			_mapWrapper.setCrtCenterAndZoom(defaultCenter.lat, defaultCenter.lon, defaultZoom);
		}
		this.refreshCrtArea = function() {
			_mapWrapper.refreshCrtArea();
		}
		this.getAutoZoomAndCenter = function() {
			return _autoZoomAndCenter;
		}
		this.setAutoZoomAndCenter = function(val) {
			_autoZoomAndCenter = val;
		}
		this.getSearchEntireMap = function() {
			return _searchEntireMap;
		}
		this.setSearchEntireMap = function(val) {
			_searchEntireMap = val;
		}
		this.getCrtZoomLevel = function() {
			return _mapWrapper.getCrtZoomLevel();
		}
		this.setCrtZoomLevel = function(val) {
			_mapWrapper.setCrtZoomLevel(val);
		}
		this.getCrtMapCenter = function() {
			return _mapWrapper.getCrtMapCenter();
		}
		this.setCrtMapCenter = function(lat, lon) {
			_mapWrapper.setCrtMapCenter(lat, lon);
		}
		this.setCrtCenterAndZoom = function(lat, lon, zoom) {
			_mapWrapper.setCrtCenterAndZoom(lat, lon, zoom);
		}
		this.getPoiTitlesZoomLevel = function() {
			return _mapWrapper.getPoiTitlesZoomLevel();
		}
		this.setPoiTitlesZoomLevel = function(zoomLevel) {
			_mapWrapper.setPoiTitlesZoomLevel(zoomLevel);
		}
		this.centerOnPoi = function(poiId) {
			var url = this.getPoiDetailsUrl(poiId);
			url += "&format=json";

			makeScriptCall(url, function(result) {
				if (result) {
					if (_autoZoomAndCenter) {
						this.zoomToLocation(result.Latitude, result.Longitude);
					}
					else {
						_mapWrapper.setCrtMapCenter(result.Latitude, result.Longitude);
					}
				}
			});
		}
		this.invalidatePoiDescription = function(poiId) {
			_mapWrapper.invalidatePoiDescription(poiId);
		}
		this.removePoiFromMap = function(poiId) {
			_mapWrapper.removePoiFromMap(poiId);
		}
		this.addPoiToMap = function(profileId, poiId, lat, lon) {
			_mapWrapper.addPoiToMap(profileId, poiId, lat, lon);
		}
		this.searchInCurrentProfiles = function(searchString) {
			_mapWrapper.searchInCurrentProfiles(searchString);
		}
		this.clearSearch = function() {
			_mapWrapper.clearSearch();
		}
		this.showPoiBookmarks = function() {
			_mapWrapper.showPoiBookmarks();
		}
		this.hidePoiBookmarks = function() {
			_mapWrapper.hidePoiBookmarks();
		}
		this.showProfilesLayers = function() {
			_mapWrapper.showProfilesLayers();
		}
		this.onDisplayPopupHandler = function(poiId, clickedUrl) {
			if(clickedUrl !== '#' && clickedUrl !== window.location.href && clickedUrl !== (window.location.href + '#')) {
				var callUrl = self.getStatisticsPopupLinkClickedUrl(poiId, clickedUrl);
				makeScriptCall(callUrl);
			}
		}
		this.onPoiDetailsClick = function(poiId) {
			if(_poiDetailsFunc) {
				var callUrl = self.getStatisticsDetailsClickedUrl(poiId);
				makeScriptCall(callUrl);

				var consumerFunc = _poiDetailsFunc + '(' + poiId.toString() + ');'
				eval(consumerFunc);
			}
		}
		
		// load the map
		if(mapSupplier == 'Microsoft')
			_mapWrapper = new MsMapWrapper(mapObject, this);
		else if(mapSupplier == 'BingV7')
			_mapWrapper = new BingV7MapWrapper(mapObject, this);
		else
			throw 'Unsupported mapSupplier';
			
		_mapWrapper.onProfileLoaded = function(args) {
			ev = _eventsTable['onprofileload'];
			return fireEvent(ev, args);
		}
		_mapWrapper.onProfileHidden = function(args) {
			ev = _eventsTable['onprofilehide'];
			return fireEvent(ev, args);
		}
		_mapWrapper.onSearchLoaded = function(args) {
			ev = _eventsTable['onsearchloaded'];
			return fireEvent(ev, args);
		}
		_mapWrapper.onBookmarksLoaded = function(args) {
			ev = _eventsTable['onbookmarksloaded'];
			return fireEvent(ev, args);
		}
		_mapWrapper.onBeforeAddPoi = function(args) {
			ev = _eventsTable['onbeforeaddpoi'];
			return fireEvent(ev, args);
		}
		// ev - list of handlers for the event
		function fireEvent(ev, args) {
			if(!ev)
				return true;
			for(var i=0; i<ev.length; i++)
				ev[i](args);
		}

		getPopupManagerInstace().setOnDisplayPopup(this.onDisplayPopupHandler);

		// set default map state (has to be at the end so that all the methods and variables from this class
		// have been loaded)
		if (_autoZoomAndCenter) {
			this.zoomAndCenterToDefault();
		}
	}
	// MgbService class end


	// MsMapWrapper class start
	function MsMapWrapper(mapObject, mgbServiceObj) {
		var self = this;
		var _profiles = [];
		var _searchString = null;
		var _searchLayer = new VEShapeLayer();
		var _poiBookmarksLayer = new VEShapeLayer();
		var _defaultZoom = 11;
		var _defaultLatLong = new VELatLong(45, -35);
		var _displayMode = MAP_PROFILE_DISPLAY_MODE;
		var _poiTitlesZoomLevel = -1;

		mapObject.AddShapeLayer(_searchLayer);
		mapObject.AddShapeLayer(_poiBookmarksLayer);
		mapObject.AttachEvent("onmouseover", onMouseOver);
		mapObject.AttachEvent("onmouseout", onMouseOut);
		mapObject.AttachEvent("onmousedown", onMouseDown);
		/* let the map first load and then attach the view change event
 		// otherwise, sometimes the event is fired for the initial map position instead for the map position
 		// after profiles have been loaded. I suspect this is due to a race condition in asynchronous
 		// operations */
		setTimeout( function() {
			mapObject.AttachEvent("onchangeview", onChangeView);
		}, 250);
		// mapObject event handlers
		function onMouseOver(e) {
			var shape = mapObject.GetShapeByID(e.elementID);
			if(shape === null || shape.GetType() !== VEShapeType.Pushpin)
				return true;
			if (shape.poiId !== undefined && shape.poiId !== null && shape.descriptionLoaded !== undefined && shape.descriptionLoaded === false) {
				mgbServiceObj.loadPoiDescription(shape.poiId.toString(), shape.GetPoints()[0].Latitude, shape.GetPoints()[0].Longitude, function(descriptionHtml) {
					shape.SetDescription(descriptionHtml);
					shape.descriptionLoaded = true;
					getPopupManagerInstace().displayPopup(mapObject, e.elementID, shape.GetDescription(), shape.poiId);
				}
				);
			}
			if(shape.GetDescription())
				getPopupManagerInstace().displayPopup(mapObject, e.elementID, shape.GetDescription(), shape.poiId);

			return true;
		}

		function onMouseOut(e) {
			var shape = mapObject.GetShapeByID(e.elementID);
			if(shape === null)
				return false;

			getPopupManagerInstace().hidePopup();
			// let also the default handler to do its job
			return false;
		}

		function onMouseDown(e) {
			getPopupManagerInstace().hidePopup();

			// let also the default handler to do its job
			return false;
		}

		function onChangeView(e) {
			internalRefreshCrtArea(false);
		}

		function internalRefreshCrtArea(forceRefresh) {
			if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {
				refreshProfiles(forceRefresh, true, false);
			}
			else if (_displayMode === MAP_SEARCH_DISPLAY_MODE) {
				refreshSearchInCrtArea(forceRefresh);
			}
			else if (_displayMode === MAP_POI_BOOKMARKS_DISPLAY_MODE) {
				refreshPoiBookmarksInCrtArea(forceRefresh);
			}
		}

		function refreshProfiles(forceRefresh, currentArea, autoZoomAndCenter) {
			var mapRect = mapObject.GetMapView();
			var profilesToUpdate = [];

			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					var shapeLayer = _profiles[profileId];
					var notAllPoisDisplayed = shapeLayer !== undefined && shapeLayer.totalPoisInProfileCount !== undefined && shapeLayer.totalPoisInProfileCount > shapeLayer.GetShapeCount();
					var titlesNotLoaded =  shouldUsePoiTitles() && typeof(shapeLayer.arePoiTitlesLoaded) !== undefined && shapeLayer.arePoiTitlesLoaded === false;
					var titlesLoaded =  !shouldUsePoiTitles() && typeof(shapeLayer.arePoiTitlesLoaded) !== undefined && shapeLayer.arePoiTitlesLoaded === true;
					if (forceRefresh || notAllPoisDisplayed  || titlesNotLoaded || titlesLoaded) {
						profilesToUpdate.push(profileId);
					}
				}
			}

			for (var i = 0; i < profilesToUpdate.length; i++) {
				var ajaxUrl;
				if(currentArea) {
				    ajaxUrl = mgbServiceObj.getProfileUrl(profilesToUpdate[i],
                        mapRect.TopLeftLatLong.Latitude, mapRect.TopLeftLatLong.Longitude,
                        mapRect.BottomRightLatLong.Latitude, mapRect.BottomRightLatLong.Longitude);
				}
				else {
				    ajaxUrl = mgbServiceObj.getProfileUrl(profilesToUpdate[i], null, null, null, null);
				}
				    
				internalShowProfile(profilesToUpdate[i], ajaxUrl, autoZoomAndCenter, false);
			}
		}

		function refreshSearchInCrtArea(forceRefresh, autoZoomAndCenter) {
			if (forceRefresh || (_searchLayer.totalSearchMatchesCount !== undefined && _searchLayer.totalSearchMatchesCount > _searchLayer.GetShapeCount())) {
				internalShowSearch(_searchString, autoZoomAndCenter, false);
			}
		}

		function refreshPoiBookmarksInCrtArea(forceRefresh) {
			if (forceRefresh || (_poiBookmarksLayer.totalSearchMatchesCount !== undefined && _poiBookmarksLayer.totalSearchMatchesCount > _poiBookmarksLayer.GetShapeCount())) {
				var mapRect = mapObject.GetMapView();
				var ajaxUrl = mgbServiceObj.getPoiBookmarksUrl(
					mapRect.TopLeftLatLong.Latitude, mapRect.TopLeftLatLong.Longitude, 
					mapRect.BottomRightLatLong.Latitude, mapRect.BottomRightLatLong.Longitude);
				internalShowPoiBookmarksLayer(ajaxUrl, false, false);
			}
		}

		function markProfileLayersHidden(clearPois) {
			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					_profiles[profileId].Hide();
					
					if(clearPois) {
					    _profiles[profileId].DeleteAllShapes();
					}
				}
			}
		}

		function markProfileLayersVisible() {
			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					_profiles[profileId].Show();
				}
			}
		}

		function getPoiShape(poiId) {
			var shapeLayer;
			var shape;
			var shapeLayerCount = mapObject.GetShapeLayerCount();
			var shapeCount;

			for (var i = 0; i < shapeLayerCount; i++) {
				shapeLayer = mapObject.GetShapeLayerByIndex(i);
				shapeCount = shapeLayer.GetShapeCount();
				for (var j = 0; j < shapeCount; j++) {
					shape = shapeLayer.GetShapeByIndex(j);
					if (shape.poiId === poiId) {
						return shape;
					}
				}
			}

			return null;
		}

		// all pois must be added throught this method
		function addPoiToLayer(layer, poiId, profileId, lat, lon, poiTitle) {
			if (self.onBeforeAddPoi) {
				var args = new AddPoiArgs(poiId, profileId, false);
				self.onBeforeAddPoi(args);
				if(args.cancelAdd)
					return;
			}

			var latLong = new VELatLong(lat, lon);
			var pushPin = new VEShape(VEShapeType.Pushpin, latLong);

			var iconUrl = null;
			if (layer.profileIcon) {
				iconUrl = layer.profileIcon;
			}
			else if (layer.profileIcons) {
				if (layer.profileIcons[profileId]) {
					iconUrl = layer.profileIcons[profileId];
				}
			}

			if (iconUrl) {
				if (poiTitle) {
					var iconHtml = createHtmlIcon(iconUrl, poiTitle);
					pushPin.SetCustomIcon(iconHtml);
				}
				else {
					pushPin.SetCustomIcon(iconUrl);
				}
			}

			pushPin.SetTitle('');
			pushPin.poiId = poiId;
			pushPin.descriptionLoaded = false;

			layer.AddShape(pushPin);
		}

		function internalShowProfile(id, ajaxUrl, autoZoomAndCenter, triggerOnProfileLoaded) {
			var layer = null;
			if(_profiles[id]) // if profile already added a refresh is made on it
				layer = _profiles[id];
			else
				layer = new VEShapeLayer();
			var usePoiTitles = shouldUsePoiTitles();
			if (usePoiTitles === true) {
				ajaxUrl = mgbServiceObj.addPoiTitlesRequestToUrl(ajaxUrl);
			}

			makeScriptCall(ajaxUrl, function(responseData) {
				layer.Hide();
				layer.DeleteAllShapes();
				layer.isMgbProfileLayer = true;
				layer.arePoiTitlesLoaded = usePoiTitles;
				layer.profileIcon = responseData.IconUrl;
				layer.totalPoisInProfileCount = responseData.TotalPoisInProfileCount;

				if (!_profiles[id]) { // add the profile if not already added
					mapObject.AddShapeLayer(layer);
					_profiles[id] = layer;
				}
				if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {
					for(var i=0; i<responseData.POIs.length; i++) {
						var crtPoi = responseData.POIs[i];
						addPoiToLayer(layer, crtPoi.Id, id, crtPoi.Lat, crtPoi.Lon, crtPoi.Title);
					}
					
					layer.Show();
				}
				else if(_displayMode === MAP_SEARCH_DISPLAY_MODE) {
					refreshSearchInCrtArea(true, true);
				}

				if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {
					// Delay all remaining operations by a very short amount of time
					// Since this method is executed asynchronously (on a script load), some strange layout behavior happens on the map unless the
					// delay is made.
					setTimeout( function() {
						if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
							doAutoZoomAndCenter();
						}

						if (triggerOnProfileLoaded && self.onProfileLoaded)
							self.onProfileLoaded({
								'Id': id
							});
					}, 50);
				}
			});
		}

		function internalHideProfile(id, autoZoomAndCenter, triggerOnProfileLoaded) {
			if (!_profiles[id])
				return true;
			var layer = _profiles[id];
			mapObject.DeleteShapeLayer(layer);
			delete _profiles[id];

			if (_displayMode == MAP_PROFILE_DISPLAY_MODE) {
				if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
					doAutoZoomAndCenter();
				}

				if (triggerOnProfileLoaded && self.onProfileHidden)
					self.onProfileHidden({
						'Id': id
					});
			}
			else if(_displayMode === MAP_SEARCH_DISPLAY_MODE) {
				refreshSearchInCrtArea(true);
			}
		}

		function internalShowSearch(searchString, autoZoomAndCenter, triggerOnSearchLoaded) {
			var mapRect = mapObject.GetMapView();
			var ajaxUrl;

			if(mgbServiceObj.getSearchEntireMap()) {
			    ajaxUrl = mgbServiceObj.getSearchUrl(searchString, _profiles, null, null, null, null);
			}
			else {				
                ajaxUrl = mgbServiceObj.getSearchUrl(searchString, _profiles,
                mapRect.TopLeftLatLong.Latitude, mapRect.TopLeftLatLong.Longitude,
                mapRect.BottomRightLatLong.Latitude, mapRect.BottomRightLatLong.Longitude);
			}

			makeScriptCall(ajaxUrl, function(responseData) {
				_searchLayer.Hide();
				_searchLayer.DeleteAllShapes();

				_searchLayer.isMgbSearchLayer = true;
				_searchLayer.totalSearchMatchesCount = responseData.TotalSearchMatchesCount;
				_searchLayer.profileIcons = [];
				for (var i = 0; i < responseData.ProfileIcons.length; i++) {
					_searchLayer.profileIcons[responseData.ProfileIcons[i].Id] = responseData.ProfileIcons[i].Icon;
				}
				for(var i=0; i<responseData.POIs.length; i++) {
					var crtPoi = responseData.POIs[i];
					addPoiToLayer(_searchLayer, crtPoi.Id, crtPoi.ProfileId, crtPoi.Lat, crtPoi.Lon, false);
				}

				if (_displayMode == MAP_SEARCH_DISPLAY_MODE) {
					_searchLayer.Show();

					// Delay all remaining operations by a very short amount of time
					// Since this method is executed asynchronously (on a script load), some strange layout behavior happens on the map unless the
					// delay is made.
					setTimeout( function() {
						if (autoZoomAndCenter && mgbServiceObj.getSearchEntireMap() && mgbServiceObj.getAutoZoomAndCenter()) {
							doAutoZoomAndCenter();
						}

						if (triggerOnSearchLoaded && self.onProfileLoaded) {
							var args = new MgbMapLayerInfo(responseData.TotalSearchMatchesCount, responseData.POIs.length);
							self.onSearchLoaded(args);
						}
					}, 50);
				}
			});
		}

		function removeCrtSearchLayer() {
			_searchLayer.Hide();
			_searchLayer.DeleteAllShapes();
		}

		function internalShowPoiBookmarksLayer(ajaxUrl, autoZoomAndCenter, triggerOnBookmarksLoaded) {
			makeScriptCall(ajaxUrl, function(responseData) {
				_poiBookmarksLayer.Hide();
				_poiBookmarksLayer.DeleteAllShapes();

				_poiBookmarksLayer.isPoiBookmarksLayer = true;
				_poiBookmarksLayer.totalSearchMatchesCount = responseData.TotalSearchMatchesCount;
				_poiBookmarksLayer.profileIcons = [];
				for (var i = 0; i < responseData.ProfileIcons.length; i++) {
					_poiBookmarksLayer.profileIcons[responseData.ProfileIcons[i].Id] = responseData.ProfileIcons[i].Icon;
				}
				for(var i=0; i<responseData.POIs.length; i++) {
					var crtPoi = responseData.POIs[i];
					addPoiToLayer(_poiBookmarksLayer, crtPoi.Id, crtPoi.ProfileId, crtPoi.Lat, crtPoi.Lon, false);
				}

				if (_displayMode === MAP_POI_BOOKMARKS_DISPLAY_MODE) {
					_poiBookmarksLayer.Show();

					if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
						doAutoZoomAndCenter();
					}

					if (triggerOnBookmarksLoaded && self.onProfileLoaded) {
						var args = new MgbMapLayerInfo(responseData.TotalSearchMatchesCount, responseData.POIs.length);
						self.onBookmarksLoaded(args);
					}
				}
			});
		}

		function hidePoiBookmarksLayer() {
			_poiBookmarksLayer.Hide();
			_poiBookmarksLayer.DeleteAllShapes();
		}

		function doAutoZoomAndCenter() {
			mapObject.ShowAllShapeLayers();

			var zoom = mapObject.GetZoomLevel();
			if (zoom > _defaultZoom) {
				mapObject.SetZoomLevel(_defaultZoom);
			}
		}

		function createHtmlIcon(iconUrl, title) {
			var result = '<div class="mgb-poi-icon-container" ><div class="mgb-poi-icon-image-container" >' +
			'<img class="mgb-poi-icon-image" src="' + iconUrl + '"/></div>' +
			'<div class="mgb-poi-icon-title">' + title + '</div></div>';

			return result;
		}

		function shouldUsePoiTitles() {
			if (_poiTitlesZoomLevel >= 1 && _poiTitlesZoomLevel <= 19 && mapObject.GetZoomLevel() >= _poiTitlesZoomLevel) {
				return true;
			}
			else
				return false;
		}

		function internalSetZoomAndCenter(latitude, longitude, zoom){
			var veLatLon = new VELatLong(latitude, longitude);
			mapObject.SetCenterAndZoom(veLatLon, zoom);
		}
		
		function internalGetDefaultCenter(){
			var result = new MgbLatLon(_defaultLatLong.Latitude, _defaultLatLong.Longitude);
			return result;
		}
		
		function internalGetZoomLevel(){
			return mapObject.GetZoomLevel();
		}
		
		function internalSetZoomLevel(zoom){
			mapObject.SetZoomLevel(zoom);
		}
		
		function internalGetMapCenter(){
			var veLatLon = mapObject.GetCenter();
			var result = new MgbLatLon(ve.Latitude, ve.Longitude);

			return result;
		}
		
		function internalSetMapCenter(latitude, longitude){
			var veLatLon = new VELatLong(latitude, longitude);
			mapObject.SetCenter(veLatLon);
		}
		
		function internalRemoveShape(shape){
			var layer = shape.layer;
			if(layer){
				layer.remove(shape);
			}
		}
	
		// common map wrapper methods
		this.onProfileLoaded;
		this.onProfileHidden;
		this.onSearchLoaded;
		this.onBookmarksLoaded;
		this.onBeforeAddPoi;

		this.showProfile = function(id) {
			var url = mgbServiceObj.getProfileUrl(id, null, null, null, null);
			internalShowProfile(id, url, true, true);
		}
		this.hideProfile = function(id) {
			internalHideProfile(id, true, true);
		}
		this.getDefaultZoom = function() {
			return _defaultZoom;
		}
		this.getDefaultCenter = function() {
			return internalGetDefaultCenter();
		}
		this.zoomAndCenterToDefault = function() {
			mapObject.SetCenterAndZoom(_defaultLatLong, _defaultZoom);
		}
		this.refreshCrtArea = function() {
			internalRefreshCrtArea(true);
		}
		this.getCrtZoomLevel = function() {
			return internalGetZoomLevel();
		}
		this.setCrtZoomLevel = function(val) {
			internalSetZoomLevel(val);
		}
		this.getCrtMapCenter = function() {
			return internalGetMapCenter();
		}
		this.setCrtMapCenter = function(lat, lon) {
			internalSetMapCenter(lat, lon);
		}
		this.getPoiTitlesZoomLevel = function() {
			return _poiTitlesZoomLevel;
		}
		this.setPoiTitlesZoomLevel = function(zoomLevel) {
			_poiTitlesZoomLevel = zoomLevel;
		}
		this.setCrtCenterAndZoom = function(lat, lon, zoom) {
			internalSetZoomAndCenter(lat, lon, zoom);
		}
		this.invalidatePoiDescription = function(poiId) {
			var shape = getPoiShape(poiId);
			if(shape !== null)
				shape.descriptionLoaded = false;
		}
		this.removePoiFromMap = function(poiId) {
			var shape = getPoiShape(poiId);
			if (shape === null) {
				return;
			}

			var shapeLayer = shape.GetShapeLayer();
			shapeLayer.DeleteShape(shape);
		}
		this.addPoiToMap = function(profileId, poiId, lat, lon) {
			var shape = getPoiShape(poiId);
			if (shape !== null) { // POI already added
				return;
			}

			if (!_profiles[profileId]) { // the profile is not added to the map
				return;
			}

			addPoiToLayer(_profiles[profileId], poiId, profileId, lat, lon, false);
		}
		this.searchInCurrentProfiles = function(searchString) {
			_displayMode = MAP_SEARCH_DISPLAY_MODE;
			markProfileLayersHidden(true);
			hidePoiBookmarksLayer();
			removeCrtSearchLayer();

			_searchString = searchString;
			internalShowSearch(searchString, true, true);
		}
		this.clearSearch = function() {
			removeCrtSearchLayer();
			_searchString = null;

			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			var searchEntireMap = mgbServiceObj.getSearchEntireMap();
			refreshProfiles(true, !searchEntireMap, searchEntireMap);
            markProfileLayersVisible();
		}
		this.showPoiBookmarks = function() {
			_displayMode = MAP_POI_BOOKMARKS_DISPLAY_MODE;
			markProfileLayersHidden(true);
			removeCrtSearchLayer();

			internalShowPoiBookmarksLayer(mgbServiceObj.getPoiBookmarksUrl(), true, true);
		}
		this.hidePoiBookmarks = function() {
			hidePoiBookmarksLayer();

			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			refreshProfiles(true, true, false);
			markProfileLayersVisible();
		}
		this.showProfilesLayers = function() {
			hidePoiBookmarksLayer();
			removeCrtSearchLayer();
			_searchString = null;

			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			refreshProfiles(false, true, false);
			markProfileLayersVisible();
		}
	}
	// MsMapWrapper class end

	
	// BingV7MapWrapper class start
	function BingV7MapWrapper(mapObject, mgbServiceObj){
		var self = this;
		var _profiles = {};
		var _searchString = null;
		var _searchLayer = new Microsoft.Maps.EntityCollection();
		var _poiBookmarksLayer = new Microsoft.Maps.EntityCollection();
		var _defaultZoom = 11;
		var _defaultLatLong = new Microsoft.Maps.Location(45, -35);
		var _displayMode = MAP_PROFILE_DISPLAY_MODE;
		var _poiTitlesZoomLevel = -1;
	
		mapObject.entities.push(_searchLayer);
		mapObject.entities.push(_poiBookmarksLayer);
		Microsoft.Maps.Events.addHandler(mapObject, 'mousedown', onMouseDown);
		Microsoft.Maps.Events.addThrottledHandler(mapObject, 'viewchangeend', onChangeView, 1500);
		
		// mapObject event handlers		
		function onMouseOver(e) {
			var shape = e.target;			
			if(!shape || e.targetType !== 'pushpin') {
				e.handled = true;
				return;
			}
			
			var elementClass = 'pushPinPoiId' + shape.poiId.toString(); 
			if (shape.poiId && !shape.description) {
				mgbServiceObj.loadPoiDescription(shape.poiId.toString(), shape.getLocation().latitude, shape.getLocation().longitude, function(descriptionHtml) {
					shape.description = descriptionHtml;
					getPopupManagerInstace().displayPopup(mapObject, null, shape.description, shape.poiId, elementClass);
				});
			}
			if(shape.description)
				getPopupManagerInstace().displayPopup(mapObject, null, shape.description, shape.poiId, elementClass);
			
			e.handled = true;
		}
	
		function onMouseOut(e) {
			if(e.targetType !== 'pushpin') {
				e.handled = false;
				return;
			}
	
			getPopupManagerInstace().hidePopup();
			// let also the default handler to do its job
			e.handled = false;
		}
	
		function onMouseDown(e) {
			getPopupManagerInstace().hidePopup();
	
			// let also the default handler to do its job
			e.handled = false;
		}
	
		function onChangeView(e) {
			internalRefreshCrtArea(false);
		}
	
		function internalRefreshCrtArea(forceRefresh) {
			if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {
				refreshProfiles(forceRefresh, true, false);
			}
			else if (_displayMode === MAP_SEARCH_DISPLAY_MODE) {
				refreshSearchInCrtArea(forceRefresh);
			}
			else if (_displayMode === MAP_POI_BOOKMARKS_DISPLAY_MODE) {
				refreshPoiBookmarksInCrtArea(forceRefresh);
			}
		}
	
		function refreshProfiles(forceRefresh, currentArea, autoZoomAndCenter) {
			var mapRect = mapObject.getBounds();
			var profilesToUpdate = [];
	
			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					var shapeLayer = _profiles[profileId];
					var notAllPoisDisplayed = shapeLayer !== undefined && shapeLayer.totalPoisInProfileCount !== undefined && shapeLayer.totalPoisInProfileCount > shapeLayer.getLength();
					var titlesNotLoaded =  shouldUsePoiTitles() && typeof(shapeLayer.arePoiTitlesLoaded) !== undefined && shapeLayer.arePoiTitlesLoaded === false;
					var titlesLoaded =  !shouldUsePoiTitles() && typeof(shapeLayer.arePoiTitlesLoaded) !== undefined && shapeLayer.arePoiTitlesLoaded === true;
					if (forceRefresh || notAllPoisDisplayed  || titlesNotLoaded || titlesLoaded) {
						profilesToUpdate.push(profileId);
					}
				}
			}
	
			for (var i = 0; i < profilesToUpdate.length; i++) {
				var ajaxUrl;
				if(currentArea) {
				    ajaxUrl = mgbServiceObj.getProfileUrl(profilesToUpdate[i],
	                    mapRect.getNorth(), mapRect.getWest(),
	                    mapRect.getSouth(), mapRect.getEast());
				}
				else {
				    ajaxUrl = mgbServiceObj.getProfileUrl(profilesToUpdate[i], null, null, null, null);
				}
				    
				internalShowProfile(profilesToUpdate[i], ajaxUrl, autoZoomAndCenter, false);
			}
		}
	
		function refreshSearchInCrtArea(forceRefresh, autoZoomAndCenter) {
			if (forceRefresh || (_searchLayer.totalSearchMatchesCount !== undefined && _searchLayer.totalSearchMatchesCount > _searchLayer.getLength())) {
				internalShowSearch(_searchString, autoZoomAndCenter, false);
			}
		}
	
		function refreshPoiBookmarksInCrtArea(forceRefresh) {
			if (forceRefresh || (_poiBookmarksLayer.totalSearchMatchesCount !== undefined && _poiBookmarksLayer.totalSearchMatchesCount > _poiBookmarksLayer.getLength())) {
				var mapRect = mapObject.getBounds();
				var ajaxUrl = mgbServiceObj.getPoiBookmarksUrl(
					mapRect.getNorth(), mapRect.getWest(),
	                mapRect.getSouth(), mapRect.getEast());
				internalShowPoiBookmarksLayer(ajaxUrl, false, false);
			}
		}
	
		function markProfileLayersHidden(clearPois) {
			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					_profiles[profileId].setOptions({visible: false});
					
					if(clearPois) {
					    _profiles[profileId].clear();
					}
				}
			}
		}
	
		function markProfileLayersVisible() {
			for (var profileId in _profiles) {
				if (_profiles.hasOwnProperty(profileId)) {
					_profiles[profileId].setOptions({visible: true});
				}
			}
		}
	
		function getPoiShape(poiId) {
			var shapeLayer;
			var shape;
			var shapeLayerCount = mapObject.entities.getLength();
			var shapeCount;
	
			for (var i = 0; i < shapeLayerCount; i++) {
				shapeLayer = mapObject.entities.get(i);
				shapeCount = shapeLayer.getLength();
				for (var j = 0; j < shapeCount; j++) {
					shape = shapeLayer.get(j);
					if (shape.poiId === poiId) {
						return shape;
					}
				}
			}
	
			return null;
		}
	
		// all pois must be added throught this method
		function addPoiToLayer(layer, poiId, profileId, lat, lon, poiTitle) {
			if (self.onBeforeAddPoi) {
				var args = new AddPoiArgs(poiId, profileId, false);
				self.onBeforeAddPoi(args);
				if(args.cancelAdd)
					return;
			}
	
			var latLong = new Microsoft.Maps.Location(lat, lon);
			var pushPin = new Microsoft.Maps.Pushpin(latLong);
			pushPin.layer = layer;
			pushPin.setOptions({typeName: 'mgbPushPin pushPinPoiId' + poiId});
			pushPin.setOptions({height: 16, width: 16});
			Microsoft.Maps.Events.addHandler(pushPin, 'mouseover', onMouseOver);
			Microsoft.Maps.Events.addHandler(pushPin, 'mouseout', onMouseOut);
	
			var iconUrl = null;
			if (layer.profileIcon) {
				iconUrl = layer.profileIcon;
			}
			else if (layer.profileIcons) {
				if (layer.profileIcons[profileId]) {
					iconUrl = layer.profileIcons[profileId];
				}
			}
	
			if (iconUrl) {
				pushPin.setOptions({icon: iconUrl});
			}
			if (poiTitle) {
				pushPin.setOptions({text: poiTitle});
			}
	
			pushPin.poiId = poiId;
			pushPin.descriptionLoaded = false;
	
			layer.push(pushPin);
		}
	
		function internalShowProfile(id, ajaxUrl, autoZoomAndCenter, triggerOnProfileLoaded) {
			var layer = null;
			if(_profiles[id]) // if profile already added a refresh is made on it
				layer = _profiles[id];
			else
				layer = new Microsoft.Maps.EntityCollection();
			var usePoiTitles = shouldUsePoiTitles();
			if (usePoiTitles === true) {
				ajaxUrl = mgbServiceObj.addPoiTitlesRequestToUrl(ajaxUrl);
			}
	
			makeScriptCall(ajaxUrl, function(responseData) {
				layer.setOptions({visible: false});
				layer.clear();
				layer.isMgbProfileLayer = true;
				layer.arePoiTitlesLoaded = usePoiTitles;
				layer.profileIcon = responseData.IconUrl;
				layer.totalPoisInProfileCount = responseData.TotalPoisInProfileCount;
	
				if (!_profiles[id]) { // add the profile if not already added
					mapObject.entities.push(layer);
					_profiles[id] = layer;
				}
				if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {					
					for(var i=0; i<responseData.POIs.length; i++) {
						var crtPoi = responseData.POIs[i];
						addPoiToLayer(layer, crtPoi.Id, id, crtPoi.Lat, crtPoi.Lon, crtPoi.Title);
					}
					
					layer.setOptions({visible: true});
				}
				else if(_displayMode === MAP_SEARCH_DISPLAY_MODE) {
					refreshSearchInCrtArea(true, true);
				}
	
				if (_displayMode === MAP_PROFILE_DISPLAY_MODE) {
					// Delay all remaining operations by a very short amount of time
					// Since this method is executed asynchronously (on a script load), some strange layout behavior happens on the map unless the
					// delay is made.
					setTimeout( function() {
						if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
							doAutoZoomAndCenter();
						}
	
						if (triggerOnProfileLoaded && self.onProfileLoaded)
							self.onProfileLoaded({
								'Id': id
							});
					}, 50);
				}
			});
		}
	
		function internalHideProfile(id, autoZoomAndCenter, triggerOnProfileLoaded) {
			if (!_profiles[id])
				return true;
			var layer = _profiles[id];
			mapObject.entities.remove(layer);
			delete _profiles[id];
	
			if (_displayMode == MAP_PROFILE_DISPLAY_MODE) {
				if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
					doAutoZoomAndCenter();
				}
	
				if (triggerOnProfileLoaded && self.onProfileHidden)
					self.onProfileHidden({
						'Id': id
					});
			}
			else if(_displayMode === MAP_SEARCH_DISPLAY_MODE) {
				refreshSearchInCrtArea(true);
			}
		}
	
		function internalShowSearch(searchString, autoZoomAndCenter, triggerOnSearchLoaded) {
			var mapRect = mapObject.getBounds();
			var ajaxUrl;
	
			if(mgbServiceObj.getSearchEntireMap()) {
			    ajaxUrl = mgbServiceObj.getSearchUrl(searchString, _profiles, null, null, null, null);
			}
			else {				
	            ajaxUrl = mgbServiceObj.getSearchUrl(searchString, _profiles,
	            	mapRect.getNorth(), mapRect.getWest(),
	                mapRect.getSouth(), mapRect.getEast());
			}
	
			makeScriptCall(ajaxUrl, function(responseData) {
				_searchLayer.setOptions({visible: false});
				_searchLayer.clear();
	
				_searchLayer.isMgbSearchLayer = true;
				_searchLayer.totalSearchMatchesCount = responseData.TotalSearchMatchesCount;
				_searchLayer.profileIcons = [];
				for (var i = 0; i < responseData.ProfileIcons.length; i++) {
					_searchLayer.profileIcons[responseData.ProfileIcons[i].Id] = responseData.ProfileIcons[i].Icon;
				}
				for(var i=0; i<responseData.POIs.length; i++) {
					var crtPoi = responseData.POIs[i];
					addPoiToLayer(_searchLayer, crtPoi.Id, crtPoi.ProfileId, crtPoi.Lat, crtPoi.Lon, false);
				}
	
				if (_displayMode == MAP_SEARCH_DISPLAY_MODE) {
					_searchLayer.setOptions({visible: true});
	
					// Delay all remaining operations by a very short amount of time
					// Since this method is executed asynchronously (on a script load), some strange layout behavior happens on the map unless the
					// delay is made.
					setTimeout( function() {
						if (autoZoomAndCenter && mgbServiceObj.getSearchEntireMap() && mgbServiceObj.getAutoZoomAndCenter()) {
							doAutoZoomAndCenter();
						}
	
						if (triggerOnSearchLoaded && self.onProfileLoaded) {
							var args = new MgbMapLayerInfo(responseData.TotalSearchMatchesCount, responseData.POIs.length);
							self.onSearchLoaded(args);
						}
					}, 50);
				}
			});
		}
	
		function removeCrtSearchLayer() {
			_searchLayer.setOptions({visible: false});
			_searchLayer.clear();
		}
	
		function internalShowPoiBookmarksLayer(ajaxUrl, autoZoomAndCenter, triggerOnBookmarksLoaded) {
			makeScriptCall(ajaxUrl, function(responseData) {
				_poiBookmarksLayer.setOptions({visible: false});
				_poiBookmarksLayer.DeleteAllShapes();
	
				_poiBookmarksLayer.isPoiBookmarksLayer = true;
				_poiBookmarksLayer.totalSearchMatchesCount = responseData.TotalSearchMatchesCount;
				_poiBookmarksLayer.profileIcons = [];
				for (var i = 0; i < responseData.ProfileIcons.length; i++) {
					_poiBookmarksLayer.profileIcons[responseData.ProfileIcons[i].Id] = responseData.ProfileIcons[i].Icon;
				}
				for(var i=0; i<responseData.POIs.length; i++) {
					var crtPoi = responseData.POIs[i];
					addPoiToLayer(_poiBookmarksLayer, crtPoi.Id, crtPoi.ProfileId, crtPoi.Lat, crtPoi.Lon, false);
				}
	
				if (_displayMode === MAP_POI_BOOKMARKS_DISPLAY_MODE) {
					_poiBookmarksLayer.setOptions({visible: true});
	
					if (autoZoomAndCenter && mgbServiceObj.getAutoZoomAndCenter()) {
						doAutoZoomAndCenter();
					}
	
					if (triggerOnBookmarksLoaded && self.onProfileLoaded) {
						var args = new MgbMapLayerInfo(responseData.TotalSearchMatchesCount, responseData.POIs.length);
						self.onBookmarksLoaded(args);
					}
				}
			});
		}
	
		function hidePoiBookmarksLayer() {
			_poiBookmarksLayer.setOptions({visible: false});
			_poiBookmarksLayer.clear();
		}
	
		function doAutoZoomAndCenter() {
			var shapeLayer;
			var shape;
			var shapeLayerCount = mapObject.entities.getLength();
			var shapeCount;
			var allPoiLocations = [];
			
			for (var i = 0; i < shapeLayerCount; i++) {
				shapeLayer = mapObject.entities.get(i);
				if(shapeLayer.getVisible() == false)
					continue;
				
				shapeCount = shapeLayer.getLength();
				for (var j = 0; j < shapeCount; j++) {
					shape = shapeLayer.get(j);
					if (shape instanceof Microsoft.Maps.Pushpin) {
						allPoiLocations.push(shape.getLocation());
					}
				}
			}
			
			if(allPoiLocations.length == 0) {
				return;
			}
			
			var bestview = Microsoft.Maps.LocationRect.fromLocations(allPoiLocations);
			//var bestview = new Microsoft.Maps.LocationRect(new Microsoft.Maps.Location(45, 45), 1, 1); 
			mapObject.setView({bounds: bestview});
	
			var zoom = mapObject.getZoom();
			if (zoom > _defaultZoom) {
				mapObject.setView({zoom: _defaultZoom});
			}
		}
	
		function shouldUsePoiTitles() {
			if (_poiTitlesZoomLevel >= 1 && _poiTitlesZoomLevel <= 19 && mapObject.getZoom() >= _poiTitlesZoomLevel) {
				return true;
			}
			else
				return false;
		}
			
		function internalSetZoomAndCenter(latitude, longitude, zoom){
			var location = new Microsoft.Maps.Location(latitude, longitude);
			mapObject.setView({zoom: zoom, center: location});
		}

		function internalGetDefaultCenter(){
			var result = new MgbLatLon(_defaultLatLong.latitude, _defaultLatLong.longitude);
			return result;
		}
		
		function internalGetZoomLevel(){
			return mapObject.getZoom();
		}
		
		function internalSetZoomLevel(zoom){
			mapObject.setView({zoom: zoom});
		}
		
		function internalGetMapCenter(){
			var location = mapObject.getCenter();
			var result = new MgbLatLon(location.latitude, location.longitude);
	
			return result;
		}
		
		function internalSetMapCenter(latitude, longitude){
			var location = new new Microsoft.Maps.Location(latitude, longitude);
			mapObject.setView({center: location});
		}
		
		function internalRemoveShape(shape){
			var layer = shape.layer;
			if(layer){
				layer.remove(shape);
			}
		}
	
		// common map wrapper methods
		this.onProfileLoaded;
		this.onProfileHidden;
		this.onSearchLoaded;
		this.onBookmarksLoaded;
		this.onBeforeAddPoi;
	
		this.showProfile = function(id) {
			var url = mgbServiceObj.getProfileUrl(id, null, null, null, null);
			internalShowProfile(id, url, true, true);
		}
		this.hideProfile = function(id) {
			internalHideProfile(id, true, true);
		}
		this.getDefaultZoom = function() {
			return _defaultZoom;
		}
		this.getDefaultCenter = function() {
			return internalGetDefaultCenter();
		}
		this.zoomAndCenterToDefault = function() {
			mapObject.SetCenterAndZoom(_defaultLatLong, _defaultZoom);
		}
		this.refreshCrtArea = function() {
			internalRefreshCrtArea(true);
		}
		this.getCrtZoomLevel = function() {
			return internalGetZoomLevel();
		}
		this.setCrtZoomLevel = function(val) {
			internalSetZoomLevel(val);
		}
		this.getCrtMapCenter = function() {
			return internalGetMapCenter();
		}
		this.setCrtMapCenter = function(lat, lon) {
			internalSetMapCenter(lat, lon);
		}
		this.getPoiTitlesZoomLevel = function() {
			return _poiTitlesZoomLevel;
		}
		this.setPoiTitlesZoomLevel = function(zoomLevel) {
			_poiTitlesZoomLevel = zoomLevel;
		}
		this.setCrtCenterAndZoom = function(lat, lon, zoom) {
			internalSetZoomAndCenter(lat, lon, zoom);
		}
		this.invalidatePoiDescription = function(poiId) {
			var shape = getPoiShape(poiId);
			if(shape !== null)
				shape.descriptionLoaded = false;
		}
		this.removePoiFromMap = function(poiId) {
			var shape = getPoiShape(poiId);
			if (shape === null) {
				return;
			}
	
			internalRemoveShape(shape);
		}
		this.addPoiToMap = function(profileId, poiId, lat, lon) {
			var shape = getPoiShape(poiId);
			if (shape !== null) { // POI already added
				return;
			}
	
			if (!_profiles[profileId]) { // the profile is not added to the map
				return;
			}
	
			addPoiToLayer(_profiles[profileId], poiId, profileId, lat, lon, false);
		}
		this.searchInCurrentProfiles = function(searchString) {
			_displayMode = MAP_SEARCH_DISPLAY_MODE;
			markProfileLayersHidden(true);
			hidePoiBookmarksLayer();
			removeCrtSearchLayer();
	
			_searchString = searchString;
			internalShowSearch(searchString, true, true);
		}
		this.clearSearch = function() {
			removeCrtSearchLayer();
			_searchString = null;
	
			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			var searchEntireMap = mgbServiceObj.getSearchEntireMap();
			refreshProfiles(true, !searchEntireMap, searchEntireMap);
	        markProfileLayersVisible();
		}
		this.showPoiBookmarks = function() {
			_displayMode = MAP_POI_BOOKMARKS_DISPLAY_MODE;
			markProfileLayersHidden(true);
			removeCrtSearchLayer();
	
			internalShowPoiBookmarksLayer(mgbServiceObj.getPoiBookmarksUrl(), true, true);
		}
		this.hidePoiBookmarks = function() {
			hidePoiBookmarksLayer();
	
			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			refreshProfiles(true, true, false);
			markProfileLayersVisible();
		}
		this.showProfilesLayers = function() {
			hidePoiBookmarksLayer();
			removeCrtSearchLayer();
			_searchString = null;
	
			_displayMode = MAP_PROFILE_DISPLAY_MODE;
			refreshProfiles(false, true, false);
			markProfileLayersVisible();
		}	
	}
	// BingV7MapWrapper class end


	// Data structures start
	registerNamespaceOrClass('MgbLatLon', MgbLatLon);
	function MgbLatLon(lat, lon) {
		this.lat = lat;
		this.lon = lon;
	}

	registerNamespaceOrClass('MgbMapLayerInfo', MgbMapLayerInfo);
	function MgbMapLayerInfo(totalItemsOnMap, totalItemsDisplayed) {
		this.totalItemsOnMap = totalItemsOnMap;
		this.totalItemsDisplayed = totalItemsDisplayed;
	}

	registerNamespaceOrClass('AddPoiArgs', AddPoiArgs);
	function AddPoiArgs(poiId, profileId, cancelAdd) {
		this.poiId = poiId;
		this.profileId = profileId;
		this.cancelAdd = cancelAdd;
	}
	// Data structures end
})();
