function set_cookie(name, value, ttl)
{
	if (is_empty(ttl))
		ttl = 90; /* implicitly this value is in days when no unit sign is appended */
	
	// Store in a domain- and site-wide cookie. JSON encode unless we got null which means kill cookie:
	$.cookie(name, value == null ? value : JSON.stringify(value), {expires:ttl, path:'/', domain:'.'+site_hostname});
}




function get_cookie(name)
{
	return JSON.parse($.cookie(name));
}




/**
 * Data cache object to fetch and cache data queries to the server. Uses a private cache member
 * to prevent fiddling ;)
 */
var cache = (function()
{
	// Private member to keep loaded data cached for possible reuse.
	var data_cache = new Array();
	
	/**
	 * Basically jQuery.get wrapped in caching, and dataType fixed to 'json', i.e.
	 * .get(url, [ data ], [ callback(data, textStatus, XMLHttpRequest) ])
	 */
	this.get = function(url, d, c)
	{
		var data, callback;
		
		if (arguments.length == 3)
		{
			data = d;
			callback = c;
		}
		else
		{
			if (typeof(d) == 'function')
			{
				data = {};
				callback = d;
			}
			else
			{
				data = d;
				callback = function(blackhole) {};
			}
		}
		
		var cache_token = set_request_get_vars(url, data);
		
		if ((data_cache[cache_token]) != null)
		{
			callback(data_cache[cache_token][0], data_cache[cache_token][1], data_cache[cache_token][2]);
		}
		else
		{
			$.get(url, data, function()
				{
					// We only cache successful responses:
					if (arguments[0].success)
					{
						data_cache[cache_token] = arguments;
					}
					
					// But we deliver anything to the callback:
					callback(arguments[0], arguments[1], arguments[2]);
				},
				'json'
			);
		}
	};
	
	return this;
})();




/* TODO: Document this one. It handles all "activation triggers" as described in themes/webtv_generic_v3/templates/video_player_description.php
 */
