var map;
var geocoder;
var showMap = true;
var jQueryTabsToInit = new Array(); // This stores the IDs of jQuery tab elements that we need to initialize after the HTML is rendered
var jQueryAjaxTabsToInit = new Array();
var jQueryTabsToBindEvents = new Object(); // Psuedo-associative array containing the IDs of tabs that need code executed on selection, and the name of the function to execute
var jQueryCarouselsToInit = new Array(); // Ditto for the carousels (thumbnails of images)

function codeAddress() {
	geocoder = new GClientGeocoder();
	var d_address = document.getElementById("default_address").value;
	//alert(address);
		 geocoder.getLatLng(d_address, function(latlng) {
			document.getElementById("default_lat").value = latlng.lat();
			document.getElementById("default_lng").value = latlng.lng();
		 });
}

function codeNewAddress() {
	if (document.getElementById("store_lat").value != '' && document.getElementById("store_lng").value != '') {
		document.new_location_form.submit();
	}
	else {
		geocoder = new GClientGeocoder();
		var address = '';
		var street = document.getElementById("store_address").value;
		var city = document.getElementById("store_city").value;
		var state = document.getElementById("store_state").value;
		var country = document.getElementById("store_country").value;
		
		if (street) {address += street + ', ';}
		if (city) {address += city + ', ';}
		if (state) {address += state + ', ';}
		address += country;
	
		geocoder.getLatLng(address, function(latlng) {
			document.getElementById("store_lat").value = latlng.lat();
			document.getElementById("store_lng").value = latlng.lng();
			document.new_location_form.submit();
		});
	}
}

function codeChangedAddress() {
	geocoder = new GClientGeocoder();
	var address = '';
	var street = document.getElementById("store_address").value;
	var city = document.getElementById("store_city").value;
	var state = document.getElementById("store_state").value;
	var country = document.getElementById("store_country").value;
	
	if (street) {address += street + ', ';}
	if (city) {address += city + ', ';}
	if (state) {address += state + ', ';}
	address += country;

	geocoder.getLatLng(address, function(latlng) {
		document.getElementById("store_lat").value = latlng.lat();
		document.getElementById("store_lng").value = latlng.lng();
	});
}

function searchLocations(categories, page_type, content, geographically_limited) {
    var address = document.getElementById('addressInput').value;
    address = address.replace(/&/gi, " ");
    address = address.replace(/=/gi, " ");
    geocoder.getLatLng(address, function(latlng) {
        if (!latlng) {
             latlng = new GLatLng(150,100);
             searchLocationsNear(latlng, address, "search", "unlock", categories, page_type, content, geographically_limited);
        } else {
             searchLocationsNear(latlng, address, "search", "unlock", categories, page_type, content, geographically_limited);
        }
    });
}

