// Initialisation
actClickCtr = 0;
alertTimerId = 0;
var hlIni = {};

// Make (empty) firebug functions if firebug isn't available
if (!window.console || !console.firebug) {
	var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
	window.console = {};
	for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {}
}

function popUp(URL) {
	//Pop-up window
	day = new Date();
	id = day.getTime();
	eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=1,location=0,statusbar=1,menubar=1,resizable=1,width=800,height=600,left = 500,top = 225');");
}

function urlencode (str) {
    // URL-encodes string 
	var hexStr = function (dec) {
		return '%' + (dec < 16 ? '0' : '') + dec.toString(16).toUpperCase();
	};

	var ret = '',
			unreserved = /[\w.-]/; // A-Za-z0-9_.- // Tilde is not here for historical reasons; to preserve it, use rawurlencode instead
	str = (str+'').toString();

	for (var i = 0, dl = str.length; i < dl; i++) {
		var ch = str.charAt(i);
		if (unreserved.test(ch)) {
			ret += ch;
		}
		else {
			var code = str.charCodeAt(i);
			if (0xD800 <= code && code <= 0xDBFF) { // High surrogate (could change last hex to 0xDB7F to treat high private surrogates as single characters); https://developer.mozilla.org/index.php?title=en/Core_JavaScript_1.5_Reference/Global_Objects/String/charCodeAt
				ret += ((code - 0xD800) * 0x400) + (str.charCodeAt(i+1) - 0xDC00) + 0x10000;
				i++; // skip the next one as we just retrieved it as a low surrogate
			}
			// We never come across a low surrogate because we skip them, unless invalid
			// Reserved assumed to be in UTF-8, as in PHP
			else if (code === 32) {
				ret += '+'; // %20 in rawurlencode
			}
			else if (code < 128) { // 1 byte
				ret += hexStr(code);
			}
			else if (code >= 128 && code < 2048) { // 2 bytes
				ret += hexStr((code >> 6) | 0xC0);
				ret += hexStr((code & 0x3F) | 0x80);
			}
			else if (code >= 2048) { // 3 bytes (code < 65536)
				ret += hexStr((code >> 12) | 0xE0);
				ret += hexStr(((code >> 6) & 0x3F) | 0x80);
				ret += hexStr((code & 0x3F) | 0x80);
			}
		}
	}
	return ret;
}
function colorStrToAry (str) {
	// Converts a string colour code ('#ffffff', '#fff', 'rgb(255,255,255)' etc) to an array {R,G,B}
	var rtn = new Array(3);
	if (str[0]=='#') {
		// Convert hex color code
		str = str.slice(1);
		if (str.length==3) {
			rtn[0] = parseInt(str[0],16);
			rtn[1] = parseInt(str[1],16);
			rtn[2] = parseInt(str[2],16);
			for (i=0; i<=2; i++) rtn[i]=17*rtn[i];
		} else if (str.length==6) {
			rtn[0] = parseInt(str.substring(0,2),16);
			rtn[1] = parseInt(str.substring(2,4),16);
			rtn[2] = parseInt(str.substring(4,6),16);
		} else return false;
	} else {
		// Split from rgb() to array
		tmp = str.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); // Returns array {'rgb(x,y,z)',x,y,z}
		for (i=0; i<=2; i++) rtn[i]=tmp[i+1]; // Remove the first array item 'rgb(x,y,z)'
	}
	
	return rtn;
}
function colorShift (col, shift) {
	// Shifts the given colour component (0 - 255) towards white (255) by the amount given by shift.
	// shift = 0 gives no movement towards white (always returns col)
	// shift = 1 shifts completely towards white (always returns 255)
	return Math.floor( (255*shift) + (1-shift)*parseInt(col) )
}
function setHash () {
	// Sets the hash to reflect any updates made to the page by jQuery
	newHash = "";
	if ($('#hl1').text()!="") newHash += (newHash!=""?".":"") + "hl1=" + $('#hl1').text().substr(1);
	if ($('#hl2').text()!="") newHash += (newHash!=""?".":"") + "hl2=" + $('#hl2').text().substr(1);
	if ($('#hl3').text()!="") newHash += (newHash!=""?".":"") + "hl3=" + $('#hl3').text().substr(1);
	if ($('#hl4').text()!="") newHash += (newHash!=""?".":"") + "hl4=" + $('#hl4').text().substr(1);
	if ($('#jq-excl-day').text()!="") newHash += (newHash!=""?".":"") + "excl-day=" + $('#jq-excl-day').text().substr(1);
	window.location.hash = (newHash==""?"none":newHash);
}
function getActHighlightString (actNm, hlNm) {
	// Returns a string describing the highlights of the given short act name and highlight name
	// If the act isn't highlighted at all, returns empty string
	// If all the occurences of the act are highlighted, returns the short act name
	// If some but not all of the occurences of the act are highlighted, returns act name + the occurences -- eg. floren(1)(3)
	numHls = $('div.act tr.'+actNm+' td.'+hlNm).length	
	if (numHls == 0) { return ''; }
	
	if (typeof(appearances)=='undefined') { totalAppr = $('div.act tr.'+actNm).length; }
	else if (typeof(appearances[actNm])=='undefined') { totalAppr = 1; }
	else { totalAppr = appearances[actNm]; }
	
	if (numHls == totalAppr) { return actNm; }

	// Not all occurences are highlighted so we need to find which ones are and record the information with the act names
	actNums = '';
	$('div.act tr.'+actNm+' td.'+hlNm).each( function() { actNums += '(' + $(this).parent().attr('class').split(' ')[1] + ')'; } );
	return actNm+actNums;
}

