/*extern booking, Drag, GBounds, GBrowserIsCompatible, GDownloadUrl, GEvent, GIcon, GLargeMapControl, GLatLng, GLatLngBounds, GMap2, GMapTypeControl, GMarker, GMarkerManager, GOverlay, GPoint, GScaleControl, GSize, GUnload, G_DEFAULT_ICON, G_MAP_FLOAT_PANE, addListener, document, removeListener, gClientIsIE, gClientIsIE5, gClientIsIE55, gClientIsIE6, gClientIsIE7, navigator, G_SATELLITE_MAP */

// Our booking namespace is defined in /static/defaults/js/base.js.

/*
	Don’t display maps if:
	the client is IE 5, or
	we fail Google’s own compatibility test.
*/
if (!gClientIsIE5 && GBrowserIsCompatible())
{
	// Class to build infowindow on a marker
	booking.BuildInfoWindow = function (item)
	{
		this.item_ = item;
	};
	booking.BuildInfoWindow.prototype.it = function ()
	{
		GEvent.removeListener(this.item_.marker.handleBuildInfoWindow);
		this.item_.marker.infoWindow = new booking.InfoWindow(this.item_);
		booking.map.obj.addOverlay(this.item_.marker.infoWindow);
		this.item_.mouseoverHandle = GEvent.addListener(
			this.item_.marker,
			'mouseover',
			function ()
			{
				this.infoWindow.show();
			});
		this.item_.mouseoutHandle = GEvent.addListener(
			this.item_.marker,
			'mouseout',
			function ()
			{
				this.infoWindow.hide();
			});
		GEvent.trigger(this.item_.marker, 'mouseover');
	};
	booking.Go = function (url)
	{
		this.url_ = url;
	};
	booking.Go.prototype.to = function ()
	{
		document.location = this.url_;
	};
	// Class to display an infowindow
	booking.InfoWindow = function (record)
	{ /* Constructor function with privacy for our InfoWindow.
		Argument:
			record: Object
			{
				b_type        : String
				id            : String  ; item identifier
				b_image_url   : String
				b_class       : Integer ; between 1 and 5
				title         : String
				b_description : String  ; only present when b_type == 'hotel'
				b_hotelcount  : Integer
			}
		*/
		this.record_ = record;
	};
	// Subclass GOverlay.
	booking.InfoWindow.prototype = new GOverlay(); //moved to within GIsBrowserCompatible block.
	booking.InfoWindow.prototype.initialize = function (map)
	{ // Called by the map after the overlay is added to the map using GMap2.addOverlay(). The overlay object can draw itself into the different panes of the map that can be obtained using GMap2.getPane().
		var
			div          = document.createElement('div'),
			h            = document.createElement('h3'),
			p            = document.createElement('p'),
			className    = 'BInfoWindow ' + this.record_.icon_type, // class is a reserved word.
			text;
		div.className = className; // Does this make InfoWindow style look right in IE 7?
		div.id = this.record_.id;
		if (this.record_.b_class)
		{
			var stars = document.createElement('img');
			stars.setAttribute('src', '' + this.record_.b_class + 'sterren-small.png');
			stars.setAttribute('alt', this.record_.b_class + ' ' + (this.record_.b_class == 1 ? booking.env.star : booking.env.stars));
			h.appendChild(stars);
		}
		if (this.record_.b_image_url)
		{
			var photo = document.createElement('img');
			photo.setAttribute('src', this.record_.b_image_url);
			photo.setAttribute('alt', 'thumbnail photo');
			p.appendChild(photo);
		}
		if (this.record_.b_type == 'hotel')
		{
			h.appendChild(document.createTextNode(this.record_.title));
			text = this.record_.b_description;
		}
		else
		{
			var subhead = document.createElement('span');
			subhead.setAttribute('class', 'subhead');
			h.appendChild(document.createTextNode(this.record_.title));
			h.appendChild(document.createElement('br'));
			h.appendChild(document.createElement('span'));
			subhead.appendChild(document.createTextNode(this.record_.subhead));
			h.appendChild(subhead);
			text = this.record_.b_hotelcount + ' ' + (this.record_.b_hotelcount == 1 ? booking.env.map_hotel : booking.env.map_hotels);
		}
		p.appendChild(document.createTextNode(text));
		div.appendChild(h);
		div.appendChild(p);
		map.getPane(G_MAP_FLOAT_PANE).appendChild(div);
		this.map_ = map;
		this.div_ = div;
		this.p_ = p;
	};
	booking.InfoWindow.prototype.remove = function ()
	{ // Called by the map after the overlay is removed from the map using GMap2.removeOverlay() or GMap2.clearOverlays(). The overlay must remove itself from the map panes here.
		this.div_.parentNode.removeChild(this.div_);
	};
	booking.InfoWindow.prototype.copy = function ()
	{ // Returns an uninitialized copy of itself that can be added to the map.
		return new booking.InfoWindow(this.record_);
	};
	booking.InfoWindow.prototype.redraw = function (force)
	{ // Called by the map when the map display has changed. The argument force will be true if the zoom level or the pixel offset of the map view has changed, so that the pixel coordinates need to be recomputed.
		if (!force)
		{
			return;
		}
		//this.position();
	};
	// Event handler to show the infowindow.
	booking.InfoWindow.prototype.show = function ()
	{
		if (booking.map.container.className.indexOf(booking.map.CLASS_NORMAL) != -1)
		{
			if (booking.map.display.subject && (this.record_ !== booking.map.display.subject))
			{
				booking.map.display.clear();
			}
			this.position();
			this.div_.style.visibility = 'visible';
		}
	};
	// Position the infowindow immediately below the marker and ensure it fits within the map window and doesn’t overlay the map controls.
	booking.InfoWindow.prototype.position = function ()
	{
		var
			markerAnchor = this.map_.fromLatLngToDivPixel(this.record_.latLng),
			left = 'auto',
			// We use the iconSize here rather than the infoWindowAnchor because we want to abut the marker either on top or bottom, and not overlap it at all.
			markerType = booking.map.markerTypes[this.record_.icon_type],
			ddV = document.defaultView ?
				document.defaultView :
				false,
			halfwidth = ddV ?
				parseFloat(ddV.getComputedStyle(this.div_, null).width) / 2 :
				Math.round(this.div_.offsetWidth / 2),
			height = ddV ?
				parseFloat(ddV.getComputedStyle(this.div_, null).height) :
				this.div_.offsetHeight,
			pointEN,	
			pointWS,
			mapInfowindowBounds = new GBounds(),
			count = 0,
			text = this.p_.lastChild,
			top = markerAnchor.y + markerType.infoWindowAnchor.y - markerType.iconAnchor.y;
		// If booking.map.display.subject exists, construct a virtual bounds points around the subject marker.
		if (booking.map.display.subject)
		{
			var size = booking.map.obj.getSize(); // a GSize, in pixels
			pointEN  = new GPoint(
				markerAnchor.x + (size.width  / 2),
				markerAnchor.y - (size.height / 2));
			pointWS  = new GPoint(
				markerAnchor.x - (size.width  / 2),
				markerAnchor.y + (size.height / 2));
		}
		// Else get the real map bounds.
		else
		{
			var bounds = this.map_.getBounds();
			pointEN    = this.map_.fromLatLngToDivPixel(bounds.getNorthEast());
			pointWS    = this.map_.fromLatLngToDivPixel(bounds.getSouthWest());
		}
		mapInfowindowBounds.minY = pointEN.y + booking.map.MARGIN_TOP;
		mapInfowindowBounds.maxX = pointEN.x - booking.map.MARGIN_RIGHT;
		mapInfowindowBounds.maxY = pointWS.y - booking.map.MARGIN_BOTTOM;
		mapInfowindowBounds.minX = pointWS.x + booking.map.MARGIN_LEFT;
		// Should be text.truncate method but IE doesn’t allow it.
		function truncate (text, parent)
		{
			text.nodeValue = text.nodeValue.slice(0, text.nodeValue.search(/\s{1}\S+$/)) + booking.env.typographical_ellipsis;
			height = ddV ?
				parseFloat(ddV.getComputedStyle(parent, null).height) :
				parent.offsetHeight;
		}
		// If the infowindow is too tall to display below the marker,
		if (top + height > mapInfowindowBounds.maxY)
		{
			// If the marker is also in the bottom half of the viewport,
			if (markerAnchor.y > ((mapInfowindowBounds.maxY - mapInfowindowBounds.minY) / 2))
			{
				// Display it above instead.
				top = markerAnchor.y - markerType.infoWindowAnchor.y - height;
				// If the infowindow is too tall to display above the marker,
				while ((top < mapInfowindowBounds.minY) && (count++ < 200))
				{
					// Truncate the text until it fits.
					truncate(text, this.div_);
					top = markerAnchor.y - markerType.infoWindowAnchor.y - height;					
				}
			}
			// Else if the marker is in the top half of the viewport,
			else
			{
				// If the infowindow is still too tall to display below the marker,
				while ((top + height > mapInfowindowBounds.maxY) && (count++ < 200))
				{
					// Truncate the text until it fits.	
					truncate(text, this.div_);
				}
			}
		}
		this.div_.style.top = top += booking.CSS.units.px;
		// If the infowindow extends past the right boundary, bring it within that boundary.
		if (markerAnchor.x + halfwidth > mapInfowindowBounds.maxX)
		{
			left = mapInfowindowBounds.maxX - (halfwidth * 2);
		}
		// If the infowindow extends past the GLargeMapControl, adjust it so it doesn’t.
		else
		{
			if (markerAnchor.x - halfwidth < mapInfowindowBounds.minX)
			{
				left = mapInfowindowBounds.minX;
			}
			else
			{
				left = markerAnchor.x - halfwidth;
			}
		}
		this.div_.style.left = left + booking.CSS.units.px;
	};
	// Event handler to hide the infowindow.
	booking.InfoWindow.prototype.hide = function ()
	{
		this.div_.style.visibility = 'hidden';
	};
	// Method to construct the URL for our JSON placemarks feed.
	booking.buildPlacemarksUrl = function ()
	{
		var bbox = booking.map.box.getSouthWest().lng() + ',' + booking.map.box.getSouthWest().lat() + ',' + booking.map.box.getNorthEast().lng() + ',' + booking.map.box.getNorthEast().lat();
		booking.placemarksUrl = '/hotelsonmap.' + booking.env.b_lang + '.json?BBOX=' + bbox + ';limit=50';
	};
	// Declare booking.map with basic properties.
	booking.map =
	{
		description       : 'Object-literal namespace for sitewide Google Maps.',
		version           : '1.1.10',
		ZOOM_TARGET_HOTEL_PREVIEW : 12,
		ZOOM_TARGET_HOTEL_NORMAL  : 13,
		controls          : {},
		CLASS_PREVIEW     : 'preview b_popupInner',
		CLASS_NORMAL      : 'normal b_popupInner',
		mapType           : (booking.env.b_googlemaps_maptype == 'satellite') ?
		                    G_SATELLITE_MAP : 
		                    false,
		MARGIN_TOP        : 30,
		MARGIN_RIGHT      : 0,
		MARGIN_BOTTOM     : 30,
		MARGIN_LEFT       : 72,
		CSS_POSITION_TOP  : 40
	};
	// If the action provided a box, adopt it, otherwise create an empty box.
	booking.map.box = (
		booking.env.b_bbox_southwest_latitude  && 
		booking.env.b_bbox_southwest_longitude && 
		booking.env.b_bbox_northeast_latitude  && 
		booking.env.b_bbox_northeast_longitude)           ?
			new GLatLngBounds( // GLatLngBounds(sw?, ne?)
				new GLatLng(
					booking.env.b_bbox_southwest_latitude,
					booking.env.b_bbox_southwest_longitude),
				new GLatLng(
					booking.env.b_bbox_northeast_latitude,
					booking.env.b_bbox_northeast_longitude)) : 
			new GLatLngBounds();
	// If the action provided a center, adopt it, otherwise get the center of our box.
	booking.map.center = (booking.env.b_latitude && booking.env.b_longitude) ? 
		new GLatLng(booking.env.b_latitude, booking.env.b_longitude)          : booking.map.box.getCenter();
	booking.promotions.load();
	// Add booking.map methods gicon, zoom, setCenter, maximize, minimize, firstMaximize, markerType,, promotions.process and processPlacemarks.
	// Extend our current bounding box to include all promotion items.
	booking.map.box.inflate = function ()
	{
		var promo;
		for (var i in booking.promotions) if (booking.promotions.hasOwnProperty(i) && (typeof booking.promotions[i] == 'object'))
		{
			promo = booking.promotions[i];
			// Don’t extend the box for non-hotel promotions on landing pages or non-city promotions on country landing pages.
			if ((booking.env.b_action != 'searchresults' &&
					promo.b_type == 'hotel') ||
				(booking.env.b_action == 'country' &&
					promo.b_type == 'city'))
			{
				this.extend(promo.latLng);
			}
		}
	};
	booking.map.box.inflate.description = 'Called when booking.map.box.isEmpty() returns true to expand the box to fit all the page’s promotions, with various exceptions.';
	if (booking.map.box.isEmpty()) // we didn’t get a box from the action, so we can build one since the promotions are now loaded.
	{
		booking.map.box.inflate();
		// Unless we’re on a hotel page recenter the map on the inflated box.
		if (booking.env.b_action != 'hotel')
		{
			booking.map.center = booking.map.box.getCenter();
		}
	}
	// Set the zoom level of the map to fit our current bounding box.
	booking.map.computeZoom = function ()
	{ // “this” refers to booking.map.
		this.boundsZoomLevel = this.obj.getBoundsZoomLevel(this.box);
		// On hotel pages we want to adjust the zoom level to be neither too high nor low.
		if (booking.env.b_action == 'hotel')
		{
			var target = (booking.map.container.className.indexOf(booking.map.CLASS_PREVIEW) != -1) ? this.ZOOM_TARGET_HOTEL_PREVIEW : this.ZOOM_TARGET_HOTEL_NORMAL;
			// Normalize our zoom level to be nearer the specified target.
			this.boundsZoomLevel = target + Math.round((this.boundsZoomLevel - target) / 2);
		}
	};
	// Shortcut to center the map on our current center.
	booking.map.setCenter = function (center, zoom, mapType)
	{ // “this” refers to booking.map. Arguments are optional.
		this.obj.setCenter(center ? center : this.center, zoom ? zoom : this.boundsZoomLevel, mapType ? mapType : this.mapType);
	};
	// Adds our desired set of controls to the map.
	booking.map.addControls = function()
	{
		booking.map.controls.GLargeMapControl = new GLargeMapControl();
		booking.map.controls.GScaleControl    = new GScaleControl();
		booking.map.controls.GMapTypeControl  = new GMapTypeControl();
		for (var i in booking.map.controls) if (booking.map.controls.hasOwnProperty(i))
		{
			booking.map.obj.addControl(booking.map.controls[i]);
		}
	};
	booking.map.removeControls = function()
	{
		for (var i in booking.map.controls) if (booking.map.controls.hasOwnProperty(i))
		{
			booking.map.obj.removeControl(booking.map.controls[i]);
		}
	};
	// Event handler for closing the map.
	booking.map.close = function ()
	{
		if (booking.map.display.subject)
		{
			GEvent.trigger(booking.map.display.subject.marker, 'mouseout');
			booking.map.display.subject = null;
		}
		if (booking.map.container.className.indexOf(booking.map.CLASS_NORMAL) != -1)
		{
			booking.map.minimize();
		}
		else
		{
			booking.map.container.style.display = 'none';
		}
		if (gClientIsIE && !gClientIsIE7)
		{
			for (var i = 0; i < booking.selects.length; i++)
			{
				booking.selects[i].style.visibility = 'visible';
			}
		}
		return false;
	};
	// Pans the map to the subject placemark or promotion item.
	booking.map.focus = function (subject)
	{
		booking.map.obj.panTo(subject.latLng);
		GEvent.trigger(subject.marker, 'mouseover');
	};
	// Event handler for displaying the map.
	booking.map.display = function (itemId)
	{
		// If the container className contains the CLASS_PREVIEW value
		if (booking.map.container.className.indexOf(booking.map.CLASS_PREVIEW) != -1)
		{
			booking.map.maximize();
		}
		else
		{
			booking.map.container.style.display = 'block';
		}
		if (!booking.map.display.initialized)
		{
			booking.map.display.initialize();
		}
		booking.map.display.clear();
		if (itemId)
		{
			if (booking.promotions[itemId] || booking.placemarks)
			{
				booking.map.display.subject = booking.promotions[itemId] ? booking.promotions[itemId] : booking.placemarks[itemId];
				booking.map.focus(booking.map.display.subject);
			}
			else
			{
				booking.map.display.subjectID = itemId;
			}
		}
		else
		{
			booking.map.obj.panTo(booking.map.center);
		}
		if (gClientIsIE && !gClientIsIE7)
		{
			booking.map.container.hideSelectsUnderneath();
		}
		return false;
	};
	booking.map.display.clear = function ()
	{
		if (booking.map.display.subject)
		{
			GEvent.trigger(booking.map.display.subject.marker, 'mouseout');
			booking.map.display.subject = '';
		}
	};
	booking.map.display.initialize = function ()
	{
		if (booking.env.b_action == 'searchresults')
		{
			booking.hotelsMatchingCriteria.load(); // featMapHeader.inc.
		}
		booking.promotions.process();
		booking.buildPlacemarksUrl();
		GDownloadUrl(booking.placemarksUrl, booking.map.processPlacemarks);
		booking.map.shadow.style.backgroundImage = '';
		booking.map.display.initialized = true;
	};
	booking.map.initialize = function ()
	{
		booking.map.show = GEvent.addListener(booking.map.obj, 'click', booking.map.display);
		booking.map.computeZoom();
		booking.map.setCenter();
		booking.map.obj.disableDragging();
		if (gClientIsIE55)
		{
			booking.map.shadow.style.display = 'none';
		}
	};
	booking.map.maximize = function ()
	{
		if (gClientIsIE55)
		{
			booking.map.shadow.style.display = 'block';
		}
		booking.map.obj.enableDragging();
		GEvent.removeListener(booking.map.show);
		booking.map.container.className = booking.map.CLASS_NORMAL;
		// For browsers that don’t support position:fixed properly such as IE prior to version 7, we set position:absolute elsewhere (such as in /static/css/ie6lte.css). Here we detect that via currentStyle and set the top value to be within the current viewport.
		if (booking.map.container.currentStyle && (booking.map.container.currentStyle.position !== 'fixed'))
		{
			var adjustedTop = 0;
			booking.map.container.style.className = booking.map.CLASS_NORMAL + ' ie6lte';
			if (document.documentElement.scrollTop > 0)
			{
				adjustedTop = document.documentElement.scrollTop;
			}
			else
			{
				if (document.body.scrollTop > 0)
				{
					adjustedTop = document.body.scrollTop;
				}
			}
			booking.map.container.style.top = (adjustedTop + booking.map.CSS_POSITION_TOP).toString() + booking.CSS.units.px;
		}
		booking.map.obj.checkResize();
		booking.map.computeZoom();
		booking.map.setCenter();
		booking.map.zoom.normal = booking.map.obj.getZoom();
		booking.map.addControls();
	};
	booking.map.minimize = function ()
	{
		booking.map.removeControls();
		booking.map.container.className = booking.map.CLASS_PREVIEW;
		if (booking.map.container.currentStyle && (booking.map.container.currentStyle.position != 'static'))
		{ // IE6 fails to go back to static so we force it.
			booking.map.container.style.position = 'static';
		}
		booking.map.obj.checkResize();
		booking.map.initialize();
	};
	booking.map.markerTypes = {};
	booking.map.Marker = function (options)
	{
		if (!options)
		{
			options = {}; // so we can safely check for its’ properties.
		}
		this.image            = options.image            ? options.image            : this.image;
		this.shadow           = options.shadow           ? options.shadow           : this.shadow;
		this.iconSize         = options.iconSize         ? options.iconSize         : this.iconSize;
		this.shadowSize       = options.shadowSize       ? options.shadowSize       : this.shadowSize;
		this.iconAnchor       = options.iconAnchor       ? options.iconAnchor       : this.iconAnchor;
		this.infoWindowAnchor = options.infoWindowAnchor ? options.infoWindowAnchor : (this.infoWindowAnchor ? this.infoWindowAnchor : this.iconAnchor);
		this.printImage       = options.printImage       ? options.printImage       : (this.printImage ? this.printImage : this.image);
		this.mozPrintImage    = options.mozPrintImage    ? options.mozPrintImage    : (this.mozPrintImage ? this.mozPrintImage : this.image);
		this.printShadow      = options.printShadow      ? options.printShadow      : (this.printShadow ? this.printShadow : this.shadow);
		this.transparent      = options.transparent      ? options.transparent      : this.transparent;
		this.imageMap         = options.imageMap         ? options.imageMap         : this.imageMap;
		this.maxHeight        = options.maxHeight        ? options.maxHeight        : this.maxHeight;
		this.dragCrossImage   = options.dragCrossImage   ? options.dragCrossImage   : this.dragCrossImage;
		this.dragCrossSize    = options.dragCrossSize    ? options.dragCrossSize    : this.dragCrossSize;
		this.dragCrossAnchor  = options.dragCrossAnchor  ? options.dragCrossAnchor  : this.dragCrossAnchor;
	};
	booking.map.Marker.prototype = new GIcon();
	booking.map.Marker.prototype.image            = '/marker-hotel.png';
	booking.map.Marker.prototype.shadow           = '/marker-shadow-hotel.png';
	booking.map.Marker.prototype.iconSize         = new GSize(17, 20);
	booking.map.Marker.prototype.shadowSize       = new GSize(22, 16);
	booking.map.Marker.prototype.iconAnchor       = new GPoint(8.5, 20);
	booking.map.Marker.prototype.infoWindowAnchor = new GPoint(8.5, 20);
	booking.map.Marker.prototype.printImage       = undefined;
	booking.map.Marker.prototype.mozPrintImage    = undefined;
	booking.map.Marker.prototype.printShadow      = undefined;
	booking.map.Marker.prototype.transparent      = '';
	booking.map.Marker.prototype.imageMap         = [];
	booking.map.Marker.prototype.maxHeight        = 0;
	booking.map.Marker.prototype.dragCrossImage   = '';
	booking.map.Marker.prototype.dragCrossSize    = new GSize(0, 0);
	booking.map.Marker.prototype.dragCrossAnchor  = new GPoint(0, 0);
	booking.map.SmallMarker = function ()
	{
		// Subclass of booking.map.Marker.
	};
	booking.map.SmallMarker.prototype = new booking.map.Marker( {
		image      : '/marker-city.png',
		shadow     : '/marker-shadow-city.png',
		iconSize   : new GSize(9, 9),
		shadowSize : new GSize(15, 12),
		iconAnchor : new GPoint(4.5, 4.5),
		infoWindowAnchor : new GPoint(4.5, 9) } );
	booking.map.ZoomRange = function (min, max)
	{
		this.min = min ? min : Math.NEGATIVE_INFINITY;
		this.max = max ? max : Math.POSITIVE_INFINITY;
	};
	booking.map.markerTypes.hotel = new booking.map.Marker();
	booking.map.markerTypes.hotel.zoomRange = new booking.map.ZoomRange(11);
	booking.map.markerTypes.hotel_current = new booking.map.Marker( { image : '/marker-hotel-current.png' } );
	booking.map.markerTypes.hotel_current.zoomRange = new booking.map.ZoomRange(4);
	booking.map.markerTypes.hotel_available = new booking.map.Marker( { image : '/marker-hotel-current.png' } );
	booking.map.markerTypes.hotel_available.zoomRange = new booking.map.ZoomRange(10);
	booking.map.markerTypes.hotel_searchresults = new booking.map.Marker( { image : '/marker-hotel-other.png' } );
	booking.map.markerTypes.hotel_searchresults.zoomRange = new booking.map.ZoomRange(12);
	booking.map.markerTypes.airport = new booking.map.Marker( { image : '/marker-airport.png' } );
	booking.map.markerTypes.airport.zoomRange = new booking.map.ZoomRange(6);
	booking.map.markerTypes.landmark = new booking.map.Marker( { image : '/marker-landmark.png' } );
	booking.map.markerTypes.landmark.zoomRange = new booking.map.ZoomRange(12);
	booking.map.markerTypes.city = new booking.map.SmallMarker();
	booking.map.markerTypes.city.zoomRange = new booking.map.ZoomRange(4, 16);
	booking.map.markerTypes.country = new booking.map.Marker( { image : '/marker-country.png' } );
	booking.map.markerTypes.country.zoomRange = new booking.map.ZoomRange(4, 10);
	booking.map.markerTypes.region = new booking.map.Marker( { image : 'icon-region.png' } );
	booking.map.markerTypes.region.zoomRange = new booking.map.ZoomRange(5);
	booking.map.markerTypes.district = new booking.map.SmallMarker( { image : '/marker-district.png' } );
	booking.map.markerTypes.district.zoomRange = new booking.map.ZoomRange(7);
	booking.map.buildMarker = function (item)
	{ // Argument: item Object(icon_type, b_type, latLng)
		if (!item.icon_type)
		{
			item.icon_type = item.b_type;
		}
		item.marker = new GMarker(item.latLng,
			{// e.g.,	booking.map.markerTypes.country.icon
				icon : this.markerTypes[item.icon_type]
			});
			this.markerManager.addMarker(item.marker,
				this.markerTypes[item.icon_type].zoomRange.min,
				this.markerTypes[item.icon_type].zoomRange.max);
			if (item.url)
			{
				item.marker.go = new booking.Go(item.url);
				GEvent.addListener(
					item.marker,
					'click',
					function ()
					{
						this.go.to();
					}
				);
			}
			item.marker.build = new booking.BuildInfoWindow(item);
			item.marker.handleBuildInfoWindow = GEvent.addListener(
				item.marker,
				'mouseover',
				function ()
				{
					if (booking.map.container.className.indexOf(booking.map.CLASS_NORMAL) != -1)
					{ // Only build infowindows when maximized!
						this.build.it();
						delete this.build;
					}
				}
			);
	};
	booking.promotions.process = function ()
	{ // To be called on first maximize.
		for (var i in booking.promotions) if (booking.promotions.hasOwnProperty(i) && (typeof booking.promotions[i] == 'object'))
		{
			var promo = booking.promotions[i];
			if (!booking.promotions[i].marker)
			{ // Change square90 photo thumbnails to square60. To-do: in actions.
				if (promo.b_image_url)
				{
					promo.b_image_url = promo.b_image_url.replace('square90', 'square60');
				}
				booking.map.buildMarker(promo);
			}
		}
	};
	booking.map.processPlacemarks = function (response, code)
	{
		if (response && (code == 200)) // response == null on timeout
		{
			booking.placemarks = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
             response.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + response + ')');
         if (!booking.placemarks)
         {
         	booking.placemarks.hotelsonmap = 'invalid';
         	return;
         }
         else
         {
         	booking.placemarks.hotelsonmap = 'valid';
         }
         var placemark;
			for (var i in booking.placemarks) if (booking.placemarks.hasOwnProperty(i) && (typeof booking.placemarks[i] == 'object'))
			{
				placemark = booking.placemarks[i];
				if (!booking.promotions[i])
				{
					placemark.b_type = 'hotel';
					// We must build the GLatLng here because we obviously cannot in JSON.
					placemark.latLng = new GLatLng(placemark.b_latitude, placemark.b_longitude);
					delete placemark.b_latitude;
					delete placemark.b_longitude;
					placemark.icon_type = ((booking.env.b_action == 'searchresults') && (booking.hotelsMatchingCriteria[i])) ? 'hotel_available' : 'hotel_searchresults';
					placemark.url += booking.env.b_query_params_with_lang;
					booking.map.buildMarker(placemark);
				}
				else
				{
					delete booking.placemarks[i];
				}
			}
			// Call loadCurrentHotels() in featMapHeader.inc to ensure current hotels are shown.
			booking.loadCurrentHotels();
			// Focus the marker if a hotel Show Map link was clicked on a search results page.
			if (booking.map.display.subjectID)
			{
				booking.map.display.subject = booking.placemarks[booking.map.display.subjectID];
				booking.map.focus(booking.map.display.subject);
				delete booking.map.display.subjectID;
			}
		}
		else
		{
			booking.placemarks.hotelsonmap = 'failed; code ' + code + '.';
		}
	};
	booking.map.buildNode = function ()
	{
		var map = booking.map;
		map.featMap = document.getElementById('featMap');
		map.elementTree = new map.Div(booking.env);
		booking.utils.buildHtmlNode(map.elementTree, map.featMap.parentNode);
		map.container = map.elementTree.container();
		map.showMap = map.elementTree.showMap();
		map.show_map = map.elementTree.show_map();
		map.shadow = map.elementTree.shadow();
		map.element = map.elementTree.element();
		map.handle = map.elementTree.handle();
		map.close_map = map.elementTree.close_map();
	};
	booking.map.buildNode.description = 'Called from the body element descendant after which the map node should appear.';
	booking.map.Div = function(env)
	{
		this.div = {
			"@id" : "map",
			"@class" : booking.map.CLASS_PREVIEW,
			"h2" : {
				"@class" : "handle b_popupinner",
				"a" : {
					"@id" : "close_map",
					"@href" : "javascript:void(0);",
					"#text" : env.close_map
				},
				"#text" : env.map
			},
			"div" : [
				{
					"@id" : "google",
					"@class" : "b_popupInner"
				},
				{
					"@id" : "shadow"
				}
			],
			"p" : {
				"@id" : "showMap",
				"a" : {
					"@class" : "show_map",
					"@href" : "javascript:void(0);",
					"#text" : env.show_map
				}
			}
		};
	};
	booking.map.Div.prototype.close_map = function () { return this.div.h2.a._node; };
	booking.map.Div.prototype.handle = function () { return this.div.h2._node; };
	booking.map.Div.prototype.element = function () { return this.div.div[0]._node; };
	//booking.map.Div.prototype.element = function () { return this.div.div._node; };
	booking.map.Div.prototype.shadow = function () { return this.div.div[1]._node; };
	booking.map.Div.prototype.show_map = function () { return this.div.p.a._node; };
	booking.map.Div.prototype.showMap = function () { return this.div.p._node; };
	booking.map.Div.prototype.container = function () { return this.div._node; };
	booking.map.load = function ()
	{
		if (!booking.map.element)
		{
			return;
		}
		// Create the map object.
		booking.map.obj = new GMap2(booking.map.element);
		// Initialize the map by calling setCenter().
		booking.map.setCenter();
		if (gClientIsIE55)
		{
			booking.map.shadow.style.display = 'none';
		}
		booking.map.obj.enableContinuousZoom();
		booking.map.markerManager = new GMarkerManager(booking.map.obj);
		// Build a marker for the current page subject (built in featMapHeader.inc).
		if (booking.pageSubject)
		{
			booking.map.buildMarker(booking.pageSubject);
		}
		booking.map.initialize();
		booking.map.zoom = 
		{
			preview : booking.map.obj.getZoom()
		};
		var
			i,
			anchor,
			href;
		booking.map.anchors = document.getElementsByTagName('a');
		for (i = 0; i < booking.map.anchors.length; i++)
		{
			anchor = booking.map.anchors[i];
			if (anchor.className.indexOf('show_map') != -1)
			{
				var idIndex = anchor.id.search(/\d/);
				GEvent.addDomListener(anchor,
					'click',
					GEvent.callbackArgs(booking.map, booking.map.display, idIndex ? anchor.id.substring(idIndex) : null)
				);
				anchor.href = "javascript:void(0);";
			}
		}
		delete booking.map.anchors;
		// Identify those map elements we want to hide via CSS in preview mode.
		booking.map.element.anchors = booking.map.element.getElementsByTagName('a');
		for (i = 0; i < booking.map.element.anchors.length; i++)
		{
			anchor = booking.map.element.anchors[i];
			href   = anchor.getAttribute('href');
			if (href.indexOf('ct=api_logo') != -1)
			{
				anchor.className += ' api_logo';
			}
			if (href.indexOf('terms_maps')  != -1)
			{
				anchor.className += ' terms_maps';
			}
		}
		// causes error in IE7, Object doesn’t support this method: delete booking.map.element.anchors;
		booking.map.display.initialized = false;
		booking.map.close_map.removeAttribute('onclick');
		booking.map.close_map.href = 'javascript:void(0);';
		GEvent.addDomListener(booking.map.close_map, 'click', booking.map.close);
		// Make the map draggagle using dom-drag.js.
		Drag.init(booking.map.container);
		booking.map.container.hideSelectsUnderneath = function ()
		{
			booking.utils.ie.hideIntersectingElements(booking.map.container, booking.selects);
		};
		if (gClientIsIE && !gClientIsIE7)
		{
			booking.selects = document.getElementsByTagName('select');
			for (i = 0; i < booking.selects.length; i++)
			{
				booking.selects[i].style.position = 'relative';
			}
			booking.map.container.onDrag = booking.map.container.hideSelectsUnderneath;
			GEvent.addDomListener(window, 'resize', booking.map.container.hideSelectsUnderneath);
		}
	};
	booking.load = function ()
	{
		// To-do: implement logic to delay loading and ignore preview mode for small_layout affiliates.
		booking.map.load();
	};
	// Register our onload and onunload handlers.
	GEvent.addDomListener(window, 'load', booking.load);
	GEvent.addDomListener(window, 'unload', GUnload);
}