function searchLocationsNear(center, homeAddress, source, mapLock, categories, page_type, content, geographically_limited) {
    if (document.getElementById('results').innerHTML == '')
        document.getElementById('results').innerHTML = '<div id="loading"><img src=\"' + plugin_url + '/images/spinner-big-white.gif\" width=\"48\" height=\"48\" ' +
            'alt=\"Loading...\" /><br />Loading...</div>';

    if (page_type != 'locations') // Hide map:
		jQuery('#map').animate({'height': 1}, 700, 'swing', function() {jQuery('#map').css({'left': '-9999px'})});
	if (page_type == 'locations') // Show map:
		jQuery('#map').css({'width': jQuery('#map').data('default_width'), 'left': 'auto'})
            .animate({'height': jQuery('#map').data('default_height')}, 700, 'swing');
	if (document.getElementById('radiusSelect')) {
		if (units == 'mi') {
		  	var radius = parseInt(document.getElementById('radiusSelect').value);
		}
		else if (units == 'km') {
		  	var radius = parseInt(document.getElementById('radiusSelect').value) / 1.609344;
		}
	}
	else {
		if (units == 'mi') {
		  	var radius = parseInt(default_radius);
		}
		else if (units == 'km') {
		  	var radius = parseInt(default_radius) / 1.609344;
		}
	}
 
	if (source == 'auto_all') {
		var searchUrl = plugin_url + 'actions/create-xml.php?lat=' + center.lat() + '&lng=' + center.lng() + '&radius=infinite&source=' + source + '&map_lock=' + mapLock +
            '&namequery=' + homeAddress + '&limit=0&categories=' + categories + '&page_type=' + page_type + '&content=' + content + '&geographically_limited=' + geographically_limited;
	} else if (source == 'default') {
		var searchUrl = plugin_url + 'actions/create-xml.php?lat=' + center.lat() + '&lng=' + center.lng() + '&radius=0&source=' + source + '&map_lock=' + mapLock + '&namequery=' +
            homeAddress + '&limit=0&categories=' + categories + '&page_type=' + page_type + '&content=' + content + '&geographically_limited=' + geographically_limited;
	}
	else {
		var searchUrl = plugin_url + 'actions/create-xml.php?lat=' + center.lat() + '&lng=' + center.lng() + '&radius=' + radius + '&source=' + source + '&map_lock=' + mapLock + 
            '&namequery=' + homeAddress + '&limit=' + limit + '&categories=' + categories + '&page_type=' + page_type + '&content=' + content + '&geographically_limited=' +
            geographically_limited;
	}
	GDownloadUrl(searchUrl, function(data) {
		var results = document.getElementById('results');
		results.innerHTML = '';
		var xml = GXml.parse(data);
		var options = xml.documentElement.getElementsByTagName('options')[0];
            // The [0] makes it return the actual options element, rather than an array of options elements that contains only one element
		if (typeof hideSearchingIndicator == 'function' && jQuery('#addressInput') !== undefined && jQuery('#addressSubmit').data('indicatorOn'))
			hideSearchingIndicator();
		if (document.getElementById('cookie_address') && options.getAttribute('source') == 'search')
			document.getElementById('cookie_address').innerHTML = options.getAttribute('namequery');
		else if (options.getAttribute('source') == 'search') {
			var oldSearchFormPrompt = jQuery('#search_form_prompt').html();
			if (oldSearchFormPrompt == 'Please enter an address to find theaters and showtimes near you:') // If it's the default prompt
				jQuery('#search_form_prompt').html('Showing theaters and showtimes near <span id="cookieAddress">' + options.getAttribute('namequery') +
                    '</span>. Somewhere else? Enter a new address below:');
			else // Custom search form prompt; TO-DO: Keep alive a custom search prompt somehow
				jQuery('#search_form_prompt').html('Showing theaters and showtimes near <span id="cookieAddress">' + options.getAttribute('namequery') +
                    '</span>. Somewhere else? Enter a new address below:');
		}
		
		if (jQuery('h1.page_headline:first') && jQuery('h1.page_headline:first').html() != options.getAttribute('page_title')) { // If the new results trigger a new title, update it:
			jQuery('h1.page_headline:first').html(options.getAttribute('page_title'));
        }

		if (page_type == 'locations') {
			var markers = xml.documentElement.getElementsByTagName('marker');
			showMap = true;
			map.clearOverlays();
			if (markers.length == 0) {
				results.innerHTML = templateNoResultsFound();
					map.setCenter(new GLatLng(default_lat, default_lng), zoom_level);
				return;
			}
		} else if (page_type == 'film') { // TO-DO: Make this a completely separate template than series
			var films = xml.documentElement.getElementsByTagName('film');
			showMap = false;
			if (films.length == 0) {
				results.innerHTML = templateNoResultsFound();
				return;
			}
		} else if (page_type == 'series') {
			var films = xml.documentElement.getElementsByTagName('film');
			showMap = false;
			if (films.length == 0) {
				results.innerHTML = templateNoResultsFound();
				return;
			}
		} else if (page_type == 'seriesgroup') {
			var series = xml.documentElement.getElementsByTagName('seriesgroup')[0].getElementsByTagName('series');
			showMap = false;
			if (series.length == 0) {
				results.innerHTML = templateNoResultsFound();
				return;
			}
		} else {
			results.innerHTML = 'Error: Unknown page type requested. Please refer to the Marquee admin Help page for proper syntax.'
			return;
		}
		

		if (page_type == 'locations' && showMap) {
			var bounds = new GLatLngBounds();
			for (var i = 0; i < markers.length; i++) {
	            var markersExtraInfo = new Array(); // This stores the secondary tabs for each map bubble (Description, etc.)
	            var resultsExtraInfo = new Array(); // This stores the same and additional data, formatted for display in the results list
	            var location_id = markers[i].getAttribute('location_id');
				var name = markers[i].getAttribute('name');
				var address = markers[i].getAttribute('address');
				var address2 = markers[i].getAttribute('address2');
				var city = markers[i].getAttribute('city');
				var state = markers[i].getAttribute('state');
				var zip = markers[i].getAttribute('zip');
				var country = markers[i].getAttribute('country');
				var distance = parseFloat(markers[i].getAttribute('distance'));
				var point = new GLatLng(parseFloat(markers[i].getAttribute('lat')), parseFloat(markers[i].getAttribute('lng')));
				var url = markers[i].getAttribute('url');
				var phone = markers[i].getAttribute('phone');
				var fax = markers[i].getAttribute('fax');
				var category = markers[i].getAttribute('category'); // TO-DO: elimatine this defunct field, update it to handle cat_ids and category names
				var tags = markers[i].getAttribute('tags');
	            
	            if (markers[i].hasChildNodes()) {
	                for (var j = 0; j < markers[i].childNodes.length; j++) { // Iterate through marker's child nodes

	                    // A node with no sub-nodes, but that does have some text, nonetheless has one firstChild--the text itself
	                    if (markers[i].childNodes[j].nodeName == 'showtimes') {
							markersExtraInfo.push(['Now Playing', templateFormatScreeningsArray(page_type, options, "markers", location_id, 'Now Playing',
                                markers[i].childNodes[j].getElementsByTagName('screening'))]);
							resultsExtraInfo.push(['Now Playing', templateFormatScreeningsArray(page_type, options, "results", location_id, 'Now Playing',
                                markers[i].childNodes[j].getElementsByTagName('screening'))]);
							markersExtraInfo.push(['Coming Soon', templateFormatScreeningsArray(page_type, options, "markers", location_id, 'Coming Soon',
                                markers[i].childNodes[j].getElementsByTagName('screening'))]);
							resultsExtraInfo.push(['Coming Soon', templateFormatScreeningsArray(page_type, options, "results", location_id, 'Coming Soon',
                                markers[i].childNodes[j].getElementsByTagName('screening'))]);
						} else if (markers[i].childNodes[j].firstChild) { // If the node has any data in it (the data is the firstChild)
	                        markersExtraInfo.push([markers[i].childNodes[j].getAttribute('name'), markers[i].childNodes[j].firstChild.nodeValue]);
	                        resultsExtraInfo.push([markers[i].childNodes[j].getAttribute('name'), markers[i].childNodes[j].firstChild.nodeValue]);
	                    }
	                }
	            }

				var marker = createMarker(point, name, address, address2, city, state, zip, country, homeAddress, phone, fax, url, category, tags, markersExtraInfo);
				map.addOverlay(marker);
				var sidebarEntry = createSidebarEntry(marker, name, address, address2, city, state, zip, country, distance, homeAddress, phone, fax, url, category, tags,
                    resultsExtraInfo, options);
				results.appendChild(sidebarEntry);
				bounds.extend(point);
	        }
	        
	        
		} else if (page_type == 'film') { // TO-DO: Make this a completely separate template than series
			for (var i = 0; i < films.length; i++) {
				results.innerHTML += '<div class="result">' + templateBigSearchResultForSeries(films[i], options) + '</div><div style="clear: both;"></div>';
			}
		} else if (page_type == 'series') {
			for (var i = 0; i < films.length; i++) {
				results.innerHTML += '<div class="result">' + templateBigSearchResultForSeries(films[i], options) + '</div><div style="clear: both;"></div>';
			}
		} else if (page_type == 'seriesgroup') {
			for (var i = 0; i < series.length; i++) {
				results.innerHTML += '<div class="result">' + templateBigSearchResultForSeries(series[i], options) + '</div><div style="clear: both;"></div>';
			}
		} // if (page_type == 'locations') else if (page_type == 'series') else if (page_type == 'seriesgroup')
		
		
        for (i = 0; i < jQueryTabsToInit.length; i++) { // We have to initialize the jQuery UI elements after they've been rendered
            // So a global array is used to collect all the names of elements to initialize, which we do here
            jQuery(jQueryTabsToInit[i]).tabs({
            	fx: {height: 'toggle', opacity: 'toggle', duration: 'fast'},
            	select: function(event, ui) {
					if (jQueryTabsToBindEvents[ui.panel.getAttribute('id')]) // If the id of the selected tab exists in the ToBindEvents array
						jQueryTabsSelectActions(jQueryTabsToBindEvents[ui.panel.getAttribute('id')][0], jQueryTabsToBindEvents[ui.panel.getAttribute('id')][1],
                            jQueryTabsToBindEvents[ui.panel.getAttribute('id')][2]);
					return true; // Allow the tab to be shown
            	}
            }); 
        }
        for (i = 0; i < jQueryAjaxTabsToInit.length; i++) {
            jQuery(jQueryAjaxTabsToInit[i]).tabs({
            	fx: {height: 'toggle', opacity: 'toggle', duration: 'fast'},
            	spinner: '<img src="' + plugin_url + 'images/spinner-big-results.gif" width=48 height=48 alt="Loading&#8230;" />'
            }); 
        }
/*        jQuery('span.has_time').live('click', function() { // When the coming soon dates that have times are clicked, slide out the times:
        	jQuery(this).parent('span.coming_soon_date_group').children('span.coming_soon_time:eq(0)').toggle();
        }) // (use the .live event binding so that this propogates to new spans created whenever map bubbles are opened)
*/ // this was replaced with an inline onclick="" declaration in the span element itself, as the .live function proved unreliable across ajax page changes

		if (showMap && source == "search") {
			map.setCenter(bounds.getCenter(), (map.getBoundsZoomLevel(bounds) - 1));
		}
		else if (showMap && mapLock == "unlock") {
			map.setCenter(bounds.getCenter(), autozoom);
		}
	}); // GDownloadUrl
} // searchLocationsNear