function setSocialNetworks() {
	// Sets the social network share links to reflect any updates made to the page by jQuery
	
	// Update all the current highlight lists
	for (i=1; i<=4; i++) {
		newHlTxt = '';
		// Get the name of every act on the clashfinder that has this highlight
		hlNm = '.hl'+i;
		$('div.act td'+hlNm).parent().each( function() {
			// If we haven't already recorded this act name, record it now
			// (An act may appear several times on the clashfinder)
			actNm = $(this).attr('class').split(' ')[0];
			if (newHlTxt.indexOf(actNm)==-1) {
				// Check if the number of times the act's been highlighted is equal to the number of times the act appears
				// If all occurences are highlighted, we only need to record the act name
				hlString = getActHighlightString(actNm, hlNm);
				if (hlString!='') newHlTxt += (newHlTxt==''?'':',')+hlString;
			}
		});
		// Sort the highlighted acts so that they match the output of the customise clashfinder page
		// This will help when we're comparing two pages to see if they're the same
		newHlTxt = newHlTxt.split(',').sort().join(',');
		// Update the result
		$('#new-hl'+i).text(newHlTxt);
	}
	// Update the excluded days list
	exclDay = ""
	$('div.header').each( function() {
		if ($(this).parent().hasClass('page-hidden')) {
			exclDay += (exclDay==""?"":",") + $(this).find('span.page-name').text();
		}
	});
	$('#jq-new-excl-day').text(exclDay);
	
	var newGets = "";
	for (i=1; i<=4; i++) {
		if (typeof(defaults)=='undefined' || typeof(defaults['hl'+i]) == 'undefined') defaultHl = ''; else defaultHl = defaults['hl'+i];
		if ($('#new-hl'+i).text()!=defaultHl) { newGets += "&hl"+i+'='+($('#new-hl'+i).text()==''?'none':$('#new-hl'+i).text()); }
	}
	if (typeof(defaults)=='undefined' || typeof(defaults['excl-day']) == 'undefined') defaultExDy = ''; else defaultExDy = defaults['excl-day'];	
	if ($('#jq-new-excl-day').text()!=defaultExDy) newGets += "&excl-day="+($('#jq-new-excl-day').text()==''?'none':$('#jq-new-excl-day').text());
	newGets += "&"+getOther;
	popUpAdr = topPath+"show/customise_clashfinder.php?name="+getName+newGets;
	myAdr = topPath+"s/"+getName+"/"+newGets.substr(1);
	$('#delicious').attr("href", "http://del.icio.us/post?url="+urlencode(myAdr)+"&"+shareTitle);
	$('#digg').attr("href", "http://digg.com/submit?url="+urlencode(myAdr)+"&"+shareTitle);
	$('#reddit').attr("href", "http://reddit.com/submit?url="+urlencode(myAdr)+"&"+shareTitle);
	$('#facebook').attr("href", "http://www.facebook.com/sharer.php?u="+urlencode(myAdr));
	$('#stumbleupon').attr("href", "http://www.stumbleupon.com/submit?url="+urlencode(myAdr)+"&"+shareTitle);
	$('#jq-myUrl').val(myAdr);
	
	if (myAdr.length>urlWarnLen) $('#jq-long-adr-warning').show(); else $('#jq-long-adr-warning').hide()
	
}
function updateSaveRestore() {
// To do: get these to animate
	if (prevAdr == myAdr) {
		// alert ($('#jq-save-link').css('display'));
		// if ($('#jq-save-link:visible').length > 0) $('#jq-save-link, #jq-restore-link').hide();
		// $('#jq-save-link, #jq-restore-link').hide("fast");
		$('#jq-save-link, #jq-restore-link').hide();
	} else {
		// $('#jq-save-link, #jq-restore-link').show("fast");
		$('#jq-save-link, #jq-restore-link').show();
	}
	// $('#jq-save-link, #jq-restore-link').css('display', (prevAdr == myAdr ? 'none' : ''));
}