$('.activation_trigger').livequery(function()
{
	var trigger = $(this);
	
	if (trigger.is('a'))
		trigger.attr('href', 'javascript:;');
	
	// Find any chained elements on this trigger, if requested:
	var chained_element_ids	= trigger.attr('class').match(/(^| )chains_[^ ]+/g);
	var chained_elements	= [];
	
	if (!is_empty(chained_element_ids))
	{
		for (var i=0; i<chained_element_ids.length; ++i)
		{
			var target_id = $.trim(chained_element_ids[i]).substr(7);
			
			if (!is_empty(target_id))
			{
				var target = $('#'+target_id);
				
				if (is_empty(target))
					trigger_error("I'm trying to set up an activation trigger on '"+trigger.attr('id')+"', and found a chained element, '"+target_id+"', but it looks like you forgot to specify the DOM id on this chained element? I can't find it.");
				else
					chained_elements.push(target);
			}
		}
	}
	
	// Currently active?
	var currently_activated = trigger.hasClass('ie6_mode') ? is_empty(trigger.attr('class').match(/_inactive($| )/)) : !trigger.hasClass('inactive');
	
	// If state retainment is requested, load cookie if it exists, and update state if it differs:
	if (trigger.hasClass('remember_state'))
	{
		if (is_empty(trigger.attr('id')))
			trigger_error("You've chosen to remember the state on an 'activation_trigger' element, but you forgot to give it an id value!\n\nFor the same reason I can't tell you which element it is, but it's inside the element with the id '"+trigger.parents('[id!=""]:first').attr('id')+"'.");
		else
		{
			var saved_state = $.cookie(trigger.attr('id') + '_active');
			
			if (saved_state != null)
				currently_activated = (saved_state == 'yes');
		}
	}
	
	// Set up active state toggling on mouse clicks, with the optional 'activate' parameter to force a specific state:
	trigger.bind('click', function(event, activate)
	{
		if (arguments.length == 1)
			activate = !currently_activated;
		
		// Save state for next time:
		currently_activated = activate;
		
		// In IE6 mode we can't just toggle "inactive" class on and off due to broken CSS class chaining, we have to change the base class names:
		if (trigger.hasClass('ie6_mode'))
		{
			// Get base class names for the trigger, then toggle that and all chained classes:
			var trigger_class_base_name	= $.trim(trigger.attr('class').replace(/(^| )activation_trigger($| )/, ' ').replace(/(^| )remember_state($| )/, ' ').replace(/(^| )activation_trigger_processed($| )/, ' ').replace(/(^| )ie6_mode($| )/, ' ').replace(/(^| )group_[^ ]+/, ' ').replace(/(^| )chains_[^ ]+/g, ' ')).replace(/_inactive$/, '');
			
			// Switcharoo:
			trigger.removeClass(trigger_class_base_name).removeClass(trigger_class_base_name+'_inactive').addClass(trigger_class_base_name + (activate ? '' : '_inactive'));
			
			// And the chained ones:
			for (var i=0; i<chained_elements.length; ++i)
			{
				var chain_class_attr = $.trim(chained_elements[i].attr('class'));
				
				if (is_empty(chain_class_attr))
					trigger_error("I'm trying to toggle the active state on chained element with id '"+chained_elements[i].attr('id')+"', but it doesn't seem to have any classes assigned at all?");
				else
				{
					// The horrible IE6 hack has the requirement that the class name whose state is to be programatically toggled as active
					// must be first in the list (IE6 only reads and understands the first class, and other jQuery classes may be appended).
					// So we start by getting the first listed name, and stripping it of _inactive suffix if present, so that we can replace
					// it with the known current state:
					var base_class_name = $.trim(chained_elements[i].attr('class').split(' ')[0]).replace('_inactive', '');
					chained_elements[i].attr('class', chain_class_attr.replace(/^.+($|[ ]+)/, activate ? base_class_name : base_class_name+'_inactive'));
				}
			}
		}
		else
		{
			// Switcharoo:
			if (activate)
			{
				trigger.removeClass('inactive');
				
				for (var i=0; i<chained_elements.length; ++i)
					chained_elements[i].removeClass('inactive');
			}
			else
			{
				trigger.addClass('inactive');
				
				for (var i=0; i<chained_elements.length; ++i)
					chained_elements[i].addClass('inactive');
			}
		}
		
		// If we've been activated and this is a group member, we make sure the other members are deactivated:
		if (activate)
		{
			var group_class_name = trigger.attr('class').match(/(^| )group_[^ ]+/);
			
			if (!is_empty(group_class_name))
			{
				group_class_name = $.trim(group_class_name[0]);
				
				// But! While doing so, we gotta temporarily remove the group class from ourselves firstly (to
				// not disable ourselves again), secondly from every other group member just before toggling its
				// state (so we don't end up in an infinite loop):
				trigger.removeClass(group_class_name);
				
				$('.'+group_class_name).each(function() {
					$(this).removeClass(group_class_name);
					$(this).trigger('click', false);
					$(this).addClass(group_class_name);
				});
				
				trigger.addClass(group_class_name);
			}
		}
		
		// If retaining state, set a cookie:
		if (trigger.hasClass('remember_state'))
		{
			if (is_empty(trigger.attr('id')))
				trigger_error("You've chosen to remember the state on an 'activation_trigger' element, but you forgot to give it an id value!\n\nFor the same reason I can't tell you which element it is, but it's inside the element with the id '"+trigger.parents('[id!=""]:first').attr('id')+"'.");
			else
				$.cookie(trigger.attr('id') + '_active', (activate ? 'yes' : 'no'), {expires:90, path:'/', domain:'.'+site_hostname});
		}
	});
	
	// Make sure we've got the right state now:
	$(this).trigger('click', currently_activated);
});




/* TODO: Document. This is to replace the old rater that takes a shitload of arguments.
 * This new one assumes rules are followed instead (see video_player_toolbar.php), and
 * doesn't need arguments. It will just pick up values from the DOM elements themselves
 * and use the clip_id value that exists in the current request_state, and from global
 * scope, current_clip_rating...
 */
