//<![CDATA[


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	SETUP GLOBAL VARIABLES FOR THE MAP SEARCH
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 



var map; // map object
var minimap; // holds minimap control object

var CRITICAL_ZOOM = 12; // minimum zoom level to go to when clicking a city marker.
var CURRENT_CENTERPOINT; // stores current center point as a GLatLng Object - 
// this is used when the results column opens and closes so the map can recenter itself after changing width

var LISTINGS_PER_PAGE = 10; // listings to show on each page of search results
var IMG_BASE_URL = "/images/gmap/"; // images in the script are referenced by this variable concatenated with their filename

// If true, this will prevent map movements from changing the bounds as a search parameter.
// Used when results column is open so you can view listing markers on the map for a given set of results
// without having your map movements change the set of results you're viewing.
var BOUNDS_LOCKED = false;
var MAP_RESET = false; // flag set to true only while map is being reset

var VIEWING_RESULTS_SET = false; // flag that gets set to true if you are in the context of a set of results
var TOTAL_LISTINGS = 0; // stores how many total listings in a given set of results

// determines what the map does onload
// if true, it will use the search parameters to set starting center point and zoom 
// otherwise the map will automatically set its zoom level so that all the city markers show on it
// this happens only on initial page load.
var SAVED_SEARCH = false;


// These MARKERS arrays store all the marker objects that are currently viewable on the map so they can be referenced from outside the map
// if they exist in these global variables, a matching marker will also exist on the map
var CITY_MARKERS = new Object();
var LISTING_MARKERS = new Object(); // js object acts as an associative array - indexed by listing_id (mls_id + "_" + mls_no)

var CURRENT_LISTING_ID = ""; // stores id of current listing
var CURRENT_PAGE; // stores current page in search results 

// stores current filter settings
// used to prevent a new db request if the search filter parameters haven't changed.
var FILTERS = new Object();


// Create a base city icon that specifies the shadow, icon dimensions, etc.
// This base icon style is used by all city markers
var baseCityIcon = new GIcon();
baseCityIcon.shadow = IMG_BASE_URL+"city_shdw.png";
baseCityIcon.iconSize = new GSize(27, 31);
baseCityIcon.shadowSize = new GSize(56, 31);
baseCityIcon.iconAnchor = new GPoint(17, 31);
baseCityIcon.infoWindowAnchor = new GPoint(19, 12);


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	CUSTOM MAP CONTROLS: Zoom Box Control
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

// define function
function zoomBoxCtrl() {}
// create an instance of the GControl object
zoomBoxCtrl.prototype = new GControl();

// called by the api when this control is added to the map
zoomBoxCtrl.prototype.initialize = function(map) {
	// If MSIE  we need to use AlphaImageLoader
	var agent = navigator.userAgent.toLowerCase();
	if ((agent.indexOf("msie") > -1) && (agent.indexOf("opera") < 1)) {this.ie = true} else {this.ie = false}

	// create the background graphic as a <div> containing an image
	var container = document.createElement("div");
	container.id = "ZoomBox";
	container.style.display = "none";

	// Handle transparent PNG files in MSIE
	if (this.ie) {
		var loader = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+IMG_BASE_URL+"zoom_box.png', sizingMethod='scale');";
		container.innerHTML = '<div style="' +loader+ '" ></div>';
	} else {
		container.innerHTML = '<img src="'+IMG_BASE_URL+'zoom_box.png" />';
	}
	container.innerHTML += '<span id="ZoomBoxText"><span id="ZoomBoxCount">n</span> listings<br />Zoom in to view them.</span>';
	
	// onclick zoom in the map
	GEvent.addDomListener(container, "click", function() {
		map.zoomIn();
	});
	
	// attach the control to the map
	map.getContainer().appendChild(container);
	return container;
}


// Set the default position for the control ==
zoomBoxCtrl.prototype.getDefaultPosition = function() {
	var ctrl_left_offset = Math.round(map.getSize().width/2) - 88; // 88 is half the zoombox width
	var ctrl_top_offset = Math.round(map.getSize().height/2) - 38; // 38 is half the zoombox height
	return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(ctrl_left_offset, ctrl_top_offset));
}

// recenter the control- can be called when the map changes size
// unfortunately there's not a map method for onresize, so we have to call this manually instead of
// using an event listener on the map
function repositionZoomBox() {
	var ctrl_left_offset = Math.round(map.getSize().width/2) - 88; // 88 is half the zoombox width
	var ctrl_top_offset = Math.round(map.getSize().height/2) - 38; // 38 is half the zoombox height
	$("ZoomBox").setStyle({
		left: ctrl_left_offset + 'px',
		top: ctrl_top_offset + 'px'
	});
}



// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	INTERACTIVITY FUNCTIONS
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