function stringFilter(s) {
	filteredValues = "emnpxt%";     // Characters stripped out
	var i;
	var returnString = "";
	for (i = 0; i < s.length; i++) {  // Search through string and append to unfiltered values to returnString.
		var c = s.charAt(i);
		if (filteredValues.indexOf(c) == -1) returnString += c;
	}
	return returnString;
}

function createMarker(point, name, address, address2, city, state, zip, country, homeAddress, phone, fax, url, category, tags, markersExtraInfo) {
	var marker = new GMarker(point);

    var mapwidth = Number(stringFilter(map_width));
	var mapheight = Number(stringFilter(map_height));
	var maxbubblewidth = Math.round(mapwidth / 1.5);
	var maxbubbleheight = Math.round(mapheight / 2.2);
	
	var fontsize = 12;
	var lineheight = 12;
	
	var titleheight = 3 + Math.floor((name.length + category.length) * fontsize / (maxbubblewidth * 1.5));
	//var titleheight = 2;
	var addressheight = 2;
	if (address2 != '') {
		addressheight += 1;
	}
	if (phone != '' || fax != '') {
		addressheight += 1;
		if (phone != '') {
			addressheight += 1;
		}
		if (fax != '') {
			addressheight += 1;
		}
	}
	var tagsheight = 3;
	var linksheight = 2;
	var totalheight = (titleheight + addressheight + tagsheight + linksheight + 1) * fontsize;
		
	if (totalheight > maxbubbleheight) {
		totalheight = maxbubbleheight;
	}
	
	var html = '	<div class="markertext" style="height: ' + totalheight + 'px;">';
    html += templateBubbleLocationTab(name, address, address2, city, state, zip, country, phone, fax, homeAddress, url);
	html += '	</div>';

    if (markersExtraInfo.length > 0) { // If the map bubble is going to have tabs
        var bubbleTabs = new Array();
        bubbleTabs.push(new GInfoWindowTab(location_tab_text, html));
        for (tab = 0; tab < markersExtraInfo.length; tab++) {
            var tabText = markersExtraInfo[tab][1]; // markersExtraInfo[tab] = tab number; markersExtraInfo[tab][0] = tab name; markersExtraInfo[tab][1] = tab text
            
            var numlines = Math.ceil(tabText.length / 40);
		    var newlines = tabText.split('<br />').length - 1;
            
		    var tabTotalHeight = 0;
		    if (tabText.indexOf('<img') == -1) {
			    tabTotalHeight = (numlines + newlines + 1) * fontsize;
		    }
		    else {
			    var numberindex = tabText.indexOf('height=') + 8;
			    var numberend = tabText.indexOf('"', numberindex);
			    var imageheight = Number(tabText.substring(numberindex, numberend));
			    
			    tabTotalHeight = ((numlines + newlines - 2) * fontsize) + imageheight;
		    }
		    
		    if (tabTotalHeight > maxbubbleheight) {
			    tabTotalHeight = maxbubbleheight;
		    }
		    
            var tabTotalWidth = maxbubblewidth;
            if (tabTotalWidth < markersExtraInfo.length*88 + 88)
                tabTotalWidth = markersExtraInfo.length*88 + 88; // The "+ 88" is for the first tab, Location, which is not included in MarkersExtraInfo
            
		    var tabHtml = '	<div class="markertext" style="height: ' + tabTotalHeight + 'px; width: ' + tabTotalWidth + 'px; overflow-y: auto; overflow-x: hidden;">' + tabText + '</div>';
            bubbleTabs.push(new GInfoWindowTab(markersExtraInfo[tab][0], tabHtml));
        }
		
		GEvent.addListener(marker, 'click', function() {
		//	marker.openInfoWindowTabsHtml([new GInfoWindowTab(location_tab_text, html), new GInfoWindowTab(description_tab_text, html2)], {maxWidth: maxbubblewidth});
            marker.openInfoWindowTabsHtml(bubbleTabs);
		});
	}
	else {  // The map bubble has no tabs, just the bubble
		GEvent.addListener(marker, 'click', function() {
			marker.openInfoWindowHtml(html, {maxWidth: maxbubblewidth});
		});
	}
	return marker;
}