$('.ajax_clip_rater').livequery(function() {
	
	if (request_state.clip_id == null)
		trigger_error("Core Error: I found a star rater template to ajaxify, but the current page doesn't seem to contain an active clip!");
	
	if (current_clip_rating == null)
		trigger_error("Core Error: I found a star rater template to ajaxify, but the current page doesn't seem to contain a current rating for the active clip!");
	
	/*	Get the classes for stars for achieved and unachieved rating scores. We remove the tokens that are
		there to help us get at them, and filter out the 'rating_X' classes. This way we don't have to assume
		that the designer limits himself or herself to just one actual class name for the design. And there's
		no limit to how many classname you can feed to addClass() and removeClass(), so they'll be happy with
		whatever. */
	var class_for_achieved_rating	= $(this).find('[class*=rating_].achieved_rating').removeClass('achieved_rating').attr('class').replace(/ ?rating_[0-9]+ ?/, '');
	var class_for_unachieved_rating	= $(this).find('[class*=rating_].unachieved_rating').removeClass('unachieved_rating').attr('class').replace(/ ?rating_[0-9]+ ?/, '');
	
	// Process each star element contained within the clip rater element:
	$(this).find('[class*=rating_]').each(function() {
		// First, set the appropriate class for this element based on the clip's current average rating:
		rating_val = parseInt($(this).attr('class').replace(/^.*rating_/, '').replace(/[^0-9].*$/, ''), 10);
		
		$(this).removeClass(class_for_unachieved_rating).removeClass(class_for_achieved_rating).addClass(rating_val > current_clip_rating ? class_for_unachieved_rating : class_for_achieved_rating);
		
		// We make sure we don't have some garbage href in case we're an A element:
		if ($(this).is('a'))
			$(this).attr('href', 'javascript:;');
		
		// On mouse enter, we need to add hover classes to all stars up to and including this one
		$(this).bind('mouseenter', function() {
			// Add hover class to ourselves:
			$(this).addClass('hover');
			
			// Then, if there's a sibling with a lower ratings than us, chain the event:
			rating_val = parseInt($(this).attr('class').replace(/^.*rating_/, '').replace(/[^0-9].*$/, ''), 10);
			if (rating_val > 1)
				$(this).siblings('.rating_'+(rating_val-1)).trigger('mouseenter');
		});
		
		// Conversely, on mouse leave, we need to remove the classes to all stars up to and including this one
		$(this).bind('mouseleave', function() {
			// Remove hover class from ourselves:
			$(this).removeClass('hover');
			
			// Then, if there's a sibling with a lower ratings than us, forward the event:
			rating_val = parseInt($(this).attr('class').replace(/^.*rating_/, '').replace(/[^0-9].*$/, ''), 10);
			if (rating_val > 1)
				$(this).siblings('.rating_'+(rating_val-1)).trigger('mouseleave');
		});
		
		// And, of course, we need to act on mouse clicks:
		$(this).bind('click', function() {
			// Get the rating of the clicked element:
			rating_val		= parseInt($(this).attr('class').replace(/^.*rating_/, '').replace(/[^0-9].*$/, ''), 10);
			// Can't reach the element using $(this) inside the response function, so we scope it here:
			clicked_element	= $(this);
			
			// Post the rating:
			$.post('/json/core_rate_clip.php', object_without_null_members(merge_objects(request_state, {rating:rating_val})), function(response) {
					// If successful we update the rating:
					
					if (response.success && is_defined(response.updated_rating))
					{
						// Update the current clip rating in case the user changes her rating before browsing on:
						current_clip_rating = response.updated_rating;
						// And refresh states on all star elements, including this one:
						clicked_element.siblings('[class*=rating_]').andSelf().each(function() {
							var element_rating = parseInt($(this).attr('class').replace(/^.*rating_/, '').replace(/[^0-9].*$/, ''), 10);
							$(this).removeClass(class_for_unachieved_rating).removeClass(class_for_achieved_rating).addClass(element_rating > current_clip_rating ? class_for_unachieved_rating : class_for_achieved_rating);
						});
					}
					
					// The alert dialog will take focus, but this won't always trigger the mouseleave event:
					$(this).trigger('mouseleave');
					
					alert(response.message);
			}, 'json');
		});
	});
	
	// Make sure the rater is visible:
	$(this).show();
});