function sendPopScores() {

	// Don't send scores if we've got nothing new to send since the last time
	if (actClickCtr==0) return; 

	// If we've saved the clashfinder, use that address for scoring instead.
	if (savedAdr!='') return;
	
	// Update the record which will give us the "Restore Previous Clashfinder" link address
	// This doesn't have anything to do with the Pop Scoring but it's a convenient time to do it
	$.get(topPath+"show/update_user_info.php",{'name':getName, 'uid':uID, 'key':'last', 'val':myAdr}, function(rtn) {
		if (debug && rtn!='') alert("update_user_info.php: "+rtn);
	});


	// Don't send scores if popularity scoring isn't enabled (if we can't set cookies)
	if (!popEnbl) return; 
	
	newHls = "";
	for (i=1; i<=4; i++) {
		newHl = $('#new-hl'+i).text();
		if (newHl != "") newHls += (newHls==""?"":".")+"hl"+i+"="+newHl;
	}
	if (newHls == "") return;
	
	// $('#jq-click-ctr').text(newHls);
	$.get(topPath+"show/pop_scoring.php",{'name':getName, 'score-add':newHls, 'uid':uID});
	
	actClickCtr = 0;
	$('#jq-click-ctr').text(actClickCtr);

}

function setScrollToActLinks() {
	// Make the act names into links to allow the user to click them to go to them
	$('div.output-details span.jq-scroll-to-act').each(function() {
		actNm = $(this).attr('class').split(' ')[1];
		
		// Remove any click functions we've already got
		$(this).unbind('click');
		
		// Quit if the act isn't visible on the clashfinder.
		// If the act's got a link already, remove it
		if ($('div.act tr.'+actNm+':visible').length == 0) {
			$(this).click( function() {} ); // Remove click function
			$(this).html($(this).text());
			return;
		}
		
		newTxt = '<a href="javascript:void(0)" class="'+actNm+'" title="Jump to this act on the clashfinder">'+$(this).text()+'</a>';
		$(this).html(newTxt);

		$(this).click( function() {
			// Find the parent we want to promote -- div.act
			actNm = $(this).attr('class').split(' ')[1];
			thisActDiv = $('div.act tr.'+actNm+':visible');
			scrollToAct(thisActDiv);
		});
	});
}