function createSidebarEntry(marker, name, address, address2, city, state, zip, country, distance, homeAddress, phone, fax, url, category, tags, resultsExtraInfo, options) {
    var div = document.createElement('div');

    // Beginning of result
    var html = '<div class="result">';
    html += templateBigSearchResultForLocations(marker, name, address, address2, city, state, zip, country, distance, homeAddress, phone, fax, url, category, tags, resultsExtraInfo,
        options);
    html += '<div style="clear: both;"></div>';

    // End of result
    html += '</div>';

    div.innerHTML = html;
    
    div.style.cursor = 'pointer'; 
    div.style.margin = 0;
    GEvent.addDomListener(div, 'click', function() {
        GEvent.trigger(marker, 'click');
    });
    GEvent.addDomListener(div, 'mouseover', function() {
        //div.style.backgroundColor = '#eee';
    });
    GEvent.addDomListener(div, 'mouseout', function() {
        //div.style.backgroundColor = '#fff';
    });
    return div;
}

// Support functions for templates.js:
function createScreeningsByDayArray(screeningsArray, page_type, options, daysToProcess, monthShortNames) {
    var screeningsByDay = new Array(daysToProcess.length + 1); // The + 1 is for an additional last 'day' to hold Coming Soon data
    var futureScreeningsByFilm = new Object(); // Temporary variable to hold the Coming Soon data until it can be assembled into strings for screeningsByDay
    for (i = 0; i < screeningsByDay.length; i++) {
        screeningsByDay[i] = new Object(); // Not array! We're creating a pseudo-associative array here, where screeningsByDay[dayIndex] key -> value = title -> screening_time
    }

    for (i = 0; i < screeningsArray.length; i++) { // For each screening . . .
        var screeningDay = null;
        var titleOrName = null;
        var screening_id = ifExistsOutput(screeningsArray[i].getAttribute('screening_id'));
	    var film_id = ifExistsOutput(screeningsArray[i].getAttribute('film_id'));
	    var title = ifExistsOutput(screeningsArray[i].getAttribute('title'));
		var location_id = ifExistsOutput(screeningsArray[i].getAttribute('location_id'));
		var name = ifExistsOutput(screeningsArray[i].getAttribute('name'));
	    var lat = ifExistsOutput(screeningsArray[i].getAttribute('lat'));
	    var lng = ifExistsOutput(screeningsArray[i].getAttribute('lng'));
	    var screening_date = ifExistsOutput(screeningsArray[i].getAttribute('screening_date'));
	    var screening_time = ifExistsOutput(screeningsArray[i].getAttribute('screening_time'));
   	    var screening_month = ifExistsOutput(parseInt(screening_date.substr(5, 2), 10));
	    var screening_day = ifExistsOutput(parseInt(screening_date.substr(8, 2), 10));
	    var url = ifExistsOutput(screeningsArray[i].getAttribute('url'));
	    var today = new Date();
	    var screeningDate = new Date();
	    
	    if (lat && lng)
	    	name = '<a href="#q=h&lat=' + options.getAttribute('center_lat') + '&lng=' + options.getAttribute('center_lng') + '&homeAddress=' + escape(options.getAttribute('namequery')) +
                '&source=' + ((options.getAttribute('radius') == 'infinite') ? 'auto_all' : 'search') + '&mapLock=' + options.getAttribute('mapLock') + '&categories=' +
                options.getAttribute('categories') + '&page_type=locations&content=&geographically_limited=' + options.getAttribute('geographically_limited') + '" rel="history">' +
                name + '</a>';

	    
	    if (page_type == 'locations') // The screenings are grouped by day, but within each day:
	    	titleOrName = title; // Sort by title (for locations pages)
	    else
	    	titleOrName = name; // Or sort by location name (for seriesgroup/series/film pages)
        
        if (screening_time) {
			var reformattingArray = formatScreeningTime(screening_date, screening_time); // Reformat '19:00' to '7:00', or '11:00' to '11:00 am'
			screening_date = reformattingArray[0]; // If before 6 am, change the screening date to be the day before
			screening_time = reformattingArray[1];
			screening_month = parseInt(screening_date.substr(5, 2), 10);
			screening_day = parseInt(screening_date.substr(8, 2), 10);
			if (url && url != '')
				screening_time = '<a href="' + url + '" target="_new">' + screening_time + '</a>';
		}

		if (screening_date) {
			screeningDate.setFullYear(screening_date.substr(0, 4));
			screeningDate.setMonth(parseInt(screening_month, 10)-1);
			screeningDate.setDate(screening_day);
		}
        
        for (j = 0; j < daysToProcess.length; j++) {
            if (screening_date == daysToProcess[j])
                screeningDay = j; // Assign screeningDay a number, 0 = today, 1 = tomorrow, etc. to use as the index in the screeningsByDay array later
        }
        
        if (screeningDay != null) { // If this screening is scheduled for a day listed in one of the daysToProcess
            if (screeningsByDay[screeningDay][titleOrName]) { // The current day already has this title or location name defined as an object
                if (screening_time && screeningsByDay[screeningDay][titleOrName].length == titleOrName.length) // We have a showtime to add, and this title/location doesn't yet have any
                    screeningsByDay[screeningDay][titleOrName] += ": " + screening_time;
                else if (screening_time && screeningsByDay[screeningDay][titleOrName].length > titleOrName.length) { // We have a showtime to add, and this title/location has some already
                	screeningsByDay[screeningDay][titleOrName] = addShowtimeToStringOfShowtimes(screeningsByDay[screeningDay][titleOrName], screening_time);
				}
            } else if (titleOrName) { // The current day doesn't yet have this title/location defined for it, and we have a title/location to add
                screeningsByDay[screeningDay][titleOrName] = titleOrName;
                if (screening_time)
                    screeningsByDay[screeningDay][titleOrName] += ": " + screening_time;
            }
            
        } else if (screening_date != '' && screeningDate > today) {
                // This screening is not listed as one of the Now Playing daysToProcess, so it's farther in the future; add it to the Coming Soon 'day'
        
        	if (!futureScreeningsByFilm[titleOrName]) { // The current location doesn't yet have this title defined as an object
	            futureScreeningsByFilm[titleOrName] = new Array(13); // Index 0 will store the title, indexes 1 through 12 the screenings for Jan-Dec
	            futureScreeningsByFilm[titleOrName][0] = titleOrName; // This is a bit of a hack, I admit. But there is never a month 0, so this is safe.
	        }
	        if (screening_month && screening_day && !futureScreeningsByFilm[titleOrName][parseInt(screening_month)]) { // We have a screening to add, and the month isn't yet defined
	            futureScreeningsByFilm[titleOrName][parseInt(screening_month)] = new Array(32); // Index 0 will store a string  of this month's showtimes
	            futureScreeningsByFilm[titleOrName][parseInt(screening_month)][0] = monthShortNames[screening_month-1] + '&nbsp;'; // Just add the month name for now
	            futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)] = screening_time; // Define this day
			} else if (screening_month && screening_day && futureScreeningsByFilm[titleOrName][parseInt(screening_month)]) {
	        	if (!futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)]) {// This day in this month isn't yet defined
	            	//futureScreeningsByFilm[titleOrName][parseInt(screening_month)][0] += ', ' + screening_day; // Add to the string version of this month's showtimes
	            	futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)] = screening_time; // Define this day
				} else if (futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)]) { // Day is already defined, so we're just adding more screening times
					futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)] = addShowtimeToStringOfShowtimes(
                        futureScreeningsByFilm[titleOrName][parseInt(screening_month)][parseInt(screening_day)], screening_time);
				}
	        }
		}
    } // For each screening
    
    // Now reformat the data from its array-like organization in futureScreeningsByFilm into strings sorted by title in screeningsByDay[screeningsByDay.length-1]
    var currentMonthNum = (new Date).getMonth()+1;
	var processMonthsInOrder = new Array(13);
        // We want to cycle through the months starting with the current one, to the end of the year, then January through the month before this one of next year; 0 is skipped
	for (var i = 0; i < 12; i++) {
		if (currentMonthNum + i < 13)
			processMonthsInOrder[i+1] = currentMonthNum + i;
		else
			processMonthsInOrder[i+1] = currentMonthNum + i - 12; // next year
	}
	
	for (i in futureScreeningsByFilm) { // For each film:
	    screeningsByDay[screeningsByDay.length-1][futureScreeningsByFilm[i][0]] = futureScreeningsByFilm[i][0];
            // 0 was set to hold the film title; 01 through 12 are screenings for Jan through Dec, wrapping around to always contain the current month and then the next eleven;
            // i.e. if now is Nov 2009, then 01 = Jan 2010, 02 = Feb 2010 . . . 10 = Oct 2010, 11 = Nov 2009, 12 = Dec 2009
	    var firstScreening = true;
	    for (var j = 1; j < 13; j++) { // From the current month, wrapping around to this month next year
	        if (futureScreeningsByFilm[i][processMonthsInOrder[j]]) {
	            if (firstScreening) { // If this month is the first one processed for this film, put a colon after the title
	                screeningsByDay[screeningsByDay.length-1][futureScreeningsByFilm[i][0]] += ": ";
	                firstScreening = false;
	            } else {
	                screeningsByDay[screeningsByDay.length-1][futureScreeningsByFilm[i][0]] += "; ";
	            }
		        var daysForString = new Array();
		        for (var k = 1; k < 31; k++) { // Assemble each day's string into the daysForString array
					if (futureScreeningsByFilm[i][processMonthsInOrder[j]][k] && futureScreeningsByFilm[i][processMonthsInOrder[j]][k] != '')
						daysForString.push('<span class="coming_soon_date_group"><span class="has_time" ' +
                            'onclick="jQuery(this).parent(\'span.coming_soon_date_group\').children(\'span.coming_soon_time:eq(0)\').toggle();">' + k +
                            '</span><span class="coming_soon_time">:&nbsp;' +
                            futureScreeningsByFilm[i][processMonthsInOrder[j]][k] + '</span></span>'); // Has screening times, so add them as a slide-out for this day number
					else if (futureScreeningsByFilm[i][processMonthsInOrder[j]][k] == '') // Has screenings, but no times
						daysForString.push('' + k); // So just add the screening day number as a string
		        }
		        screeningsByDay[screeningsByDay.length-1][futureScreeningsByFilm[i][0]] += futureScreeningsByFilm[i][processMonthsInOrder[j]][0] + daysForString.join(', ');
                    // 'Nov ' + days separated by ', '
	        }
	    }
	}
    
    return screeningsByDay;
}