// toggles the specified tab and frame
function toggleFrame(frameid) {
	var tabObj = $("MapTab"+frameid);
	if (tabObj.className != 'map_tab_selected') { // don't do anything unless this tab is not yet selected.
		
		// TAB BEHAVIORS
		// deselect the currently selected tab
		$("MapSearchTabs").select('a.map_tab_selected').each(function(s) {
			s.className = "map_tab";
		});
		// select the new tab
		tabObj.className = "map_tab_selected";
		
		// FRAME BEHAVIORS
		if(frameid == "Results") {
			BOUNDS_LOCKED = true; // when the results frame is open, lock in the bounds
			LISTING_SLIDE_SHOW.each(function(ss) {
				ss.value.pause();
			});
			showMapAndResults();
			//$("mapAndResults").show();
			$("FilterBar").show();
			$("MapResultsBar").show();
			$("Map").show();
			
			// if toggling between detail and a results set, we want to maintain the page of results we were on
			// (don't refresh the results column)
			if ($("MapSearchDetails").visible() && VIEWING_RESULTS_SET) {
				// don't refresh listing results
			} else { // only refresh results if NOT coming from a listing detail
				refreshListingResults();
			}
			$("MapSearchDetails").hide();
			
			expandResults();
		}
		if(frameid == "Map") {
			BOUNDS_LOCKED = false; // after the results frame is closed, unlock the bounds
			VIEWING_RESULTS_SET = false; // no longer in context of a set of results
			LISTING_SLIDE_SHOW.each(function(ss) {
				ss.value.pause();
			});
			showMapAndResults();
			$("Map").show();
			$("FilterBar").show();
			$("MapResultsBar").show();
			$("MapMsg").show();
			$("ResultsMsg").hide();
			$("MapSearchDetails").hide();
			collapseResults();
			// show zoom box and city markers if they belong
			if (TOTAL_LISTINGS > 150) {
				$("ZoomBox").show();
			}
			// show city markers and get rid of any orphaned info windows and 
			// listing markers that may be open from the "locate" button
			if (TOTAL_LISTINGS > 150 || TOTAL_LISTINGS == 0) {
				showCityMarkers();
				for (x in LISTING_MARKERS) {
					GEvent.trigger(LISTING_MARKERS[x], "infowindowclose");
					map.removeOverlay(LISTING_MARKERS[x]);
					delete LISTING_MARKERS[x]; // remove from global array
				}
			}
		}
		if(frameid == "Details") {
			// show this stuff first, then hide the other stuff
			// keeps the page from jumping position
			$("MapTabDetails").show();
			$("MapSearchDetails").show();
			
			hideMapAndResults();
			$("FilterBar").hide();
			$("MapResultsBar").hide();
			collapseResults();
			$("Map").hide();
		}
		
	}
}

// using these functions as an alternative to the normal hide/show methods
// because the map tweaks out and thinks it has no listings (at least temporarily)
// if it is set to display:none and then reset to display:block
function hideMapAndResults() {
	$("MapAndResults").setStyle({
		height: '1px',
		visibility: 'hidden'
	});
}

function showMapAndResults() {
	$("MapAndResults").setStyle({
		height: '472px',
		visibility: 'visible'
	});
}



// triggers marker click event from outside of map
// selects marker and opens its info window
function findMarker(mls_id, mls_no, lat, lng, showdetails) {
	var listing_id = mls_id + "_" + mls_no;
	if (showdetails == true) {	
		// populate the details page with info then toggle it
		showMoreInfo(listing_id);
		toggleFrame("Details");
	}
	
	// zoom in to a minimum level where listings can start showing
	if (map.getZoom() < CRITICAL_ZOOM) {
		map.setZoom(CRITICAL_ZOOM);
	}
	
	// pan to the marker
	//map.panTo(new GLatLng(lat, lng));
	
	// check if the marker has been created and exists on the map
	// (not necessarily in the viewport of the map)
	if (!LISTING_MARKERS[listing_id]) {
		// if marker doesn't exist yet, create it.
		addListingMarker(mls_id, mls_no, lat, lng);
	}
	
	GEvent.trigger(LISTING_MARKERS[listing_id], "click");
}

// triggers marker mouseover event from outside of map
function mouseoverMarker(listing_id, lat, lng) {
	// if the marker exists and is showing on the map
	if (LISTING_MARKERS[listing_id] && isInBounds(lat, lng) && (map.getZoom() >= CRITICAL_ZOOM)) {
		GEvent.trigger(LISTING_MARKERS[listing_id], "mouseover"); // trigger the marker event which highlights both the marker and the left hand listing
	} else {
		highlightListing(listing_id); // just highlight the left hand listing
	}
}

// triggers marker mouseout event from outside of map
function mouseoutMarker(listing_id, lat, lng) {
	// if marker exists and is showing on the map
		if (LISTING_MARKERS[listing_id] && isInBounds(lat, lng) && (map.getZoom() >= CRITICAL_ZOOM)) { 
			GEvent.trigger(LISTING_MARKERS[listing_id], "mouseout"); // deselect both the marker and the left hand listing
		} else {
			deselectListing(listing_id); // just deselect the left hand listing
		}
}

// functions to highlight, select, and deselect left hand listing results
function highlightListing(id) {
	var mylisting = $(id);
	if(mylisting) { // if listing exists
		mylisting.className = "listing_tile_highlighted";
	}
}
function selectListing(id) {
	var mylisting = $(id);
	if(mylisting) {
		mylisting.className = "listing_tile_selected";
	}
}
function deselectListing(id) {
	var mylisting = $(id);
	if(mylisting) {
		mylisting.className = "listing_tile";
	}
}

// functions to expand and collapse the left hand column
function collapseResults() {
	$("MapSearchResults").hide();
	$("MapAndResults").setStyle({
		paddingLeft:'0px'				 
	});
	// Safari needs hack - when results column opens or closes, map does not properly resize - force it:
	// set Map width equal to container width
	if (Prototype.Browser.WebKit) {
		var mapwidth = $("MapAndResults").getWidth() + 'px';
		$("Map").setStyle({width:mapwidth});
	}
	map.checkResize();
	repositionZoomBox();
	//alert($F("SEARCH_centerpoint"));
	map.setCenter(CURRENT_CENTERPOINT);
}
function expandResults() {
	$("MapSearchResults").show();
	$("MapAndResults").setStyle({
		paddingLeft:'300px'			 
	});
	// Safari needs hack - when results column opens or closes, map does not properly resize - force it:
	// set map width to the container width minus the results column width (300px)
	if (Prototype.Browser.WebKit) {
		var mapwidth = ($("MapAndResults").getWidth()-300) + 'px';
		$("Map").setStyle({width:mapwidth});
	}
	map.checkResize();
	repositionZoomBox();
	//alert($F("SEARCH_centerpoint"));
	map.setCenter(CURRENT_CENTERPOINT);
}