function scrollToAct(thisActDiv) {
	// Shows an act to the user by greying everything else out and scrolling to it

	// Scroll to the matching element
	// Get the height and width of the window so we can position the element as required (default for scrollTo is to align the element to the top left)
	winHt = $(window).height();
	winWth = $(window).width();
	// If the height and width are very large, restrict them because we may have got the document dimensions by mistake
	if (winHt>1200) winHt = 1200;
	if (winWth>1600) winWth = 1600;
	// Scroll so that the element is two-thirds to the right, two-thirds up.
	// Note: default position is top left.  We specify a top offset of 1/3 winHt to leave a gap of 1/3 at the top, we make it negative because we need to move up the document.
	$.scrollTo(thisActDiv, {duration:800, offset: {top:-winHt*1/3, left:-winWth*2/3}} );

	// Expand the selection to all the matching act divs so we light all of them up
	shortName = thisActDiv.parents('.act').find('tr').attr('class').split(' ')[0];
	thisActDiv = $('div.act tr.'+shortName).parents('.act');
	if (thisActDiv.length==0) return;
	
	// Bring the div to the front
	thisActDiv.css('z-index', '9999');
	
	// Fade in the grey area
	$('#grey-out').css('opacity', '0.5').css('display', 'block');

	// Set a timer to fade out the grey area
	window.setTimeout(function() {
		$('#grey-out').fadeOut('slow', function() {
			$(this).css('display','none');
			thisActDiv.css('z-index', 'auto');
		});
		// The hash has been altered becuase the user clicked a bookmark link.
		// Return the hash to its evaluated state.
		setHash();
	}, 1000);
}
function amazon_department(sIn) {
	// Takes a string and converts to the amazon department type for use in the sponsored link
	// If the string isn't recognised, it returns the string
	switch (sIn) {
	case 'music':	return 'popular'; break;
	case 'general':	return 'aps'; break;
	case 'film': 	return 'dvd'; break;
	default:		return sIn;
	}
}
function setUpActs() {

	if (actDivs.length==0 && actTDs.length==0) return;

	sliceLen = 50;
	if (actDivs.length>0) {
		newActDivs = actDivs.slice(0, sliceLen);
		actDivs = actDivs.slice(sliceLen);
	}
	if (actTDs.length>0) {
		newActTDs = actTDs.slice(0, sliceLen);
		actTDs = actTDs.slice(sliceLen);
	}

	msg = 'setUpActs() ['+actTDs.length+' to go]';
	console.time(msg);
	newActDivs.mouseenter(function() {
		// Use a mouse enter function to add the mouseover popup table to each div
		// .append() takes a long time so we do it this way to avoid doing the whole lot at page load
		if ($(this).children('table.rollover').length==0) {
			actNm = $(this).find('h6').text();
			shortNm = $(this).find('tr').attr('class').split(' ')[0];
			if ($('div.act tr.'+shortNm).length > 1) hasNext = true; else hasNext = false;
			
			searchAlias = 'general'; // Set default search alias (Amazon department)
			if (typeof(actTypes)=='object') {
				if (typeof(actTypes[shortNm])!='undefined') searchAlias = actTypes[shortNm];
				else if (typeof(actTypes['~default'])!='undefined') searchAlias = actTypes['~default'];
			}
			searchAlias = amazon_department(searchAlias);
			
			// If the act has a defined name, use it in the amazon link
			try { if (typeof(actInfo[shortNm]['artist'])!='undefined') actNm = actInfo[shortNm]['artist']; }
			catch(err) { /* Don't set the name, then */ }
			
			newHTML = '<table class="rollover"><tr><td>';
			if (showDonate) newHTML += '<a class="amazon-link" href="http://www.amazon.co.uk/s/?url=search-alias='+searchAlias+'&field-keywords='+urlencode(actNm)+'" title="Search Amazon for '+actNm+'" target="_blank"></a>';
			else newHTML += '<a class="amazon-link" href="http://www.amazon.co.uk/s/?url=search-alias='+searchAlias+'&field-keywords='+urlencode(actNm)+'&tag=cfg-21&link_code=wql&camp=2486&creative=8946&_encoding=UTF-8" title="Search Amazon for '+actNm+'" target="_blank"></a>';
			newHTML += '<a class="myspace-link" href="http://searchservice.myspace.com/index.cfm?fuseaction=sitesearch.results&qry='+urlencode(actNm)+'" title="Search MySpace for '+actNm+'" target="_blank"></a>';
			newHTML += '<a class="youtube-link" href="http://www.youtube.com/results?search_query='+urlencode(actNm)+'" title="Search YouTube for '+actNm+'" target="_blank"></a>';
			newHTML += '<a class="spotify-link" href="spotify:search:'+urlencode(actNm)+'" title="Search Spotify for '+actNm+' (Spotify required)"></a>';
			if (hasNext) newHTML += '<a class="next-act '+shortNm+'" href="javascript:void(0)" title="Jump to the next occurrence of this act on the clashfinder"></a>';
			newHTML += '</td></tr></table>';
			$(this).append(newHTML);
			
			// Add a click event to the next-act item
			if (hasNext) {
				$(this).find('a.next-act').click(function() {
					console.time('next-act.click()');
					
					// Get the short name of the act
					actTR = $(this).parents('div.act').find('tr').slice(0,1);
					shortNm = actTR.attr('class').split(' ')[0];
					
					// Find the next act
					// First look in the subsequent acts on the stage, then on the subsequent stages that day, then the subsequent days.
					// If the act still isn't found, loop back and search from the top.
					// This isn't an ideal way of getting the next act because it's heavily dependent on the page structure.
					nextAct = $(this).parents('div.act').nextAll().find('tr.'+shortNm);
					if (nextAct.length == 0) { nextAct = $(this).parents('div.stage-container').nextAll().find('tr.'+shortNm); }
					if (nextAct.length == 0) { nextAct = $(this).parents('div.page-container').nextAll().find('tr.'+shortNm); }
					if (nextAct.length == 0) { nextAct = $('tr.'+shortNm); }
					if (nextAct.length == 0) { alert('Not found'); return; }

					nextAct = nextAct.slice(0,1);
					scrollToAct(nextAct);

					console.timeEnd('next-act.click()');
				});
			}
		}
		// Also add a title to the div.  This will give us hover text.
		rolloverTxt = $(this).find('h6').text();
		if ($(this).find('p.time').length != 0) rolloverTxt += ' (' + $(this).find('p.time').text() + ')';
		$(this).attr('title', rolloverTxt);
	});
	// console.timeEnd('actDivs.each');

	// console.time('actTDs.click');
	newActTDs.click(function (){
		console.time('actTDClick');
		myName = $(this).parent().attr('class').split(' ')[0];
		myGroup = 'hl'+$('#hl-group').text();
		myActs = $('#'+myGroup).text();

		if ($('.plusminus.hl-one-or-all span.val').text()=='All') matchingActs = $('div.act tr.'+myName).children('td');
		else matchingActs = $(this);
		
		if ($(this).hasClass(myGroup)) { matchingActs.removeClass(myGroup); }
		else { matchingActs.addClass(myGroup); }
		matchingActs.setHighlights();

		// Remove the act if it's already named in the highlight string
		// I should do this with a regex, really.
		myActsAry = myActs.split(',');
		myActs = '';
		for (i in myActsAry) { if (myActsAry[i]!='' && myActsAry[i]!=myName && myActsAry[i]!='-'+myName && myActsAry[i].indexOf(myName+'(')!=0) myActs += ',' + myActsAry[i]; }
		
		// Now have a look to see if we want to add the act to the highlight string
		hlString = '';
		if ($('.plusminus.hl-one-or-all span.val').text()=='All') {
			if ($(this).hasClass(myGroup)) { hlString = myName; }
//			else { hlString = '-'+myName; }
// alert (myActs+'; '+hlString);
		} else { hlString = getActHighlightString(myName, myGroup); }
		hlIniTmp = ','+hlIni[$('#hl-group').text()]+',';
		if (hlString!='') { 
			// If the highlight string has a value and that value doesn't match the initial state, add the new state to the list of highlights
			if (hlIniTmp.indexOf(','+hlString+',') == -1) myActs += ',' + hlString;
		} else if (hlIniTmp.indexOf(','+myName+',') != -1 || hlIniTmp.indexOf(','+myName+'(') != -1) {
			// If the highlight string doesn't have a value (i.e. no current highlights) but it is mentioned in the initial state, we need to set a string to say that it needs to be removed
			myActs += ',' + '-' + myName;
		}
// alert (myActs);
		$('#'+myGroup).text(myActs);
		
		setHash();
		setSocialNetworks();
		updateSaveRestore();
		
		// Show the reset link, even if we've got nothing to reset to -- it's too much bother to check if we're at the vanilla clashfinder
		$('#jq-reset-link').css('display', '');
		

		// Count the number of clicks.  If we reach 10 clicks, send the scores for popularity
		actClickCtr++;
		if (actClickCtr>=10) {
			sendPopScores();
		}
		// Send the scores after 10 seconds without a click.
		// The unload event doesn't always fire so it's worth clearing the clicks after a while
		if (typeof(tmrClick) != 'undefined') clearTimeout(tmrClick);
		tmrClick = setTimeout("sendPopScores()",10000);
		
		$('#jq-click-ctr').text(actClickCtr);
		
		// Show or hide the link that allows the user to update recommendations.
		// This is displayed if there have been changes to the highlights since page load
		$('#recommendations li.jq-update').fadeTo('fast', 1.0);
		// Remove the flag that says that the recommendations have been updated recently
		recsUpdated = false;
		
		console.timeEnd('actTDClick');
	});
	
	console.timeEnd(msg);

	window.setTimeout('setUpActs()', 1);

}
function setPageSetupCookie() {
	// Sets the cookie describing basic page setup -- details boxes collapsed, selected highlight group etc
	newCookie = '';
	$('div.details-box div.banner').each( function() {
		// Compile a list of all the boxes 
		if ($(this).hasClass('collapse')) newCookie += (newCookie==''?'':',')+$(this).attr('id');
	});
	newCookie = 'details-box='+newCookie;
	newCookie += '&hl-group='+$('#hl-group').text();
	newCookie += '&hl-one-or-all='+$('div.hl-one-or-all span.val').text();
	$.cookie('pageSetup', newCookie, { path: '/' });
}

