
// So we can do things like if ($('.classname').exists()) {}
jQuery.fn.exists = function(){return jQuery(this).length>0;}

var superme = {
	
	facebookApiKey: '',

    // App profile page - used for inviting/suggesting to friends
    facebookFanPage: 'http://www.facebook.com/playsuperme',
	
	// This will be an array of objects with id, name and description of every possible achievement.
	achievements: [],
	
	// This will be populated with uid, name, friends, etc once logged in.
	// Initialised in superme.initializeUser().
	user: {},
	
	// Either 'in' or 'out'.
	dashboardPosition: 'in',
	
	// This will be the Youtube Player object that is created in superme.initializeVideo().
	youtubePlayer: null,
		
	// We store whether we've already sent the "You've watched this video" achievement
	// just to avoid sending it more than once on a page.
	videoAchievementSent: false,
	
	// small for 'hacks' pages, big otherwise.
	videoSize: 'big',
	
	// This will be the id of a Game if we're on the Game Entry page.
	gameId: null,
	
	// On the Game Entry page, this will be set.
	flashVars: {},
	
	// Could be an associative array of content_item_id, attr1, attr2, attr3, attr4, score, achievements.
	// For the featured video/quiz on this page. Used in superme.sendScores().
	// (Games set this when the game is ready to submit its score.)
	scoreableItem: {},
	
	// On a Learning page this will contain 'slug' and 'name' keys for the relevant Attribute.
	// So we can call the correct Boost content.
	attribute: {},
	
	// Will be populated when we fetch messages. These will then be displayed one after the other.
	messageQueue: [],

    // Will be populated with cached dashboard user_score.  
    // Used to generate 'pending' dash scores from sendScores()
    // Associative array of attr1, attr2, attr3, attr4, progress, overall_score
    // with sub-keys max, percent, score
    cachedUserScore: {},
    // Will be populated with cached user score for this content item (as returned from fetchUsersGameScores() )
    // Associative array of attr1, attr2, attr3, attr4, progress, overall_score
    // with sub-keys max, percent, score
    cachedUserContentScore: {},
    // set true if content item has been previously viewed 
    // called from markContentItemIfPreviouslyScored
    previouslyScoredContentItem: false,
	
	// All the Ajax requests will be relative to this.
	// Should not end in a slash.
	baseUrl: '',
	
	initialize: function(options) {
		superme.baseUrl = options.baseUrl;
		superme.facebookApiKey = options.facebookApiKey;
		superme.achievements = options.achievements;
		if (options.videoSize) {
			superme.videoSize = options.videoSize;
		}
		if (options.scoreableItem) {
			superme.scoreableItem = options.scoreableItem;
		}
		if (options.attribute) {
			superme.attribute = options.attribute;
		}
		
		superme.initializeDashboard();
		
		superme.initializeUser();
		
		// Will also call superme.fetchAsyncMessages(), as we need to wait till we're
		// logged in (or not) in order to use the user's name from Facebook.
		superme.initializeFacebookConnect();
		
		superme.initializePage();
		
		if (options.gameId) {
			// On a Games Entry page.
			superme.gameId = options.gameId;
			if (options.flashVars) {
				superme.flashVars = options.flashVars;
			}
			
			superme.initializeGame();
			superme.fetchUsersGameScores();

		} else if (options.videoYouTubeId) {
			// Video Entry or the home page.
			superme.videoLoad(options.videoYouTubeId);
		}
		
		superme.initializeSlides();
	},

	/**
	 * Cache the retrieved dash user scores
	 * Called from superme.fetchDashboard() 
	 */
    cacheUserScore: function(userScore) {
        superme.cachedUserScore = userScore;
    },


    /**
	 * Cache the retrieved user score for this content_item
	 * Called from superme.fetchUsersGameScores() 
	 */
    cacheUserContentScore: function(userContentScore) {
        superme.cachedUserContentScore = userContentScore;
    },


	/**
	 * The Achievements panel of the extended Dashboard.
	 * achivements comes from superme.fetchDashboardExtended()
	 */
	drawDashboardAchievements: function(achievements) {
		
		$div = $('#panel-2 .panel-loggedin');

		$div.html('');

		list = '';
		$.each(achievements, function(n, ach){
			list += '<li id="achievement-'+ach.id+'"';
			if (ach.active) {
				list += ' class="won"';
			}
			list += '</li>';
		});
		
		$div.append( $('<ul/>').attr({id:'achievements'}).html(list) );
		$div.append( $('<div/>').attr({id:'achievement-desc'}) );
		
		// Now they're in there, add their hover actions.
		$.each(achievements, function(n, ach){
			$('#achievement-'+ach.id).hover(
				// Show it...
				function() {
					$('<h4/>').html(ach.name).appendTo('#achievement-desc');
					$('<p/>').html(ach.description).appendTo('#achievement-desc');
					$('#achievement-desc').show(0);
				},
				// Hide it...
				function() {
					$('#achievement-desc').hide(0, function() {
						$('#achievement-desc').html('');
					});
				}
			);
		});
		
		$('#panel-2 .panel-waiting').hide();
		$div.show();
	},
	
	
	/**
	 * The Boost Content panel of the extended Dashboard.
	 * content comes from superme.fetchDashboardExtended()
	 */
	drawDashboardBoostContent: function(content) {

		$div = $('#panel-3 .panel-loggedin');
		
		$div.html('');
		
		$div.append( $('<p/>').html(content.text_analysis) );
		$div.append( 
			$('<p/>').addClass('thumbnail').append(
				$('<a/>').attr({
					href: content.content_item.url
				}).html(
					$('<img/>').attr({
						src: content.content_item.thumbnail_url,
						width: content.content_item.thumbnail_width,
						height: content.content_item.thumbnail_height,
						alt: "Thumbnail of " + content.content_item.title
					})
				)
			)
		);
		$div.append(
			$('<a/>').attr({
				href: content.content_item.url
			}).html( content.content_item.title ).wrap( $('<h4/>') )
		);
		$div.append( $('<p/>').html(content.text_improvement) );
		
		$('#panel-3 .panel-waiting').hide();
		$div.show();
	},
	
	
	/**
	 * Draw the fourth panel in the extended dashboard.
	 * activity and scores come from superme.fetchDashboardExtended().
	 * This method only goes as far as getting data about Facebook Friends, and then 
	 * passes the data on to supermedrawDashboardFriendsFinish().
	 */
	drawDashboardFriends: function(activity, scores) {
		
		// Get all the Facebook IDs of friends in both sets of data.
		var friendIds = [];
		$.each(activity, function(n, activityData) {
			friendIds.push(activityData.facebookUID);
		});
		$.each(scores, function(n, scoreData) {
			friendIds.push(scoreData.facebookUID);
		});
		
		// Get the Facebook friends' details, then contine on to superme.drawDashboardFriendsFinish().
		superme.fetchFriendsFacebookDetails(
			friendIds,
			superme.drawDashboardFriendsFinish,
			scores,
			activity
		);
	},
	
	
	/**
	 * The second half of drawing the fourth Extended Dashboard panel.
	 * This is called after we've got the Facebook friends' data and can carry on.
	 * facebookFriends is an associative array of ID => {name:"Fred Bloggs", profile_url:"http://..."}
	 */
	drawDashboardFriendsFinish: function(facebookFriends, scores, activity) {
		$div = $('#panel-4 .panel-loggedin');
		
		$div.html('');

		// FIRST, output the activity stuff.
		
		var activityHtml = '';
		
		// List each activity item and who did what.
		$.each(activity, function(n, act) {
			// activityHtml += '<br />' + facebookFriends[act.facebookUID].name + ' ' + act.verb + ' ' + act.content_item_title;
			activityHtml += '<br />' + facebookFriends[act.facebookUID].name + ' ' 
                + act.verb + ' ' 
                + '<a href="/content/' + act.content_item_slug + '/">' 
                + act.content_item_title 
                + '</a>';
		});
		
		if (activityHtml) {
			activityHtml = 'Since you last looked:' + activityHtml;
		} else {
			activityHtml = 'No recent activity.  Invite your friends to play on the <a href="' + superme.facebookFanPage + '">SuperMe fan page</a>';
		}
		
		$div.append(
			$('<p/>').html(activityHtml)
		);
		
		// LAST, output the score stuff.
		
		var scoreHtml = superme.makeScoreboardHtml(facebookFriends, scores);

        // if there's no scores, use nothing text
        if (scoreHtml) {
            // there's a table here
        } else {
			scoreHtml = "<p>Your friends haven&#8217;t scored anything yet.</p>";
        }

		// Put all the scores HTML together.
		$div.append(
			$('<div/>').addClass('scores').html(scoreHtml).prepend(
				$('<h4/>').html('<a href="'+superme.baseUrl+'/facebook/canvas/" title="See more scores on Facebook">High scores</a>')
			)
			// .append(
			// 				$('<p/>').addClass('scoreboard').append(
			// 					$('<a/>').attr({
			// 						href: '/scoreboard/'
			// 					}).html('<span class="arrow"></span><span class="text">View all scores</span>')
			// 				)
			// 			)
		);
		
		$('#panel-4 .panel-waiting').hide();
		$div.show();
	},
	
	
	/**
	 * Starts preparing data for displaying friends' scores on the Game Entry page.
	 * then passes that on to superme.drawFriendsGameScoresFinish().
	 * Called from superme.fetchFriendsGameScores().
	 */
	drawFriendsGameScores: function(scores) {
		$('.friends-scores-loggedin').hide();
		$('.friends-scores-waiting').show();
		$('.friends-scores-loggedout').hide();
		
		// Get all the friends' Facebook IDs into an array.
		var friendIds = [];
		$.each(scores, function(n, sd) {
			friendIds.push(sd.facebookUID);
		});
		
		// Get the Facebook friends' details, then contine on to superme.drawFriendsGameScoresFinish().
		superme.fetchFriendsFacebookDetails(
			friendIds,
			superme.drawFriendsGameScoresFinish,
			scores
		);
	},
	
	
	drawFriendsGameScoresFinish: function(facebookFriends, scores)
	{
		$div = $('#friends-scores .friends-scores-loggedin');
		
		$div.html('');
		
		var scoreHtml = superme.makeScoreboardHtml(facebookFriends, scores);

        // if there's no scores, use nothing text
        if (scoreHtml) {

            // Put all the scores HTML together. 
            $div.append(
                $('<div/>').addClass('scores').html(scoreHtml).append(
                    $('<p/>').addClass('scoreboard').append(
                        $('<a/>').attr({
                            href: superme.baseUrl + '/facebook/canvas/'
                        }).html('<span class="arrow"></span><span class="text">See your friends\' scores</span>')
                    )
                )
            );

        } else {
            // use a 'no friends' message
			scoreHtml = '<p>Your friends haven&#8217;t scored anything yet. Invite them to play on the <a href="' + superme.facebookFanPage + '">SuperMe fan page</a></p>';

            // Put all the scores HTML together.
            $div.append(
                $('<div/>').addClass('scores').html(scoreHtml).append(
                    $('<p/>').addClass('scoreboard').append(
                        // $('<a/>').attr({
                        //    href: superme.baseUrl + '/facebook/canvas/'
                        // }).html('<span class="arrow"></span><span class="text">See your friends\' scores</span>')
                    )
                )
            );

        }
		
		$('#friends-scores .friends-scores-waiting').hide();
		$div.show();
	},
	
	
	/**
	 * After logging the user in, this draws all the things that need to change.
	 * eg, front panel of dashboard, other bits on different pages.
	 */
	drawLoggedIn: function() {

		// Shouldn't be needed, but there was a rare bug that caused duplicate contents for #loggedin in the dashboard.
		$('#loggedin').empty();
		
		// Create a link to the user's FB page.
		var anchor = $('<a/>').attr({
			/*href: superme.user.profile_url,
			title: "To your Facebook page"*/
			href: '/facebook/canvas/',
			title: "See your scores on Facebook"
		});
		
		// Add their FB pic to the #loggedin div.
		if (superme.user.pic_square) {
			$('<img/>').attr({
				src: superme.user.pic_square,
				alt: 'Image of ' + superme.user.name,
				width: 50,
				height: 50
			}).appendTo($('#loggedin')).wrap(anchor.clone());
		}
	
		// Add a span with their FB username to the #loggedin div.
		anchor.html(superme.user.name).appendTo($('#loggedin')).wrap($('<span/>').addClass('username'));
		
		// The level name will be added from fetchDashboard() - we need to fetch the name.
		$('#loggedin').append(
			$('<span/>').addClass('level')
		);
		
		superme.fetchDashboard();
		
		// Add the logout link to the #loggedin div.
		var logout_anchor = $('<a/>').attr({
			href: '',
			title: "Log out of this site and Facebook",
			id: 'logout'
		}).click(function(){
			superme.logout();
			return false;
		});
		$('<img/>').attr({
			src: '/static/img/button_logout.gif',
			alt: 'Logout',
			width: 77,
			height: 20
		}).appendTo($('#loggedin')).wrap(logout_anchor);
		
		$('#login-waiting').hide();
		$('#loggedout').hide();
		$('#loggedin').show();
		
		// On the hack pages.
		$('#hack-loggedout').hide();
		$('#hack-loggedin').show();
		
		// Global nav
		$('#nav #how-to-play').hide();
		$('#nav #your-scores').show();
		
		// Extended dashboard.
		$('.panel-loggedin').show();
		$('.panel-waiting').hide();
		$('.panel-loggedout').hide();
		
		// Game Entry page.
		$('.friends-scores-loggedin').hide();
		$('.friends-scores-waiting').show();
		$('.friends-scores-loggedout').hide();
		
		// Messages.
		// In case the user clicked a 'Connect with Facebook' button in a message, now hide it.
		$('#message-connect-button').hide();
	},
	
	
	/**
	 * Draws the div in the Dashboard which shows the 'Connect' button.
	 * Called from logout() (when the user clicks the 'Log out' link) or when loading the page and not logged in.
	 * Also reveals/hides various other things on other pages.
	 */
	drawLoggedOut: function() {
		$('#login-waiting').hide();
		$('#loggedin').hide();
		$('#loggedin').html('');
		$('#loggedout').show();
		
		superme.fetchDashboard();
		
		// On the hack pages.
		$('#hack-loggedin').hide();
		$('#hack-loggedout').show();
		
		// Global nav
		$('#nav #how-to-play').show();
		$('#nav #your-scores').hide();
		
		// Extended dashboard.
		$('.panel-loggedin').html('').hide();
		$('.panel-waiting').hide();
		$('.panel-loggedout').show();
		
		// Scoreboard page.
		superme.resetScoreboards();
		
		// Game Entry page.
		$('.friends-scores-loggedin').html('').hide();
		$('.friends-scores-waiting').hide();
		$('.friends-scores-loggedout').show();
		$('#users-game-scores').html('None!');
		
		$('.watched').removeClass('watched');
	},
	
	
	/**
	 * Called from superme.fetchPageBoost(), we've fetched details of a content item to display.
	 */
	drawPageBoost: function(contentItem) {

		var imgHeight = contentItem.image_height * (222 / contentItem.image_width);

        var tweaked_attribute_name = superme.attribute.name;
        if (tweaked_attribute_name == 'Grit') {
            tweaked_attribute_name = 'score';
        }

		// $('#boost').append('<h3 class="tk-1">Boost your '+superme.attribute.name+ '</h3>');
		$('#boost').append('<h3 class="tk-1">Boost your '+tweaked_attribute_name+ '</h3>');
		$('#boost').append('<a href="'+contentItem.url+'"><img src="' + superme.baseUrl + contentItem.image_url +'" width="222" height="' + imgHeight + '" alt="Thumbnail image" /></a>');
		$('#boost').append('<h4><a href="'+contentItem.url+'">'+contentItem.title+'</a></h4>');
		$('#boost').append('<p>'+contentItem.promo_text+'</p>');
		
	},
	
	
	/**
	 * Gets the data about friends for displaying the scoreboards on the scoreboard page,
	 * then passes that on to superme.drawScoreboardsFinish().
	 * Called from superme.fetchScoreboards().
	 */
	drawScoreboards: function(scores) {

		$('.board-loggedin').hide();
		$('.board-waiting').show();
		$('.board-loggedout').hide();
		
		// Get all the Facebook IDs of friends.
		var friendIds = [];
		if (scores.attributes) {
			$.each(scores.attributes, function(attr, attrData) {
				$.each(attrData.scores, function(n, sd) {
					friendIds.push(sd.facebookUID);
				});
			});
		}
		
		// Get the Facebook friends' details, then contine on to superme.drawScoreboardsFinish().
		superme.fetchFriendsFacebookDetails(
			friendIds,
			superme.drawScoreboardsFinish,
			scores
		);
	},
	
	
	/**
	 * Called from superme.drawScoreboards() where we got all the Facebook Friend data.
	 * Does the actual drawing of the scoreboards on the scoreboard page.
	 */
	drawScoreboardsFinish: function(facebookFriends, scores) {
		// There are four main sets of data we need to put in the page...
		
		// 1) The intro block.
        // figure out whether to use 'a' or 'an'...
        var firstCharacter = scores.level.user_levelname.charAt(0).toLowerCase();
        var theArticle = 'a';
        if (firstCharacter && 'aeiou'.match(firstCharacter)) { 
            theArticle = 'an'; 
        } 
		$('#scoreboard-intro #level-name-article').html(theArticle);

		$('#scoreboard-intro #level-name').html(scores.level.user_levelname);
		if (scores.level.attribute_info && scores.level.attribute_info != '') {
			$('#scoreboard-intro h3').html("What's this?");
			$('#scoreboard-intro p').html(scores.level.attribute_info);
		}
		$('#scoreboard-intro #level-illustration').addClass('level'+scores.level.level_id);
	
		// 2) The Progress.
		$('#intro-progress .number').html(scores.progress.percent);
		
		// 3) Now, the main scoreboard.
		
		// Get width of score bars. Assume they're all the same.
		// Actually, just hard-wiring it now. We'd have to draw all the bars, then get their size
		// then draw the score bars. Bah.
		var totalWidthOfBar = 113;
		
		if (scores.attributes) {
			$.each(scores.attributes, function(attr, attrData) {
				$div = $('.board-'+attr+' .board-loggedin');

				var barHtml = '';
				$.each(attrData.scores, function(n, sd) {
					// Right position of the score bar.
					var rightPos = totalWidthOfBar - (totalWidthOfBar * (sd.percent / 100));
					barHtml += '<li class="attr '+attr+'"><span class="label">'+n+'</span><span class="bar"><span class="slider" style="right:' + rightPos + 'px;"></span><span class="total">'+superme.numberFormat(sd.score)+'</span></span><span class="name">' + facebookFriends[sd.facebookUID].name;
					if (sd.facebookUID == superme.user.uid) {
						barHtml += '<img src="/static/img/board_arrow.png" width="10" height="14" />';
					}
					barHtml += '</span></li>';
				});
			
				if (barHtml) {
					barHtml = '<ol class="bars">' + barHtml + '</ol>';
					barHtml += '<hr /><p class="rank">You rank '+ attrData.friends_rank + superme.numberSuffix(attrData.friends_rank) + ' out of your friends</p>';
					barHtml += '<p class="rank">You rank '+ attrData.global_rank + superme.numberSuffix(attrData.global_rank) + ' overall</p>';

				} else {
					barHtml = "<p>You and your friends haven't scored anything yet!</p>"
				}
			
				$div.html(barHtml);
			
				$('.board-'+attr+' .board-waiting').hide();
				$div.show();
			});
		}
		
		// Set up the hover-over-the-bars-to-see-the-scores action.
		superme.initializeScoreBars();
		
		
		// 4. The sidebar scores.
		$.each(scores.content_items, function(id, ciData) {
			// Each span that needs a score will have an id like "ci-34" where 34 is the content item ID.
			// So put the score in there and remove the 'not-set' class.
			$('#user-scores #ci-'+ciData.content_item_id).html(superme.numberFormat(ciData.score)).removeClass('not-set');
		});
		// Now, any left with the 'not-set' class, we set the score to 0.
		$('#user-scores .not-set').html(0);
		
	},
	
	
	/**
	 * The user has posted the message to Facebook, so remove the button/link.
	 */
	facebookMessagePosted: function(post_id, exception) {
		if (post_id) {
			$('#post-to-facebook').hide();
		}
	},
	
	
	/**
	 * Get any messages that are waiting for the user.
	 */
	fetchAsyncMessages: function() {
		$.ajax({
			url: superme.baseUrl + '/messaging/messages/',
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(data) {
				// Got a result.
				if (data.messages) {
					// Yay, all good.
					superme.showMessages(data.messages);

				} else {
					// No message to display.
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when fetching an async message. (Error 11): " +textStatus+' '+errorThrown);
			}
		})
	},
	
	
	/**
	 * Gets the content that will go in the 'boost' slot on the Learning item page.
	 * Called from superme.initializePage().
	 */
	fetchPageBoost: function() {
		if (superme.attribute && superme.attribute.slug != '') {
			$.ajax({
				url: superme.baseUrl + '/scoring/boost/' + superme.attribute.slug + '/',
				type: 'GET',
				cache: false,
				dataType: 'json',
				data: ({}),
				success: function(returnedData) {
					superme.drawPageBoost(returnedData.boost_content.content_item);
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					alert("Something went wrong when getting the boost data. (Error 14): " +textStatus+' '+errorThrown);
				}
			});
		} else {
			alert("We don't have an attributeSlug to fetch the Boost content. (Error 15)");
		}
	},
	
	
	/**
	 * Get the content for the always-visible first panel of the dashboard.
	 */
	fetchDashboard: function() {
		$.ajax({
			url: superme.baseUrl + '/scoring/dash/',
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(returnedData) {
				// Got a result.
				if (returnedData.user_score.attr1) {
					// Yay, all good.
                    superme.cacheUserScore(returnedData.user_score);

					superme.updateDashboardBar(1, returnedData.user_score.attr1);
					superme.updateDashboardBar(2, returnedData.user_score.attr2);
					superme.updateDashboardBar(3, returnedData.user_score.attr3);
					superme.updateDashboardBar(4, returnedData.user_score.attr4);
					superme.updateDashboardBar(5, returnedData.user_score.progress);
					superme.updateDashboardScore(returnedData.user_score.overall_score);
					
					superme.user.level_name = returnedData.level_levelname + ': ' + returnedData.user_levelname;
					superme.updateLevelName();
					
					superme.markViewedContentItems(returnedData.viewed_ids);
					
				} else {
					alert("Something went wrong when getting data for the Dashboard. (Error 2)");
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when getting data for the Dashboard. (Error 3): " +textStatus+' '+errorThrown);
			}
		});
	},


	/**
	 * Get the content for the three usually-hidden panels of the dashboard.
	 */
	fetchDashboardExtended: function() {
		if (superme.user.loggedIn) {
			$('.panel-waiting').show();
			$.ajax({
				url: superme.baseUrl + '/scoring/extendeddash/',
				type: 'GET',
				cache: false,
				dataType: 'json',
				data: ({}),
				success: function(returnedData) {
					// Got a result.
					superme.drawDashboardAchievements(returnedData.user_achievements);
					superme.drawDashboardBoostContent(returnedData.boost_content);
					superme.drawDashboardFriends(returnedData.friends_activity, returnedData.friends_scores);
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					alert("Something went wrong when getting data for the Extended Dashboard. (Error 7): " +textStatus+' '+errorThrown);
				}
			});
		}
	},
	
	
	/**
	 * Used by a few methods to fetch details of Facebook friends.
	 * facebookIDs is just an array of Facebook UIDs.
	 * functionRef is the method we should go to once we've got the data.
	 * arg1 and arg2 are arguments that need to be passed on to functionRef().
	 * arg2 is optional.
	 */
	fetchFriendsFacebookDetails: function(facebookIDs, functionRef, arg1, arg2)
	{
		FB.Facebook.apiClient.users_getInfo(
			facebookIDs,
			['name', 'profile_url'],
			function(returnedData) {
				// Got the data, carry on...
				
				// We want to key the data by Facebook ID.
				var facebookDetails = {};
				$.each(returnedData, function(n, fb){
					facebookDetails[fb.uid] = {
						name: fb.name,
						profile_url: fb.profile_url
					};
				});
				
				// Right, carry on with rendering.
				if (arg2) {
					functionRef(facebookDetails, arg1, arg2);
				} else {
					functionRef(facebookDetails, arg1);
					
				}
			}
		);
	},
	
	
	/**
	 * We're on a game page and we need to fetch the user's friends' scores for this game. And display them.
	 * Called when the user logs in.
	 */
	fetchFriendsGameScores: function() {
		if (isNaN(superme.gameId)) {
			return;
		}
		$.ajax({
			url: superme.baseUrl + '/scoring/usercontentfriendsscores/' + superme.gameId + '/',
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(returnedData) {
				// Got a result.
				if (returnedData) {
					superme.drawFriendsGameScores(returnedData.friends_scores);
					
				} else {
					alert("Something went wrong when getting friends' scores for this game. (Error 14)");
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when getting friends' scores for this game. (Error 13): " +textStatus+' '+errorThrown);
			}
		})
	},
	
	
	/**
	 * Get the scores for scoreboard page.
	 * Called if we're on the scoreboard page and we've logged in.
	 */
	fetchScoreboards: function() {
		if (superme.user.loggedIn) {
			$('.panel-waiting').show();
			$.ajax({
				url: superme.baseUrl + '/scoring/scoreboard/',
				type: 'GET',
				cache: false,
				dataType: 'json',
				data: ({}),
				success: function(returnedData) {
					// Got a result.
					superme.drawScoreboards(returnedData.scoreboard);
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					alert("Something went wrong when getting data for the scoreboards. (Error 12): " +textStatus+' '+errorThrown);
				}
			});
		}
	},
	
	
	/**
	 * We're on a game page and we need to fetch the Attribute scores the user has previous got in this game.
	 */
	fetchUsersGameScores: function() {
		if (isNaN(superme.gameId)) {
			return;
		}
		$.ajax({
			url: superme.baseUrl + '/scoring/usercontentscore/' + superme.gameId + '/',
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(returnedData) {
				// Got a result.
				if (returnedData.attr1) {
					// Yay, all good.
                    
                    // cache this...
                    superme.cacheUserContentScore(returnedData);

					var html = '';
					$.each(returnedData, function(attr, attrdata){
						if (attrdata.amount > 0) {
                            var attrLabel = attrdata.name == 'Score' ? 'Your High Score' : attrdata.name;
							html += '<strong>' + attrLabel + ':</strong> ' + superme.numberFormat(attrdata.amount) + ', ';
						}
					});
					html = html.substr(0, html.length-2);
					if (html == '') {
						html = 'None!'
					}
					$('#users-game-scores').html(html);
					if (returnedData.score.amount) {
						// So we know when the user beats their high score.
						superme.user.game_high_score = returnedData.score.amount;
					}
				} else {
					alert("Something went wrong when getting user's scores for this game. (Error 5)");
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when getting user's scores for this game. (Error 4): " +textStatus+' '+errorThrown);
			}
		})
	},
	
	
	/**
	 * Query the back-end to see if there's a message to display.
	 */
	fetchVoiceOfSuperme: function(contentItemId) {
		$.ajax({
			url: superme.baseUrl + '/scoring/voiceofsuperme/'+contentItemId,
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(data) {
				// Got a result.
				if (data.messages) {
					// Yay, all good.
					superme.showMessages(data.messages);

				} else {
					// No message to display.
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when fetching a message. (Error 8): " +textStatus+' '+errorThrown);
			}
		})
	},

	
	/**
	 * Prepare the clicky, slideyness of the dashboard.
	 */
	initializeDashboard: function() {
		
		$('.no-js').hide();
		$('#login-waiting img').show();
		
		// Prepare the clickiness of the slide in/out Dashboard.
		$('#grip').click(function() {
			// When clicked, we either move the dashboard in or out...
			if ($('#dashboard').hasClass('in')) {
				// The dashboard is in, so prepare it to move out.
				var dashboard_left = '0px';
				var dashboard_width = '963px';
				$('#dashboard').addClass('out').removeClass('in');
				var l_background_position = '-22px 0px';
				
				superme.dashboardPosition = 'out';
				
				// Get all the content for the hidden part of the dashboard.
				superme.fetchDashboardExtended();

			} else {
				// The dashboard is out, so prepare it to move in.
				var dashboard_left = '717px';
				var dashboard_width = '246px'
				$('#dashboard').addClass('in').removeClass('out');
				var l_background_position = '0px 0px';
				
				superme.dashboardPosition = 'in';
			}

			// This thin little bar covers up the white gap that appears when the dashboard
			// slides in and out. It will be a tad too dark, but looks better than white.
			$('#dashboard-modesty').show();
			// Move!
			$('#dashboard').animate({
				'left': dashboard_left,
				'width': dashboard_width
			},
			750,
			'swing',
			function(){
				// Don't need this now we've finished moving.
				$('#dashboard-modesty').hide();
				// Change to the correct text on the 'grip'.
				$('#dashboard .l').css({
					'background-position': l_background_position
				});
				if (superme.dashboardPosition == 'in') {
					$('.panel-loggedin').html('');
				};
			})
		});
		
		// Make the scores appear when user hovers over the score bars.
		superme.initializeScoreBars();
	},
	
	
	/**
	 * Works out if the user is logged in (and connected with this App) in Facebook.
	 * If they are, get their details and display it.
	 */
	initializeFacebookConnect: function() {
		// Docs: http://wiki.developers.facebook.com/index.php/FB_RequireFeatures
		FB_RequireFeatures(['Api'], function() {
		//	console.log('before FB.init');
			FB.init(superme.facebookApiKey, '/xd_receiver.htm', {});
		//	console.log('after FB.init');
		
			FB.ensureInit(function() {
		//		console.log('in FB.ensureInit');
				
				// Docs: http://developers.facebook.com/docs/?u=facebook.jslib.FB.Connect.get_status
				// More: http://wiki.developers.facebook.com/index.php/Detecting_Connect_Status
				FB.Connect.get_status().waitUntilReady( function( status ) {
		//			console.log('in waitUntilReady');
					
					switch (status) {
						case FB.ConnectState.connected:
							superme.login('pageload');
							break;
						case FB.ConnectState.appNotAuthorized:
						case FB.ConnectState.userNotLoggedIn:
							superme.drawLoggedOut();
							// Fetch any messages that need to be displayed)
							// For logged-in users this happens in superme.login().
							superme.fetchAsyncMessages();
					};
				});
			});
		});

		$('.login-button').click(function() {
			superme.login('clicked');
			return false;
		});
	},
	
	
	/**
	 * Load the Flash game.
	 */
	initializeGame: function() {
		var params = { wmode: 'opaque' };
		// The element id of the Flash embed
		var attrs = { id: 'game-player' };
		
		swfobject.embedSWF('/static/content/games/'+superme.gameId+'.swf', 'flash-container', '700', '500', '10.0.45', '/static/flash/expressInstall.swf', superme.flashVars, params, attrs); 
	},
	
	
	/**
	 * Set up various things that might or might not be on the page.
	 */
	initializePage: function() {
		// Thumbnail carousels
		$('.carousel-horizontal').jcarousel({
			scroll: 5
		});
		$('.carousel-vertical').jcarousel({
			vertical: true,
			scroll: 3
		});
		
		// Highlight the whole of the embed <input> when selected.
		$('input#embed-code').focus(function() {
			$(this).select();
		}).mouseup(function(e){
			e.preventDefault();
		});
		

		// Set the position of background images to be exactly above the Dashboard.
		// (#content, which contains the Dashboard, moves up and down depending on the height of #top-row)
		
		// Different illustrations for different attributes need offsetting different amounts.
		// (Depends on how far up the illustration needs to be in line with the top of the Dashboard.)
		
		// Min space required to fit #illustration-2 in above the dashboard.
		var heightRequired = 142;
		
		if ($('body').hasClass('videos') || $('body').hasClass('games')) {
			var illOffset = 220;
		}
		if ($('body').hasClass('home')) {
			var illOffset = 220;
		} else if ($('body').hasClass('attr1')) {
			var illOffset = 145;
			heightRequired = 100;
		} else if ($('body').hasClass('attr2')) {
			var illOffset = 197;
		} else if ($('body').hasClass('attr3')) {
			var illOffset = 263;
		} else if ($('body').hasClass('attr4')) {
			var illOffset = 192;
		} else if ($('body').hasClass('attr5')) {
			var illOffset = 278;
			heightRequired = 162;
		}
		
		// We may need to make the #top-row taller so there's enough room to fit #illustration-2 above the Dashboard.
		if ($('#top-row').height() < heightRequired) {
			$('#top-row').css({height: heightRequired});
		}
		
		// Set the top position of #illustration-2.
		if (illOffset) {
			$('#illustration-2').css({
				top: $('#content').offset().top - illOffset
			}).show();
		}
		

		
		if ($('body.learning').exists()) {
			
			superme.fetchPageBoost();
			
			// On Learning Entry page.
			$('#learning-extended').hide();
			$('#reveal-extended').show();
			$('#reveal-extended a').click(function(){
				$('#reveal-extended a').hide();
				$('#learning-extended').slideDown();
				return false;
			});
		}
	},
	
	
	/**
	 * Sets up the 'hover over a score bar to see the score' action.
	 * Used by the Dashboard and the Scoreboard page.
	 */
	initializeScoreBars: function() {
		$('.bars .bar').each(function(index) {
			$(this).hover(
				function() {
					$(this).find('.total').show();
				},
				function() {
					$(this).find('.total').hide();
				}
			);
		});
	},
	
	
	/**
	 * If there are 'slides' - the video/game previews - set up the slidiness.
	 */
	initializeSlides: function() {
		if ($('#slides').length > 0) {
			$('#slides').slideView();
		
			// Where we'll move the overlay back to.
			var overlayStartPos;
		
			$('.slide').hover(function(){
				overlayStartPos = $(".slide-overlay", this).position().top;
				// Where do we move the overlay up to so that it is all displayed?
				var overlayEndPos = $(this).innerHeight() - $(".slide-overlay", this).outerHeight();
		        $(".slide-overlay", this).stop().animate({top:overlayEndPos},{queue:false,duration:300});
		    }, function() {
		        $(".slide-overlay", this).stop().animate({top:overlayStartPos},{queue:false,duration:300});
		    });
		}
	},
	
	
	/**
	 * Sets the superme.user to the initial state.
	 * Called when we load the page, and when we log out.
	 */
	initializeUser: function() {
		superme.user = {
			loggedIn: false,
			uid: '',
			name: '',
			first_name: '',
			pic_square: '',
			profile_url: '',
			level_name: '', // Set in superme.fetchDashboard().
			game_high_score: 0 // Their highest score in the game they're viewing now, if any. Set in superme.fetchUsersGameScores()
		};
		// In case there were any messages in there and we've now logged out.
		superme.messageQueue = [];
	},
	
	
	/**
	 * Called from javascript in the page itself, when all the YouTube API code is ready.
	 */
	initializeVideo: function(playerId) {
		superme.youtubePlayer = document.getElementById('youtube-player');
		setInterval(superme.videoOnIntervalChange, 1000);
		superme.videoOnIntervalChange();
		superme.youtubePlayer.addEventListener('onStateChange', 'superme.videoOnPlayerStateChange');
	},
	
	
	/**
	 * Called when the user clicks the 'Connect' button, or when loading the page while already connected.
	 * 'action' can be either 'pageload' - the page has just been loaded and the user was previously logged in or 'clicked' - the user clicked the 'Login' button. 
	 */
	login: function(action) {
		FB.Bootstrap.requireFeatures(["Connect"], function() {
		FB.Connect.requireSession(function() {
			if (FB.Connect.get_loggedInUser()) {
				var uid = FB.Connect.get_loggedInUser();
				// Docs: http://wiki.developers.facebook.com/index.php/Users.getInfo
				FB.Facebook.apiClient.users_getInfo(
					[uid],
					['uid', 'name', 'first_name', 'pic_square', 'profile_url'],
					function(users_info){
						// We're logged in and have got the user's data.
						if (users_info && users_info.length > 0) {
							superme.user.uid = users_info[0].uid;
							superme.user.name = users_info[0].name;
							superme.user.first_name = users_info[0].first_name;
							superme.user.pic_square = users_info[0].pic_square;
							superme.user.profile_url = users_info[0].profile_url;
						
							superme.user.loggedIn = true;

							superme.drawLoggedIn();
							
							if (superme.dashboardPosition == 'out') {
								// The user logged in while the Dashboard was extended,
								// so populate it with fresh data.
								superme.fetchDashboardExtended();
							}
							
							if ($('body.scoreboard').exists()) {
								// We're on the scoreboard page, so fetch the data for the page.
								superme.fetchScoreboards();
							}
							
							if ($('#friends-scores').exists()) {
								// We're on the game entry page, so fetch the data for the friends' scores.
								superme.fetchFriendsGameScores();
							}
							if (superme.gameId) {
								// On a Game Entry page, and we need to update what the user has already scored for this game.
								superme.fetchUsersGameScores();
							}

							if (action == 'clicked') {
								// User actively logged in, not page refresh:
								superme.sendSessionPayload();
							}
							
							// Now we're logged in, fetch any messages that need to be displayed).
							// For logged-out users this happens in superme.initializeFacebookConnect().
							superme.fetchAsyncMessages();
							
						} else {
							superme.drawLoggedOut();
						}
					}
				);
			} else {
				if (console) {
					console.log("requireSession worked but get_loggedInUser() didn't. (Error 1)");
				}
			//	alert("We were trying to connect you to Facebook but something went wrong. Maybe try again? (Error 1)");
				superme.drawLoggedOut();
			}
		});
		});
	},
	
	
	/**
	 * Only called when the user clicks the 'Log out' link.
	 */
	logout: function() {
		FB.Connect.logout(function(){
			superme.initializeUser();
			superme.drawLoggedOut();
		});
	},
	
	
	/**
	 * Makes a table of scores.
	 * Used for both drawDashboardFriendsFinish() and drawFriendsGameScoresFinish().
	 * facebookFriends is a dict of UID => fb_data (another dict)
	 * scores is a dictionary of n => score_data (another dictionary).
	 */
	makeScoreboardHtml: function(facebookFriends, scores) {
		var scoreHtml = '';
		
		// List each row of the friends' scores.
		$.each(scores, function(n, sc) {
			scoreHtml += '<tr><td class="num">' + n + '</td><td>' + facebookFriends[sc.facebookUID].name + '</td><td class="num">' + superme.numberFormat(sc.score_sum) + '</td></tr>';
		});
		
		if (scoreHtml) {
			scoreHtml = '<table class="scores"><tbody>' + scoreHtml + '</tbody></table>';
		} else {
            scoreHtml = '';
			// scoreHtml = "<p>Your friends haven&#8217;t scored anything yet.</p>";
			// scoreHtml = '<p>Your friends haven&#8217;t scored anything yet. Invite your friends to play at the <a href="' + superme.facebookFanPage + '">SuperMe facebook page</a></p>';
		}
		
		return scoreHtml;
	},
	
	
    /**
	 * Called from superme.fetchDashboard() with an array of Content Item IDs.
	 * We'll add a class to all the thumbnails on the page which the user has already viewed.
     * Also tag if this content item has previously been scored
	 */
	markViewedContentItems: function(contentIDs) {
		$.each(contentIDs, function(n, id) {
			$('#thumb-ci-'+id).addClass('watched');
			$('#thumb-ci-'+id+' a.thumb').append('<span></span>');
            if (id == superme.scoreableItem.content_item_id) {
                superme.previouslyScoredContentItem = true;
            }
		});
	},
	
	
	/**
	 * Add commas to format the number. 123456789 -> 123,456,789.
	 */
	numberFormat: function(num) {
		num += '';
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(num)) {
			num = num.replace(rgx, '$1' + ',' + '$2');
		}
		return num;
	},
	
	
	/**
	 * Add 'th', 'st' etc to a number.
	 */
	numberSuffix: function(num) {
		num = String(num);
		return num.substr(-(Math.min(num.length, 2))) > 3 && num.substr(-(Math.min(num.length, 2))) < 21 ? "th" : ["th", "st", "nd", "rd", "th"][Math.min(Number(num)%10, 4)];
	},
	
	
	/**
	 * Post a message to the user's Facebook stream, if they're logged in.
	 * messageData is all the stuff for the 'attachment' part.
	 * messagePrompt is the text to prompt the user with in the dialog. null for default message.
	 * ev is the click event that prompted this.
	 */
	postFacebookMessage: function(messageData, messagePrompt, ev) {
		if (superme.user.loggedIn) {
			FB.ensureInit(function() {
				FB.Connect.streamPublish(
					'',  // user_message
					messageData, // attachment
					[{
						'text': 'Play at PlaySuperMe.com',
						'href': 'http://www.playsuperme.com/'
					}], // action_links
					null, // target_id
					messagePrompt, // user_message_prompt
					superme.facebookMessagePosted, // callback
					false // auto_publish
				);
			});
		}
	},
	
	
	/**
	 * Display any messages in the queue.
	 */
	processMessageQueue: function() {
		// Get the first one.
		var messageData = superme.messageQueue.shift();
		
		if ( ! messageData) {
			return;
		}

		var message = messageData.message;
		
		// Same title for all messages.
		var message_title = 'Hey you';
		if (superme.user.loggedIn) {
			message_title = 'Hey ' + superme.user.first_name;
		}
		
		$messageDiv = $('<div/>').attr({id:'message'}).html('<div id="message-content" class="clearfix"><h3></h3><p></p><a href="#" class="arrow"><span>Go</span></a><a href="" id="post-to-facebook" title="Tell your friends on Facebook"><span><img src="/static/img/button_post.png" width="126" height="20" alt="Post to Facebok"></span></a></div><div id="message-footer"></div>').appendTo($('body'));
		
		$('#message h3').html(message_title);
		
		var message_text = message.text;
		if (message.content_item) {
			// We have a link to a game/video/etc.
			$('#message a.arrow').attr({
				href: message.content_item.url,
				title: message.content_item.title
			}).show();
			// Replace the '#' (which will be in an href="#" bit) with the URL.
			message_text = message_text.replace(/#/g, message.content_item.url);
		} else {
			$('#message a.arrow').hide();
		}
		
		$('#message p').html(message_text);
		
		if (message.type == 'session') {
			// We need to add a 'Connect to Facebook' button.
			$('#message p').append(
				$('<br />')
			).append(
				$('<a/>').append(
					$('<img/>').attr({
						src: superme.baseUrl + '/static/img/button_connect.png',
						width: 176,
						height: 23,
						alt: 'Connect with Facebook',
						id: 'message-connect-button'
					})
				).click(function(){
					superme.login('clicked');
					return false;
				})
			);
		}
		
		if (message.facebook && message.facebook.title && message.facebook.title != '') {

			media = [];
			var message_href = 'http://www.playsuperme.com/';
			if (message.facebook.caption && message.facebook.caption != '') {
				message_href = message.facebook.caption;
			}
			if (message.image && message.image != '') {
				// If we have an image to show in the message, add it:
				media = [{
					'type': 'image',
					'src': superme.baseUrl + message.image,
					'href': message_href
				}];
			} else {
				// Use a generic image instead.
				media = [{
					'type': 'image',
					'src': superme.baseUrl + '/static/img/facebook_message_logo.png',
					'href': message_href
				}];
			}
			
			// Facebook only replaces {*actor*} with the user's name in the caption.
			title = message.facebook.title;
			title = title.replace(/\{\*actor\*\}/g, superme.user.first_name);
			caption = message.facebook.caption;
			description = message.facebook.copy;
			description = description.replace(/\{\*actor\*\}/g, superme.user.first_name);
			
			// We're going to add a 'post to Facebook' link.
			$('#post-to-facebook').click(function() {
				superme.postFacebookMessage(
					{
						'name': title,
						'href': 'http://www.playsuperme.com/',
					// Uncomment in order to add the caption to Facebook postings.
					//	'caption': caption,
						'description': description,
						'media': media
					},
					'Tell your friends!',
					this
				);
				return false;
			}).show();
		} else {
			$('#post-to-facebook').hide();
		}
		
		if ($.browser.msie && $.browser.version.substr(0,1)<7) {
			// Nasty, but for IE6 (which has #message positioned absolutely), we have to work out where to position this just off the top of the viewport.
			var messageTop = $(window).scrollTop() - $('#message').outerHeight();
			// Where we move the message to.
			var messageTopMoved = $(window).scrollTop();
		} else {
			// Position just off-screen, for fixed position.
			var messageTop = - $('#message').outerHeight();
			// Where we move the message to.
			var messageTopMoved = 0;
		}
		
		// Position it so it's centered in the window.
		var messageLeft = ( $(window).width() - $('#message').outerWidth() ) / 2;
		
		// Now we've prepared the content, reveal the message.
		$('#message').css({
			// Position just off-screen.
			top: messageTop,
			left: messageLeft
		}).delay(500).show().removeClass().addClass(message.type).animate({
			// Slide it down so the top edge is at top of window.
			top: messageTopMoved
		}, 1500).delay(10000).animate({
			// Slide it back off-screen after a pause.
			top: messageTop
		}, 1500, function() {
			// Once it's off-screen...
			$(this).remove();
			if (superme.messageQueue.length) {
				// There are more messages to show, so let's go...
				superme.processMessageQueue();
			}
		});
	},
	
	
	/**
	 * If we're on the Scoreboards page and we log out, we have to reset a lot of stuff back
	 * to its initial state.
	 * Called from superme.drawLoggedOut().
	 */
	resetScoreboards: function() {
		if ( ! $('.board-loggedin').exists()) {
			// Doesn't look like we're on the scoreboard page.
			return;
		}
		
		$('.board-loggedin').html('').hide();
		$('.board-waiting').hide();
		$('.board-loggedout').show();
		
		$('#intro-progress .number').html('&#8230;');
		
		$('#scoreboard-intro #level-name').html('&#8230;');
		$('#scoreboard-intro h3').html('');
		$('#scoreboard-intro p').html('');
		$('#scoreboard-intro #level-illustration').removeClass();
		
		$('#user-scores span.score span').html('&#8230;').addClass('not-set');
	},
	
	
	/**
	 * Send a set of scores to the back-end.
	 * factor is a multiplier to multiply ALL of the attribute scores by.
	 * By default it should be 1. But for quizzes it might be smaller, eg, you only get 0.25 times the full score.
	 */
	sendScore: function(factor) {
		$.ajax({
			url: superme.baseUrl + '/scoring/submit/',
			type: 'POST',
			cache: false,
			dataType: 'json',
			data: ({
				content_item_id: superme.scoreableItem.content_item_id,
				attr1: (superme.scoreableItem.attr1 * factor),
				attr2: (superme.scoreableItem.attr2 * factor),
				attr3: (superme.scoreableItem.attr3 * factor),
				attr4: (superme.scoreableItem.attr4 * factor),
				score: (superme.scoreableItem.score * factor),
				achievement_ids: superme.scoreableItem.achievement_ids
			}),
			success: function(returnedData) {
				// Got a result.
                
                // update dashboard with these pending scores..
                superme.updateDashboardWithPendingScores({
                    attr1: {score: (superme.scoreableItem.attr1 * factor)},
                    attr2: {score: (superme.scoreableItem.attr2 * factor)},
                    attr3: {score: (superme.scoreableItem.attr3 * factor)},
                    attr4: {score: (superme.scoreableItem.attr4 * factor)},
                    overall_score: (superme.scoreableItem.score * factor)
                });

				// Send Google Analytics tracking data.
				_gaq.push([
					'_trackEvent',
					superme.scoreableItem.itemType, // Video/Game/Quiz
					'score',
					superme.scoreableItem.title,
					(superme.scoreableItem.score * factor)
				]);
                
				// Wait a couple of seconds then get any message as a response to posting the score.
				setTimeout(function() {
					superme.fetchVoiceOfSuperme(superme.scoreableItem.content_item_id);
				}, 2000);
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when sending the score. (Error 9): " +textStatus+' '+errorThrown);
			}
		})
	},
	
	
	/**
	 * The user finished the game, and the submitScore() function on the page
	 * called this method.
	 * Call the 'validategamescore' ajax backend first to see if the score payload has been tampered with.
	 */
	sendGameScore: function(gameID, score, attributes, achievements, signature, timestamp) {
	
		$.ajax({
			url: superme.baseUrl + '/scoring/validategamescore/',
			type: 'POST',
			cache: false,
			dataType: 'json',
			data: ({
				gameID: gameID,
				score: score,
				attributes: attributes,
				achievements: achievements,
				signature: signature,
				timestamp: timestamp
			}),
			success: function(returnedData) {
				// Got a result.
				// Wait a couple of seconds then get any message as a response to posting the score.
                
                // Cache the original scoreableItem - may need some of the key/values!
                // Won't just update the existing scoreableItem with the new values: 
                //   may raise other issues...
                var existingSupermeScoreableItem = superme.scoreableItem;

				attrArray = attributes.split(',');
				
				superme.scoreableItem = {
					content_item_id: gameID,
					attr1: attrArray[0],
					attr2: attrArray[1],
					attr3: attrArray[2],
					attr4: attrArray[3],
					score: score,
					achievement_ids: achievements,
                    slug: existingSupermeScoreableItem.slug,
                    title: existingSupermeScoreableItem.title,
                    itemType: existingSupermeScoreableItem.itemType
				}
				
				superme.sendScore(1);
				
				// Do we show the high score message?
				if (score > superme.user.game_high_score && superme.user.game_high_score > 0) {
					superme.user.game_high_score = score;
					$('#high-score-alert').fadeIn(500).delay(2000).fadeOut(1000);
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Could not validate the game score - it may have been tampered with. (Error 20): " +textStatus+' '+errorThrown);
			}
		})
	},
	
	
	/**
	 * The user just logged in, so send the details to the backend.
	 */
	sendSessionPayload: function() {
		$.ajax({
			url: superme.baseUrl + '/scoring/logsessionpayloads/',
			type: 'GET',
			cache: false,
			dataType: 'json',
			data: ({}),
			success: function(returnedData) {
				// Got a result.
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {
				alert("Something went wrong when sending the session payload. (Error 6): " +textStatus+' '+errorThrown);
			}
		})
	},

	
	/**
	 * Display (and later, hide) a message.
	 * messagesData is an array of data fetched by either fetchVoiceOfSuperme() or fetchAsyncMessages().
	 */
	showMessages: function(messagesData) {
		
		// Add them to the queue.
		superme.messageQueue = messagesData;

		// Let's start processing.
		superme.processMessageQueue();
	},
	

    /**
     * Shows/hides the Channel 4 footer.
     * Called from showBanner() and hideBanner() functions in the page <head>.
     * action is either 'hide' or 'show'.
     */
    toggleFooter: function(action) {
        if (action == 'show') {
            var footerClass = 'show'; 
        } else {
            var footerClass = 'hide';
        }
        $('#c4-footer-container').removeClass().addClass(footerClass);
    },

	
	/**
	 * Update one of the Dashboard score sliders.
	 * attr is a number, 1 to 5 (5 being overall progress).
	 * attrData is an associative array with 'max', 'percent' and 'score' keys
	 * OR, attrData is simply 0, when we're logged out.
	 */
	updateDashboardBar: function(attr, attrData) {
		var totalWidthOfBar = $('#panel-1 .attr'+attr+' .bar').width(); // Currently 113px.
		if (attrData == 0) {
			var rightPos = totalWidthOfBar;
			if (attr == 5) {
				var displayScore = '0%';
			}
		} else {
			var rightPos = totalWidthOfBar - (totalWidthOfBar * (attrData.percent / 100));
			var displayScore = superme.numberFormat(attrData.score);
			if (attr == 5) {
				displayScore = attrData.percent+'%';
			}
		}
		$('#panel-1 .attr'+attr+' .total').html(displayScore);
		$('#panel-1 .attr'+attr+' .slider').animate({'right': rightPos}, 2500);
	},
	
	
	/**
	 * Update the user's overall score in the Dashboard.
	 * We're assuming 'score' is always an integer.
	 */
	updateDashboardScore: function(score) {
		score = superme.numberFormat(score);
		$('#panel-1 #score span').html(score);
	},


	/**
	 * Update the dash with 'pending' scores - combining the cached and submitted scores.
	 * Called from sendScore() 
	 */
    updateDashboardWithPendingScores: function(pendingScore) {
        combineScores = function(theCachedScore, thePendingScore, thePreviousScore) { 
            if (thePendingScore.score == 0) {
                return theCachedScore;
            }
            if (thePendingScore.score > thePreviousScore.score) {
                var s = theCachedScore.score - thePreviousScore.score + thePendingScore.score;
                theNewScore = {
                    max: theCachedScore.max,
                    score: s,
                    percent: parseInt(( s / theCachedScore.max ) * 100)
                };
                return theNewScore;
            }
            return theCachedScore;
        };

        var zeroScore = {score: 0};

        // check if this item's been scored previously
        if (superme.previouslyScoredContentItem) {

            // if it has a game score, do fancy calculations
            if (superme.cachedUserContentScore.attr1) { 
                pendingData = {
                    attr1: combineScores(superme.cachedUserScore.attr1, 
                                pendingScore.attr1, 
                                {score: superme.cachedUserContentScore.attr1.amount}),
                    attr2: combineScores(superme.cachedUserScore.attr2, 
                                pendingScore.attr2, 
                                {score: superme.cachedUserContentScore.attr2.amount}),
                    attr3: combineScores(superme.cachedUserScore.attr3, 
                                pendingScore.attr3, 
                                {score: superme.cachedUserContentScore.attr3.amount}),
                    attr4: combineScores(superme.cachedUserScore.attr4, 
                                pendingScore.attr4, 
                                {score: superme.cachedUserContentScore.attr4.amount}),
                    progress: combineScores(superme.cachedUserScore.progress, 
                                    zeroScore, 
                                    zeroScore),
                    overall_score: superme.cachedUserScore.overall_score
                };
                if (pendingScore.overall_score > superme.cachedUserContentScore.score.amount) {
                    pendingData.overall_score = 
                        (superme.cachedUserScore.overall_score 
                        - superme.cachedUserContentScore.score.amount 
                        + pendingScore.overall_score);
                }
            } else { // Scored this item already - and not a game - can't increase
                return;
            }

        // not scored previously - regular dash update... 
        } else {
            
            pendingData = {
                attr1: combineScores(superme.cachedUserScore.attr1, 
                        pendingScore.attr1, zeroScore),
                attr2: combineScores(superme.cachedUserScore.attr2, 
                        pendingScore.attr2, zeroScore),
                attr3: combineScores(superme.cachedUserScore.attr3, 
                        pendingScore.attr3, zeroScore),
                attr4: combineScores(superme.cachedUserScore.attr4, 
                        pendingScore.attr4, zeroScore),
                progress: combineScores(superme.cachedUserScore.progress, 
                        {score: 1}, zeroScore),
                overall_score: (superme.cachedUserScore.overall_score + pendingScore.overall_score)
            };
        }

        superme.updateDashboardBar(1, pendingData.attr1);
        superme.updateDashboardBar(2, pendingData.attr2);
        superme.updateDashboardBar(3, pendingData.attr3);
        superme.updateDashboardBar(4, pendingData.attr4);
        superme.updateDashboardBar(5, pendingData.progress);
        superme.updateDashboardScore(pendingData.overall_score);
    },

	
	/**
	 * The .level span should have been drawn by drawLoggedIn()
	 * but this bit gets called after we've done fetchDashboard(), which is probably afterwards.
	 */
	updateLevelName: function() {
		$('#loggedin .level').html(superme.user.level_name);
	},
	
	
	/**
	 * Load the YouTube video we have the ID for.
	 */
	videoLoad: function(videoID) {
		// Lets Flash from another domain call JavaScript
		var params = { allowScriptAccess: 'always', wmode: 'opaque' };
		// The element id of the Flash embed
		var attrs = { id: 'youtube-player' };
		// All of the magic handled by SWFObject (http://code.google.com/p/swfobject/)
		var width = 700;
		var height = 400;
		if (superme.videoSize == 'small') {
			width = 650;
			height = 400;
		}
		swfobject.embedSWF('http://www.youtube.com/v/' + videoID + '&enablejsapi=1&rel=0&playerapiid=player1', 'flash-container', width, height, '8', '/static/flash/expressInstall.swf', null, params, attrs);
	},
	
	
	/**
	 * Runs every second.
	 */
	videoOnIntervalChange: function() {
		// Also check that at least one function exists since when IE unloads the
		// page, it will destroy the SWF before clearing the interval.
		if(superme.youtubePlayer && superme.youtubePlayer.getDuration) {
			$('#video-time').html( Math.round(superme.youtubePlayer.getCurrentTime()) );
			
			var currentTime = Math.round(superme.youtubePlayer.getCurrentTime());
			
			// The user gets the achievement ten seconds from the end of the video.
			var achievementTime = Math.round(superme.youtubePlayer.getDuration()) - 10;
			
			if (currentTime != 0 && currentTime == achievementTime && superme.videoAchievementSent == false) {
				superme.sendScore(1);
				superme.videoAchievementSent = true;
			}
		}
	},
	
	
	/**
	 * Runs when the video's state changes.
	 */
	videoOnPlayerStateChange: function(newState) {
		var states = {
			'-1': 'Unstarted',
			'0': 'Ended',
			'1': 'Playing',
			'2': 'Paused',
			'3': 'Buffering',
			'4': '4',
			'5': 'Cued'
		};
		$('#player-state').html(states[newState]);
		
		// eg:
		// if (newState == 0) {
		// 	$('#player-award').html("You've watched the video! Well done you.");
		// }
	}
}