/**
 * .tooltip live handler.
 * 
 * This persistent handler sets up elements with appended "fancy" tooltips. Simply add the class
 * 'tooltip' to a tooltip, and the sibling element preceding it will become its trigger element.
 * 
 * Any existing title attribute on the trigger will be cleared to avoid messing up the display.
 * 
 * The code is wrapped in an anonymous function to scope some shared vars and functions.
 * 
 */
(function() {
	// Keep track of active tooltips in case of overlaps where the active tooltip trigger
	// does not cause a mouseout event, so that we can force close old tooltips:
	var active_tooltip = null;
	var active_trigger = null;
	
	var xOffset = 5; // Don't use negative values here
	var yOffset = 5;
	
	var position_tooltip = function(e)
	{
		var ttw		= active_tooltip.outerWidth();
		var tth		= active_tooltip.outerHeight();
		var wscrY	= $(document).scrollTop();
		var wscrX	= $(document).scrollLeft();
		var curX	= (document.all) ? event.clientX + wscrX : e.pageX;
		var curY	= (document.all) ? event.clientY + wscrY : e.pageY;
		var ttleft	= ((curX - wscrX + xOffset * 2 + ttw) > $(window).width()) ? curX - ttw - xOffset : curX + xOffset;
		if (ttleft < wscrX + xOffset)
			ttleft = wscrX + xOffset;
		var tttop	= ((curY - wscrY + yOffset * 2 + tth) > $(window).height()) ? curY - tth - yOffset : curY + yOffset;
		if (tttop < wscrY + yOffset)
			tttop = curY + yOffset;
		
		active_tooltip.css('top', tttop + 'px').css('left', ttleft + 'px').css('z-index', 65000).css('position', 'absolute');
	};
	
	$('.tooltip').livequery(function() {
		var tooltip = $(this);
		var trigger = tooltip.prev();
		
		// Remove existing title attribute on the trigger, if present:
		trigger.attr('title', '');
		
		trigger.bind('mouseover', function(e) {
			// If another tooltip is currently active, remove it first:
			if (active_trigger != null)
				active_trigger.trigger('mouseout');
			
			// Then set up this trigger and its tooltip:
			active_trigger = trigger;
			active_tooltip = tooltip;
			
			// Make sure the tooltip is repositioned when the mouse moves:
			$(document).unbind('mousemove', position_tooltip);
			$(document).bind('mousemove', position_tooltip);
			
			// And that the tooltip is correctly positioned right now:
			$(document).trigger('mousemove');
			
			// And show it:
			active_tooltip.show();
		});
		
		trigger.bind('mouseout', function(e) {
			// Hide the tooltip if still visible (might've been hidden by another trigger's mouseover event):
			if (active_tooltip != null)
				active_tooltip.hide();
			
			// Unbind the repositioning handler on the document:
			$(document).unbind('mousemove', position_tooltip);
			
			// And clean up:
			active_tooltip = null;
			active_trigger = null;
		});
		
		
		trigger.bind('click', function(e) {
			// Fade out the tooltip if still visible (might've been hidden by another trigger's mouseover event):
			if (active_tooltip != null)
				active_tooltip.fadeOut('fast');
			
			// Unbind the repositioning handler on the document:
			$(document).unbind('mousemove', position_tooltip);
			
			// And clean up:
			active_tooltip = null;
			active_trigger = null;
		});
	});
})();




/**
 * Livequery-based advanced search form ajaxifier.
 */