// More support functions:
function processHash(hash, defaults) { // Called from display-map.php JavaScript code inserted into page header
    var hashvars = hash.split("&");
    var lat, lng, homeAddress, radius, source, mapLock, categories, page_type, content, geographically_limited;

    if (hashvars[0].split("=")[0] == "q" && hashvars[0].split("=")[1] == "a" && hashvars.length == 3) { // #q=a, the output from another page's inline search form:
        if (hashvars[1].split("=")[0] == "a") homeAddress = decodeURIComponent(hashvars[1].split("=")[1]);
        if (hashvars[2].split("=")[0] == "r") radius = hashvars[2].split("=")[1];
        // Take the input and search it:
        jQuery('#addressInput').val(homeAddress);
        jQuery('#radiusSelect').val(radius);
        searchLocations(defaults["categories"], defaults["page_type"], defaults["content"], defaults["geographically_limited"]);
    } else if (hashvars[0].split("=")[0] == "q" && hashvars[0].split("=")[1] == "h" && hashvars.length == 10) { // #q=h, fully-formed hash, probably from jquery history:
  		if (hashvars[1].split("=")[0] == "lat") lat = hashvars[1].split("=")[1];
  		if (hashvars[2].split("=")[0] == "lng") lng = hashvars[2].split("=")[1];
  		if (hashvars[3].split("=")[0] == "homeAddress") homeAddress = hashvars[3].split("=")[1];
  		if (hashvars[4].split("=")[0] == "source") source = hashvars[4].split("=")[1];
  		if (hashvars[5].split("=")[0] == "mapLock") mapLock = hashvars[5].split("=")[1];
  		if (hashvars[6].split("=")[0] == "categories") categories = hashvars[6].split("=")[1];
  		if (hashvars[7].split("=")[0] == "page_type") page_type = hashvars[7].split("=")[1];
  		if (hashvars[8].split("=")[0] == "content") content = hashvars[8].split("=")[1];
  		if (hashvars[9].split("=")[0] == "geographically_limited") geographically_limited = hashvars[9].split("=")[1];
        // Take the input and run it:
        searchLocationsNear(new GLatLng(lat, lng), homeAddress, source, mapLock, categories, page_type, content, geographically_limited);
    }
}