$(function () {
	// Start document.ready timer
	console.time('init');
	
	// Save the highlight sets at page load.  We need to compare these with the edited highlights later.
	for (i=1; i<=4; i++) hlIni[i] = $('#new-hl'+i).text();
	
	console.time('attachClickEvents');
	$('.plusminus.hl-group a.minus').click(function (){
		if ($('#hl-group').text() > 1) { 
			$('.plusminus.hl-group div.color-preview').removeClass('hl'+$('#hl-group').text());
			newGrp = parseInt($('#hl-group').text())-1;
			$('#hl-group').text(newGrp);
			if (hlName[newGrp]!=undefined && hlName[newGrp]!="") $('.plusminus.hl-group span.val').text(hlName[newGrp]); 
			else $('.plusminus.hl-group span.val').text('Highlight Group: ' + newGrp);
			$('.plusminus.hl-group div.color-preview').addClass('hl'+newGrp);
		}
		setPageSetupCookie();
		$(this).blur();
	});
	$('.plusminus.hl-group a.plus').click(function (){
		if ($('#hl-group').text() < 4) {
			$('.plusminus.hl-group div.color-preview').removeClass('hl'+$('#hl-group').text());
			newGrp = parseInt($('#hl-group').text())+1;
			$('#hl-group').text(newGrp);
			if (hlName[newGrp]!=undefined && hlName[newGrp]!="") $('.plusminus.hl-group span.val').text(hlName[newGrp]);
			else $('.plusminus.hl-group span.val').text('Highlight Group: ' + newGrp);
			$('.plusminus.hl-group div.color-preview').addClass('hl'+newGrp);
		}
		setPageSetupCookie();
		$(this).blur();
	});
	$('.plusminus.hl-one-or-all a.minus').click(function (){
		$('.plusminus.hl-one-or-all span.val').text('One');
		setPageSetupCookie();
		$(this).blur();
	});
	$('.plusminus.hl-one-or-all a.plus').click(function (){
		$('.plusminus.hl-one-or-all span.val').text('All');
		setPageSetupCookie();
		$(this).blur();
	});

	console.time('divHeaderClick');
	$('div.header').click(function (){
		// Show or hide a page when the header is clicked
		pageContnr = $(this).parent();
		thisDay = $(this).find('span.page-name').text();
		exclDay = $('#jq-excl-day').text();
		if (pageContnr.hasClass('page-hidden')) {
			pageContnr.removeClass('page-hidden');
			if (exclDay.indexOf(thisDay) == -1) exclDay += ",-" + thisDay;
			else exclDay = exclDay.replace(","+thisDay, "");
		} else {
			pageContnr.addClass('page-hidden');
			if (exclDay.indexOf("-"+thisDay) == -1) exclDay += "," + thisDay;
			else exclDay = exclDay.replace(",-"+thisDay, "");
		}
		$('#jq-excl-day').text(exclDay);
		setHash();
		setSocialNetworks();
		setScrollToActLinks();  // We need to update these becuase we remove links from recommended acts that aren't visible
	});
	console.timeEnd('divHeaderClick');


	console.time('divActTD');
	// Dispatch to a function to do a bit at a time because doing the whole lot can take a while to load for a moderately sized clashfinder.
	actDivs = $('div.act');
	actTDs =  $('div.act td');
	setUpActs();
	console.timeEnd('divActTD');
	console.timeEnd('attachClickEvents');
	
	$('div.details-box div.banner').click(function (){
		// Expand and collapse the contencts of the boxes in the output details
		if ($(this).hasClass('expanded')) {
			$(this).removeClass('expanded').addClass('collapse').parent().children('div.jq-details-contents').slideUp('fast');
		} else {
			$(this).removeClass('collapse').addClass('expanded').parent().children('div.jq-details-contents').slideDown('fast');
		}
		setPageSetupCookie()
	});
	
	// Function to make 'Neapolitan' highlights when an act has more than one highlight
	jQuery.fn.setHighlights = function(){
		// Takes the <tr> element of an act (this contains the act name in the class)
		//  Edit: now takes the <td> element
		
		// Displays striped highlights in the <td> element if there is more than one highlight listed in the <td> class
		// Returns the passed collection to allow chaining
		return this.each(function(){
			if (mono) return; // Do nothing if we're displaying the clashfinder in monochrome
			
			// Check if the passed item is a TD, if not search for a containing TD, if not found quit function
			if (this.tagName=="TD") {
				thisTD = jQuery(this);
			} else {
				thisTD = jQuery(this).find('td:first');
				if (thisTD.length==0) return;
			}

			// Get a list of all the classes of the passed TD.
			classStr = jQuery.trim(thisTD.attr('class'));
			if (classStr=="") {
				numClasses=0; // If you split an empty string you still get an array with one (empty) item, I think, so we need to explicitly set the number of classes to zero
			} else { 
				classList = classStr.replace("  "," ").split(" ").sort();
				numClasses = classList.length;
				if (numClasses>0) divWidth = 100/numClasses;
//				alert(classStr+"; "+numClasses);
			}

			// Check if we've already created the Neapolitan structure for this act.  Create it if we haven't.
			if (!thisTD.find('span.jq-act-name').length) {
				// Add three divs to each act for three possible extra highlight colours (the base background gives the first highlight colour)
				newHTML = '<div class="jq-hl-div1" style=""></div><div class="jq-hl-div2" style=""></div><div class="jq-hl-div3" style=""></div>'; //Removed: <div class="jq-hl-over"></div>';
				thisTD.prepend(newHTML);
				// Now wrap the inner text in spans, which we can add colours to later.
				newHTML = '<span class="jq-act-name"></span>';
				thisTD.find('h6, p').wrapInner(newHTML);
			}
			
			// Make divs visible as required
			for (i=1; i<=3; i++) {
				myDiv = thisTD.find('div.jq-hl-div'+i+':first');
				if (i<numClasses) {
					myDiv
						.css({'display':'block', 'left':i*divWidth+'%', 'width':100-i*divWidth+'%'})
						.attr('class','jq-hl-div'+i+' '+classList[i]); // Set the class to be the identifier for this div (jq-hl-div1 etc) plus the colour class (hl1 etc)
				} else {
					myDiv.css('display','none');
				}
			}
			
			// Set the colour for the span
			// If we've got one or two classes, we want the span to have the first class.  If we've got three or four classes, we want the span to have the second class
			// Note that the array counts from zero
			
			spanGrp = thisTD.find('span.jq-act-name');
			spanGrp.css('background-color','');
			if (numClasses>0) {
				spanGrp.attr('class','jq-act-name '+classList[Math.ceil(numClasses/2)-1]);
				bgCol = colorStrToAry(spanGrp.css('background-color'));  // Get RGB array
				if (bgCol!==false) {
					// Shift the colour towards white.  Note: 3*255=765.
					whShift = 0.5  // White shift = 0: no shift towards white.  White shift = 1: all colours map to white
					newCol = 'rgb('+colorShift(bgCol[0], whShift)+' ,'+colorShift(bgCol[1], whShift)+' ,'+colorShift(bgCol[2], whShift)+')';
					spanGrp.css('background-color',newCol);
				}
			} else {
				spanGrp.attr('class','jq-act-name');
			}
		});
	}

	$('#recommendations li.jq-show-more a').click( function () {
		// If we've already unhidden more recommendations, don't do it again.
		if (moreRecs) return;
		
		// Unhide more recommendations
		$('#recommendations li:hidden').fadeIn('fast');

		// Fade out the text.
		$(this).parent().fadeTo('fast', 0.2);
		moreRecs = true;
	});
	$('#recommendations li.jq-update a').click( function () {
		// If we've already updated the recommendations, don't do it again
		if (recsUpdated) return;
		
		// Fade out the old contents to indicate we're doing something
		$('#jq-rec-contnts').fadeTo('fast', 0.2);
		
		// Compile the available GET information
		popScoreGets = { 'name':getName };
		for (i=1; i<=4; i++) {
			hlTxt = $('#new-hl'+i).text();
			if (hlTxt!='') { popScoreGets['hl'+i]=hlTxt; }
			if (hlName[i]!='') { popScoreGets['hl-name'+i] = hlName[i]; }
		}
		if (moreRecs) popScoreGets['more-recs']=1;
		
		// Send the GET request
		$.get(topPath+"show/pop_score_rtn.php", popScoreGets, function(newRecs){
			// Set the text and fade back in
			$('#jq-rec-contnts').html(newRecs).fadeTo('fast', 1.0);
			// alert (newRecs);
			setScrollToActLinks();
			if ($('#recommendations li:hidden').length!=0) $('#recommendations li.jq-show-more').fadeTo('fast', 1.0);
		});
		
		// Fade out the update link
		$(this).parent().fadeTo('fast', 0.2);
		recsUpdated = true;
	});
	
	setScrollToActLinks();

	console.time('makeDoc');
	$('div.act td.hl1, div.act td.hl2, div.act td.hl3, div.act td.hl4').setHighlights();
	console.timeEnd('makeDoc');
	
	$(window).unload(function() {
		// Run when the page is exited
		if (actClickCtr>0) sendPopScores();
	}); 

	// Hide the 'loading' box
	$('#loading-float').css('display', 'none');		
	
	// End document.ready timer
	console.timeEnd('init');
});