$('.advanced_search_formWIP').livequery(function() {
	var search_form = $(this);
	
	// Attach datepickers to date inputs:
	var datepicker_defaults = {
		showOn				: 'both',
		buttonImageOnly		: true,
		buttonText			: '',
		dateFormat			: 'yy-mm-dd',
		showWeek			: true,
		changeYear			: true,
		changeMonth			: true
	};
	
	search_form.find('[name=published_after]').datepicker(merge_objects(datepicker_defaults, { buttonImage	: '/images/timerange_from.png' }));
	search_form.find('[name=published_before]').datepicker(merge_objects(datepicker_defaults, { buttonImage	: '/images/timerange_to.png' }));
	
	// Set up checkboxes for free-text search targets:
	var search_target_checkboxes = search_form.find('[name*=query_scope_includes_]');
	search_target_checkboxes.each(function() {
		// When we're changed, make sure that if we're being unchecked, we only allow that as long as another modifier is checked
		// (otherwise we'd be allowing searches into a black hole ;)
		$(this).change(function() {
			if (!$(this).is(':checked'))
			{
				var anyone_checked_at_all = false;
				search_target_checkboxes.each(function() { if ($(this).is(':checked')) anyone_checked_at_all = true; });
				
				if (!anyone_checked_at_all)
					$(this).attr('checked', 'checked');
			}
		});
	});
	
	// Set up datepickers so that the checkbox is toggled automatically when dates change:
	var date_pickers = search_form.find('[name=published_after],[name=published_before]');
	date_pickers.each(function() {
		$(this).change(function() {
			var any_dates_at_all = false;
			date_pickers.each(function() { if (!is_empty($(this).val())) any_dates_at_all = true; });
			search_form.find('[name=publication_limit_on]').attr('checked', any_dates_at_all ? 'checked' : '');
		});
	});
	
	// Init the category tree:
	cache.get('/json/core_category_tree.php', { 'language_id':request_state.language_id }, function(response)
		{
			if (response.success)
			{
				var category_tree			= response.category_tree;
				
				var channel_selector		= search_form.find('select[name=main_category_id]');
				var category_selector		= search_form.find('select[name=sub_category_id]');
				var category_selector_label	= search_form.find('label[for=sub_category_id]');
				
				// Set up onchange so that subchannels are filled when a main channels is selected:
				channel_selector.bind('change', function() {
					// Clear options in the sub channel selector:
					category_selector.find('option[value!=""]').remove();
					category_selector.find('option:first').attr('selected', 'selected');
					
					var selected_channel_id = $(this).val();
					
					if (selected_channel_id != '')
					{
						// Find children to the current selected channel, if such exist:
						for (i in category_tree)
						{
							channel = category_tree[i];
							
							if (channel.id == selected_channel_id)
							{
								// No children? Just break out, so that we hide the sub channel select:
								if (is_empty(channel.children))
									break;
								
								for (j in channel.children)
								{
									var category = channel.children[j];
									if (!is_empty(category.name))
									{
										var option = $(document.createElement('option'));
										option.val(category.id);
										option.html(category.name);
										category_selector.append(option);
									}
								}
								category_selector_label.show();
								category_selector.show();
								
								// Return now that we're done:
								return;
							}
						}
					}
					
					// So we're here either if the selected option is the no-value "all channels" option, or if
					// the currently selected channel doesn't have any children:
					category_selector_label.hide();
					category_selector.hide();
				});
				
				// Fill channel selector:
				for (i in category_tree)
				{
					var channel = category_tree[i];
					if (!is_empty(channel.name))
					{
						var option = $(document.createElement('option'));
						option.val(channel.id);
						option.html(channel.name);
						channel_selector.append(option);
					}
				}
			}
			
			
			
			
			// Finally, make sure we apply any old settings which have been previously saved:
			var settings = get_cookie('advss');
			if (!is_empty(settings))
			{
				if (!is_empty(settings.mcid))
				{
					channel_selector.find('option').removeAttr('selected');
					channel_selector.find('option[value='+settings.mcid+']').attr('selected', 'selected');
				}
				
				if (!is_empty(settings.scid))
				{
					category_selector.find('option').removeAttr('selected');
					category_selector.find('option[value='+settings.scid+']').attr('selected', 'selected');
				}
				
				settings.ssh	= search_form.find('[name=scope][value=heading]:checked').length;
				settings.sste	= search_form.find('[name=scope][value=teaser]:checked').length;
				settings.ssd	= search_form.find('[name=scope][value=description]:checked').length;
				settings.ssta	= search_form.find('[name=scope][value=tags]:checked').length;
				
				search_form.find('[name=published_after]').val(settings.puba);
				search_form.find('[name=published_before]').val(settings.pubb);
			}
			
			
			
			
			// URL get values always take precedence over cookie settings and defaults:
			var get = get_request_get_vars();
			for (property in get)
			{
				search_form.find('[name='+property+']').val(get[property]);
			}
			
			
			
			
			// Set up submit button:
			search_form.find('.submit').bind('click', function()
			{
				
				// Assemble current settings:
				var settings = {
					mcid	: channel_selector.val(),
					scid	: category_selector.val(),
					ssh		: search_form.find('[name=scope][value=heading]:checked').length,
					sste	: search_form.find('[name=scope][value=teaser]:checked').length,
					ssd		: search_form.find('[name=scope][value=description]:checked').length,
					ssta	: search_form.find('[name=scope][value=tags]:checked').length,
					puba	: search_form.find('[name=published_after]').val(),
					pubb	: search_form.find('[name=published_before]').val()
				};
				set_cookie('advss', settings);
				
				// Prepare the search:
				var vars = { q : search_form.find('[name=q]').val() };
				
				if (!is_empty(settings.puba))
					vars.published_after = settings.puba;
				if (!is_empty(settings.pubb))
					vars.published_before = settings.pubb;
				
				// Restrict to category, if one has been chosen:
				if (!is_empty(settings.scid))
					vars.category_id = settings.scid;
				else if (!is_empty(settings.mcid))
					vars.category_id = settings.mcid;
				
				// Scoped?
				if (search_form.find('[name=scope]:checked').length)
				{
					vars.scope = new Array();
					search_form.find('[name=scope]:checked').each(function() { vars.scope.push($(this).val()); });
				}
				
				navigate_to('/search/', { replace_history:true, vars:vars });
				
//				'language_id'			=> $feed->get_language_id(),
//				'q'						=> $feed->get_search_string(),
//				'category_id'			=> $feed->get_category_ids(),
//				'include_tags'			=> $feed->get_include_tags(),
//				'exclude_tags'			=> $feed->get_exclude_tags(),
//				'media_type'			=> $feed->get_media_type(),
//				'published_after'		=> $feed->get_published_after(),
//				'published_before'		=> $feed->get_published_before(),
//				'rating_min'			=> $feed->get_rating_min(),
//				'rating_max'			=> $feed->get_rating_max(),
//				'order_by'				=> $feed->get_order_by(),
//				'order'					=> $feed->get_order(),
//				'limit'					=> $feed->get_limit()
				
			});
		}
	);
	
//	MAYBE?:
//	/* Make sure that form elements (selectors in particular) reflect their actual values rather than some random previous selection. */
//	$('form').livequery(function() { this.reset(); });

});