function addLeadingZeroIfNeeded(variable) {
	variable = parseInt(variable);
	if (variable < 10)
		return '' + '0' + variable;
	else
		return '' + variable;
}

function nodeHas(node, variable) {
	if (node.getAttribute(variable) && node.getAttribute(variable) != '')
		return true;
	else if (node.getElementsByTagName(variable).length > 0 && node.getElementsByTagName(variable)[0].hasChildNodes() && node.getElementsByTagName(variable)[0].firstChild.nodeValue != '')
		return true;
	else if (node.getElementsByTagName(variable).length > 0 && node.getElementsByTagName(variable)[0].getAttribute(variable) &&
            node.getElementsByTagName(variable)[0].getAttribute(variable) != '')
		return true;
	else
		return false;
}
function ifExistsOutput(variable) {
	if (variable && variable != '')
		return variable;
	else
		return '';
}
function ifExistsOutputWithWrapper(variable, strBefore, strAfter) {
	if (!strBefore)
		strBefore = '';
	if (!strAfter)
		strAfter = '';
	if (variable && variable != '')
		return strBefore + variable + strAfter;
	else
		return '';
}
function ifExistsOutputWithDiv(variable, className) {
	if (variable && variable != '')
		return '<div class="' + className + '">' + variable + '</div>';
	else
		return '';
}
function ifExistsOutputWithSpan(variable, className) {
	if (variable && variable != '')
		return '<span class="' + className + '">' + variable + '</span>';
	else
		return '';
}

