/**
 * This class handles all main JavaScript functionality
 */
$.iCheckMovies =
{
	// The update time and pause time for stacklist animations
	STACKLIST_ANIMATION_TIME: 1500,
	STACKLIST_PAUSE_TIME: 1500,

	// The number of movies to show
	NUMBER_OF_MOVIES: 50,

	// The minimum number of characters for the auto-completer to pop-up
	AUTO_COMPLETE_MIN_NR_CHARS: 3,

	// The total number of movies in the top list
	topListNumberOfMovies: {'all': 0, 'new': 0, 'checked': 0, 'unchecked': 0},

	// The total number of movies currently shown in the top list
	topListShownNumberOfMovies: 0,

	// We will use this variable to prevent a re-order when it is not necessary
	reOrderTypeSerializedMovies: new Array(),

	// The mutexes for prevent duplicate calls
	mutexes: {'check': false, 'favourite': false, 'hated': false, 'watch': false, 'explanationseen': false, 'ignorerecommendation': false},

	// The default options for the auto-completer
	autoCompleterOptionDefaults: {maxItemsToShow: 10, delay: 1000},

	/**
	 * Perform initialization, which main responsibility is to set-up the event
	 * listeners
	 */
	init: function() {

		// Allow for links to open in a new page without resorting to the deprecated
		// target="_blank" attribute
		$('a.external').bind('click', function()  {
			window.open($(this).attr('href'), '_blank');
			return false;
		});

		// Handle a focus on an input with a default text
		$('input.default').bind('focus', function() {
			// Check if the value is the original value, if so clear it
			if ($(this).val() == $(this).attr('title'))
				$(this).val('');
		});

		// Handle a blur on an input with a default text
		$('input.default').bind('blur', function() {
			// Check if the value is empty, if so replace it with the default
			if ($.trim($(this).val()) == '')
				$(this).val($(this).attr('title'));
		});

		// Handle a click on the checkbox that changes the checked status of all
		// other checkboxes at once
		$('#selectAllCheckboxes').bind('click', function() {
			var checked = $(this).attr('checked') ? 'checked' : '';
			$('input.checkbox').attr('checked', checked);
		});
		
		// Handle hovering over the covers
		$('.cover.expandable .dvdCoverSmall').bind('mouseenter mouseleave', function() {
			
			var mediumCover = $(this).siblings('.coverImageMedium:first');
			
			// Check to see if we need to load the background image
			if (mediumCover.hasClass('noimage')) {
				var smallCover  = $(this).siblings('.coverImage:first');
				var mediumBackground = smallCover.css('background').replace('/small/', '/medium/');
				mediumCover.css('background', mediumBackground);
				mediumCover.removeClass('noimage');				
			}

			// Toggle the visibility
			mediumCover.toggle();
		});

		// Set-up the autocompleters
		$('input.autocomplete.profiles').each(function()  { $.iCheckMovies.setupAutocompleter($(this), '/profile/autocomplete/'); });
		$('input.autocomplete.movies').each(function()    { $.iCheckMovies.setupAutocompleter($(this), '/movie/autocomplete/', {'resultsClass': ($(this).hasClass('header') ? 'ac_header_results' : 'ac_results')}); });
		$('input.autocomplete.toplists').each(function()  { $.iCheckMovies.setupAutocompleter($(this), '/list/autocomplete/'); });
		$('input.autocomplete.blogposts').each(function() { $.iCheckMovies.setupAutocompleter($(this), '/blog/autocomplete/'); });

		// Show the user menu when hovering over the logged in user name
		$('#user').hover (
			function () { $('#profileOptions').show(); },
			function () { $('#profileOptions').hide(); }
		);		
				
		// Handle a click on the messages
		$('#removeMessage, #saveMessage').bind('click', function() {
			$('#messageAction').val($(this).attr('name'))
		});

		// Handle a click on the button that retrieves the geolocaiton
		$('#geoLocationRetreive').bind('click', function() {

			// Get the current geolocation
			navigator.geolocation.getCurrentPosition(function(position) {

				// Retrieve the location of the coordinates
				$.iCheckMoviesMaps.retrieveLocation(
					position.coords.latitude,
					position.coords.longitude,
					{
						'onError': function() { alert('We\'re sorry, but we were unable to retrieve your geolocation.'); },
						'onSuccess': function(retrievedLocation) {
							var addressDetails = retrievedLocation.AddressDetails.Country;

							// Update the selected country and address
							$('#country' + addressDetails.CountryNameCode).attr('selected', 'selected');
							$('#settingsLocation').val(addressDetails.AdministrativeArea.Locality.LocalityName + ', ' + addressDetails.AdministrativeArea.AdministrativeAreaName);

							return false;
						}
					}
				);
			});

			return false;
		});

		// Handle a click on the preview element
		$('#previewMessage').bind('click', function() {

			// Get the serialized form data, which contains the blog post
			var serializedForm = $(this).closest('form').serialize();

			// Show the Thickbox
			var previewLink = '/profile/message/preview/?height=500&width=750&' + serializedForm;
			tb_show('Preview', previewLink);

			return false;
		});

		// Set-up sortables
		$('#favourites.sortable, #hated.sortable, #watchlist.sortable').each(function() {
			
			// Get the re-order type
			var reOrderType = $(this).attr('id');
			
			$(this).sortable({
				revert: true,
				start: function(event, ui) {
					
					// Update the order
					$.iCheckMovies.reOrderTypeSerializedMovies[reOrderType] = $(this).sortable('serialize');
				},
				update: function(event, ui) {
					
					// Determine the new favorites order
					var newOrder = $(this).sortable('serialize');

					// Update the order
					if (newOrder != $.iCheckMovies.reOrderTypeSerializedMovies[reOrderType]) {

						// Update the order
						$.iCheckMovies.reOrderTypeSerializedMovies[reOrderType] = newOrder;

						// Re-order
						$.iCheckMovies.reOrder(reOrderType);
					}
				}
			});
		});

		// Set-up draggables
		$('#favourites.sortable .draggable, #hated.sortable .draggable, #watchlist.sortable .draggable').each(function() {
			var containerId = $(this).closest('.sortable').attr('id');
			
			$(this).draggable({
				axis: 'y',
				containment: 'parent',
				connectToSortable: '#' + containerId,
				revert: 'invalid'
			});
		});

		$('#topListFilter.dynamic a').bind('click', function() {
			var topListCategory = $(this).attr('class');
			var containerItem   = $(this).closest('li');

			// Enable the links for the non-selected tabs
			var topListFilter = $('#topListFilter');
			topListFilter.find('li').each(function() {
				$(this).removeClass('active');
				$(this).find('a').show();
				$(this).find('strong').hide();
			});

			// Disable the link for the selected tab
			containerItem.find('a').hide();
			containerItem.find('strong').show();
			containerItem.addClass('active');

			// Only show the selected category's items
			var topLists = $('#topLists');
			topLists.find('li').hide();
			topLists.find('li.'+topListCategory).show();

			if (topListCategory == 'imdb')
				topLists.addClass('doubleList');
			else
				topLists.removeClass('doubleList');

			return false;
		});

		// Handle clicks that mark an explanation as seen
		$('.movie a.optionIgnoreRecommendation').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['ignorerecommendation'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['ignorerecommendation'] = true;

			var containerItem = $(this);
			var anchorItem = containerItem.closest('.movie');

			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId(containerItem.attr('id'));

			// Determine the correct URL to call
			var ignoreRecommendation = anchorItem.hasClass('ignorerecommendation');
			var ignoreType = (ignoreRecommendation ? 'ignore' : 'unignore');

			// Post the (un)check
			$.ajax({
				'url': '/profile/recommendationignore/',
				type: 'POST',
				data: {
					'recommendation[movie_id]': movieUserId['movieId'],
					'recommendation[user_id]':  movieUserId['userId'],
					'recommendation[type]':     ignoreType
				},
				// Handle a successful (un)favour
				success: function() {
					// Release the mutex
					$.iCheckMovies.mutexes['ignorerecommendation'] = false;
					
					// Re-load the page
					location.reload(true);
				},
				// Handle an erronous (un)favour
				error: function() {
					// Release the mutex
					$.iCheckMovies.mutexes['ignorerecommendation'] = false;
				}
			});

			return false;
		});

		// Handle clicks that mark an explanation as seen
		$('a.markexplanationseen, a.markexplanationunseen').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['explanationseen'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['explanationseen'] = true;

			var containerItem = $(this);

			// Get the user ID and the controller and action from the ID
			var params     = containerItem.attr('id').split('-');
			var userId     = params[0].substring(1);
			var controller = params[1].substring(1);
			var action     = params[2].substring(1);

			// Determine the correct URL to call
			var markAsSeen = containerItem.hasClass('markexplanationseen');
			var markType   = (markAsSeen ? 'seen' : 'unseen');

			// Post the explanation seen mark
			$.ajax({
				url: '/profile/explanationseen/',
				type: 'POST',
				data: {
					'explanation[user_id]':    userId,
					'explanation[controller]': controller,
					'explanation[action]':     action,
					'explanation[type]':       markType
				},
				// Handle a successful explanation seen marking
				success: function() {
					// Hide the explanation
					if (markAsSeen) {
						var parentContainerItem = containerItem.closest('.explanation');
						parentContainerItem.next('.explanationseen').fadeIn('slow');
					}
					else {
						var parentContainerItem = containerItem.closest('.explanationseen');
						parentContainerItem.prev('.explanation').fadeIn('slow');
					}

					parentContainerItem.hide();

					// Release the mutex
					$.iCheckMovies.mutexes['explanationseen'] = false;
				},
				error: function() {
					// Release the mutex
					$.iCheckMovies.mutexes['explanationseen'] = false;
				}
			});

			// Prevent the click from doing anything
			return false;
		});
		
		// Prevent a click on the context menu button from doing anything
		$('a.showOptionMenu').bind('click', function() {			
			return false;
		});
		
		// Show a context menu on hovering its button
		$('a.showOptionMenu').bind('mouseenter', function() {
			// Close other opened context menus first
			$('ul.optionMenu').hide();
			
			// Show this option menu
			$(this).next().show();
		});
		
		// Close a context menu when leaving its space
		$('ul.optionMenu').bind('mouseleave', function() {		
			$(this).hide();
		});
		
		// Handle (un)checking of a movie
		$('.movie.checked a.checkbox, .movie.unchecked a.checkbox, .movie a.optionCheckMovie').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['check'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['check'] = true;

			// Get the anchor element into a separate variable
			var anchorElement = $(this);
			
			// Determine if this is a check not on a checkbox, but through the
			// option list, which
			var isLoggedInUserCheck = anchorElement.hasClass('optionCheckMovie');

			// Get the container that contains the movie status and add a
			// pending class to it to indicate that we're busy
			var containerItem = anchorElement.closest('.movie');
			
			var uncheckedClass = isLoggedInUserCheck ? 'loggedinuserunchecked'    : 'unchecked';
			var checkedClass   = isLoggedInUserCheck ? 'loggedinuserchecked'      : 'checked';
			var pendingClass   = isLoggedInUserCheck ? 'pendingloggedinusercheck' : 'pendingcheck';
					
			containerItem.addClass(pendingClass);

			// Determine if the movie is unchecked and if the filter and/or
			// count need to be updated
			var unchecked    = containerItem.hasClass(uncheckedClass);
			var updateFilter = !isLoggedInUserCheck && containerItem.hasClass('updateFilter');
			var updateCount  = containerItem.hasClass('updateCount');
			var movieDetails = containerItem.hasClass('movieDetails');

			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId(anchorElement.attr('id'));

			var checkType = (unchecked ? 'check' : 'uncheck');

			// Post the (un)check
			$.ajax({
				'url': '/movie/check/',
				type: 'POST',
				data: {
					'check[movie_id]': movieUserId['movieId'],
					'check[user_id]':  movieUserId['userId'],
					'check[type]':     checkType
				},
				// Handle a successful (un)check
				success: function() {
					// Change a checked status to unchecked and vice versa and
					// subsequently remove the pending class
					containerItem.toggleClass(checkedClass);
					containerItem.toggleClass(uncheckedClass);
					containerItem.removeClass(pendingClass);

					// Check if we are viewing a movie toplist, in which case
					// we need to do some additional re-filtering or the tabs
					if (updateFilter)
						$.iCheckMovies.updateCheckedFilter(unchecked);

					// Check if the top list count should be updated
					if (updateCount)
						$.iCheckMovies.updateTopListCount(unchecked);

					// Check if we are viewing the movie details page, which
					// means that we need to update the favourite count
					if (movieDetails) {
						$.iCheckMovies.updateMovieCheckedCount(unchecked);
					}
					
					// Check if the option text needs to be updated
					if (isLoggedInUserCheck) {
						anchorElement.text((unchecked ? 'Uncheck' : 'Check' ) + ' this movie');
					}
					else {
						anchorElement.attr('title', anchorElement.attr('title').replace((unchecked ? 'Check' : 'Uncheck'), (unchecked ? 'Uncheck' : 'Check')));
					}

					// Update the profile's checked count
					$.iCheckMovies.updateProfileCheckedCount(unchecked);

					// Release the mutex
					$.iCheckMovies.mutexes['check'] = false;
				},

				// Handle an erronous (un)check
				error: function() {
					// Remove the pending class
					containerItem.removeClass(pendingClass);

					// Release the mutex
					$.iCheckMovies.mutexes['check'] = false;
				}
			});

			// Prevent the click from doing anything
			return false;
		});
		
		// Handle clicks that favour or unfavour a movie
		$('.movie a.optionMarkFavorite').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['favourite'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['favourite'] = true;

			// Get the anchor element into a separate variable
			var anchorElement = $(this);

			// Get the container that contains the movie status and add a
			// pending class to it to indicate that we're busy
			var containerItem = anchorElement.closest('.movie');
			var removable     = containerItem.hasClass('removablefavorite');
			var movieDetails  = containerItem.hasClass('movieDetails');
			containerItem.addClass('pendingfavorite');

			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId(anchorElement.attr('id'));

			// Determine if the movie is not a favourite
			var favour = containerItem.hasClass('notfavorite');
			var favourType = (favour ? 'favour' : 'unfavour');

			// Post the (un)check
			$.ajax({
				'url': '/movie/favour/',
				type: 'POST',
				data: {
					'favour[movie_id]': movieUserId['movieId'],
					'favour[user_id]':  movieUserId['userId'],
					'favour[type]':     favourType
				},
				// Handle a successful (un)favour
				success: function() {
					// Toggle the classes
					containerItem.toggleClass('favorite');
					containerItem.toggleClass('notfavorite');
					containerItem.removeClass('pendingfavorite');
					
					// If the list has been marked as removable, finish the
					// re-ordering (which takes care of changed ranks)
					if (removable) {
						$.iCheckMovies.finishReOrder('favourites', containerItem, !favour);
					}
					
					// Update the text
					anchorElement.text((favour ? 'Remove' : 'Mark' ) + ' movie as favorite');
					
					// Check if we are viewing the movie details page, which
					// means that we need to update the favourite count
					if (movieDetails) {
						$.iCheckMovies.updateMovieFavouriteCount(favour);
					}

					// Release the mutex
					$.iCheckMovies.mutexes['favourite'] = false;
				},
				// Handle an erronous (un)favour
				error: function() {
					// Remove the pending class
					containerItem.removeClass('pendingfavorite');

					// Release the mutex
					$.iCheckMovies.mutexes['favourite'] = false;
				}
			});

			// Prevent the click from doing anything
			return false;
		});

		// Handle hating of a movie
		$('.movie a.optionMarkNotLike').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['hated'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['hated'] = true;
			
			// Get the anchor element into a separate variable
			var anchorElement = $(this);

			// Get the container that contains the movie status and add a
			// pending class to it to indicate that we're busy
			var containerItem = anchorElement.closest('.movie');
			var removable     = containerItem.hasClass('removablehated');
			containerItem.addClass('pendinghate');

			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId(anchorElement.attr('id'));

			// Determine if the movie is unhated
			var notHated  = containerItem.hasClass('nothated');
			var hateType = (notHated ? 'hate' : 'unhate');

			// Post the (un)check
			$.ajax({
				'url': '/movie/hate/',
				type: 'POST',
				data: {
					'hate[movie_id]': movieUserId['movieId'],
					'hate[user_id]':  movieUserId['userId'],
					'hate[type]':     hateType
				},
				// Handle a successful (un)check
				success: function() {
					// Change a hated status to unhated and vice versa and
					// subsequently remove the pending class
					containerItem.toggleClass('hated');
					containerItem.toggleClass('nothated');
					containerItem.removeClass('pendinghate');
					
					// If the list has been marked as removable, finish the
					// re-ordering (which takes care of changed ranks)
					if (removable) {
						$.iCheckMovies.finishReOrder('hated', containerItem, !notHated);
					}

					// Update the text
					anchorElement.text(notHated ? 'Remove movie dislike' : 'Dislike this movie');
					
					// Release the mutex
					$.iCheckMovies.mutexes['hated'] = false;
				},

				// Handle an erronous (un)check
				error: function() {
					// Remove the pending class
					containerItem.removeClass('pendinghate');

					// Release the mutex
					$.iCheckMovies.mutexes['hated'] = false;
				}
			});

			// Prevent the click from doing anything
			return false;
		});

		// Handle to-see modification of a movie
		$('.movie a.optionAddWatchlist').bind('click', function() {

			// Check if the resource is already held
			if ($.iCheckMovies.mutexes['watch'])
				return false;

			// Acquire the mutex
			$.iCheckMovies.mutexes['watch'] = true;
			
			// Get the anchor element into a separate variable
			var anchorElement = $(this);

			// Get the container that contains the movie status and add a
			// pending class to it to indicate that we're busy
			var containerItem = anchorElement.closest('.movie');
			var removable     = containerItem.hasClass('removablewatchlist');
			containerItem.addClass('pendingwatch');

			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId($(this).attr('id'));

			// Determine if the movie is not watched
			var notWatch  = containerItem.hasClass('notwatch');
			var watchType = (notWatch ? 'watch' : 'unwatch');

			// Post the (un)check
			$.ajax({
				'url': '/movie/watch/',
				type: 'POST',
				data: {
					'watch[movie_id]': movieUserId['movieId'],
					'watch[user_id]':  movieUserId['userId'],
					'watch[type]':     watchType
				},
				// Handle a successful to-see modification
				success: function() {
					// Change a watch status to not watched and vice versa and
					// subsequently remove the pending class
					containerItem.toggleClass('watch');
					containerItem.toggleClass('notwatch');
					containerItem.removeClass('pendingwatch');
					
					// If the list has been marked as removable, finish the
					// re-ordering (which takes care of changed ranks)
					if (removable) {
						$.iCheckMovies.finishReOrder('watchlist', containerItem, !notWatch);
					}
					
					// Update the text
					anchorElement.text((notWatch ? 'Remove movie from': 'Add movie to') + ' watch list');

					// Release the mutex
					$.iCheckMovies.mutexes['watch'] = false;
				},

				// Handle an erronous to-see modification
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					// Remove the pending class
					containerItem.removeClass('pendingwatch');

					// Release the mutex
					$.iCheckMovies.mutexes['watch'] = false;
				}
			});

			// Prevent the click from doing anything
			return false;
		});

		// Handle clicks on the top list movies filter
		$('#topListMoviesFilter a, #topListNextMovies, #topListAllMovies').bind('click', function() {
			// Get the filter class
			var clickedElement = $(this);
			var currentFilter = filter = clickedElement.attr('class');

			// Add the waiting class
			clickedElement.addClass('waiting');

			// We only switch the active tab when one of the tabs has been
			// clicked, not one of the load more results links
			if (filter != 'hidden' && filter != 'allfiltered') {
				// Make the selected tab active by first removing the active status
				// of all tabs and then add it to the currently clicked tab
				$('#topListMoviesFilter li.active').removeClass('active');
				clickedElement.closest('li').addClass('active');
			}
			else {
				// The 'hidden' and 'allfiltered' filters are like meta-filters
				// that operate on the current selected filter
				currentFilter = $('#topListMoviesFilter li.active a:first').attr('class');
			}

			// Apply the filter belonging to the selected tab
			$.iCheckMovies.applyFilter(filter, currentFilter);

			// Remove the waiting class
			clickedElement.removeClass('waiting');
			
			return false;
		});

		// Handle clicks on the new movies filter
		$('#newMoviesFilter a').bind('click', function() {
			// Get the filter class
			var currentFilter = filter = $(this).attr('class');

			// Make the selected tab active by first removing the active status
			// of all tabs and then add it to the currently clicked tab
			$('#newMoviesFilter li.active').removeClass('active');
			$(this).closest('li').addClass('active');

			// Hide the other block and show the active filter tab's block
			$('div.moviesList').hide();
			$('#' + filter).show();
		});

		// Handle clicks on the awards filter
		$('#awardsFilter a').bind('click', function() {
			// Get the filter class
			var filter = $(this).attr('class');

			// Make the selected tab active by first removing the active status
			// of all tabs and then add it to the currently clicked tab
			$('#awardsFilter li.active').removeClass('active');
			$(this).closest('li').addClass('active');

			var awardsElement = $('#awards');
			
			// Show the awards the user wants to see
			awardsElement.find('li').hide();
			awardsElement.find('li.' + filter).show();
			
			return false;
		});
		
		// Handle clicks on the widgets filter
		$('#widgetsFilter a').bind('click', function() {
			// Get the filter class
			var filter = $(this).attr('class');

			// Make the selected tab active by first removing the active status
			// of all tabs and then add it to the currently clicked tab
			$('#widgetsFilter li.active').removeClass('active');
			$(this).closest('li').addClass('active');

			// Show the desired type of widgets
			$('.widgetList').hide();
			$('#' + filter).show();
			
			return false;
		});

		// Handle clicks on the settings filter
		$('#settingsFilter a').bind('click', function() {
			// Get the filter class
			var filter = $(this).attr('class');
			var explanation = $(this).attr('title');

			// Add the waiting class
			$(this).addClass('waiting');

			// Make the selected tab active by first removing the active status
			// of all tabs and then add it to the currently clicked tab
			$('#settingsFilter li.active').removeClass('active');
			$(this).closest('li').addClass('active');

			// Hide the other block and show the active filter tab's block
			$('#settings div.blockDivider').hide();
			$('#' + filter).show();

			// Update the explanation text
			$('#filterExplanation').html(explanation);

			// Remove the waiting class
			$(this).removeClass('waiting');

			return false;
		});

		// Focus on the first enabled and visible input field in
		// the first form in the content div
		$('#content form[class!="noFocus"]:first :input:visible:enabled:first').focus();

		// Display the help message for the first visible enabled input field
		$('#content form:first :input:visible:enabled:first').next("div").css("display","block");

		// Display the help text when focused on a form field or drop-down
		$('#content form input, #content form select, #content form textarea').focus(function () {
			 $(this).next("div").css("display","block");
		});

		// Hide the help text when focused away from a form field or drop-down
		$('#content form input, #content form select, #content form textarea').blur(function () {
			 $(this).next("div").css("display","none");
		});

		// Handle clicks on the UBB markup buttons
		$('#ubbBold, #ubbItalic, #ubbUnderline, #ubbStrikethrough, #ubbImage, #ubbUrl, #ubbQuote, #ubbList').bind('click', function() {
			// Get the UBB tag type
			var ubbTagType = $(this).attr('id').substring(3);

			// Get the ID of the textarea linked to the UBB buttons, which is
			// the next textarea that is a sibling of the <ul>
			var relatedTextareaId = $(this).closest('ul').next('textarea').attr('id');

			// Apply the UBB markup to the textarea
			$.iCheckMovies.applyUbbMarkup(ubbTagType, document.getElementById(relatedTextareaId));

			return false;
		});

		// Submit the save message form when the save message link is clicked
		$('#saveMessage, #removeMessage').bind('click', function() {
			$(this).closest('form').submit();
			return false;
		});

		// Handle clicks on the widget 'get HTML code' links
		$('a.getHTML, a.getUBB').bind('click', function() {
			// Get the UBB tag type
			var signatureType = $(this).attr('class').substring(3);
			var otherSignatureType = (signatureType == 'UBB') ? 'HTML' : 'UBB';

			// Toggle the one and hide the other code
			$(this).closest('ul').siblings('.' + signatureType + 'code').toggle().select();
			$(this).closest('ul').siblings('.' + otherSignatureType + 'code').hide();

			return false;
		});

		// Handle hovering over user steps
		$('#userSteps > li').bind({
			mouseenter: function() {
				// Show the subnavigation
				$(this).children('ul').show();
			},
			mouseleave: function() {
				// Hide the subnavigation
				$(this).children('ul').hide();
			}
		});
		
		// Enable list view switching
		$('#topListViewswitch').show();
		$('#topListViewNormal a').bind('click', function() {
			$('#topListViewCompact').removeClass('active');
			$(this).parent().addClass('active');
			$('ol.topListMovies').removeClass('topListViewCompact');
			$('ol.topListMovies').addClass('topListViewNormal');
			return false;
		});
		$('#topListViewCompact a').bind('click', function() {
			$('#topListViewNormal').removeClass('active');
			$(this).parent().addClass('active');
			$('ol.topListMovies').removeClass('topListViewNormal');
			$('ol.topListMovies').addClass('topListViewCompact');
			return false;
		});
		
		// Check if there is a top list, if so we retrieve the count information
		if ($('#topListMoviesFilter').size()) {
			$.iCheckMovies.topListNumberOfMovies['all']       = $.iCheckMovies.getCountFromId('topListMovieCount');
			$.iCheckMovies.topListNumberOfMovies['new']       = $.iCheckMovies.getCountFromId('topListMovieNewCount');
			$.iCheckMovies.topListNumberOfMovies['checked']   = $.iCheckMovies.getCountFromId('topListMovieCheckedCount');
			$.iCheckMovies.topListNumberOfMovies['unchecked'] = $.iCheckMovies.getCountFromId('topListMovieUncheckedCount');
			$.iCheckMovies.topListShownNumberOfMovies = Math.min($.iCheckMovies.topListNumberOfMovies['all'] - $.iCheckMovies.getCountFromId('topListMovieUnseenCount', 0, 0), $.iCheckMovies.NUMBER_OF_MOVIES);
		}

		// Show the geolocation retrieval button if the browser supports it
		if (navigator.geolocation != undefined)
			$('#geoLocationLink').show();

		// Determine the total number of last checks
		var nrOfLastChecks = $('ol.stackList li').size();
		if (nrOfLastChecks > 0) {
			// Determine the height of the stack list
			var listElementHeight = $('ol.stackList').height() / nrOfLastChecks;

			$('ol.stackList li').each(function(i) {
				if (i < nrOfLastChecks-5) {
					// Set the lenght of shifting
					var shiftLength = i*listElementHeight;

					// Shift the items one level up in the stack list
					$('ol.stackList').animate({top: -shiftLength}, $.iCheckMovies.STACKLIST_ANIMATION_TIME, null).animate({opacity: 1.0}, $.iCheckMovies.STACKLIST_PAUSE_TIME);
				}
			});
		}
	},

	/**
	 * Set-up an auto-completer
	 *
	 * @param {String} autoCompleteElement The HTML input field that will serve as the auto-completer's input field
	 * @param {String} autoCompleteUri The URI to call to retrieve the auto-completer's values
	 * @param {Array} options Additional options 
	 */
	setupAutocompleter: function(autoCompleteElement, autoCompleteUri, options) {

		// Use a default options array if nothing is specified
		if (options == undefined || options == null)
			options = {};

		// Overwrite non-specified values with their defaults
		for (var option in $.iCheckMovies.autoCompleterOptionDefaults)
			if (options[option] == undefined) options[option] = $.iCheckMovies.autoCompleterOptionDefaults[option];

		// Check if the auto-completer needs to redirect
		if (autoCompleteElement.hasClass('redirect'))
			options['onItemSelect'] = function(li) { location.href = li.extra[0]; }

			// Set-up the auto-completer
		autoCompleteElement.autocomplete(autoCompleteUri, options);
	},

	/**
	 * Apply UBB markup to a text area
	 *
	 * @param {String} ubbTagType The UBB tag type
	 * @param {String} ta The HTML text area element that contains the UBB markup text
	 */
	applyUbbMarkup: function(ubbTagType, ta) {
		var ubbTag = '';

		// Determine the corresponding UBB tag
		if (ubbTagType == 'Bold')
			ubbTag = 'b';
		else if (ubbTagType == 'Italic')
			ubbTag = 'i';
		else if (ubbTagType == 'Strikethrough')
			ubbTag = 's';
		else if (ubbTagType == 'Underline')
			ubbTag = 'u';
		else if (ubbTagType == 'Image')
			ubbTag = 'img';
		else if (ubbTagType == 'Url')
			ubbTag = 'url';
		else if (ubbTagType == 'Quote')
			ubbTag = 'quote';
		else if (ubbTagType == 'List')
			ubbTag = 'list';
		else
			return;

		// IE replacement code
		if (document.selection) {

			// First focus on the text area
			ta.focus();

			// Get the selected text and put it between the specified tag
			var sel = document.selection.createRange();
			sel.text = '[' + ubbTag + ']' + sel.text + '[/' + ubbTag + ']';
		}
		// non-IE replacement code
		else {
			ta.focus();

			// Get the selected text and put it between the specified tag
			var len   = ta.value.length;
			var start = ta.selectionStart;
			var end   = ta.selectionEnd;
			var sel   = ta.value.substring(start, end);
			var replace = '[b]' + sel + '[/b]';
			ta.value =  ta.value.substring(0,start) + '[' + ubbTag + ']' + sel+ '[/' + ubbTag + ']' + ta.value.substring(end,len);
		}
	},

	/**
	 * Apply the specified filter to the displayed list of movies
	 *
	 * @param {String|null} filter The filter to apply; null if no filtering is to be applied
	 * @param {String|null} currentFilter The current filter
	 */
	applyFilter: function(filter, currentFilter) {

		// Change the cursor to an hourglass
		var currentCursor = document.body.style.cursor;
		document.body.style.cursor = 'wait';

		// Check if no filtering should be applied
		if (filter != null) {

			// Check to see if all filtered elements need to be shown
			var showAllFiltered = (filter == 'allfiltered');

			// Determine the filter selector that will be used to determine if a
			// movie should be displayed
			var movieFilterSelector = 'li' + '.' + currentFilter + ', li.movieListAdSense';

			// Reset the total number of shown movies
			$.iCheckMovies.topListShownNumberOfMovies = 0;

			// Keep track of how many new movies have been shown
			var newlyShownMovies = 0;

			// Loop through all movies
			$('ol.topListMovies li.movie').each(function() {

				// Check if this list item matches the selector
				var matchesFilter = $(this).is(movieFilterSelector);
				var isHidden = $(this).hasClass('hidden');

				// The first condition for a movie to be shown is that is matches
				// the filter criterium (obviously); the second condition is that
				// we should either show all filtered items or that we have showed
				// less than the specified limit
				if (matchesFilter && (showAllFiltered || newlyShownMovies < $.iCheckMovies.NUMBER_OF_MOVIES)) {
					$(this).show();
					$(this).removeClass('hidden');

					// Increment the number of shown movies counter
					++$.iCheckMovies.topListShownNumberOfMovies;

					// We update the number of newly shown
					if (isHidden || filter != 'hidden')
						++newlyShownMovies;
				}
				else {
					$(this).hide();

					// Check if this movie matches the filter, which means that it
					// just is not shown yet
					if (matchesFilter)
						$(this).addClass('hidden');
				}
			});
		}

		// Update the load more movies links
		$.iCheckMovies.updateTopListMovieLoadLinks(currentFilter);

		// Restore the cursor
		document.body.style.cursor = currentCursor;
	},

	/**
	 * Update the two "load more movies" links
	 *
	 * @param {String} filter The filter to apply
	 */
	updateTopListMovieLoadLinks: function(filter) {

		// Determine how many movies are not shown
		var topListNumberOfMoviesNotShown = $.iCheckMovies.topListNumberOfMovies['all'] - $.iCheckMovies.topListShownNumberOfMovies;
		var numberUnseenFilterMovies = ($.iCheckMovies.topListNumberOfMovies[filter] - $.iCheckMovies.topListShownNumberOfMovies);
		numberUnseenFilterMovies = Math.min(numberUnseenFilterMovies, $.iCheckMovies.NUMBER_OF_MOVIES);
		numberUnseenFilterMovies = Math.max(numberUnseenFilterMovies, 0);

		// Get the element's that allow the user to load more movies
		var topListAllMoviesLinkElement  = $('#topListAllMovies');
		var topListNextMoviesLinkElement = $('#topListNextMovies');

		// Check if we are already showing all movies, if so we hide the link
		// that allows us to show all movies
		if (topListNumberOfMoviesNotShown == 0 || numberUnseenFilterMovies == 0) {
			topListAllMoviesLinkElement.hide();
			topListNextMoviesLinkElement.hide();
		}
		else {
			var filterText = ' ';
			if (filter == 'checked')        filterText = ' checked';
			else if (filter == 'unchecked') filterText = ' unchecked';
			else if (filter == 'new')       filterText = ' new';

			// Update the load all movies text
			var topListAllMoviesLinkText  = 'Show all ' + $.iCheckMovies.topListNumberOfMovies[filter] + filterText + ' movies';
			topListAllMoviesLinkElement.html(topListAllMoviesLinkText);
			topListAllMoviesLinkElement.attr('title', topListAllMoviesLinkText);
			topListAllMoviesLinkElement.show();

			// Update the load nexy movies text
			var topListNextMoviesLinkText = 'Show the next ' + numberUnseenFilterMovies + filterText + ' movies';

			// Update the text and title of the next movies link
			topListNextMoviesLinkElement.html(topListNextMoviesLinkText);
			topListNextMoviesLinkElement.attr('title', topListNextMoviesLinkText);
			topListNextMoviesLinkElement.show();
		}
	},

	/**
	 * Update the (un)checked filter tabs
	 *
	 * @param {Boolean} hasChecked Indicates if a movie has been checked
	 */
	updateCheckedFilter: function(hasChecked) {
		// Get the container elements that contain the checked and unchecked count
		var checkedCountContainer   = $('#topListMoviesFilter a.checked span.count:first');
		var uncheckedCountContainer = $('#topListMoviesFilter a.unchecked span.count:first');

		// Get the list items for the (un)checked tabs
		var checkedCountListItem   = checkedCountContainer.closest('li');
		var uncheckedCountListItem = uncheckedCountContainer.closest('li');

		// Determine if one of the two filters was active
		var checkedCountListItemActive   = checkedCountListItem.hasClass('active');
		var uncheckedCountListItemActive = uncheckedCountListItem.hasClass('active');

		// Get the current checked and unchecked count from the inner HTML of the
		// checked and unchecked containers
		var checkedCount   = parseInt(checkedCountContainer.html().replace(/\(|\)/g, ''));
		var uncheckedCount = parseInt(uncheckedCountContainer.html().replace(/\(|\)/g, ''));

		// Check if the user has checked an item
		if (hasChecked) {
			++checkedCount; ++$.iCheckMovies.topListNumberOfMovies['checked'];
			--uncheckedCount; --$.iCheckMovies.topListNumberOfMovies['unchecked'];
		}
		else {
			--checkedCount; --$.iCheckMovies.topListNumberOfMovies['checked'];
			++uncheckedCount; ++$.iCheckMovies.topListNumberOfMovies['unchecked'];
		}

		// Update the containers to have them reflect the new (un)checked count
		checkedCountContainer.html('(' + checkedCount + ')');
		uncheckedCountContainer.html('(' + uncheckedCount + ')');

		// Show or hide the filter tabs according to the count
		if (checkedCount > 0) {
			checkedCountListItem.show();
		}
		else {
			checkedCountListItem.hide();

			// Check if the now empty checked filter tab is active, in which
			// case we will set the unchecked tab as our active tab
			if (checkedCountListItemActive) {
				checkedCountListItem.removeClass('active');
				uncheckedCountListItem.addClass('active');

				checkedCountListItemActive   = false;
				uncheckedCountListItemActive = true;
			}
		}

		if (uncheckedCount > 0) {
			uncheckedCountListItem.show();
		}
		else {
			uncheckedCountListItem.hide();

			// Check if the now empty unchecked filter tab is active, in which
			// case we will set the checked tab as our active tab
			if (uncheckedCountListItemActive) {
				checkedCountListItem.addClass('active');
				uncheckedCountListItem.removeClass('active');

				checkedCountListItemActive   = true;
				uncheckedCountListItemActive = false;
			}
		}

		// If the checked or unchecked filter is active, a check or uncheck
		// requires the filter to be re-applied
		if (checkedCountListItemActive) {
			$.iCheckMovies.applyFilter(null, 'checked');
		}
		else if (uncheckedCountListItemActive) {
			$.iCheckMovies.applyFilter(null, 'unchecked');
		}
	},

	/**
	 * Update the checked count of a top list
	 *
	 * @param {Boolean} increment Indicates if the count should be incremented
	 */
	updateTopListCount: function(increment) {
		// Get the current top list count and percentage
		var countElement      = $('#topLists li.current:first span.count:first');
		var percentageElement = $('#topLists li.current:first span.percentage:first');
		var countDetails = countElement.html().split(' / ');
		var checkedCount = parseInt(countDetails[0]);
		var movieCount   = parseInt(countDetails[1]);

		if (increment)
			++checkedCount;
		else
			--checkedCount;

		// Calculate the new seen percentage
		var percentageSeen = Math.round((checkedCount / movieCount) * 100);

		// Update the count
		countDetails[0] = checkedCount;
		countElement.html(countDetails.join(' / '));

		// Update the percentage
		percentageElement.html(percentageSeen + '');
		percentageElement.attr('style', 'width: ' + percentageSeen + '%');
	},

	/**
	 * Update the checked count of the profile
	 *
	 * @param {Boolean} increment Indicates if the count should be incremented
	 */
	updateProfileCheckedCount: function(increment) {
		// Get the current top list count and percentage
		var countElement = $('#profileCheckedMovieCount');
		var countHtml    = countElement.html();
		var checkedCount = $.iCheckMovies.stringToNumber(countHtml.substring(1, countHtml.length - 1));

		if (increment)
			++checkedCount;
		else
			--checkedCount;

		// Update the count
		countElement.html('(' + $.iCheckMovies.numberToString(checkedCount) + ')');
	},

	/**
	 * Update the movie's favourite count
	 *
	 * @param {Boolean} increment Indicates if the count should be incremented
	 */
	updateMovieFavouriteCount: function(increment) {
		// Get the current top list count and percentage
		var countElement   = $('#movieFavourites');
		var favouriteCount = $.iCheckMovies.stringToNumber(countElement.html());

		if (increment)
			++favouriteCount;
		else
			--favouriteCount;

		// Update the count
		countElement.html($.iCheckMovies.numberToString(favouriteCount) + '');
	},

	/**
	 * Update the movie's checked count
	 *
	 * @param {Boolean} increment Indicates if the count should be incremented
	 */
	updateMovieCheckedCount: function(increment) {
		// Get the current top list count and percentage
		var countElement   = $('#movieChecks');
		var checkedCount = $.iCheckMovies.stringToNumber(countElement.html());

		if (increment)
			++checkedCount;
		else
			--checkedCount;

		// Update the count
		countElement.html($.iCheckMovies.numberToString(checkedCount) + '');
	},
	
	/**
	 * Finish the re-ordering
	 *
	 * @param {String} reOrderType The re-order type
	 * @param {Element} containerItem The container item
	 * @param {Boolean} removed Indicates if the item was removed
	 */
	finishReOrder: function(reOrderType, containerItem, removed) {
		// Check if we removed a movie
		if (removed) {
			// Give the removed movie an empty rank and then update the 
			// positions of the other items to reflect this change
			containerItem.find('.rank:first').html('-');
			$.iCheckMovies.updatePositions(reOrderType);
		}
		else  {
			// Re-order the list as we have just added an element that was 
			// previously not in it; if were-order the list, the movie will be 
			// at the same position it was before
			$.iCheckMovies.reOrder(reOrderType);
		}
	},

	/**
	 * Update the positions of the favourites to reflect the current ordering
	 */
	updateFavouritePositions: function() {
		$.iCheckMovies.updatePositions('favourites');
	},

	/**
	 * Update the positions of the favourites to reflect the current ordering
	 */
	updateHatedPositions: function() {
		$.iCheckMovies.updatePositions('hated');
	},

	/**
	 * Update the positions of the watchlist to reflect the current ordering
	 */
	updateWatchlistPositions: function() {
		$.iCheckMovies.updatePositions('watchlist');
	},

	/**
	 * Update the positions of the movies to reflect the current ordering
	 */
	updatePositions: function(reOrderType) {
		var moviesListSelector = '#' + reOrderType;
		var movieSelector = '.movie.';
		
		// Determine the selector for the ranked movies
		if      (reOrderType == 'favourites') movieSelector += 'favorite';
		else if (reOrderType == 'hated')      movieSelector += 'hated';
		else if (reOrderType == 'watchlist')  movieSelector += 'watch';
		
		movieSelector += ' .rank';
		var selector = moviesListSelector + ' ' + movieSelector;
		
		// Update the rank of each element
		$(selector).each(function(index){
			$(this).html((index + 1) + '');
		});
	},

	/**
	 * Re-order the favourites (and store the order in the database)
	 */
	reOrderFavourites: function() {
		$.iCheckMovies.reOrder('favourites');
	},

	/**
	 * Re-order the hated movies (and store the order in the database)
	 */
	reOrderHated: function() {
		$.iCheckMovies.reOrder('hated');
	},

	/**
	 * Re-order the watchlist movies (and store the order in the database)
	 */
	reOrderWatchlist: function() {
		$.iCheckMovies.reOrder('watchlist');
	},

	/**
	 * Re-order a list (and store the order in the database)
	 */
	reOrder: function(reOrderType) {
		
		var userId = null;
		var movieIds = new Array();
		
		var moviesListSelector = '#' + reOrderType;
		var movieSelector = '.movie.';
		
		// Determine the selector for the ranked movies
		if      (reOrderType == 'favourites') movieSelector += 'favorite .optionMarkFavorite';
		else if (reOrderType == 'hated')      movieSelector += 'hated .optionMarkNotLike';
		else if (reOrderType == 'watchlist')  movieSelector += 'watch .optionAddWatchlist';

		var selector = moviesListSelector + ' ' + movieSelector;

		// Get the movie IDs in the order in which the user has
		// placed them
		$(selector).each(function() {
			// Get the movie and user ID from the anchor tag's ID
			var movieUserId = $.iCheckMovies.getMovieUserIdFromId($(this).attr('id'));

			if (userId == null) {
				userId = movieUserId['userId'];
			}

			// Append the movie ID
			movieIds[movieIds.length] = movieUserId['movieId'];
		});

		// Post the re-ordered favourite movies
		$.ajax({
			'url': '/movie/reorder/',
			type: 'POST',
			data: {'reorder[type]': reOrderType, 'reorder[movie_ids]': movieIds, 'reorder[user_id]': userId},
			success: function() {
				$.iCheckMovies.updatePositions(reOrderType);
			}
		});
	},

	/**
	 * Get the movie and user ID from an ID
	 *
	 * @param {int} id The ID containing the movie and user ID
	 *
	 * @return array (movie ID, user ID)
	 */
	getMovieUserIdFromId: function(id) {
		// Get the movie and user ID from the ID
		var params  = id.split('-');
		var movieId = params[0].substring(1);
		var userId  = params[1].substring(1);

		return {'movieId': movieId, 'userId': userId};
	},

	/**
	 * Get the count from an element
	 *
	 * @param {int} id The ID of the element containing the count
	 * @param {int} leftOffset The left offset
	 * @param {int} rightOffset The right offset
	 *
	 * @return int The count
	 */
	getCountFromId: function(id, leftOffset, rightOffset) {
		// Define default offsets
		if (leftOffset == undefined)
			leftOffset = 1;
		if (rightOffset == undefined)
			rightOffset = 1;

		// Get the count element
		var countElement = $('#' + id);
		if (countElement == null)
			return 0;

		// Get the count element's inner HTML, which contains the count
		var countHtml = countElement.html();
		if (countHtml == null)
			return 0;

		return parseInt(countHtml.substring(leftOffset, countHtml.length - rightOffset));
	},

	/**
	 * Handle a click on an address book link
	 *
	 * @param {String} anchorElement anchorElement The anchor element clicked
	 * @param {String} receiverId The ID of the receiving input
	 */
	addressBookClick: function(anchorElement, receiverId) {

		// Fill in the selected name
		$('#' + receiverId).val($(anchorElement).html());

		// Close the ThickBox
		tb_remove();

		return false;
	},

	/**
	 * Convert a string to a number
	 *
	 * @param {String} str anchorElement The string to convert
	 *
	 * @return int The number
	 */
	stringToNumber: function(str) {
		str = str.replace(/,/g, '');

		return parseInt(str);
	},

	/**
	 * Convert a number to a string
	 *
	 * @param {int} number The number to convert
	 *
	 * @return string The number as a string
	 */
	numberToString: function(number) {

		number = number.toString();

		// Check if the number has less than four characters, in which case we
		// cannot have a comma in it
		if (number.length < 4)
			return number;

		number = number.split('').reverse();
		var numberLength = number.length;
		var numberBlocks = new Array();

		for (var i = 0; i < numberLength; ++i) {

			numberBlocks[numberBlocks.length] = number[i];

			// Check if we are at the separation and need to add a comma
			if ((i + 1) % 3 == 0)
				numberBlocks[numberBlocks.length] = ',';
		}

		return numberBlocks.reverse().join('');
	}
};

// Initialize the iCheckMovies object, which handles all initialization
$(document).ready(function() {
	$.iCheckMovies.init();
});