/**
 * @fileOverview ListingMapSearch - Support for ListingMapSearch.cfm
 * Creates a singleton object, LMS, which encapsulates the
 * functionality required to interact with the Listing Search map.
 * 
 * @requires CustomMap.js (which requires GoogleMap.js)
 * @requires jquery.js
 * @requires jquery.form.js
 * @requires jquery.color.js
 * @requires json.js
 * @requires HNLSite.js (for popupFullWindow)
 * 
 * @author Larry Reinhard
 */
var LMS = function() {

	// A reference to a map manager
	var mapMgr;
	
	// Reference to jQuery objects
	var searchingMsg, resultMsg;
	var mainSearchForm;
	var infoWindow;
	var countResale, countNew, countOpen;

	// Tag names for marker collections
	var TAG_MYTAGS = 'mytag';
	var TAG_RESALE = 'resale';
	var TAG_NEWHOME = 'newhome';
	var TAG_OPENHOUSE = 'openhouse';
	var markersHidden = {
		mytag     : false,
		resale    : false,
		newhome   : false,
		openhouse : false
	};

	// the current search options
	var searchOpt = {};
	// The listing data for all of the active markers
	var markerData = {};
	var autoZoom = false;
	
	/**
	 * Formats the given number with commas separating groups of three digits
	 * 
	 * @param {Number}
	 *            value The number to be formatted
	 * @return {String} The formatted number
	 */
	function fmtNumberCommas(value) {
		value += '';
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(value)) {
			value = value.replace(rgx, '$1' + ',' + '$2');
		}
		return value;
	}

	/**
	 * Handles the submit button for the form-based search (which adds criteria)
	 */
	function submitForm() {
		
		// Reset search options
		searchOpt = {};

		// Collect new search options from form parameters
		$.each(mainSearchForm.get(0).elements, function(i,ele) {
			var val = $(ele).fieldValue().toString();
			if (val.length) {
				if (searchOpt[ele.name] == undefined)
					searchOpt[ele.name] = val;
				else
					searchOpt[ele.name] = searchOpt[ele.name] + ',' + val;				
			}
		});
		
		// new criteria, remove markers
		mapMgr.clearMarkers();
		markerData = {};

		// fire off submit via Ajax
		searchListings(mapMgr.getBounds());
		
		//prevent traditional submit
		return false;
	}
	
	/**
	 * Requests a search for listings based on geographical coordinates
	 * and other optional listing criteria
	 * 
	 * @param {Object} geo The boundary geo-coords: nLat,sLat,eLng,wLng
	 * @param {Object} [opt] Optional search criteria 
	 * 
	 * @private
	 */
	function searchListings(geo,opt) {

		$.extend(searchOpt,opt);

		hideResultMessage();
		infoWindow.hide();
		searchingMsg.show();
		
		$.ajax( {
			type : 'POST',
			url : '/ListingMapSearch.cfm',
			data : {
				action : 'SearchListings',
				nLat : geo.nLat,
				sLat : geo.sLat,
				eLng : geo.eLng,
				wLng : geo.wLng,
				params : JSON.stringify(searchOpt)
			},
			dataType : "json",
			success : handleSearchResponse
		});
	}

	/**
	 * Display the results of a listing search
	 * 
	 * @param {any} dp The data packet returned from the server via
	 * the Ajax request; dp.data should contain the data itself.
	 * 
	 * @param {string} textStatus The status of the Ajax request (
	 */

	function handleSearchResponse(dp, textStatus) {
		
		searchingMsg.hide();
		
		// update the value displayed in the Search Assistant
		$('#TotalListings')
			.text(fmtNumberCommas(dp.data.totalcount) + ' ')
			.css({ backgroundColor : "#FFFF64" }, 100)
			.animate({ backgroundColor : "white" }, 3000);
		
		if (dp.data.totalcount == 0) {
			countResale.text('0');
			countNew.text('0');
			countOpen.text('0');
		}

		if (dp.success) {
//			console.log('Found: '+ dp.data.totalcount + ' ' + dp.data.source);

			// place new markers on the map
			addMarkers(dp.data.query);
			mapMgr.trimMarkers();
			if (autoZoom) {
//				console.log('autoZoom');
				mapMgr.zoomToMarkers();
			}
			countResale.text(mapMgr.countMarkers(TAG_RESALE));
			countNew.text(mapMgr.countMarkers(TAG_NEWHOME));
			countOpen.text(mapMgr.countMarkers(TAG_OPENHOUSE));
		}
		else {
//			console.log('Failed: ' + dp.message + ' (' + fmtNumberCommas(dp.data.totalcount) + ' ' + dp.data.source +')');
			if (dp.message.match(/Too Many/i))
				showResultMessage("<h1>Found " + fmtNumberCommas(dp.data.totalcount) +
					" listings.</h1><p><em>There are too many listings to show on the map.</em></p>"+
					"<p>Zoom in to view fewer listings or modify your search criteria with the Search Assistant.</p>");
			else if (dp.message.match(/No Records/i))
				showResultMessage("<h1>Found " + fmtNumberCommas(dp.data.totalcount) +
					" listings.</h1><p><em>There are no listings to show on the map.</em></p>"+
					"<p>Zoom out to search a wider area or modify your search criteria with the Search Assistant.</p>");
			else
				showResultMessage("<h1>No Listings Found.</h1><p><em>An error occurred during the search:</em></p>"+
					"<p>"+dp.message+"</p>");

		}
	}

	/**
	 * Displays the result message and sets and timer to remove it from the screen
	 */
	function showResultMessage(msg) {
		resultMsg.html(msg).css({opacity: 1.0}).show().animate({opacity: 1.0}, 8000).fadeOut(500);
	}
	/**
	 * Stops any animation and hides the result message
	 */
	function hideResultMessage() {
		resultMsg.stop().hide();
	}
	
	function hideInfoWindow() {
		infoWindow.hide();
	}
	
	/**
	 * Builds and adds markers to the map from listing query data
	 * 
	 * @param {object} lq Listing Query data (an array of structs/objects)
	 */
	 function addMarkers(lq){
	 	
	 	var mrkrs = [];
	 	
	 	for (var i = 0; i < lq.length; i++) {
		 	var tagName = chooseTag(lq[i]);
	 		mrkrs.push({
	 			id       : lq[i].listingid,
	 			latitude : lq[i].latitude,
	 			longitude: lq[i].longitude,
	 			html     : null,
	 			title    : lq[i].lead,
	 			icon     : chooseMarkerIcon(lq[i]),
	 			tag      : tagName,
	 			hidden   : markersHidden[tagName],
	 			click    : handleMarkerClick
	 		});
	 		markerData[lq[i].listingid] = lq[i];
	 	}

		mapMgr.addMarkers(mrkrs);
	}
	
	/**
	 * Constructs HTML for a map marker's information popup
	 * (Deprecated)
	 * 
	 * @param {object} ld The listing data
	 */
	function buildMarkerInfoWindow(ld) {
		return 	'<table class="infoWindow"><tr><td><a href="/Listing.cfm?ListingId=' + ld.listingid +
	 			'"><img src="' + ld.image + '" /></a></td><td>' +
				(ld.price ? ('Price: $' + fmtNumberCommas(ld.price)) : '') +
				(ld.beds ? ('<br />Bed: ' + ld.beds) : '') +
				(ld.baths ? ('<br />Baths: ' + ld.baths) : '') +	 				
	 			'</td></tr></table>';
	 }
	
	/**
	 * Chooses the correct Marker icon and returns an object
	 * with the icon's metadata
	 * 
	 * @param {object} ld The listing data
	 */
	function chooseMarkerIcon(ld) {
		var icons = {
			resale : {
 				image:'/Images/Maps/Interactive/Pin/Resale.png',
 				shadow: '/Images/Maps/Interactive/Pin/Shadow.png',
 				iconsize: '23,34',
 				shadowsize: '41,34',
 				iconanchor: '12,34',
 				infowindowanchor: '12,12'
			},
			newhome : {
 				image:'/Images/Maps/Interactive/Pin/NewHome.png',
 				shadow: '/Images/Maps/Interactive/Pin/Shadow.png',
 				iconsize: '23,34',
 				shadowsize: '41,34',
 				iconanchor: '12,34',
 				infowindowanchor: '12,12'
			},
			openhouse : {
 				image:'/Images/Maps/Interactive/Pin/OpenHouse.png',
 				shadow: '/Images/Maps/Interactive/Pin/Shadow.png',
 				iconsize: '23,34',
 				shadowsize: '41,34',
 				iconanchor: '12,34',
 				infowindowanchor: '12,12'
			},
			mytag : {
 				image:'/Images/Maps/Interactive/Pin/MyTag.png',
 				shadow: '/Images/Maps/Interactive/Pin/Shadow.png',
 				iconsize: '23,34',
 				shadowsize: '41,34',
 				iconanchor: '12,34',
 				infowindowanchor: '12,12'
			},
			miniresale: {
 				image:'/Images/Maps/Interactive/Pin/miniResale.png',
 				shadow: '/Images/Maps/Interactive/Pin/miniShadow.png',
 				iconsize: '12,12',
 				shadowsize: '10,10',
 				iconanchor: '6,6',
 				infowindowanchor: '6,6'
			},
			mininewhome: {
 				image:'/Images/Maps/Interactive/Pin/miniNewHome.png',
 				shadow: '/Images/Maps/Interactive/Pin/miniShadow.png',
 				iconsize: '12,12',
 				shadowsize: '10,10',
 				iconanchor: '6,6',
 				infowindowanchor: '6,6'
			},
			miniopenhouse: {
 				image:'/Images/Maps/Interactive/Pin/miniOpenHouse.png',
 				shadow: '/Images/Maps/Interactive/Pin/miniShadow.png',
 				iconsize: '12,12',
 				shadowsize: '10,10',
 				iconanchor: '6,6',
 				infowindowanchor: '6,6'
			},
			minimytag: {
 				image:'/Images/Maps/Interactive/Pin/miniMyTag.png',
 				shadow: '/Images/Maps/Interactive/Pin/miniShadow.png',
 				iconsize: '12,12',
 				shadowsize: '10,10',
 				iconanchor: '6,6',
 				infowindowanchor: '6,6'
			}
		}

		if (ld.openhouse == 1) { return icons.openhouse; }
		else if (ld.newhome == 1) { return icons.newhome; }
		
		return icons.resale;
	}
	
	/**
	 * Returns the proper marker tag based on listing characteristics
	 * 
	 * @param {object} ld The listing data
	 */
	function chooseTag(ld) {
		if (ld.openhouse == 1) { return TAG_OPENHOUSE; }
		else if (ld.newhome == 1) { return TAG_NEWHOME; }
		
		return TAG_RESALE;
	}	

	/**
	 * Attach event handlers to the elements that toggle tags
	 */
	function prepTagToggleControls(tags) {
		$('#'+tags.lt).click( function () { handleTagToggleClick(this,TAG_MYTAGS) });
		$('#'+tags.lr).click( function () { handleTagToggleClick(this,TAG_RESALE) });
		$('#'+tags.ln).click( function () { handleTagToggleClick(this,TAG_NEWHOME) });
		$('#'+tags.lo).click( function () { handleTagToggleClick(this,TAG_OPENHOUSE) });
		countResale = $('#'+tags.lr).find('span');
		countNew = $('#'+tags.ln).find('span');
		countOpen = $('#'+tags.lo).find('span');
	}
	
	/**
	 * Updates map and controls; called on a click event
	 * 
	 * @param {Object} el The clicked DOM element
	 * @param {String} tag A tag name of the marker collection to affect 
	 */
	function handleTagToggleClick(el,tag) {
		if (markersHidden[tag]) {
			mapMgr.showMarkers(tag);
			$(el).find('a').text('HIDE');
			markersHidden[tag] = false;
		} else {
			mapMgr.hideMarkers(tag);
			$(el).find('a').text('SHOW');			
			markersHidden[tag] = true;
		}
	}
	
	/**
	 * Updates the map after a pan event.
	 */
	function panHandler() {
//		console.log('panHandler');
		if (autoZoom)
			// we receive a pan event after autoZoom which just 
			// followed a search, don't search again!
			autoZoom = false;
		else
			searchListings(mapMgr.getBounds());
	}
	
	function zoomHandler() {
//		console.log('zoomHandler');
		hideInfoWindow();
	}

	/**
	 * Handle the click event on the map
	 */
	function handleMapClick(overlay,point) {
		if (!overlay) {
			infoWindow.hide();
			hideResultMessage();
		}
	}
	/**
	 * Handle the click event on a marker
	 */
	function handleMarkerClick(id) {
		
		// compute marker anchor point
		var bnds=mapMgr.getBounds();
		var origin = mapMgr.xlateLatLngToPix(bnds.nLat,bnds.wLng);
		var ll = this.getLatLng();
		var pt = mapMgr.xlateLatLngToPix(ll.lat(),ll.lng());
		
		// build up listing information
		var ld = markerData[id];
		var url,heading;
		if (ld.eventid.toString().length) {
			url = "javascript:popupFullWindow('/OpenHouse.cfm?EventId=" + ld.eventid + "','Open_House_" + ld.eventid + "',700,600)";
			heading = 'Open House: <br />' + ld.price;
		} else {
			url = '/Listing.cfm?ListingId='+ id + '&ListingSource=' + ld.source + (ld.openhouse == 1 ? '&ListingAdditionalDetail=OpenHouse' : '');
            heading = '$' + fmtNumberCommas(ld.price);
		}
		var loc = ld.city;
		loc += ((loc.length && ld.state.length) ? ', ' : '') + ld.state + ' ' + ld.zip;
		var ao = ld.agentname;
		ao += ((ao.length && ld.officename.length) ? '<br />' : '') + ld.officename
		ao = ao.length ? ('<em>Offered By:</em><br />' + ao) : '';
		
		infoWindow
			.hide()
			.find('.CommonPanelHeader').html(heading)
			.end().find('.Location').text(loc)
			.end().find('.Photo a').attr('href',url)
			.end().find('.Photo a img').each(function(i) { ld.image.length ? $(this).attr('src',ld.image).show() : $(this).hide(); })
			.end().find('.AgentOffice').html(ao)
			.end().find('.BedBath').each(function (i) {	ld.bedbaths.length ? $(this).text(ld.bedbaths).show() :	$(this).hide();	})
			.end().find('.Buttons a').attr('href',url)
			.end().css('left',pt.x-origin.x).css('top',pt.y-origin.y)
			.show();
	}
	
	
	// public interface
	return {

		showDebug : false,

		/**
		 * Initializes the Listing Map Search object
		 * 
		 * @param {object} cfg Configuration parameters: 
		 * @cfg	{string or element} map: An ID name or DOM element; the map container
		 * @cfg	{object} zoom (see CustomMap.js constructor for details)
		 * @cfg	{object} pan (see CustomMap.js constructor for details)
		 * @cfg	{object} maptype (see CustomMap.js constructor for details)
		 * @cfg	{object} tags An object with these members:
		 *  	lt: The ID of the toggle "my tags" markers element
		 *  	lr: The ID of the toggle "resale" markers element
		 *  	ln: The ID of the toggle "new home" markers element
		 *  	lo: The ID of the toggle "open house" markers element
		 * @cfg	{object} start (see CustomMap.js init method for details)
		 * @cfg	{object} markers a single marker or array of marker objects
		 *  	(see GoogleMap.js addMarkers method for details)
 		 * @cfg	{object} message An object with these members:
		 *  	wm: The ID of the element that is displayed while a search is occurring
		 *  	rm: The ID of the element that displays a search result message
		 *  	iw: The ID of the custom Info Window popup
		 * @cfg {object} search 
		 * 		{string} sf The ID of the form element that is submitted for searching
		 *  	{object} params The initial search parameters (case is important!)
		 *  		MsaId: MSA code
		 *  		County: County FIPS code
		 *  		City: City name
		 *  		State: State code
		 *  		Zip: ZIP code 
		 */
		init : function(cfg) {

			// get a new Map Manager object
			mapMgr = new CustomMap( {
				zoom   : cfg.zoom,
				pan    : cfg.pan,
				maptype: cfg.maptype
			});
			
			// draw initial map
			mapMgr.init(cfg.map, cfg.start);

			// add markers
			if (cfg.markers)
				mapMgr.addMarkers(cfg.markers);
			
			// set up tag toggle controls
			if (cfg.tags)
				prepTagToggleControls(cfg.tags);

			// cache reference to custom info window
			infoWindow = $('#'+cfg.message.iw);
			infoWindow.find('div.close').click(hideInfoWindow);
			$('#'+cfg.pan.pn).click(hideInfoWindow);
			$('#'+cfg.pan.pe).click(hideInfoWindow);
			$('#'+cfg.pan.pw).click(hideInfoWindow);
			$('#'+cfg.pan.ps).click(hideInfoWindow);

			// cache reference to messsage elements
			searchingMsg = $('#'+cfg.message.wm);
			resultMsg = $('#'+cfg.message.rm);

			// link into search criteria form submit event
			mainSearchForm = $('#'+cfg.search.sf);
			mainSearchForm.submit(submitForm);
			
			// add map event handlers
			mapMgr.addMapClickCallback(handleMapClick);
			mapMgr.addZoomCallback(zoomHandler);
			mapMgr.addPanCallback(panHandler);
			mapMgr.addDragStartCallback(hideInfoWindow);
		
			// do initial search
			$.extend(searchOpt, cfg.search.params);
			if (cfg.start.auto)
				autoZoom = true;
			searchListings(mapMgr.getBounds());
	
		},
	
		/**
		 * Submits the nav form to change the listing search return view type 
		 */
		submitViewNav : function(viewSelector) {
			mainSearchForm
				.find('input[name=ViewType]').remove().end()
				.find('input[name=PerPage]').remove().end()
				.append('<input type="hidden" name="ViewType" value="'+  viewSelector.value + '" />')
				.append('<input type="hidden" name="PerPage" value="'+  (viewSelector.value === 'PhotoAlbum' ? '9':'9') + '" />')
				.get(0).submit();
			return false;
		},
		/**
		 * Submits the nav form to change the listing search return view type 
		 */
		submitViewNavType : function(view) {
			mainSearchForm
				.find('input[name=ViewType]').remove().end()
				.append('<input type="hidden" name="ViewType" value="'+  view + '" />')
				.get(0).submit();
			return false;
		}
	}
}(); // create singleton