/**
 * Cookie plugin
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 *
 * Create a cookie with the given name and value and other optional parameters.
 *
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Set the value of a cookie.
 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
 * @desc Create a cookie with all available options.
 * @example $.cookie('the_cookie', null);
 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
 *       used when the cookie was set.
 * @example $.cookie('the_cookie');
 * @desc Get the value of a cookie.
 *
 */
jQuery.cookie=function(name,value,options){if(typeof value!='undefined'){options=options||{};if(value===null){value='';options.expires=-1;}var expires='';if(options.expires&&(typeof options.expires=='number'||options.expires.toUTCString)){var date;if(typeof options.expires=='number'){date=new Date();date.setTime(date.getTime()+(options.expires*24*60*60*1000));}else{date=options.expires;}expires='; expires='+date.toUTCString();}var path=options.path?'; path='+(options.path):'';var domain=options.domain?'; domain='+(options.domain):'';var secure=options.secure?'; secure':'';document.cookie=[name,'=',encodeURIComponent(value),expires,path,domain,secure].join('');}else{var cookieValue=null;if(document.cookie&&document.cookie!=''){var cookies=document.cookie.split(';');for(var i=0;i<cookies.length;i++){var cookie=jQuery.trim(cookies[i]);if(cookie.substring(0,name.length+1)==(name+'=')){cookieValue=decodeURIComponent(cookie.substring(name.length+1));break;}}}return cookieValue;}};

