/*
   Global js functions.
   One big B.object to keep things tidy.
   document.ready is below.
*/
var n = 0;
var footerScrolled = false;
var B = {
	settings: {
		context: "mobile",
		analytics: true,
		ajax: true,
		debug: true,
		hash: window.location.hash,
		local: document.getElementsByTagName('html')[0].getAttribute("lang"),
		JSONContentPath: '/layout/templates/api_cache/',
		// Location for raw JSON api returns. for things like categories, error messages, translation strings...
		baseURL: window.location.protocol + "//" + window.location.host,
		basePath: $('meta[name=linkpath]').length > 0 ? $('meta[name=linkpath]').attr("content") : "",
		//hopefully going away once we get rid of /mobile/ and /html/
		uagent: navigator.userAgent.toLowerCase(),
		// http://www.zytrax.com/tech/web/mobile_ids.html
		height: $(window).height(),
		docHeight: $(document).height()
	},

	// run this on document.ready.
	// we only set this stuff once on page/app load. in ajax land, this matters.
	init: function (e, ui) {

		// request location on homepage
		if ($('.ui-page-active').is('#screen-get-location-front')) {
			this.user.getLocation();
		}

		// if you end up on the find page with a category id, we should expand that one.
		if ($('.ui-page-active').is('#screen-find') || $('body').is('.page-find')) {
			if (window.location.href.indexOf('bl_category-id') > 0) {
				var catIDFromURL = B.tools.getURLparam("bl_category-id");
				this.resources.showSubCatsfromID(catIDFromURL);
			}
		}


		// this.resources.getCats();
		
		// set some default functional support classes
		this.tools.support();

		this.tools.ie6_is_terrible();

		// hook up the tracking
		B.tools.analytics();

		// alert people before they leave the signup form unfinished - even if they've not entered a thing.
		B.user.signupFormExitAlert();

		// alert people before they leave the add resource or settings form unfinished. This only fires if at least one field has been changed.
		// particularly important with mobile tapping and fixed footers
		$('#add-resource-form, #settings-form').incompleteFormWarning();

		// confirm and cancel on logout links
		$('.logout').live('click tap', B.user.logout);
		$('.logout-cancel').live('click tap', B.user.logoutCancel);

		// we need this to unobfuscate emails
		String.prototype.reverse = function () {
			return this.split("").reverse().join("");
		};

		B.tools.emailFix();

		B.tools.hideFlashMessages();

		

		if (B.settings.context == "mobile") {

			this.map.init(e, ui);

			B.tools.maintenanceMode();

			// TODO, move to listenerINIT or something. there will be more.
			$('.results-view-map, .detail-map-link').live('click tap', function (e) {
				if ($(this).is('.inactive')) {
					e.preventDefault();
					return false;
				}
				//no history version
				//$.mobile.changePage($('#page-map'), "slidedown", false, false);
				$.mobile.changePage($('#page-map'), "slidedown", false, false);

				return false;
			});

			// iOS wants to make this a link and so colors it incorrectly and fails when linking. 
			$('time').css('color', 'white').click(function (e) {
				e.preventDefault();
				
				return false;
			});

/*
			TEMPORARY FIX FOR https://bugs.one-economy.com/jira/browse/BL-220
		 */
			if ($.mobile.ajaxEnabled === false) {
				$('#page-map .results-view-toggle, #page-map .screen-header .back').live('click', function (e) {
					//we need the back buttons to just flip to the already loaded dom element.
					$.mobile.changePage($('#primary .ui-page:eq(0)'), "slideup", false, true);
					e.preventDefault();
					return false;
				});
			}
/*
			END TEMP FIX
		 */
		} // mobile context check
		// B.initComplete = true;
	},

	// event hander for $.mobile.pagecreate - we want this stuff to run anytime a new page is added to the DOM
	bootstrap: function (event, ui) {

		var $target = B.settings.context == "mobile" ? event.target : $('body');

		// adds a close link to flash messages
		// TODO, this will add more than one if already loaded. Add check to plugin? Add class to .flash when added and check here?
		$('.flash').each(function () {
			closer($(this));
		});

		// set up any new tabs
		B.tools.tabsInit($target);

		if (B.settings.context == "mobile") {

			$('div').live('pagehide', function (event, ui) {
				// Not needed in the non-ajax version
				// make sure cached versions of our pages don't show the old flash message.
				$(this).find('.flash').remove();
				$(this).find('.active-item').removeClass('active-item');
			});


			// help, I need somebody
			$('a.help').click(this.tools.toggleHelp);
			$('.help-screen').hide().live('click', function () {
				$(this).hide();
			});

			B.user.leaderBoardAccordionInit();

		}

		//$("li[data-role*='collapsible']").live("expand collapse", B.tools.collapsingFooterFix);
		if ($('#add-resource-form').length) {
			B.tools.validateAddResourceForm();
			$('<div id="charlimitinfo"></div>').insertAfter('#res_description');
			$('#res_description').keyup(function(){
				B.tools.limitChars($(this)[0], 1000, 'charlimitinfo');
			});
		}
		if ($('#contact-form').length) {
			B.tools.validateContactForm();
		}
		if ($('#work-here-form').length) {
			$("#work-here-form").bind('submit', B.tools.validateWorkHereForm);
		}

		// when a user clicks 'use my location' we need to clear out some stuff or we'll be in infinite loop land
		$('.get-location-trigger').live('click', function () {
			B.user.clearLocation();
		});


		// asynchronous interactions: like, helpful, remove and related stuff
		$('.res-like, .comment-helpful, .comment-remove-helpful').bind('click tap', this.resources.countButton);

		$('.comment-remove-button').bind('click tap', this.resources.commentRemove);

		// because browsers are dumb.
		$('.ui-mobile [data-role=page]').css('min-height', B.height - 40);


	},

	user: {
		// we'll fill this later with location.lat/lon. 
		location: {},

		// these locations methods are a mess of 'wait, what are we doing?'
		getLocation: function (user) {


			// do we already have a location?
			if (B.tools.readCookie('beelocal_has_localized') == "1") { //B.tools.readCookie('beelocal_location') !== null || 
				return;
			} else if (navigator.geolocation) {
				if (user == "user") {
					//when the user initiates the 'get location', we want to handle errors differently. Feel bad but it works.
					navigator.geolocation.getCurrentPosition(this.positionSuccess, this.positionFailureUser, {
						enableHighAccuracy: true,
						maximumAge: 300000
					});
				} else {
					navigator.geolocation.getCurrentPosition(this.positionSuccess, this.positionFailure, {
						enableHighAccuracy: true,
						maximumAge: 300000
					});
				}
			} else {
				//Maxmind
				//show form?
			}
		},
		// getLocation
		localizePHP: function (lat, lon) {
			$.ajax({
				type: 'GET',
				url: B.settings.baseURL + '/json/cangeolocate',
				data: {
					lat: lat,
					lon: lon
				},
				success: function (ret) {

					//$.mobile.changePage(ret);
					window.location = ret;
				}
			});
		},
		setLocation: function (lat, lon) {

			if (lat) {
				B.user.location.lat = lat;
				B.user.location.lon = lon;

				B.tools.createCookie('beelocal_location', [lat, lon], 365);
				B.user.localizePHP(lat, lon);

				return true;
			} else {
				// do something better? window.location = form?
				return false;
			}

			// store it in a cookie
			//B.tools.createCookie('beelocal_location', [position.coords.latitude, position.coords.longitude], 365);
			return true;
		},
		clearLocation: function () {
			if (B.tools.readCookie('beelocal_location') !== null) {
				B.tools.eraseCookie("beelocal_location");
			}
			if (B.tools.readCookie('beelocal_has_localized') !== null) {
				B.tools.eraseCookie("beelocal_has_localized");
			}
			B.user.getLocation('user');
		},
		//success and fail handlers for navigator.geolocation.getCurrentPosition. separated so we can also get it from the php cookie.
		positionSuccess: function (position) {
			B.user.setLocation(position.coords.latitude, position.coords.longitude);
		},
		positionFailure: function (error) {
			// we only show the alert if the user tried to geolocate but the browser was unable to find them
			if (error.code == 2) {
				alert(_t["geolocationError"]);
			}
		},
		positionFailureUser: function (error) {
			// allowed but we can't find you!
			if (error.code == 2) {
				alert(_t["geolocationError"]);
			}
			// NO, creepy app, you can't know where I'm at.
			if (error.code == 1) {
				alert(_t["geolocationError"]);
			}

		},
		isLocalizedCheck: function (e) {
			if (B.user.serverLocalized) { // inserted into footer by twig
				return true;
			} else {
				return B.tools.readCookie('beelocal_has_localized');
			}
		},

		// TODO movetomobile
		leaderBoardAccordionInit: function (e) {
			// leaderboard accordion. Were initially using jqm collapsible but that still has bugs. Hand rolled version buil
			// to key off the same events as the jqm version so it can be replaced once they're out of alpha.
			// $(".top-contributors li[data-role*='collapsible']").live("expand collapse", this.tools.leaderboardTextToggle);
			// update the text on show/hide. this remains even if li[data-role*='collapsible'] comes back.
			$(".top-contributors li").live("expand collapse", B.user.leaderboardTextToggle);

			//the li[data-role*='collapsible'] replacement
			$(".top-contributors li").live("expand collapse", B.user.leaderboardAccordion);
			$('.top-contributors ol').hide();

			var accEvent = B.settings.context == "mobile" ? "tap" : "click";

			$('.top-contributors .fold-toggler').bind(accEvent, function () {
				var $this = $(this);
				if ($this.is('.expanded')) {
					$this.parents('li').trigger('collapse');
				} else {
					$this.parents('li').trigger('expand');
				}
				$(this).toggleClass('expanded').parents('.list-toggler').toggleClass('expanded');

			});
		},

		leaderboardAccordion: function (e) {
			$(e.target).find('ol').slideToggle();
			return false;
		},

		leaderboardTextToggle: function (e) {
			var $t = $(e.target);
			// what if e is not the li?
			if (e.type == "expand") {
				$t.find(".toggle-bees-knees-top-10").text(_t["hideOthers"]);
			} else {
				$t.find(".toggle-bees-knees-top-10").text(_t["showOthers"]);
			}

		},
		// logout and logoutCancel just show/hide an 'are you sure?' link when some one clicks logout.	 
		logout: function (e) {
			var t = $(e.target),
				// jqm wrapping means the target may be a <span> inside of the <a>. we need the <a>
				$target = t.is('a') ? t : t.closest('a'),
				url = $target.attr('href');

			var confirmText = '<p class="confirm">' + _t['areYouSure'] + ' <a href="' + url + '">' + _t['yes'] + '</a> <a href="#" class="logout-cancel">' + _t['cancel'] + '</a></p>';
			$target.hide().after(confirmText);
			if ($target.parents('.user-nav').length) {

			} else {

			}
			e.preventDefault();
			return false;
		},

		logoutCancel: function (e) {
			$(e.target).parent('p').hide().prev('a').show();
			e.preventDefault();
			return false;
		},

		// alert people before they leave the signup form unfinished
		signupFormExitAlert: function (e) {
			if ($('#screen-signup').is(':visible')) {

				var alertSet = false;

				if (!alertSet) {
					$('body').delegate("a", "click", function (e) {

						if (!$(this).is('.lang-link')) {
							var omg = confirm(_t['signupFormExitConfirm']);

							if (!omg) {
								$('.ui-btn-active').removeClass('ui-btn-active');
								e.preventDefault();
								e.stopPropagation();
								e.stopImmediatePropagation();
							}
						}

					});
					alertSet = true;
				}
			}

		}


	},
	// end user
	resources: {
		// json object of resource locator cats. used mainly for building the variable selects (buildSelects) on the add form page. 
		// Could also be used to create the find/browse listing instead of remote load.
		cats: {},

		// for like, unlike, helpful and unhelpful buttons
		// parens are on .count (2) and are added via css :before/:after but if the total is 0, 
		// there is no number and we need to add a class to tell CSS not to add the parens.
		// this function is a DOM manipulation mess. Jesus, I'm sorry. HTML for the buttons 
		// should be normalized and wrapped in div.count-button or similar..
		countButton: function (e) {

			var t = $(e.target),
				// jqm wrapping means the target may be a <span> inside of the <a>. we need the <a>
				$target = t.is('a') ? t : t.closest('a'),
				$label = $target.find('.label'),
				$count = $target.find('.count').length ? $target.find('.count') : $target.prev('.count'),
				cur = $count.text() * 1,
				url = $target.attr("href"),
				newTotal, newText;
			url = url.split("?");

			//if they need to login first, the url will say so. let's just go there.	
			if (url[0].indexOf('login') > 0) {
				return true;
			}

			$count.addClass('loading');

			// php outputs the correct url of each interaction and is aware if it should be incremented or decremented in the db.
			$.get(B.settings.baseURL + url[0], url[1], function (ret) {
				// just in case php says to go to login
				if (ret.redirect) {
					window.location = B.settings.baseURL + B.settings.basePath + ret.redirect;
				} else if (ret.status.id == "SUCCESS") {
					//for the helpful buttons, php outputs two links, one is initially hidden.
					if ($target.is('.comment-helpful')) {
						newTotal = B.tools.countSet(cur, "inc");
						$target.parent('.helpful').hide().prev('.helpful').show().find('.count').removeClass('null').text(newTotal);
					} else if ($target.is('.comment-remove-helpful')) {
						newTotal = B.tools.countSet(cur, "dec");
						if (newTotal === "") {
							$target.parent('.helpful').hide().next('.helpful').show().find('.count').addClass('null').text(newTotal);
						} else {
							$target.parent('.helpful').hide().next('.helpful').show().find('.count').text(newTotal);
						}
						// like buttons have different markup. 
					} else if ($target.is('.remove-like')) {
						newTotal = B.tools.countSet(cur, "dec");
						newText = _t['like'];
						$target.removeClass('remove-like');
						$label.text(newText);
						if (newTotal === "") {
							$count.addClass('null');
						}
					} else {
						newTotal = B.tools.countSet(cur, "inc");
						newText = _t['removeLike'];
						$target.addClass('remove-like');
						$label.text(newText);
						$count.removeClass('null');
					}
					//update the original too
					$count.removeClass('loading').text(newTotal);

				}

			}, "json");

			return false;
		},

		commentLike: function (e) {
			return false;
		},
		commentRemove: function (e) {
			var $t = $(e.target),
				url = $t.attr("href");
			url = url.split("?");

			$.get(B.settings.baseURL + url[0], url[1], function (ret) {

				if (ret.status.id == "SUCCESS") {

					var commentCount, newNum;

					$t.parents('li.comment').fadeOut('fast', function () {
						$(this).remove();
					});

					// slightly different DOM stuff for profile pages	
					if ($('.ui-page-active').is('#screen-profile') || $('body').is('.page-my-profile')) {
						commentCount = $('h3#user-comments').find('.count');
					} else {
						commentCount = $('h3.toggle-comments').find('.count');
					}

					newNum = commentCount.text() - 1;

					// hide the heading entirely if there are 0 comments.
					if (newNum === 0) {
						commentCount.parent('h3').hide();
					} else {
						commentCount.text(newNum);
					}

				}

			}, "json");

			return false;
		},
		// when a user lands on a listing page with a category in the URL...
		showSubCatsfromID: function (catID) {
			if (B.settings.context == "mobile") {
				$('li#cat-' + catID).each(function () {
					if ($(this).is('.ui-collapsible-contain')) {
						$(this).trigger('expand');
					} else {
						$(this).parents('.ui-collapsible-contain').trigger('expand');
					}

				});
			} else {
				$('li#cat-' + catID).each(function () {
					if ($(this).is('[data-role="collapsible"]')) {
						$(this).click();
					} else {
						$(this).parents('li').click();
					}

				});
			}
		}
/*
	   * following methods are unused. left in place for phonegap.
	   *
	   *
	  getCats : function(callback){
			var lang = B.settings.local;
		 //loadContent is a generic content loaded. Grabs it and sticks inside of B.cont with the name passed (B.content.mycontentname)
		 B.resources.cats = B.content.load('getallcategories_'+ lang);
	  },
	  
	  // create the second level <select>s on the add resource page.
	  buildSelects: function(ID){

		 // TODO: wtf is up with event timing? DEFFERRED.
		 // try bind to window.document and listen there instead of B?
		 setTimeout(function() { 
			// are the categories loaded? 
			if(typeof(B.content.getallcategories) === "object"){
			   
			   // future home
			   B.content.selects = {};
			   
			   var cats = B.content.getallcategories.data;				

			   for(var i=0;i<cats.length;i++){
				  cat = cats[i];
				  
				  // if this is a top level category, create a new object in B.content.selects filled with <option>s. object name == this categoryID
				  if(cat.childcategories.length > 0){
					 // future home
					 B.content.selects[cat.categoryid] = "";

					 for(var j=0; j<cat.childcategories.length; j++){
						sub = cat.childcategories[j];
						// we'll key off the vals later
						B.content.selects[cat.categoryid] += '<option value="'+ sub.categoryid+'" id="catlist-'+ sub.categoryid+'">'+ sub.categorytitle +'</option>';
					 }	
				  }
			   }
			} else{
			   //try remote load again? 
			}
		 }, 400); 
		 
		 
		 
	  },
	  
	  // this is called when a top level category is changed on the add resource page dual <select>
	  buildSubSelect : function(e){
		 var $target = $(e.target),
			cat		 = $target.val();
			this.$subContainer = $target.parents('.add-resource-cats').next('.add-resource-sub-cats').find('select');

			this.$subContainer[0].innerHTML = B.content.selects[cat];

			//force jqm to update the new select with its mojo
			this.$subContainer[0].selectedIndex = 0;
			$(this.$subContainer).selectmenu("refresh");

	  },
	  
	  // not used atm. Could be used instead of the big ajax call for the Find/browse listing.
	  buildLists: function(ID){
		 var cats = B.resources.cats.categories, // array of categories (with arrays of sub cats)
			$target;
		 
		 // is this a sublist?
		 if(ID && ID.indexOf('cat') != -1){
			// which one?
			var theID = ID.substring(4);
			// find the referenced ID and add a sublist. target it.
			$("#"+ID).append('<ul class="sublist"></ul>');
			$target = $('.sublist', '#'+ID);
			// loop through the subs and look for the ID.
			for(var i=0;i<cats.length;i++){
			   if(cats[i].catID==theID){
				  cats = cats[i].childCats;
			   }
			}
		 } else{ // top level
			$target = $('<ul id="resource-listing"></ul>');
		 }	
		 // loop through each and append them to the $target. would be faster as innerHTML.
		 for(var cat in cats){
			$target.append('<li id="cat-'+cats[cat].catID+'"><a href="/resources/'+cats[cat].catID+'">'+cats[cat].desc+'</a></li>');			
		 }

		 //appendLists as separate method?
		 // err, I dunno what this is.
		 if(!$('body #resource-listing').length){
			$("body").append($target);
		 }
		 
		 
	  }
	  // end unused methods
	  //
	  */

	},
	// end resources
	tools: {

		analytics: function () {

			if (B.settings.analytics) {
				// for 'page show' url tracking
				$("div[data-role=page]'").live("pageshow", function (event, ui) {
					var pageTitle = $(this).attr('data-url');
					_gaq.push(["_trackPageview", pageTitle]);
				});

				// Custom Events
				$('.loc-change').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Location Change']);
				});

				$('.tel').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Phone Number Clicked']);
				});

				$('.res-online').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Resource URL Clicked']);
				});

				$('.lang-link').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Language Switched']);
				});
				$('.get-location-trigger').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Manual Geolcation']);
				});

				$('#direction-submit').live('click', function () {
					_gaq.push(['_trackEvent', 'Other', 'Directions']);
				});

				// tracking when things are shared
				$('#share').delegate('a', 'click', function (e) {
					var $t = $(this),
						service = $t.attr('id');
					service = service.substring(6);

					_gaq.push(['_trackEvent', 'Sharing', service]);

				});
			} // if B.settings.analytics
		},



		// utility method for resource/comment like, helpful...
		countSet: function (num, dir) {
			var n = num || 0,
				newNum;
			n = n * 1;


			if (n === 0 && dir == "dec") {
				newNum = "";
			} else if (dir == "inc") {
				newNum = num + 1;
			} else if (num == 1) {
				newNum = "";
			} else {
				newNum = num - 1;
			}
			return newNum;
		},
		// unobfuscate emails (they come out of cake all rearranged to trip up spam bots). 
		emailFix: function () {
			$('.res-online a.email').each(function () {
				var $this = $(this),
					href = $this.attr('href'),
					text = $this.text(),
					toflip = text.substring(0, 4),
					remainder = text.substring(4),
					rev = toflip.reverse(),
					email = rev + remainder;

				$this.text(email);
				$this.attr('href', 'mailto:' + email);

			});
		},
		
		/* some flash messages (non-errors) should go away after a bit. the hideable class is added in error-messages.html */
		hideFlashMessages: function () {
			$('.flash.hideable').each(function () {
				var $this = $(this);
				setTimeout(function () {
					$this.fadeOut('slow');
				}, 8000);
			});
		},

		ie6_is_terrible: function () {
			if ($('html').is('.ie6')) {
				$('button, li, .login-wrap div').hover(function () {
					$(this).addClass('over');
				}, function () {
					$(this).removeClass('over');
				});
			}
		},

		isNumber: function (n) {
		  return !isNaN(parseFloat(n)) && isFinite(n);
		},
		
		// used for resource descriptions but is generic enough to use anywhere
		limitChars: function (textarea, limit, infodiv) {
			var text = textarea.value; 
			var textlength = text.length;
			var info = document.getElementById(infodiv);
			
			
			if (textlength >= limit) {
				info.innerHTML = '<strong class="error">0</strong> ' + _t['characters_remaining'];
				textarea.value = text.substr(0,limit);
				return false;
			} else {
				info.innerHTML = '<strong>'+ (limit - textlength) +'</strong> ' + _t['characters_remaining'];
				return true;
			}
		},
		
		// position absolute on the viewport makes sliding the maintenance message on top impossible. 
		// grab it if it's there and move it some place visible
		maintenanceMode : function (){
		
			if($('#maintenance').length){
			    $('#maintenance').insertBefore('.screen-header');
			}
			
		},
		
		// platformCheck not actually used at the moment - pulled over from MyTaxBack
		platformCheck: function () {
			//should just return classes to support(); not using B.platform anywhere for custom reporting
			if (window.location.href.indexOf('Applications') > 1) {
				B.platform = 'iphone';
				$('html').addClass("iphone pg-iphone");
			} else if (window.location.href.indexOf('android_asset') > 1) {
				B.platform = 'android';
				$('html').addClass("android pg-android");
			} else { //if(window.location.href.indexOf('beehive') > 1){ 
				//if we're on a one-economy site, it's web. are we mobile? If not, we're desktop.
				if (B.settings.uagent.indexOf('iphone') > 1) {
					B.platform = "mobile web, iphone";
				} else if (B.settings.uagent.indexOf('android') > 1) {
					B.platform = "mobile web, android";
					$('html').addClass("android");
				} else if (B.settings.uagent.indexOf('blackberry') > 1) {
					B.platform = "mobile web, blackberry";
					$('html').addClass("blackberry");
				} else {
					B.platform = "unknown";
				}

			}
		},

		// tools.support is a mini modernizr. adding classes to the html element to 
		// describe device capabilities. we only care about a few so we don't need the overhead modernizr.
		support: function () {
			var classes = [];

			if (this.supports_input_placeholder()) {
				classes.push('placeholder');
			}
			if (this.supports_geo_location()) {
				classes.push('geolocation');
			}
			B.tools.platformCheck();

			document.documentElement.className = document.documentElement.className + ' ' + classes.join(' ');
			
			$('html').removeClass('no-js').addClass('js');
		},

		// placeholder test
		supports_input_placeholder: function () {
			var i = document.createElement('input');
			return 'placeholder' in i;
		},
		
		supports_geo_location: function () {
			if (typeof (navigator.geolocation) != "undefined") {
				return true;
			}
		},

		tabsInit: function ($target) {
			$target = $target || $('body');

			$('.tab-wrap', $target).each(function () {
				var $t = $(this);

				// have we already done this? Avoid binding everyting twice. Relevant for async loading.
				if ($t.is('.tabs-intialized')) {
					return;
				}

				var $tabs = $t.find('.tabs>ul>li>a'),
					$bodies = $('>div', this),
					$mapButton = $t.prev('.lead').find('.results-view-toggle'),
					target, num, $activeTab, activePosition;


				// setup	
				$bodies.hide();

				// first tab may be 'inactive' if there are no local results so let's find the real one.
				$activeTab = $('.tabs>ul>.active').eq('0');
				$activeTab.find('a').addClass('tabs-active');

				// mobile map button
				if ($activeTab.is('.regional') || $activeTab.is('.national')) {
					$mapButton.addClass('inactive');
				}

				// find the active tab, show the right body
				activePosition = $('>nav>ul li', this).index($activeTab);
				$bodies.eq(activePosition).addClass('active-tab').show();
				

				// mark it as built
				$t.addClass("tabs-intialized");

				// hidden with css via .js
				$('.tab-wrap .resource-listing').show().parents('.tab-wrap').removeClass('loading');


				// make magic
				$($tabs).live("click tap", function (e) {
					num = $tabs.index(this);

					// nope.
					if ($(this).parent('li').is('.inactive')) {
						return false;
					}
					
					// no map view for region and national results. mobile only, switches state on each tab click/tap
					if (num !== 0) {
						$mapButton.addClass('inactive');
					} else {
						$mapButton.removeClass('inactive');
					}
					
					// um, three rewrites of this tabs business == mess-o-rama.
					// $('.tabs .ui-btn-active').removeClass('active');
					$('.tabs .tabs-active').removeClass('tabs-active');
					$(this).addClass('tabs-active').find('a').addClass('tabs-active');

					// Make the back button work
					// jquery mobile gets really mad about changing the hash. perhaps it will be fixed when updating to non-alpha jqm
					if (B.settings.context !== "mobile") {
						window.location.hash = "results-" + $(this).attr('data-tabname');
					}
					$('.active-tab').hide().removeClass('active-tab');
					$bodies.eq(num).show().addClass('active-tab');
					
					// jqm is annoying
					setTimeout(function () {
						$('.tab-wrap').find('.ui-btn-active').removeClass('ui-btn-active');
					}, 80);
					return false;
				});

				// window.onhashchange = function(){ 
				// 	var tabHasha = B.settings.hash.substring(9);
				// 	$('[data-tabname='+tabHasha+']').click();
				// };

				// make the back button work, part 2.
				if(B.settings.hash.indexOf('results') > 0){
					var tabHash = B.settings.hash.substring(9);
					$('[data-tabname='+tabHash+']').click();
				}

				//$tabs.eq(0).click();
				//$($bodies[0]).show();
			});

		},

		toggleHelp: function (e) {
			var $target = $(e.target);
			if ($target.is('.help-screen')) {
				$target.slideUp();
			} else {
				//find in parents?
				$target.next('.help-screen').slideToggle();
				$target.parent().next('.help-screen').slideToggle();
				e.stopImmediatePropagation();
				e.preventDefault();

			}
			_gaq.push(['_trackEvent', 'Other', 'Help Button clicked']);

		},

		validateAddResourceForm: function (e) {
			//validate on submit
			$("#add-resource-form").validation();
			
			// can't both "work here" and add the resource anonymously.
			$('#res_workhere, #res_submit_as_anon').change(function (e) {
				var $t = $(e.target);
				if ($t.is(':checked')) {
					if ($t.is('#res_workhere') && $('#res_submit_as_anon').is(':checked')) {
						alert(_t['workhereAnonError']);
						$t.attr('checked', "");
					} else if ($t.is('#res_submit_as_anon') && $('#res_workhere').is(':checked')) {
						alert(_t['workhereAnonError']);
						$('#res_submit_as_user').attr('checked', 'checked');
						$t.attr('checked', '');
					}
				}
			});


		},

		validateWorkHereForm: function (e) {
			var toValidate = $(e.target).find('input[type=checkbox]');

			if (!toValidate.is(":checked")) {
				toValidate.parents('li').addClass('error').prepend('<label class="error">' + _t["requiredError"] + '</label>');
				return false;
			}

		},

		validateContactForm: function (e) {
			// first we hack to make the radio buttons validatable with our current plugin.
			// it doesn't like radio groups so we'll validate a hidden input
			$('#issue-type input:radio').bind('change', function () {
				$('#contact-type-checked').val('checked').keyup();
			});

			// then we run said plugin
			$("#contact-form").validation();

		},

		getURLparam: function (name) {
			name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
			var regexS = "[\\?&]" + name + "=([^&#]*)";
			var regex = new RegExp(regexS);
			var results = regex.exec(window.location.href);
			if (results === null) {
				return "";
			} else {
				return results[1];
			}
		},

		hideURLbar: function (top) {
			var topPos = top || 1;
			window.scrollTo(0, topPos);
		},

		createCookie: function (name, value, days) {
			var expires;
			if (days) {
				var date = new Date();
				date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
				expires = "; expires=" + date.toGMTString();
			} else {
				expires = "";
			}
			document.cookie = name + "=" + value + expires + "; path=/";
		},

		readCookie: function (name) {
			var nameEQ = name + "=";
			var c;
			var ca = document.cookie.split(';');
			for (var i = 0; i < ca.length; i++) {
				c = ca[i];
				while (c.charAt(0) == ' ') c = c.substring(1, c.length);
				if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
			}
			return null;
		},

		eraseCookie: function (name) {
			B.tools.createCookie(name, "", -1);
		}

	},

	map: {

		mapData: {},

		// build the map and set some options. create related bits.
		init: function () {

			// TODO?: if there is no mapdata and the current hash is #pagemap a) throw an error or b) use the map data saved in localstorage
			// in map.init this would only happen if some one were to reload the page or land here randomly)
			if (!window.google) {
				$('#page-map .tab-button, #page-map .results-view-toggle, aside#page-map').hide();
				$('#page-map .content').html('<h2 class="sorry">Sorry, maps are unavailable at this time. Please refresh your browser in a few seconds.</h2>');
				return false;
			}

			if (document.getElementById("map_canvas") === null) {
				return false;
			}

			// start over the ocean.
			var initialLocation = new google.maps.LatLng('15.1', '-122.1');


			var defaultUI = B.settings.context == 'mobile' ? true : false;
			var mapOptions = {
				zoom: 18,
				center: initialLocation,
				disableDefaultUI: defaultUI,
				mapTypeId: google.maps.MapTypeId.ROADMAP
			};

			// yes, B.map.map
			this.map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
			this.infowindow = new google.maps.InfoWindow(); // one infowindow for all points


			// in order to fix a race condition, we only want to start the directions code once the map has loaded
			// but the event for that is triggered everytime the tiles are refreshed - including when new directions are loaded
			// so we have stupid flag.
			B.directionsInitSet = false;

			// let's do directions.
			if ($('#driving-directions').length) {
				$('#driving-directions').hide();
				// we need the map to be fully loaded before running the directions init or route will not show up on the map.
				google.maps.event.addListener(B.map.map, 'tilesloaded', B.map.directionsInit);
			}

			this.mapPageHeader = $('#page-map .heading');

			// when we actually get to the map page, let's show it.
			$('#page-map').live('pageshow', function (e, ui) {

				B.map.showMap(e, ui);
				// show the 'list view' button if we came from a results page
				if ($(ui.prevPage).is("#screen-results")) {
					$(e.target).find('.results-view-toggle').show().end().find('.back').addClass('back-to-results');

				}
				_gaq.push(['_trackEvent', 'Other', 'Map Used']);

			});

			$('#page-map').live('pagehide', function (event, ui) {
				$('#page-map .results-view-toggle').hide();
			});

			if (B.settings.context == "desktop") {
				B.map.showMap();
			}

			return true;
		},
		// called whenever the map 'page' is shown.
		showMap: function (e, ui) {
			// B.map.mapData[XXX] is filled on load of each view containing map data. 
			// [XXX] == cat+catID like 'cat1234' for search results or 'res1234' for individual resources).
			// this pattern will break if we ever do 'resize/pan' == new search. Zoomed sets always temporary?
			// make them all markers immediately? Too slow? to show, detach each in the previous from the map and attach the current set

			// mobile or desktop?
			var currentMapDataKey = ui ? ui.prevPage.attr('data-map') : $('div[data-map]').attr('data-map');

			B.mapData = B.map.mapData[currentMapDataKey];

			// when a user gets content with map data, it's written to B.mapData
			if (B.mapData != "no-results" && B.mapData !== "" && B.mapData) {

				// hide the map and stop processing if we're on a single resource that doesn't allow drop-ins
				if (B.searchResults.data.length == 1 && B.mapData[0].dropincenter == "n") {
					$('aside#page-map').hide();
					return;
				}

				// mobile only
				if (ui) {
					//make sure it wakes up
					google.maps.event.trigger(B.map.map, 'resize');

					B.map.uiBuilder(e, ui);

					// remove any existing points
					this.clearPoints('del');
				}

				// rewriteable property. create in init and reset here?
				this.bounds = new google.maps.LatLngBounds();

				//loop through the map data and create our markers
				var marker, addy;
				$.each(B.mapData, function (i, data) {
					// filter out regional/national results and anything else that can't be plotted. just in case.
					if (data.latitude === "") {
						return;
					}
					marker = [data.latitude, data.longitude];

					// infowindow html template
					addy = B.map.makeInfoWindow(data);
					
					//add a point to the map and pass the geocode, title and info window content to associate with each marker.
					B.map.addPoint(marker, data.agencyname, addy);
				});

				/*
					TODO: Replace with B.map.mapResizer()
				*/
				var newLoc;
				// B.map.totalResults;
				// B.map.addPoint extends the bounds on each call. fix the map now that we're done.
				if (B.searchResults.data.length > 1) {
					
					if (B.map.markersArray.length > 1) {
						B.map.totalResults = B.map.totalResults || B.map.markersArray.length;
						this.map.fitBounds(this.bounds);
					} else {
						B.map.totalResults = 1;
						// B.searchResults.data is in order by distance so the first one will always be the single item in local
						newLoc = new google.maps.LatLng(B.searchResults.data[0].latitude, B.searchResults.data[0].longitude);

						this.map.setCenter(newLoc);
						B.map.map.setZoom(15);
					}

				}
				// If we're mapping a single item we don't want to zoom that far in so we manually set a zoom afterwards
				else {

					B.map.totalResults = 1;
					$.each(B.mapData, function (i, data) {
						// filter out regional/national results and anything else that can't be plotted. just in case.
						if (data.latitude === "") {
							return;
						}
						newLoc = new google.maps.LatLng(data.latitude, data.longitude);
					});
					this.map.setCenter(newLoc);
					B.map.map.setZoom(15);
				}

			} else {
				//show error?
				//_debug('show map else. error?');
			}

			//wake up!
			google.maps.event.trigger(B.map.map, 'resize');
		},
		// templater called by addPoint
		makeInfoWindow: function (data) {
			var addy = '<div class="info-window">';
			
			if (B.searchResults.data.length > 1) {
				addy += '<h4><a href="' + data.resource_link + '">' + data.agencyname + '</a></h4>';
			} else {
				addy += '<h4>' + data.agencyname + '</h4>';
			}
			
			addy += '<p style="font-weight: normal;">' + data.streetaddress + '<br /> ';
			addy += data.city + ', ' + data.state + ' ' + data.zipcode + '</p>';
			addy += '</div>';

			return addy;
		},

		markersArray: [],

		// guess what this does? 
		addPoint: function (points, title, infowindowcontent) {
			var latlng = new google.maps.LatLng(points[0], points[1]),
				markerTitle = title || "",
				content = infowindowcontent || "",
				image = new google.maps.MarkerImage("/layout/img/css/icon-map-marker-yellow.png", new google.maps.Size(26.0, 32.0), new google.maps.Point(0, 0), new google.maps.Point(13.0, 16.0)),
				shadow = new google.maps.MarkerImage("/layout/img/css/icon-map-shadow.png", new google.maps.Size(43.0, 32.0), new google.maps.Point(0, 0), new google.maps.Point(13.0, 16.0));

			var marker = new google.maps.Marker({
				position: latlng,
				map: B.map.map,
				title: markerTitle,
				shadow: shadow,
				icon: image
			});

			// add an event lister for 'click' on each marker that sets the content of the infowindow and shows it.
			google.maps.event.addListener(marker, 'click', function (e) {
				B.map.infowindow.setContent(content);
				B.map.infowindow.open(this.map, this);
			});

			this.markersArray.push(marker);
			this.bounds.extend(latlng); // reset the map window to fit the markers
		},

		clearPoints: function (del) {
			if (this.markersArray) {
				for (i in this.markersArray) {
					this.markersArray[i].setMap(null);
				}
				if (del) {
					this.markersArray.length = 0;
				}
			}
		},

		// extra ui stuff like moving the title around on the large map
		uiBuilder: function (e, ui) {

			var $src = ui.prevPage, 
				newHeader, newButtonText;
				
			if ($src.is('#resource-detail')) {
				newHeader = $src.find('.fn').clone(); // full ('.vcard')?
				newButtonText = _t['detailView'];
				$('#page-map .results-view-toggle').hide();
			} else {
				newHeader = $src.find('.breadcrumbs').clone();
				newButtonText = _t['listView'];
				$('#page-map .results-view-toggle').show();
			}
			B.map.mapPageHeader.html(newHeader);
			$('#page-map .results-view-toggle .ui-btn-text').text(newButtonText);
		},
		
		viewLargerMap: function (e) {
			// once and future home
			B.settings.sidebarMapPosition = $('#page-map').prev('aside');

			// update the text link
			$('.larger-map').text(_t['smaller_map']).removeClass('larger-map').addClass('smaller-map');

			// move it and show the good stuff.
			$('#page-map').insertAfter('#header').addClass('large-map');
			$('#driving-directions, #directions-panel').show();
			window.scrollTo(0, 0);

			B.map.mapResizer(e);
			B.map.resourceVcardMover();

			// 'keep directions' by resubmitting the form
			//if ($('#origin').val() !== "") {
			//	 $('#driving-directions').submit(); 
			//}
		},
		
		viewSmallerMap: function (e) {
			// update the text link
			$('.smaller-map').text(_t['larger_map']).removeClass('smaller-map').addClass('larger-map');

			// move it and update the wrapping class
			$('#page-map').insertAfter(B.settings.sidebarMapPosition).removeClass('large-map').removeClass('directions');

			B.map.mapResizer(e);

			// flags are for suckers.
			B.directionsInitSet = false;

			// clean stuff out.
			$('#directions-panel').html('').hide();
			$('#driving-directions').hide();

			B.map.resourceVcardMover('delete');

		},

		// generic method to get the map to wake up.
		mapResizer: function (e) {

			// most of this noise is because we need to reset the map entirely when going small if the directions panel is open.
			// addListenerOnce allows us to bind the function only one time. Handy.
			google.maps.event.addListenerOnce(B.map.map, 'resize', function () {
				// Do we have a set of results on a listing page? Set zoom and center automatically.
				if (B.map.totalResults > 1) {
					B.map.map.fitBounds(B.map.bounds);
					B.map.map.setCenter(B.map.bounds.getCenter());
				} else {
					// if there were directions, we need to reset the entire map before resizing.
					if ($('#directions-panel').children().length) {
						B.map.init();
					} else {
						// Single resource or results page with a single result. Manually set the zoom and center.
						newLoc = new google.maps.LatLng(B.mapData[0].latitude, B.mapData[0].longitude);
						B.map.map.setZoom(14);
						B.map.map.setCenter(newLoc);
					}
				}
				// make sure there are lingering info windows.
				B.map.infowindow.close();

			});

			google.maps.event.trigger(B.map.map, 'resize');
			e.preventDefault();
		},

		resourceVcardMover: function (del) {
			// @TODO: extend UI Builder instead?
			if (!del) {
				var vcard = $('.vcard').clone();
				$('#driving-directions').prepend(vcard);
			} else {
				$('#driving-directions').find('.vcard').remove();
				$('.vcard').show();
			}
		},

		// clear the slate for directions
		directionsInit: function () {
			if (B.directionsInitSet === false) {

				B.map.directionsService = new google.maps.DirectionsService();
				B.map.directionsDisplay = new google.maps.DirectionsRenderer();
				B.map.directionsDisplay.setMap(B.map.map);
				B.map.directionsDisplay.setPanel(document.getElementById("directions-panel"));

				$('#driving-directions').submit(B.map.directionsSubmitHandler);
			}
			B.directionsInitSet = true;
		},

		// run when someone submits a request for driving directions.
		directionsSubmitHandler: function (e) {
			var $this = $(e.target),
				origin = $('#origin').val(),
				dest = $('#destination').val();

			// validate!
			if (origin === "" || dest === "") {
				if (!$this.find('h3').length) {
					$this.find('fieldset:eq(0)').prepend('<h3 class="error" style="margin:15px 0 -5px 0;">' + _t.all_fields + '</h3>');
					return false;
				}
			} else {
				$this.find('.error').remove();
				B.map.directionsSet = true;
				//calcRoute(origin, dest);
				B.map.getDirections(origin, dest);
				e.preventDefault();
			}
			return true;
		},

		// the good part.
		getDirections: function (origin, dest) {
			$('#directions-panel').html('');
			var selectedMode = $('input[name=mode]:checked').val();

			var request = {
				origin: origin,
				destination: dest,
				travelMode: google.maps.TravelMode[selectedMode]
			};

			B.map.directionsService.route(request, function (response, status) {
				if (status == google.maps.DirectionsStatus.OK) {
					$('#page-map').addClass('directions');
					google.maps.event.trigger(B.map.map, 'resize');
					B.map.directionsDisplay.setDirections(response);

					// B.map.directionsDisplay.setMap(B.map.map);
				}
			});

		},

		//// place holder for awesomeness. called when map is zoomed atm.
		mapChange: function () {
			//add overlay to research within area?
			//just research and update add makers?
			//console.log("map changed");
			return true;
		}


	},

	// a place to store stuff.
	content: {
		// generic JSON content loader
		// file = filename_on_server = B.content.filename_on_server
		load: function (file, callback) {
			//_debug("loadContent: " + file);
			$.getJSON(B.settings.JSONContentPath + file + '.php', function (data) {

				// works for generic.json (no parent node). 
				// category.json becomes b.content.category.category because it has a root element.
				// NEW! not sure what the above means.
				B.content[file] = data;


				// don't think this settimeout is really needed anymore. Event timing weirdness.
				setTimeout(function () {
					$(B).trigger("getallcategoriesloaded");
					//console.log('triggered getallcategoriesloaded');
				}, 100);

				if (typeof (callback) === "function") {
					callback.apply();
				}
			});
		},

		// creates the category selects on the add resource page.
		// TODO break me up (template vs action)
		catSelectTemplate: function (e) {
			//_debug('catSelectTemplate called');
			var $target = $(e.target),
				num = $target.attr('rel'),
				newNum = num + 1,
				label = $('.add-resource-cats:eq(0) label').html(),
				labelsub = $('.add-resource-sub-cats:eq(0) label').html();

			// TODO. actual templating?   
			var tpl = ' <li class="add-resource-cats">';
				tpl += ' <label for="res_parent_cat_' + num + '">' + label + '</label>';
				tpl += ' <select id="res_parent_cat_' + num + '" name="res_parent_cat_' + num + '"></select>';
				tpl += ' </li><li class="add-resource-sub-cats">';
				tpl += ' <label for="res_sub_cat_' + num + '">' + labelsub + '</label>';
				tpl += ' <select id="res_sub_cat_' + num + '" name="res_sub_cat_' + num + '"></select></li>';

			// append and populate. TODO: more general	  
			$target.prev('ol').append(tpl);

			// grab the first select from the dom (always the same set)
			$('#res_parent_cat_' + num)[0].innerHTML = $('#res_parent_cat_1').html();

			// default the new select to the first category's sub options
			$('#res_sub_cat_' + num)[0].innerHTML = B.content.selects['139'];

			// make it all jquerymobile-y
			$('#res_parent_cat_' + num).selectmenu();
			$('#res_sub_cat_' + num).selectmenu();

			// update the link so it will continue to work for additional categories
			$target.attr('rel', newNum);
			e.preventDefault();

		}
	} //B.content 
}; // end B

jQuery(document).ready(function ($) {

	B.init();

	// temp to remove place holders from form display
	$('input[placeholder]').attr('placeholder', '');

}); //ready