// toggles filter menu
function toggleFilterMenu() {
	if(!$("FilterMenu").visible()) {
		$("FilterMenu").show();
		$("SEARCH_city").focus();
	} else {
		$("FilterMenu").hide();
	}
}




// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	MAP FUNCTIONS
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

// checks if a marker is in bounds
function isInBounds(lat, lng) {
	var thispoint = new GLatLng(lat, lng);
	var themap = map.getBounds();
	if (themap.contains(thispoint)) return true;
}

// resets the map to the original view - only city markers showing?
function resetMap() {
	MAP_RESET = true; // flag for use in refreshListingMarkers
	toggleFrame("Map"); // go to the map frame
	$("FilterMenu").hide();
	// get number values for lat, lng from the string
	var ctrpt = $F("avg_city_centerpoint").split(",");
	var myzoom = parseInt($F("avg_city_zoom"));
	// reset the starting centerpoint and zoom level for the map
	map.setCenter(new GLatLng(parseFloat(ctrpt[0]), parseFloat(ctrpt[1])), myzoom);
}

// show more info - uses ajax to populate a div with the listing details
function showMoreInfo(listing_id) {
	var loading_msg = "<div id='MapSearchDetailsLoading' style='display:block;'><img src='/images/anim_loading_icon.gif' /><br /><span id='MapSearchDetailsLoadingMsg'>Loading listing details... Please Wait</span></div>";
	
	// show the loading overlay while refreshing details
	$("MapSearchDetails").update(loading_msg);
//	$("MapSearchDetailsLoadingMsg").update("Updating details... please wait.");
	
	if(listing_id) { // if a listing_id is specified.
		// set its listing to selected in the sidebar
		selectListing(listing_id);
	} else { // if no listing is specified then exit the function
		alert("cannot show details because no listing was specified");
		return;
		// use whatever was last selected
		//selectListing(CURRENT_LISTING_ID);
	}
	
	// split listing id into mls parts - not optimal to do string manipulations here... pass through other params?
	var mlsparts = listing_id.split("_", 2);
	var mls_id = mlsparts[0];
	var mls_no = mlsparts[1];
	
	
	var details_url = "?";
	
	new Ajax.Request(details_url,
	{
		method:'post',
		parameters: {
			controller: 'ListingSearch',
			action: 'details',
			mls_id: mls_id,
			mls_no: mls_no,
			from: 'map_search'
		},
		//insertion: Insertion:bottom,
		onSuccess: function(transport) {
			var resultsHTML = transport.responseText;
			
			// check if ok to view a details page
			if (resultsHTML != "register") {
				// update the contents of the listing results column
				$("MapSearchDetails").update(resultsHTML);
				attachListingDetailEvents("map");
				attachSaveListingEvents();
				$$('img.sold').invoke('pngFix');
				
			// must register to view the details page - redirect to the appropriate page
			} else {
				$("MapSearchDetails").update("<div style='padding:20px; text-align:center; font-weight:bold;'>Please register or login to continue to view listing details.<br />Redirecting to the registration page now.</div>");
				window.location = "/Web/" + ACNT + "/WebUser/register/";
			}
		},
		onFailure: function() {
			$("MapSearchDetailsLoading").hide();
			alert("The ajax request failed - was trying to show the details for a listing.");
		}
	});
}


// creates a listing marker object
function createListingMarker(mls_id, mls_no, lat, lng) { 
	var point = new GLatLng(lat, lng);
	var listingmarker = new GMarker(point); // use default icon
	listingmarker.id = mls_id + "_" + mls_no;
	listingmarker.active = false;
	
	// attach events to the marker
	GEvent.addListener(listingmarker, "mouseover", function() {
		if(listingmarker.active != true) {
			listingmarker.setImage(IMG_BASE_URL+"marker_teal.png");
			highlightListing(listingmarker.id);
		}
	});
	GEvent.addListener(listingmarker, "mouseout", function() {
		if(listingmarker.active != true) {
			listingmarker.setImage(G_DEFAULT_ICON.image);
			deselectListing(listingmarker.id);
		}
	});
	// build the html to go in the info window
	GEvent.addListener(listingmarker, "click", function() {
		if(listingmarker.active != true) {
			CURRENT_LISTING_ID = listingmarker.id;
			listingmarker.active = true;
			listingmarker.setImage(IMG_BASE_URL+"marker_green.png");
			selectListing(listingmarker.id);
			// populate info window with summary content
			var infowindow_html;
			var infowindow_url="?";
			new Ajax.Request(infowindow_url,
			{
				method:'post',
				parameters:{
								controller: 'ListingSearch',
								action: 'details',
								mls_id: mls_id,
								mls_no: mls_no,
								infowindow: 1,
								from: 'map_search'
				},
				onSuccess: function(transport) {
					infowindow_html = transport.responseText;
					listingmarker.openInfoWindowHtml(infowindow_html);
					$("MapTabDetails").show();
				},
				onFailure: function() {
					alert("The ajax request failed - was trying to retrieve this listing's information to show in the info window.");
				}
			});
		}
	});
	GEvent.addListener(listingmarker, "infowindowclose", function() {
		if(listingmarker.active == true) {
			listingmarker.active = false;
			listingmarker.setImage(G_DEFAULT_ICON.image);
			deselectListing(listingmarker.id);
			// this listing has been deselected - hide details tab
			$("MapTabDetails").hide();
		} else {
			listingmarker.active = false;
		}
	});
	return listingmarker;
}