function formatScreeningTime(screening_date, screening_time) {
    var hour = screening_time.substr(0, 2); // Get the first two characters, which should always be the hour since am times are 07:00
    if (parseInt(hour, 10) < 6) { // For screenings from midnight to 5:59 am, have them show up as part of the previous day's showtimes
        screeningDate = new Date(screening_date.substr(0,4), (parseInt(screening_date.substr(5,2))-1), screening_date.substr(8,2));
        screeningDate.setTime(screeningDate.getTime() - (1000*3600*24)); // Subtract a day from the screening date
        screening_date = screeningDate.getFullYear() + "-" + addLeadingZeroIfNeeded((screeningDate.getMonth()+1)) + "-" + addLeadingZeroIfNeeded(screeningDate.getDate());
	} // TO-DO: Make the 6 am cutoff selectable in the options panel
    if (hour == '00') { // e.g. 00:00 or 00:01 or 00:05, a midnight screening
        if (screening_time == '00:00') // exactly midnight
            screening_time = 'midnight';
        else
            screening_time = '12' + screening_time.substr(2, 3) + ' am';
	} else if (hour.charAt(0) == '0') // e.g. 07:00
        screening_time = screening_time.substr(1, 4) + ' am';
    else if (hour == '10' || hour == '11') // e.g. 10:00, 11:30
			screening_time = screening_time + ' am';
    else if (hour == '12') { // e.g. 12:00, 12:30
		if (screening_time == '12:00') // exactly noon
			screening_time = 'noon';
		else
			screening_time = screening_time + ' pm';
    } else if (parseInt(hour) >= 13) // e.g. 13:00, 16:00, 23:00
        screening_time = (parseInt(hour) - 12) + screening_time.substr(2, 3) + ' pm'; // to 1:00 pm, 4:00 pm, 11:00 pm
    return new Array(screening_date, screening_time);
}