/* When the user follows clip links in any of the clip browsers, the browser itself will forget
 * any navigation or other changes that took place inside the clip browser since the page was
 * rendered.
 * 
 * E.g. the users searches for "crisis" and clicks forward to the next page. The navigation took
 * place via AJAX, so the browser does not record this in its history. If the user then clicks
 * a link on a clip, watches the clip and uses the browser's back button to return to where she
 * was, she'll be where she was initially when the search page was loaded in the browser, i.e.
 * before she did any kind of navigation, sort changing etc. in the browser. This is not just
 * annoying because she gets the feeling of "starting over" everytime she uses the back button,
 * it is also not correct, as each browser remembers its state, so once she changes something else,
 * all the other stored settings will suddenly, and confusingly, come into effect. So we need
 * to update the browsers where necessary, i.e. where they don't really reflect their current
 * states.
 * 
 * As we cannot control the sequence in which the various livequeries happen, we make painfully
 * sure to target only those clip browsers that have a "wtvmeta" tag, i.e. they've been
 * processed by the template meta data extractor.
 * 
 * Note - 2011-03-17: This used to be a work-around also for issues with server-side caching of
 * cookies, but isn't anymore. Now it's just for this back button business.
 */
$('[class*=ajaxClipBrowser_][wtvmeta]').livequery(function()
{
	/* Make sure that form elements (selectors in particular) reflect their actual values rather than some random previous selection. */
	$(this).find('form').each(function() { this.reset(); });
	
	var browser = $(this);
	var cookie = browser.attr('class').match(/(^| )ajaxClipBrowser_[^ ]+/);
	if (!is_empty(cookie))
	{
		var cookie = get_cookie('cbs'+$.trim(cookie[0]).substring(16));
		
		if (!is_empty(cookie))
		{
			// Apply page# fix... We reset page# to 1 when there's a discrepancy between
			// the category_id in the cookie and the currently active one. This is only an
			// issue with cached cookies... OMG, the cached cookies... THE HORROR...:
			// For "related clips" browsers, category isn't important, but .related_to is:
			if (cookie.related_to > 0)
			{
				if (cookie.related_to != request_state.clip_id)
					cookie.page = 1;
			}
			else if (request_state.category_id > 0)
			{
				if (cookie.page > 0 && cookie.category_id > 0 && cookie.category_id != request_state.category_id)
					cookie.page = 1;
			}
			
			// Any discrepancy between the currently preferred view mode and the one being used?
			// I.e., a thumbnail view button is active, but settings disagree, or a detailed
			// view button is active, but settings disagree:
			if ((browser.find('a.simpleView_active').length && cookie.view_mode == 'details') || (browser.find('a.detailedView_active').length && cookie.view_mode == 'thumbnails'))
				return reload_template({target:browser, vars:{view_mode:cookie.view_mode}});
			
			// Check that the currently saved order_by criterion is the one being used in select:
			if (browser.find('select[name=order_by]').length && ['hit_count', 'comments_count', 'rating_avg', 'publication_date'].indexOf(cookie.order_by) > -1 && browser.find('select[name=order_by]').val() != cookie.order_by)
				return reload_template({target:browser, vars:{order_by:cookie.order_by}});
			
			// Same deal, except for a elements:
			if (browser.find('a.archiveSort[onclick*='+cookie.order_by+']').length)
				return reload_template({target:browser, vars:{order_by:cookie.order_by}});
			
			// Make sure that the number of clips per page reflects what's been saved.
			if (browser.find('select[name=page_capacity]').length && cookie.page_capacity > 0 && parseInt(browser.find('select[name=page_capacity]').val(), 10) != cookie.page_capacity)
				return reload_template({target:browser, vars:{page_capacity:cookie.page_capacity}});
			
			// Check that a direct page link does not exist to this page since that would mean that
			// the current page isn't the one that's currently active:
			if (cookie.page > 0)
				browser.find('a.pageNum').each(function() {
					if (parseInt($(this).html(), 10) == cookie.page)
						reload_template({target:browser, vars:{page:cookie.page}});
			});
		}
	}
});