// refreshes results: changes the set of listings showing in the results column
function refreshListingResults(pagenumber) {
	// avoid making another db call if possible - only run the refresh if the search parameters have changed
	// by default, assume filter settings have not changed
	//var filter_has_changed = false;
	
	// show loading message while the ajax request is processing
	//$("MS_Results").update("Loading results... please wait.");
	
	
	// hide the zoombox cause it gets in the way (when more than 150 listings)
	$("ZoomBox").hide();
	// also hide the city markers if they are there
	hideCityMarkers();
	// change the message slightly
	$("MapMsg").hide();
	$("ResultsMsg").show();
	
	// default page is 1 if no pagenumber parameter is passed
	var mypage;
	if (pagenumber) {mypage = pagenumber;} else {mypage=1;}
	
	var filters = $("FilterForm").serialize(true); // figure out how to use this in ajax params along with other params
	
	// first check if a different page of results has been requested
	if (mypage != CURRENT_PAGE) {
		filter_has_changed = true;
	} else {
		// if page is the same, then check if search (filter) params have changed
		$H(filters).each(function(pair) {
			if ($H(FILTERS).get(pair.key) != pair.value) { // a filter value is different
				filter_has_changed = true;
				throw $break;
			}
		});
	}
	
	// if anything has changed allow the function to refresh the listing results
	//if (filter_has_changed == true) {
		// save the new filter settings
		saveGlobalFilters();
		// save current page
		CURRENT_PAGE = mypage;
		
		VIEWING_RESULTS_SET = true;
		
		// close any markers with their info window open? // not sure if this is good or not
		map.closeInfoWindow();
		
		// build the url for the ajax call
		//TODO: fix this ghettoness
		var ajax_url = "?";
		
		// if there are search parameters use them to build the query string
		var filters2 = $("FilterForm").serialize(); // collects form input values and urlencodes them
		ajax_url += filters2;
		
		new Ajax.Request(ajax_url,
		{
			method:'post',
			parameters: {
				controller: 'ListingSearch',
				action: 'results',
				//acnt: ACNT,
				SEARCH_viewport: $("SEARCH_viewport").getValue(),
				SEARCH__page__: mypage,
				SEARCH__lpp__: LISTINGS_PER_PAGE,
				map_results: 1,
				from: 'map_search'
			},
			//insertion: Insertion:bottom,
			onSuccess: function(transport) {
				var resultsHTML = transport.responseText;
				
				// update the contents of the listing results column
				$("MapSearchResults").update(resultsHTML);
			},
			onFailure: function() {
				$("MapSearchResults").update("Ajax failed");
				//alert("The ajax request failed - was trying to retrieve the listings that match your search to put in the results column.");
			}
		});
		
	//}
}

// adds a single marker to the map
// this is only used when: the results column is open and there are more than 150 listings on the map
function addListingMarker(mls_id, mls_no, lat, lng) {
	var listing_id = mls_id + "_" + mls_no;
	// check in the global array to see if a marker obj already exists for this listing_id
	// If not, add it to global array and to the marker manager
	if (!LISTING_MARKERS[listing_id]) { // marker does not yet exist
		// create a marker object for this listing
		var marker = createListingMarker(
			mls_id,
			mls_no,
			lat,
			lng
		);
		// store this listing/marker obj in a global obj (associative array) for future reference from outside the map
		LISTING_MARKERS[listing_id] = marker;
		map.addOverlay(marker);
	}
}