function addShowtimeToStringOfShowtimes(showtimesStr, screening_time) {
	if (showtimesStr.substr(showtimesStr.length-4,4) == '</a>') // Previous time had a link
        var prev_ampm = showtimesStr.substr(showtimesStr.length-6,2);
    else
        var prev_ampm = showtimesStr.substr(showtimesStr.length-2,2);
    
    if (screening_time.substr(screening_time.length-4,4) == '</a>') // Time we're adding has a link
        var next_ampm = screening_time.substr(screening_time.length-6,2);
    else
        var next_ampm = screening_time.substr(screening_time.length-2,2);
        
    if (prev_ampm == next_ampm) { // e.g. previous time ends in ' pm', and so does the one we're about to add, so delete the previous ' pm'
        var last7chars = showtimesStr.substr(showtimesStr.length-7,7); // Either ' pm</a>' or something like '2:05 am'
        last7chars = last7chars.replace(' ' + prev_ampm, ''); // Erase the ' pm' or ' am' that matches the next time
        showtimesStr = showtimesStr.substr(0, showtimesStr.length-7) + last7chars; // Put the modified final 7 chars (or fewer now) back in place
	}
    
    return showtimesStr + ', ' + screening_time;
}

function formatRuntime(runtime) {
	if (!runtime || runtime == '')
		return '';
	var output = '';
	runtime = parseInt(runtime);
	var hrs = Math.floor(runtime / 60);
	var mins = runtime % 60;
	if (hrs == 1)
		output += hrs + ' hr';
	else if (hrs > 1)
		output += hrs + ' hrs';
	if (mins == 1)
		output += ' ' + mins + ' min';
	else if (mins > 1)
		output += ' ' + mins + ' mins';
	return output;
}