/**
 * Process agenda lists, hooking into the player to control it and be informed of timeline
 * progress from it.
 */
$('.agenda').livequery(function()
{
	var agenda		= $(this),
		items		= new Array(),
		items_count	= 0
	;
	
	// Bail out if this is a live clip - doing ajaxy stuff on live clips makes no sense, as their agenda items
	// don't have meaningful cue/seek times:
	if (agenda.hasClass('liveClip'))
		return;
	
	// Pick up each agenda item with a cue_in value, while converting in and out times to cuetime format:
	agenda.find('.agendaItem').each(function(index)
	{
		var element	= $(this),
			cue_in	= element.attr('in'),
			cue_out	= element.attr('out')
		;
		
		// Convert attributes to integers, or null if empty:
		cue_in	= (cue_in	== '') ? null : parseInt(cue_in, 10);
		cue_out	= (cue_out	== '') ? null : parseInt(cue_out, 10);
		
		if (cue_in !== null)
		{
			items.push([cue_in, cue_out, element]);
			++items_count;
		}
	});
	
	// Act on timeline progress ticks from the player:
	flash_player.on_progress(function(time)
	{
		if (items_count > 0 && time.match(/[0-9]{2}\:[0-9]{2}\:[0-9]{2}/))
		{
			// Convert the received time to an offset value:
			time = cue_time_to_ms(time);
			
			// We can't just find the active agenda item by looking at cue in and out, 
			// as cue out is not required to be specifed. So, to avoid marking several
			// items as active when cue out values are missing, we keep traversing and
			// register the last possibly active agenda item.
			var active_item = null, previously_active_item = null;
			
			for (var i=0; i<items_count; ++i)
			{
				// So... Make a note in case this one's already active:
				if (items[i][2].hasClass('active'))
					previously_active_item = items[i][2];
				
				// And, if this index has a cue_in before or exactly at the reported time,
				// and the cue_out time is either undefined or in the future, it probably
				// means this is the next active one:
				if (items[i][0] <= time && (items[i][1] === null || items[i][1] > time))
					active_item = items[i][2];
			}
			
			if (active_item === null && previously_active_item !== null)
				previously_active_item.removeClass('active');
			else if (active_item != previously_active_item)
			{
				if (previously_active_item !== null)
					previously_active_item.removeClass('active');
				
				active_item.addClass('active');
				
				// Scroll to the active item in AJAX agenda(s):
				try
				{
					$('.agendaContent').each(function()
					{
						var api = $(this).data('jsp');
						
						// Let's only scroll if the agenda has a scrollbar and the item isn't visible:
						if (!api.getIsScrollableV())
							return;
						
						var	agenda_height	= $(this).height(),
							item_height		= active_item.height(),
							item_ypos		= active_item.position().top
						;
						
						// Make sure the entire item is always visible regardless of whether it's above or below the fold:
						api.scrollToY(item_ypos > agenda_height-item_height ? item_ypos+item_height : item_ypos);
					});
				}
				catch(e)
				{
					trigger_error("Hmmm... Something's wrong, I wanted to focus the active element in the agenda, but it seems it's either not present, or jScrollPane failed in some way? Error: "+e.message);
				}
			}
		}
	});
	
	// Set up player seek onclick behaviors. By default, the value of the "in" attribute
	// represents both the cue time to listen to and to seek to. That is, both when the player
	// says, "I'm playing, now @ 01:00" and when the user clicks an item with an in attribute
	// value of 1:00, this is the same cue time being passed back and forth.
	// 
	// Optionally, the "seek" attribute can be set with a different value, which is used on
	// click instead of the "in" attribute's value. This applies, for example, to the lplayer
	// when playing back clips that have been trimmed - it ticks out playhead times relative
	// to the playFrom value, but expects seek times relative to the stream's absolute start.
	agenda.find('.agendaItem').click(function()
	{
		// Work-around for dotted line that appears around the agenda after clicking when jScrollPane is active:
		$('.agendaContent').blur();
		
		var item	= $(this),
			cue_in	= item.attr('in'),
			seek	= item.attr('seek')
		;
		
		if (typeof seek != 'undefined' && seek != '')
			cue_in = seek;
		
		if (typeof cue_in != 'undefined' && cue_in != '')
			flash_player.seek(ms_to_cue_time(parseInt(cue_in, 10), {hours_pad_size:2, with_msecs:false}));
	});
});




// Set the default jScrollPane options so that they may be possibly overridden by particulars in a customer's
// frontend using a .js file - this way both the call below at document.ready will include any extras, and also
// possible inline calls (such as in video_player_agenda.php):
var jScrollPaneOptions = {
	animateScroll:true
};



$(document).ready(function()
{
	// Focus and select the search input:
	$('input[name=q]').focus().select();
	
	//Enable "fancy" and programmable scrolling as requested:
	$('.jScrollPane').jScrollPane(jScrollPaneOptions);
});

