// refreshes markers: adds and removes them to/from the map
function refreshListingMarkers() {
	// ***** BUG TO FIX ********
	// the line below creates a bug either way.
	// if commented out there is a chance of an infinite loading message when you drag an open info window off screen.
	// OR, if uncommented, it makes the info window close on the marker you just selected - because when the map moves to fit the info window in,
	// the moveend event calls refreshListingMarkers, which closes the window.
	
	// first need to close any markers with their info window open
	// if they are left open, they will persist even if filtered out, allowing for possible duplication later?
	//map.closeInfoWindow();
	
	
	// first check for case of map reset
	if (MAP_RESET == true) {
		// reset the message bar and hide the zoom box
		$("MapMsg").update("To see listings, please choose a city or <a href='' onclick='map.zoomIn(); return false'>zoom in</a>.");
		$("ZoomBox").hide();
		
		// reset the filters and update the crumbtrail
		resetFilters();
		
		// show city markers and delete all of the listing markers
		showCityMarkers();
		for (x in LISTING_MARKERS) {
			map.removeOverlay(LISTING_MARKERS[x]);
			delete LISTING_MARKERS[x]; // remove from global array
		}
		
		$("MapSearchLoading").hide(); // hide the loading overlay
		MAP_RESET = false; // turn the flag back off
		return; // don't do anything else
	}
	
			
	// Only refresh markers if results are going to be different.
	// ie. if the search parameters have changed
	// (note: search params include the bounds of the map viewport)
	if (filtersHaveChanged()) {
		// save new filters
		saveGlobalFilters();
		
		// show the loading overlay while refreshing the markers
		$("MapSearchLoadingMsg").update("Updating map... please wait.");
		$("MapSearchLoading").show();
		
		// build the url for the json ajax call
		var json_url = "?";
		
		// if there are search parameters use them to build the query string
		var filters = $("FilterForm").serialize(); // collects form input values and urlencodes them
		json_url += "&"+filters;
		
		new Ajax.Request(json_url,
		{
			method:'post',
			parameters: {
				controller: 'ListingSearch',
				action: 'results',
				from: 'map_search'
			},
			onSuccess: function(transport) {
				var json = transport.responseJSON;
				var total_listings = json.total_listings;
				TOTAL_LISTINGS = total_listings;
				var listings = json.listings;
				
				$("ZoomBoxCount").update(total_listings);
				
				// if the total number of listings is too high, there won't be any listings in the listings array
				// instead, the array of listings will be set equal to zero
				// if too many listings, only update the total count of listings in the viewport
				if (listings == 0) {
					
					if (total_listings == 0) { // truly zero listings in the selected bounds
						$("MapMsg").update("<strong>" + total_listings + "</strong> listings in the current map area. Please <a href='' onclick='map.zoomOut(); return false'>zoom out</a> or select different search filters.");
						$("ZoomBox").hide();
					} else { // more than 150 listings in the selected bounds
						if (!$("ZoomBox").visible() && !BOUNDS_LOCKED) { // don't show the zoom box while in "Results" view
							$("ZoomBox").show();
						}
						$("MapMsg").update("<strong>" + total_listings + "</strong> listings in the current map area. Please continue to <a href='' onclick='map.zoomIn(); return false'>zoom in</a>.");
					}
				
					// show cities and delete all of the listing markers if there are too many to show
					// (or if we're resetting the map)
					if (!BOUNDS_LOCKED) { // but don't show city markers while in "Results" tab view
						showCityMarkers();
					}
					for (x in LISTING_MARKERS) {
						map.removeOverlay(LISTING_MARKERS[x]);
						delete LISTING_MARKERS[x]; // remove from global array
					}
					$("MapSearchLoading").hide();
					
				} else { // few enough listings to plot
					if ($("ZoomBox").visible()) {
						$("ZoomBox").hide();
					}
					
					$("MapMsg").update("<strong>" + total_listings + "</strong> listings showing on the map.");
					
					hideCityMarkers();
					
					// local variable: assoc. array of listing ids - identifies the new listings so we can filter out the old
					var batch = new Object;
					
					// First add any new listings
					$(listings).each(function(s) {		
						var listing_id = s.mls_id + "_" + s.mls_no;
						// add this listing_id to the batch
						batch[listing_id] = true;
						
						// check in the global array to see if a marker obj already exists for this listing_id
						// If not, add it to global array and to the marker manager
						if (!LISTING_MARKERS[listing_id]) { // marker does not yet exist
							// create a marker object for this listing
							var marker = createListingMarker(
								s.mls_id,
								s.mls_no,
								s.lat,
								s.lng
							);
							
							// store this listing/marker obj in a global obj (associative array) for future reference from outside the map
							LISTING_MARKERS[listing_id] = marker;
							map.addOverlay(marker);
						}
					});
					
					// now remove any extra listings
					// new loop through global array, check for listings that aren't in batch
					// and remove them (marker objs) from marker manager and global array 
					for (x in LISTING_MARKERS) {
						if (!batch[x]) {
							map.removeOverlay(LISTING_MARKERS[x]);
							delete LISTING_MARKERS[x]; // remove from global array
						}
					}
					
					// close loading message after the markers have been added/removed.
					$("MapSearchLoading").hide();
				}
			
			},
			onFailure: function() {
				alert('The ajax request failed - was trying to create/refresh listing markers on the map.');
				$("MapSearchLoading").hide();
			}
		});

	} // end if filters have changed
}

// creates city marker object with all its events
function createCityMarker(cityname, citypoint, listingcount) {
	var cityIcon = new GIcon(baseCityIcon);
	cityIcon.image = IMG_BASE_URL+"city_red.png";	
	var markerOptions = { icon:cityIcon, title:cityname };
	
	// create new marker object
	var citymarker = new GMarker(citypoint, markerOptions);
	
	// create html for it's info window
	citymarker.cityinfohtml = '<div class="cityname">'+cityname+'</div><div class="listingcount">'+listingcount+' listings</div>';
	
	// attach city marker events
	GEvent.addListener(citymarker, "mouseover", function() {
		citymarker.setImage(IMG_BASE_URL+"city_green.png");
		showCityInfo(citymarker); // show the city info window for this city
	});
	GEvent.addListener(citymarker, "mouseout", function() {
		citymarker.setImage(IMG_BASE_URL+"city_red.png");
		hideCityInfo();
	});
	GEvent.addListener(citymarker, "click", function() {
		map.closeInfoWindow(); // closes any listing info window that is open - keeps duplicates from being made
		hideCityInfo(); // hide the city info window
		
		// set city filter equal to this city
		FILTERS.city = cityname;
		$("SEARCH_city").value = cityname;
		
		// center map on the city and zoom in
		// If zoom is way out, bring it in to at least lvl 10
		//alert("search zoom: " + $('SEARCH_zoom').getValue() + ", crit zoom: " + CRITICAL_ZOOM);
		//if ($("SEARCH_zoom").getValue() < CRITICAL_ZOOM) {
			map.setCenter(citypoint, CRITICAL_ZOOM);
		//} else {
		//	map.setCenter(citypoint);
		//	map.zoomIn();
		//}
		
		// refresh the crumbtrail that displays which search criteria are in effect
		refreshCrumbtrail();
		
	});
	return citymarker;
}