/**
 * jQuery.ScrollTo - Easy element scrolling using jQuery.
 * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 5/25/2009
 * @author Ariel Flesler
 * @version 1.4.2
 *
 * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
 */
;(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);

function Hash() {
	// Usage:
	// var myHash = new Hash('one', 1, 'two', 2, 'three', 3);	// creates a hash with 'one'=>1 etc
	// myHash.removeItem('one'); 	// unsets 'one'
	// myHash.setItem('one', 1); 	// Sets 'one'=>1
	// myHash.getItem('one'); 		// returns 1
	// myHash.hasItem('one');		// returns true
	// myHash.clear(); 				// unsets all items in myHash
	
	this.length = 0;
	this.items = new Array();
	for (var i = 0; i < arguments.length; i += 2) {
		if (typeof(arguments[i + 1]) != 'undefined') {
			this.items[arguments[i]] = arguments[i + 1];
			this.length++;
		}
	}
   
	this.removeItem = function(in_key) {
		var tmp_previous;
		if (typeof(this.items[in_key]) != 'undefined') {
			this.length--;
			var tmp_previous = this.items[in_key];
			delete this.items[in_key];
		}
		return tmp_previous;
	}

	this.getItem = function(in_key) { 
		return this.items[in_key]; 
	}

	this.setItem = function(in_key, in_value) {
		var tmp_previous;
		if (typeof(in_value) != 'undefined') {
			if (typeof(this.items[in_key]) == 'undefined') { this.length++; } 
			else { tmp_previous = this.items[in_key]; }
			this.items[in_key] = in_value;
		}
		return tmp_previous;
	}

	this.hasItem = function(in_key) {
		return typeof(this.items[in_key]) != 'undefined';
	}

	this.clear = function() {
		for (var i in this.items) { delete this.items[i]; }
		this.length = 0;
	}
}