// sets up city markers - called on map load
function setupCityMarkers() {
	$("MapSearchLoadingMsg").update("Loading Cities... please wait.");
	
	// base url for the ajax request
	//var json_url = "/idxv3/";
	//TODO: fix this ghettoness
	var json_url = "?";
	
	// do an ajax call to retrieve the cities
	new Ajax.Request(json_url,
		{
			method:'post',
			parameters: {
				controller: 'Search',
				action: 'get_cities',
				from: 'map_search'
			},
			onSuccess: function(transport) {
				var cities = transport.responseJSON;
				
				// if there are cities
				if (cities != "") {
					var cities_count = $(cities).size();
					var last_city_name = $(cities).last().name;
					var all_city_bounds = new GLatLngBounds();
					var all_city_zoom;
					
					var city_count = 0;
				
					function loadMarkerBatch(marker_batch) {	
						$(marker_batch).each(function(s, index) {
							// normalize city names by converting them to all caps (but just for the city markers array, not the display labels)
							var name_in_caps = s.name.toUpperCase();
							if (s.lat && s.lng && !CITY_MARKERS[name_in_caps]) { // if has lat,lng info and is not a dup
								
								var citypoint = new GLatLng(parseFloat(s.lat), parseFloat(s.lng));
								
								// check to see if this city point falls within the total bounds
								// if not extend bounds to contain it.
								if (!all_city_bounds.containsLatLng(citypoint)) {
									all_city_bounds.extend(citypoint);
								}
								
								var marker = createCityMarker(s.name, citypoint, s.latlng_cnt);
								CITY_MARKERS[name_in_caps] = marker;
								map.addOverlay(marker);
							
								city_count++;
								//var msg = city_count + ":" + s.name + " has been added to the map<br />";
								//$("CityMarkersCount").insert(msg);
							
							} else { // this city is not being added to the map
								city_count++;
								//var msg = city_count + ":" + s.name + " has NOT been added to the map<br />";
								//$("CityMarkersCount").insert(msg);
							
							}
							
							// when the last marker loads...
							if (last_city_name == s.name) {
								
								all_city_zoom = map.getBoundsZoomLevel(all_city_bounds);										
								$("avg_city_zoom").value = all_city_zoom;
								
								// if a saved search, refresh listings after cities have loaded
								if (SAVED_SEARCH == true) {
									refreshCrumbtrail();
									refreshListingMarkers();
									//SAVED_SEARCH = false;
								
								// else use the calculated zoom value and apply it to the map
								} else {
									MAP_RESET = true;	// this will prevent the map from trying to load listings when the map gets zoomed
									//map.setZoom(all_city_zoom);
									
									// city markers can be too compacted so zoom in one more level to spread them out a bit
									// tradeoff is that some of the fringe city markers will not show initially in the viewport
									map.setZoom(all_city_zoom+1);
									$("MapSearchLoading").hide();
									CITY_MARKERS_VISIBLE = true;
								}
							}
						});
					}
				
				
					// break up cities into batches of 20 and load them incrementally
					// every 0.75 seconds
					$(cities).eachSlice(20, function(citygroup, groupindex) {
						loadMarkerBatch.delay(groupindex*0.75, citygroup);
					});
				
				
				// case where there are no cities in the city list
				} else {
					// there are no cities - hide loading message
					MAP_RESET = true;	// this will prevent the map from trying to load listings when the map gets zoomed
					$("MapSearchLoading").hide();
					CITY_MARKERS_VISIBLE = true;
				}
				
				//$("MapSearchLoading").hide();
				//CITY_MARKES_VISIBLE = true;
				
			},
			onFailure: function() {
				alert('The ajax request failed - was trying to create city markers on the map.');
				$("MapSearchLoading").hide();
			}
		});
}

// hides all city markers
function hideCityMarkers() {
	//$(CITY_MARKERS).each(function(s) {
	//	s.hide();
	//});
	for (x in CITY_MARKERS) {
		$(CITY_MARKERS[x]).hide();
	}
	CITY_MARKERS_VISIBLE = false;
}

// shows all city markers
function showCityMarkers() {
	//$(CITY_MARKERS).each(function(s) {
	//	s.show();
	//});
	for (x in CITY_MARKERS) {
		$(CITY_MARKERS[x]).show();
		
	}
	CITY_MARKERS_VISIBLE = true;
}

// positions city info window, populates it with the correct info, and shows it
function showCityInfo(marker) {
	cityinfowindow.innerHTML = marker.cityinfohtml;
	var point = map.getCurrentMapType().getProjection().fromLatLngToPixel(map.getBounds().getSouthWest(), map.getZoom());
	var offset = map.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(), map.getZoom());
	var iconanchor = marker.getIcon().iconAnchor;
	var width = marker.getIcon().iconSize.width;
	var pos = new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(offset.x - point.x - iconanchor.x + width, -offset.y + point.y + iconanchor.y)); 
	pos.apply(cityinfowindow);
	cityinfowindow.show();
}

// hides the city info window
function hideCityInfo() {
	cityinfowindow.hide();
}



// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	FILTER FUNCTIONS
//	- for now, filtering (on the map) takes place every time any input is changed.
//	- * filtering for the results column only happens when you toggle the Results tab.
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

// saves last state of filters
function saveGlobalFilters() {
	// collects all filter form data and creates a hash of input/value pairs
	FILTERS = $("FilterForm").serialize(getHash=true);
}

// checks if the filters have changed
function filtersHaveChanged() {
	// assume that nothing has changed
	var changedStatus = false;
	// check for changes
	var filters = $("FilterForm").serialize(true); // returns a hash
	// compare the current filter params against the last saved filters (stored in the GLOBAL filters object)
	$H(filters).each(function(pair) {
		if ($H(FILTERS).get(pair.key) != pair.value) { // a filter value is different
			// instead of true, return the index of the first field that did not match
			changedStatus = pair.key;
			//changedStatus = true;
			throw $break; // it only takes one thing different
		}
	});
	return changedStatus;
}

// resets filters (and then calls refreshCrumbtrail)
function resetFilters() {
	$("SEARCH_city").value = '';
	$("SEARCH_minprice").options[0].selected = true;
	$("SEARCH_maxprice").options[0].selected = true;
	$("SEARCH_bedrooms").options[0].selected = true;
	$("SEARCH_baths").options[0].selected = true;
	$("SEARCH_sqft").options[0].selected = true;
	$("FilterMenu").select('input.checkbox').each( function(cb) {
		if (cb.hasClassName('default')) {
			cb.checked = "checked";
		} else {
			cb.checked = false;
		}
	});
	refreshCrumbtrail();
}

// refreshes crumbtrail - shows which search filters are currently being used
function refreshCrumbtrail() {
	// go through the input fields and update the crumbtrail to show what has been selected.
	// we'll do this explicitly for now...
	
	// declare local vars to be used
	var city, minprice, maxprice,  bedrooms, baths, sqft, type;
	
	// get values
	city = $("SEARCH_city").getValue();
	minprice = $("SEARCH_minprice").options[$("SEARCH_minprice").selectedIndex].text;
	maxprice = $("SEARCH_maxprice").options[$("SEARCH_maxprice").selectedIndex].text;
	bedrooms = $("SEARCH_bedrooms").options[$("SEARCH_bedrooms").selectedIndex].text;
	baths = $("SEARCH_baths").options[$("SEARCH_baths").selectedIndex].text;
	sqft = $("SEARCH_sqft").options[$("SEARCH_sqft").selectedIndex].text;
	type = $("FilterMenu").select('input.checkbox:checked').pluck('value').join(', ');
	
	// write values to crumbtrail
	$("CRUMB_city").update("city:&nbsp;<strong>"+city+"</strong>;&nbsp; ");
	$("CRUMB_minprice").update("min.&nbsp;price:&nbsp;<strong>"+minprice+"</strong>;&nbsp; ");
	$("CRUMB_maxprice").update("max.&nbsp;price:&nbsp;<strong>"+maxprice+"</strong>;&nbsp; ");
	$("CRUMB_bedrooms").update("beds:&nbsp;<strong>"+bedrooms+"</strong>;&nbsp; ");
	$("CRUMB_baths").update("baths:&nbsp;<strong>"+baths+"</strong>;&nbsp; ");
	$("CRUMB_sqft").update("sq.&nbsp;ft.:&nbsp;<strong>"+sqft+"</strong>;&nbsp; ");
	$("CRUMB_type").update("property type:&nbsp;<strong>"+type+"</strong>;&nbsp; ");
	
	// only show if an explicit value is picked (not the default)
	if (city == "") {
		$("CRUMB_city").hide();
	} else {
		$("CRUMB_city").show();
	}
	
	if (minprice == "No Min.") {
		$("CRUMB_minprice").hide();
	} else {
		$("CRUMB_minprice").show();
	}
	
	if (maxprice == "No Max.") {
		$("CRUMB_maxprice").hide();
	} else {
		$("CRUMB_maxprice").show();
	}
	
	if (bedrooms == "Any") {
		$("CRUMB_bedrooms").hide();
	} else {
		$("CRUMB_bedrooms").show();
	}
	
	if (baths == "Any") {
		$("CRUMB_baths").hide();
	} else {
		$("CRUMB_baths").show();
	}
	
	if (sqft == "Any") {
		$("CRUMB_sqft").hide();
	} else {
		$("CRUMB_sqft").show();
	}
	
	if (type == "Any") {
		$("CRUMB_type").hide();
	} else {
		$("CRUMB_type").show();
	}
}



// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	BODY ONLOAD FUNCTION
//	- map, controls, and city markers get created on page load
//	- events get attached to all page elements
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
function load() {
	if (GBrowserIsCompatible()) {
		// show loading message while map initializes
		$("MapSearchLoadingMsg").update("Map is loading... please wait.");
		$("MapSearchLoading").show();
		
		var starting_zoom;
		var starting_centerpoint;
		//var starting_type = G_HYBRID_MAP;
		var starting_type = G_NORMAL_MAP;
		
		// Starting point is determined in the Map Search Controller
		// If coming from a saved search or back to the page from a login
		// it uses values from the search params stored in session, or else it calculates it from MLS Data available
		if ($F("SEARCH_centerpoint") != "" && $F("SEARCH_zoom") != '') { // this is a saved search
			STARTING_CENTERPOINT = $F("SEARCH_centerpoint");
			starting_zoom = parseInt($F("SEARCH_zoom"));
			SAVED_SEARCH = true;
		} else {
			STARTING_CENTERPOINT = $F("avg_city_centerpoint"); // the avg lat,lng value of all city markers in MLS Data
			starting_zoom = 8; // set this to a reasonable level for now, and then when cities load,
			// it will adjust the map to fit all the city data in.
		}
		
		// get number values for lat, lng from the string
		var ctrpt = STARTING_CENTERPOINT.split(",");
		
		// initialize the map object
		map = new GMap2($("Map"));
		
		// set the starting centerpoint and zoom level for the map
		// this needs to be done before any thing else with the map
		map.setCenter(new GLatLng(parseFloat(ctrpt[0]), parseFloat(ctrpt[1])), starting_zoom, starting_type);
		
		// add map controls
		map.addControl(new GLargeMapControl()); // regular large zoom/pan control
		//map.addControl(new GMenuMapTypeControl());
		map.addControl(new GMapTypeControl()); // control buttons for map view type: eg. Map, Hybrid, Satellite
		//minimap = new GOverviewMapControl();
		//map.addControl(minimap); // the minimap box in the lower right
		//minimap.hide(); //start with the minimap collapsed
		map.addControl(new zoomBoxCtrl()); // custom zoom box control - shows how many listings on map and zooms in onclick
		
		// create custom info window element for reference by city markers
		// this single element is repurposed for all city markers
		cityinfowindow = new Element('div', {'id':'CityInfo'}).hide();
		$("Map").insert(cityinfowindow);
		
		// Attach Events to the Map itself
		GEvent.addListener(map, "zoomend", function() {
			CURRENT_CENTERPOINT = map.getCenter();
			
			$("zoom_level").innerHTML = map.getZoom();
			$("ctr_coords").innerHTML = map.getCenter().toUrlValue();
			$("bounds_coords").innerHTML = map.getBounds();
			
			// saves last viewport values before the results are locked in
			if (BOUNDS_LOCKED == false) {
				$("SEARCH_viewport").value = map.getBounds();
				
				$("SEARCH_centerpoint").value = map.getCenter().toUrlValue();
				$("SEARCH_zoom").value = map.getZoom();
			}
		});
		
		GEvent.addListener(map, "moveend", function() {
			CURRENT_CENTERPOINT = map.getCenter();
			
			$("ctr_coords").innerHTML = map.getCenter().toUrlValue();
			$("bounds_coords").innerHTML = map.getBounds();
			
			// saves last viewport values before the results are locked in
			if (BOUNDS_LOCKED == false) {
				$("SEARCH_viewport").value = map.getBounds();
				
				$("SEARCH_centerpoint").value = map.getCenter().toUrlValue();
			}
			
			refreshListingMarkers();
		});
				
		// INITIALIZE these values the first time the map loads
		CURRENT_CENTERPOINT = map.getCenter();
		
		$("SEARCH_viewport").value = map.getBounds();
		$("SEARCH_centerpoint").value = map.getCenter().toUrlValue();
		$("SEARCH_zoom").value = map.getZoom();
		
		$("zoom_level").innerHTML = map.getZoom();
		$("bounds_coords").innerHTML = map.getBounds();
		$("ctr_coords").innerHTML = map.getCenter().toUrlValue();
		
		// show coordinates of cursor on click
		GEvent.addListener(map, "click", function(overlay, point) {
			var coordinates;
			if (!overlay) { // normal map
				coordinates = point;
			} else {
				if (!overlay.getLatLng) { // for non-marker overlays
					coordinates = point; // this is undefined but doesn't throw an error
				} else { // for markers
					coordinates = overlay.getLatLng();
				}
			}
			$("click_coords").innerHTML = coordinates;
		});
		
		// attach events to the page elements
		attachMapSearchEvents();
		
		// add the city markers after a 3 second delay
		// gives the map time to load (map tile graphics may still be loading)
		setupCityMarkers.delay(3);
		//refreshListingMarkers.delay(3);
	}	
}


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
// 	ATTACH EVENTS
//	On page load, this attaches all the necessary events to elements on the page
//
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

function attachMapSearchEvents() {
	
	// ATTACH TAB EVENTS
	$('MapTabResults').observe('click', function(event) {
		Event.stop(event); // stop link from resolving
		toggleFrame('Results');
	});
	$('MapTabMap').observe('click', function(event) {
		Event.stop(event); // stop link from resolving
		toggleFrame('Map');
	});
	$('MapTabDetails').observe('click', function(event) {
		Event.stop(event); // stop link from resolving
		toggleFrame('Details');
		showMoreInfo(CURRENT_LISTING_ID);
	});
	// resets map and filters to starting position
	$('BtnResetMap').observe('click', function(event) {
		Event.stop(event); // stop link from resolving
		resetMap();
	});
	
	
	

	// ATTACH FILTER EVENTS
	// filter menu opens onclick
	$('BtnFilterMenu').observe('click', function(event) {
		toggleFilterMenu();
	});
	$('BtnCloseFilterMenu').observe('click', function(event) {
		Event.stop(event);
		toggleFilterMenu();
	});
	// property type inputs - make "Any" an exclusinve choice from the other options
	$("FilterMenu").select('input.checkbox').each( function(cb) {
		cb.observe("click", function() {
			if (cb.checked) {
				if (cb.value == "Any") {
					$("FilterMenu").select('input[value!="Any"]').each( function(cb2) {
						cb2.checked = false;
					});
				} else {
					$("FilterMenu").select('input.any').first().checked = false;
				}
			}
		});
		
	});
		
	// button applies filters and refreshes listing markers (also results if results column is open)
	$('BtnRefreshResults').observe('click', function(event) {
		toggleFilterMenu(); // close the filter menu
		refreshCrumbtrail(); // update the crumbtrail with the currently applied searches
		// if a new city was entered, trigger the click event for that city marker - this will automatically call
		// refreshListingMarkers()
		if (filtersHaveChanged() == 'SEARCH_city' && $("SEARCH_city").value != '') {
			// use underscores instead of spaces in the city name used as the marker index
			//var city_marker_index = $("SEARCH_city").value.gsub(' ','_');
			var city_marker_index = $("SEARCH_city").value.toUpperCase();
			if (CITY_MARKERS[city_marker_index]) {
				GEvent.trigger(CITY_MARKERS[city_marker_index], "click");
			}
		} else {
			refreshListingMarkers();
		}
		
		// if results column is open, we will also need to refresh the listings in the results column
		if ($("MapSearchResults").visible()) {
			refreshListingResults();
		}
		Event.stop(event); // keep form from posting?
	});
	
}


Event.observe(window, 'load', function() {
	load();
});
Event.observe(window, 'unload', function() {
	GUnload();
});

	
//]]>
