/**
 * WebTV Core javascript library.
 * 
 * Version 1.9.1218
 * 
 * History:
 * 	1.9.1218:	navigate_to() simplified, now only rewrites relative urls and no longer
 * 				kills language tokens on the request path.
 *  
 * 	1.9.1217:	More iframe fixes, enabling proper crawling by 3rd party search engines.
 * 
 * 	1.9.1027:	Fixed iframe bug with navigate_to().
 * 
 * 	1.9.1015:	Update to new Shadowbox major version 3. This one has a different setup,
 * 				and now only needs re-injection of script tags after content loadin in
 * 				IE. Hm. The event-based CSS hacking via the init object has been replaced
 * 				(mostly) by a custom CSS which also removed the need for the "mute" lang
 * 				file. Also, this release is IE8 compliant, so we can remove the meta hack.
 * 
 * (c) Copyright 2008-2009 Arkena A/S
 * 
 * @author daniel@arkena.dk
 */




function trigger_error(error)
{
	if (execution_mode != 'production') {
		alert(error);
	}
}




/**
 * Scans the DOM for links and processes these to make sure they work with iframed frontends,
 * by prepending the site_root_url base, which routes to the location of the iframe embed logic.
 * 
 * We fix relative urls and urls which are explicitly targeted at the Arkena site's hostname.
 */
function iframify_elements()
{
	// Only process links that haven't already been processed:
	$('a[iframified!=true]').each(function() {
		element = $(this);
		href = element.attr('href');
		
		// So, only links with hrefs that aren't anchors or javascript: or mailto: hrefs, get the treatment:
		if (!is_empty(href) && href.toLowerCase().substr(0, 11) != 'javascript:' && href.toLowerCase().substr(0, 7) != 'mailto:' && href.substr(0, 1) != '#')
		{
			target = element.attr('target');
			
			// Any target not _blank is changed to _parent...
			if (target != '_blank')
				element.attr('target', '_parent');
			
			// ...but only relative URLs are actually rewritten by prepending the iframe url:
			if (href.indexOf('://') == -1)
				element.attr('href', site_root_url+href);
		}
		
		// Tag as iframified, so we don't target it again:
		element.attr('iframified', 'true');
	});
	
	// Almost same deal with forms:
	$('form[iframified!=true]').each(function() {
		element = $(this);
		
		// Only forms that don't POST data (like the search form) are processed:
		if (element.attr('method').toLowerCase() != 'post')
		{
			href = element.attr('action');
			target = element.attr('target');
			
			// Any target not _blank is changed to _parent...:
			if (target != '_blank')
				element.attr('target', '_parent');
			
			// ...but only relative URLs are actually rewritten by prepending the iframe url:
			if (href.indexOf('://') == -1)
				element.attr('action', site_root_url+href);
		}
		
		// Tag as iframified, so we don't target it again:
		element.attr('iframified', 'true');
	});
}




// If we're running in an iframe, we need to make sure all our hrefs and onclicks are iframe compatible.
// Easy feat? Just add parent targets and prepend some urls? Well, pretty easy, but we need to make sure
// noone clicks any navigation links before the DOM is finished loading. Or rather, we need to make sure
// that if that happens, any links already loaded will be made iframe compatible before the mouseup
// event triggers the event:
if (iframed)
{
	// Capture mousedown events until the document is ready:
	$(document).bind('mousedown', iframify_elements);
}




// Set up the page once the document is ready:
$(document).ready(
	function()
	{
		if (iframed)
		{
			// If running in an iframe, we make sure links on all elements are valid, and also unbind the
			// onmousedown handler previously bound to capture pre-DOM-ready mouseclicks. However, the
			// document is sometimes ready before all hrefs are (e.g. when injecting HTML), so we take a
			// breath before unbinding the mousedown event and forcing the iframification on elements:
			setTimeout(iframify_elements, 1000);
			setTimeout("$(document).unbind('mousedown', iframify_elements);", 1000);
			
			// Also, in case we came here via a search engine and are rendered without a parent, reload:
			if (parent === self)
				reload_page();
		}
		
		// Extract all meta data packages dropped by the rendering engine:
		catch_template_meta_data();
		
		// Set up Shadowbox behavior:
		var shadowbox_tmp = [];
		Shadowbox.init({
			players				: ['html'],
			showMovieControls	: false,
			displayNav			: false,
			enableKeys			: false,
			animate				: false,
			resizeDuration		: 0.15, // We've hidden the loading box via CSS, and we've said animate:false, so this really has no effect.
			fadeDuration		: 0.25,
			useSizzle			: false,
			onFinish			: function(element) {
				// #sb-wrapper content wrapper is hidden because Shadowbox places its box vertically centered.
				// This is bad news for iframed sites (window height is always oversized), so we've hidden the
				// wrapper via CSS and need to show it after we've repositioned it to a more safe location:
				$('#sb-wrapper').css('top', '100px').show();
				
				// With shadowbox, javascript inside the contents is ignored, presumably because the content is hidden
				// or otherwise outside the DOM tree? This little hack just reinsert every script block and voila, it
				// evaluates...
				$('#sb-content script').each(function() {
					$(this).html('<script type="'+$(this).attr('type')+'">'+$(this).html()+'</script>');
				});
				
				// If we're iframed, make sure links inside the loaded content are iframified where applicable:
				if (iframed) iframify_elements();
			}
		});
		fix_ie6_pngs();
	}
);




/**
 * Helps iframed sites with doing iframe-safe scripted navigation that goes through the embedding parent page,
 * while not having to worry about the internals. I.e. just provide a URL to this here baby and you always end
 * up doing the right thing. (With iframed sites this is also true for regular hrefs, btw., as they're "fixed"
 * by iframify_elements() on page loading.)  
 * 
 * E.g. when browsing for instance http://some.cool.tv having chosen English, navigate_to('/') will navigate to
 * http://some.cool.tv/en/. Similarly, if this site were iframe embedded, navigate_to('/search/') would send the
 * user to http://some.embedding.page/webtv.aspx?webtv=/en/search/.
 * 
 * Urls that aren't relative will produce undefined results.
 * 
 * Version 1.9.1218
 * 
 * History:
 * 	1.9.1218:	Hmmm... Seems I was an idiot last time, as that approach stripped the language key token from the
 * 				url, too... And either way, having replaced the iframe link logic completely in version 1.9.1217,
 * 				we can now use much more simpler rules, as described above.
 * 	1.9.1027:	Hmmm... Seems we got all we need to strip the path in the location object :)
 * 				(http://www.xul.fr/javascript/tutorial/window-location.php)
 * 
 * @param	{string}	url													The url path to navigate to.
 * @param	{mixed}		vars										(OPT)	An array or object with var/val pairs to pass on via GET.
 */
function navigate_to(url, vars)
{
	// We produce a relative url by stripping away the current location protocol & host, if present, or the
	// site's absolute root url, if present, or the iframe parent base url, if present, then prepend whatever's
	// the appropriate root url (always different from current host for iframes, for instance) and open it in
	// the parent frame (which automagically resolves to our own frame if we're not iframed). We specifically do
	// NOT just strip away any protocol and host name, because it might be an external URL, and also, that wouldn't
	// strip away any tokens on the base path, such as the two-letter language designator:
	
	// Add/replace/remove GET vars:
	url = set_request_get_vars(url, vars);
	
	// Assemble the current host for use in a second:
	var current_host = window.location.protocol+'//'+window.location.host;
	
	if (url.substr(0, site_root_url.length) == site_root_url)		// Strip absolute site/iframe root url, if this is the prefix...
		url = site_root_url + url.substr(site_root_url.length);
	else if (url.substr(0, current_host.length) == current_host)	// Otherwise, if the link link is absolute and local, i.e. the
		url = site_root_url + url.substr(current_host.length);		// absolute current host root, strip that...
	else if (!is_empty(site_root_path) && url.substr(0, site_root_path.length) == site_root_path)	// Otherwise, if not absolute,
		url = site_root_url + url.substr(site_root_path.length);	// we strip the relative site root path (e.g. '/en/') and add prefix.
	
	// If no matches was found above but URL's relative, prefix with the site root url (for iframed sites particularly):
	if (url.indexOf('://') == -1)
		url = site_root_url + url;
	
	// Open the final URL in our parent:
	parent.location.href = url;
}




/**
 * Extracts template meta data packages dropped by the html template rendering engine
 * and forwards them to the appropriate template surrounding element.
 * 
 * After processing, droppers and blockers are removed from the DOM.
 * 
 * Consult the PHP renderering class documentation for further information.
 */
function catch_template_meta_data()
{
	// Select from the entire document all elements with a rel attribute of 'wtvmetapackage':
	$(document).find('[title^=wtvmetapackage]').each(function() {
		// Copy the wtvmeta data from the data dropper element to the immediately following
		// element (this is by convention; all template contents must be wrapped inside an
		// outer bounding element):
		$(this).next().attr('wtvmeta', $(this).attr('title').substr(14));
		// Remove the first blocker div encountered when traversing:
		$(this).nextAll('[title^=wtvmetablocker]:first').remove();
		// Remove the meta data dropper itself from the DOM:
		$(this).remove();
	});	
}




/**
 * Iframe safe page reload that takes optional vars to pass on to the next page as GETs.
 * 
 * @param	{mixed}		vars												An array or object with vars to pass on via GET.
 */
function reload_page(vars)
{
	navigate_to(window.location.href, vars);
}




/**
 * Loads the specified template into a the specified element(s), passing on the .vars arguments members as GET parameters, if supplied.
 * 
 * If nothing overridden by the .vars argument, page request state is retained
 * (i.e. the state brought by the most recent page request).
 * 
 * Version 1.9.1006
 * History:
 * 	1.9.1006: Wrapped the vars object that is passed in object_without_null_members() to remove all null members.
 * 
 * @param	{string}	args.template										The name of the template to load. Must be initialized in the
 * 																			frontend configuration.
 * @param	{mixed}		args.target									(OPT)	The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * 																			If omitted, the function will not inject the response, but
 * 																			you can still pass a function reference to args.method to
 * 																			process it externally.
 * @param	{mixed}		args.vars									(OPT)	Array of, or object with members representing, GET variables to
 * 																			add, replace, or remove to/in/from the generated request.
 * @param	{mixed}		args.method									(OPT)	How to process the retrieved data. If a string and one of
 * 																			'replace' (default), 'prepend', or 'append', tells how to
 * 																			inject the response data into the DOM relative to .target.
 * 																			If a function, this function will be called with the data
 * 																			being passed to it.
 * @return	{bool}															True if successful, false otherwise.
 */
function load_template(args)
{
	// Check arguments:
	if (is_empty(arguments) || !is_defined(args.template) || (is_defined(args.target) && is_empty(args.target = jquerify_element(args.target))))
	{
		trigger_error("Core::load_template(): Missing or invalid arguments. I need to know at least the template ('template') and a valid target element to load into ('target').");
		return false;
	}
	
 	// Trigger-happy people, wait for the DOM ;)
	$(document).ready(function() {
		// Load the template, passing along the request state object merged with any vars that we might have had passed to us:
		$.get('/html/'+args.template, object_without_null_members(merge_objects(request_state, args.vars)), function(response) {
			// If we have no target(s) but have a method function, we just call that:
			if (is_empty(args.target) && args.method instanceof Function)
				args.method(response);
			// Otherwise, we inject/process the content as requested into all chosen targets:
			else args.target.each(function() {
				if (args.method instanceof Function)
					args.method(response);
				else switch (args.method)
				{
					case 'prepend':
						$(this).before(response);
						break;
					case 'append':
						$(this).after(response);
						break;
					default: // 'replace'
						// We're replacing an existing element, so we start by getting the DOM Object of it:
						var insertion_point = $(this);
						// We use the existing element as the point at where to insert the loaded data. We have
						// to pull the existing DOM object out of the DOM before we insert the loaded one, though,
						// or we'll have two DOM objects with identical ids at the time the response code is
						// evaluated. This would mean any javascript inside the code referencing the element by id
						// would possibly target the wrong, if any, of the two "twin" objects, or even worse, we
						// might just f*** up the DOM tree.
						// So, we create a temporary anchor token after the original element allowing us to get
						// rid of it before injecting the response code:
						var anchor_token = $('<div></div>');
						insertion_point.after(anchor_token);
						insertion_point.remove();
						anchor_token.after(response);
						anchor_token.remove();
						break;
				}
			});
			
			// Process dropped meta data packages:
			catch_template_meta_data();
			// For IE6, fix transparency on PNGs:
			fix_ie6_pngs(args.target);
		});
	});
	
	return true;
}




/**
 * Reloads the template(s) rendered in the specified DOM target(s). This will only work for templates explicitly loaded through the core.
 * If a target itself is not a template, the DOM tree is conversed to find the nearest parent template. This way you can do stuff like,
 * onclick="reload_template({target:this});"
 * 
 * Version 1.9.1007
 * History:
 * 	1.9.1007: Added args.replace_vars.
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{mixed}		args.vars									(OPT)	Array of, or object with members representing, GET variables
 * 																			to add, replace, or remove to/in/from the generated request.
 * @param	{bool}		args.replace_vars							(OPT)	If true, the args.var passed to this function  is not merged
 * 																			with the original vars from the meta package, but replaces
 * 																			them entirely
 * 
 * @return	{bool}															True if successful, false otherwise.
 */
function reload_template(args)
{
	// Check arguments:
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("Core::reload_template(): Missing or invalid arguments. You need to pass at least the target element ('target') containing the template to reload.");
		return false;
	}
	
	args.target.each(function() {
		// If the target itself does not contain a meta object, find the closest parent that has one:
		target = $(this).closest('*[wtvmeta]');
		
		var meta;
		
		if (target.length > 0)
			meta = target.attr('wtvmeta');
		
		if (!is_empty(meta))
			eval('meta = ' + meta + ';');
		
		if (is_empty(meta) || typeof(meta) != 'object' || is_empty(meta.template))
		{
			trigger_error("Core::reload_template(): Neither the specified target nor any parent of it contains the necessary meta information. Are you sure that '"+$(this).attr('id')+"' is the correct target, and that it contains or exists within a template that was loaded by the WebTV Core?");
			return false;
		}
		else // Reload the template:
		{
			// Wipe the existing meta vars if requested to do so:
			if (args.replace_vars == true)
				meta.vars = null;
			
			return load_template(merge_objects(meta, args, {target: target, method: 'replace'}));
		}
	});
	
	return true;
}




/**
 * Loads a registered template via AJAX and renders the content inside a Shadowbox.
 * 
 * @param	{string}	args.template										The name of the registered template to load.
 * @param	{int}		args.width											The pixel width of the Shadowbox to open.
 * @param	{int}		args.height											The pixel height of the Shadowbox to open.
 * @param	{mixed}		args.vars									(OPT)	Array of, or object with, members to merge with
 * 																			the current state vars when requesting the
 * 																			template.
 * @return	{bool}															True on success, false on failure.
 */
function load_shadowbox(args)
{
	if (is_empty(arguments) || !is_defined(args.template) || !is_defined(args.width) || !is_defined(args.height))
	{
		trigger_error("Core::load_shadowbox(): Missing or invalid arguments. You need to pass at least the name of the template to load ('template'), and the dimensions of the Shadowbox to inject it to ('width' and 'height').");
		return false;
	}
	
	// Load the template, adding transient data, merged with/overridden by any args that we might have had passed to us,
	// and open the returned content in a shadowbox.
	return load_template(merge_objects(args, { method: function(response) {
		return Shadowbox.open({
			player:		'html',
			content:	response+'<script type="text/javascript">fix_ie6_pngs();</script>', // yeah, looks bad, but it works :P
			width:		args.width,
			height:		args.height
		});
	}}));
}




/**
 * Will turn a regular form, or forms, into AJAX form(s).
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{string}	args.action									(OPT)	Where to submit to. If omitted, the value of the form's
 * 																			"action" attribute will be used.
 * @param	{function}	args.on_submit								(OPT)	A function to call before submitting the form, after the form
 * 																			submission sequence has been initiated.
 * @param	{function}	args.on_response							(OPT)	A function to call after receiving a response from the server,
 * 																			with the server response object passed on.
 * @param	{function}	args.on_success								(OPT)	A function to call if receiving a success response from the
 * 																			server, with the server response object passed on.
 * @param	{function}	args.on_failure								(OPT)	A function to call if receiving a failure response from the
 * 																			server, with the server response object passed on.
 * @param	{function}	args.on_cancel								(OPT)	A function to call when the user clicks button(s) of type
 * 																			"reset" in the form.
 * @param	{bool}		args.track_input_states						(OPT)	Use this if you plan on changing input field values, e.g. to
 * 																			report back input errors to the user.
 * 																			The value, style, and class of each input field will be tracked
 * 																			and restored on field focus. Applies to <input>s and <textarea>s.
 * @param	{bool}		args.clear_texts_on_first_focus				(OPT)	If true, input fields will have their values cleared the first
 * 																			time they receive focus.
 * 																			Applies to <input>s and <textarea>s.
 * @param	{object}	args.post_data								(OPT)	Extra data to pass along with the data written in the form.
 * @return	{bool}															True on success, false on failure.
 */
function ajaxify_form(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("Core::ajaxify_form(): Not enough arguments provided. I need to know at least the DOM id of the form, or the form object itself, to ajaxify ('target').");
		return false;
	}
	
	args.target.each(function() {
		var form						= $(this);
		var action						= is_defined(args.action) ? args.action : $(form).attr('action');
		var submit_buttons				= form.find(":submit");
		var cancel_buttons				= form.find(":reset") ? form.find(":reset")[0] : null;
		var track_input_states			= args.track_input_states			== true;
		var clear_texts_on_first_focus	= args.clear_texts_on_first_focus	== true;
		// This is the array of data being posted on submit. We initialize it with our state data object:
		var post_data					= request_state;
		
		// IE6 (and possibly other retarded browsers) go weirdo with
		// AJAX'ed forms and the return key. To avoid them submitting
		// regularly (i.e. sending the user to a new page with JSON
		// data printed out in all its glory) when the enter key is
		// pressed, we simply add onsubmit="return false;" to the form:
		form.submit(function() { return false; });
		
		// To be able to do several things on the same event, we build an
		// array of functions to fire on each event. Then, finally we let
		// the event triggers traverse their respective function arrays to
		// fire any and all events on demand. Neat :)
		if (track_input_states || clear_texts_on_first_focus)
		{
			var functions_focus = [], functions_change = [];
			
			// This one to make sure (IE) that the caret isn't placed
			// at the beginning of the text in an input field after
			// doing manipulation of the text via one of our functions.
			place_caret_at_text_end = function(element)
			{
				if (element.setSelectionRange) /* DOM */
				{
					setTimeout( /* hack for select delay */
						function(t)
						{
							t.setSelectionRange(t.value.length,t.value.length);
						}
						, 0, element
					);
				}
				else if (element.createTextRange) /* IE */
				{
					r=element.createTextRange();
					r.collapse(false);
					r.select();
				}
			};
			
			/**
			 * Clearing texts on first is used when some kind of description is entered into the
			 * field itself, usually something like "Enter your e-mail address here." Of course,
			 * this applies only to text and textarea type elements.
			 */
			if (clear_texts_on_first_focus)
			{
				clear_text_on_first_focus = function(element)
				{
					var me = $(element);
					if ((me.attr('type') == 'text' || me.attr('type') == 'textarea' ) && me.attr('text_cleared_on_first_focus') != 'yes')
					{
						me.attr('text_cleared_on_first_focus', 'yes');
						me.val('');
					}
				};
				functions_focus.push(clear_text_on_first_focus);
			}
			
			if (track_input_states)
			{
				store_input_state = function(element)
				{
					var me = $(element);
					// Never on error elements, and if clear_texts_on_first_focus is enabled, we need to make sure we're not tracking the value to be cleared:
					if (!me.hasClass('error')) me.attr('cached_val', (clear_texts_on_first_focus && me.attr('text_cleared_on_first_focus') != 'yes') ? '' : me.val());
				};
				
				restore_input_state = function(element)
				{
					var me = $(element);
					if (me.hasClass('error')) me.val(me.attr('cached_val'));
				};
				
				functions_focus.push(restore_input_state);
				
				functions_change.push(store_input_state);
				
				form.find('input[type="text"], textarea').each(function(event) { store_input_state(this); });
			}
			
			form.find('input[type="text"], textarea').focus(function(event)		{ for(var i=0; i<functions_focus.length; ++i)	functions_focus[i]((this)); place_caret_at_text_end(this); $(this).removeClass('error').addClass('focus'); });
			form.find('input[type="text"], textarea').blur(function(event)		{ $(this).removeClass('focus'); });
			form.find('input[type="text"], textarea').change(function(event)	{ for(var i=0; i<functions_change.length; ++i)	functions_change[i]((this)); });
		}
		
		/**
		 * The buttons pass [this] to the submit_function, so that if there are several
		 * submit buttons, we can tell about which one was clicked.
		 */
		var submit_function = function(button_clicked)
		{
			// We don't submit if any of our fields are in a non-initial state (with regards to
			// style & class), which is synonymous to erroneous.
			if (track_input_states)
			{
				var okay_to_submit = true;
				form.find('input[type="text"], textarea').each(function(event) { if ($(this).hasClass('error')) okay_to_submit = false; });
				if (!okay_to_submit) { return false; }
			}
			
			// Store all input states on the form's text areas if we're tracking states. We don't
			// have to do any extra checks now, since we're borking out above, if any input field
			// is in an erroneous state:
			if (track_input_states) form.find('input[type="text"], textarea').each(function() { store_input_state(this); this.blur(); });
			
			if (is_defined(args.on_submit))
				args.on_submit();
			
			// Collect post data (select every element with a name attribute in the form):
			$('*[name]', form).each(function() {
				var element = $(this);
				// Note the last check - if we're supposed to be clearing text and textarea captions on first focus, we check if we have done so, otherwise we send a blank value (instead of sending the caption)
				post_data[element.attr('name')] = (element.attr('type') == 'checkbox') ? ((element.attr('checked') == true) ? 1 : 0) : ((clear_texts_on_first_focus && (element.attr('type') == 'text' || element.attr('type') == 'textarea') && element.attr('text_cleared_on_first_focus') != 'yes') ? '' : element.val());
			});
			
			// And, since we know which button was clicked, tell about it:
			post_data['submit_button_clicked'] = $(button_clicked).attr('name');
			
			// In case there's more than one submit button, we need to make sure we include the right value:
			if (!is_defined($(this).attr('name'))) post_data[$(this).attr('name')] = $(this).val();
			
			// Post:
			$.post(action,
				post_data,
				function(response)
				{
					
					// Clear any checkboxes if requested to do so:
					if (!is_empty(response.clearable_checkboxes)) // Skip this check and IE6 breaks.
					{
						$.each(response.clearable_checkboxes, function(index, box_name) {
							$('input[name="'+box_name+'"]').removeAttr('checked');
						});
					}
					
					// If there are errors on specific fields, we notify the user by adding an error class to those fields and writing a notification inside the fields.
					if (!is_empty(response.erroneous_fields))
					{
						// Change the state of bad input field to erroneous:
						$.each(response.erroneous_fields, function(field_name, message) {
							// Only for those that exist:
							if (!is_empty($(':input[name="'+field_name+'"]')))
								$(':input[name="'+field_name+'"]').val(message).addClass('error');
						});
					}
					
					// Do callbacks:
					if (is_defined(args.on_response))
					{
						if ($.isFunction(args.on_response))
							args.on_response(response);
						else
							eval(args.on_response);
					}
					
					if (response.success && is_defined(args.on_success))
					{
						if ($.isFunction(args.on_success))
							args.on_success(response);
						else
							eval(args.on_success);
					}
					
					if (!response.success && is_defined(args.on_failure))
					{
						if ($.isFunction(args.on_failure))
							args.on_failure(response);
						else
							eval(args.on_failure);
					}
				},
				'json'
			);
		};
		
		// Override the regular submit behavior on the submit buttons:
		$(submit_buttons).click(function() { submit_function(this); });
		
		// Configure the cancel button, if requested:
		if (is_defined(args.on_cancel)) $(cancel_buttons).click(args.on_cancel);
	});
	
	return true;
}




/**
 * Inserts a captcha code image in the specified DOM element.
 * 
 * @param	{string}	args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{mixed}		args.bgcol_red								(OPT)	8-bit red value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.bgcol_green							(OPT)	8-bit green value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.bgcol_blue								(OPT)	8-bit blue value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.lines_brightness_min					(OPT)	8-bit minimum value of the obfuscation lines luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.lines_brightness_max					(OPT)	8-bit maximum value of the obfuscation lines luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.chars_brightness_min					(OPT)	8-bit minimum value of the text character luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.chars_brightness_max					(OPT)	8-bit maximum value of the text character luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{int}		args.width									(OPT)	The width, in pixels, of the image.
 * @param	{int}		args.height									(OPT)	The height, in pixels, of the image.
 * @param	{int}		args.chars									(OPT)	The number of captcha characters to write.
 * @param	{int}		args.font_size								(OPT)	The font size.
 * @param	{int}		args.text_indentation						(OPT)	The number of pixels (from the left) to indent the captcha code.
 * @return	{bool}															True on success, false on failure.
 */
function insert_captcha_image(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("Core::insert_captcha_image(): Not enough arguments provided. I need to know at least the DOM id of to load the image element into ('target').");
		return false;
	}
	
	// Let's assume the captcha image in question has already been loaded once by us,
	// in which case we've stored the request string in a tag on it:
	var request_string = args.target.attr('requeststring');
	
	// If we were wrong, let's start a new request string:
	if (is_empty(request_string)) request_string = '/png/core_png_captcha.php';
	
	// Let's add any (new) arguments to the request:
	if (is_defined(args.bgcol_red))				request_string = set_request_get_vars(request_string, { bgr:	parseInt(correct_hex_prefix(args.bgcol_red)) });
	if (is_defined(args.bgcol_green))			request_string = set_request_get_vars(request_string, { bgg:	parseInt(correct_hex_prefix(args.bgcol_green)) });
	if (is_defined(args.bgcol_blue))			request_string = set_request_get_vars(request_string, { bgb:	parseInt(correct_hex_prefix(args.bgcol_blue)) });
	if (is_defined(args.lines_brightness_min))	request_string = set_request_get_vars(request_string, { lmin:	parseInt(correct_hex_prefix(args.lines_brightness_min)) });
	if (is_defined(args.lines_brightness_max))	request_string = set_request_get_vars(request_string, { lmax:	parseInt(correct_hex_prefix(args.lines_brightness_max)) });
	if (is_defined(args.chars_brightness_min))	request_string = set_request_get_vars(request_string, { cmin:	parseInt(correct_hex_prefix(args.chars_brightness_min)) });
	if (is_defined(args.chars_brightness_max))	request_string = set_request_get_vars(request_string, { cmax:	parseInt(correct_hex_prefix(args.chars_brightness_max)) });
	if (is_defined(args.width))					request_string = set_request_get_vars(request_string, { w:		parseInt(args.width) });
	if (is_defined(args.height))				request_string = set_request_get_vars(request_string, { h:		parseInt(args.height) });
	if (is_defined(args.chars))					request_string = set_request_get_vars(request_string, { ch:		parseInt(args.chars) });
	if (is_defined(args.font_size))				request_string = set_request_get_vars(request_string, { fs:		parseInt(correct_hex_prefix(args.font_size)) });
	if (is_defined(args.text_indentation))		request_string = set_request_get_vars(request_string, { ti:		parseInt(correct_hex_prefix(args.text_indentation)) });
	
	// Always add a (semi-)random var, so that we don't fetch old captchas from the browser's cache:
	request_string = set_request_get_vars(request_string, { no_cache: get_random_id() });
	
	// Make sure we don't add the onclick function more than once ;):
	if (is_empty(args.target.attr('requeststring')))
		args.target.click(function() { insert_captcha_image({ target: args.target }); this.blur(); return false; });
	
	args.target.attr('src', request_string).attr('requeststring', request_string);
	
	return false;
}




/**
 * Renders an AJAX star rater in the specified DOM element.
 * 
 * Version 1.9.1006
 * History:
 * 	1.9.1006: Wrapped the vars object that is posted in object_without_null_members() to remove any member that is nullified.
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{integer}	args.clip_id										The id of the clip for which to create a rater.
 * @param	{integer}	args.current_rating									The current clip rating.
 * @param	{integer}	args.max_rating										The maximum possible rating, and therefore also the number
 * 																			of stars to generate.
 * @param	{string}	args.img_class										CSS class to assign the created <img> elements.
 * @param	{string}	args.img_on_url										The url for the star image to signify the currently achieved
 * 																			rating of the clip.
 * @param	{string}	args.img_off_url									The url for the star image to signify the currently non-
 * 																			achieved rating of the clip.
 * @param	{string}	args.img_over_url									The url for the star image when entering mouseover event.
 * @param	{string}	args.img_captions							(OPT)	Optional tooltip captions to display for each of the stars
 * 																			on mouseover.
 * @return	{bool}															True on success, false on failure.
 */
function create_star_rater(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)) || !is_defined(args.clip_id) || !is_defined(args.current_rating) || !is_defined(args.max_rating) || !is_defined(args.img_class) || !is_defined(args.img_on_url) || !is_defined(args.img_off_url) || !is_defined(args.img_over_url))
	{
		trigger_error("Core::create_star_rater(): Not enough arguments provided. I need to know the target dom id to create stars inside ('target'), the id of the clip in question ('clip_id'), the current rating of the clip ('current_rating'), the maximum possible rating ('max_rating'), the CSS class to assign each image ('img_class'), and the image urls for the on, off, and over states ('img_on_url', 'img_off_url', and 'img_over_url').");
		return false;
	}
	
	// Utility functions:
	ajax_rater_over = function(args)
	{
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length));
		
		for (var i=1; i<=star_num; ++i)
		{
			var star = $('#'+base_id+'_'+i);
			star.attr('old_src', star.attr('src'));
			star.attr('src', args.over);
		}
	};
	ajax_rater_out = function(args)
	{
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length));
		
		for (var i=1; i<=star_num; ++i)
		{
			var star = $('#'+base_id+'_'+i);
			star.attr('src', star.attr('old_src'));
		}
	};
	ajax_rater_click = function(args)
	{
		ajax_rater_out(args);
		
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length));
		
		// Post:
		$.post('/json/core_rate_clip.php',
			object_without_null_members(merge_objects(request_state, {clip_id:args.clip_id, rating:star_num})),
			function(response)
			{
				// If successful we update the rating:
				if (response.success && is_defined(response.updated_rating))
				{
					for (var i=1; 1==1; ++i)
					{
						var star_id = '#'+base_id+'_'+i;
						// Break out if this element doesn't exist:
						if ($(star_id).length == 0) break;
						var star = $('#'+base_id+'_'+i);
						var new_state = ((i<=response.updated_rating) ? args.on : args.off);
						star.attr('src',		new_state);
						star.attr('old_src',	new_state);
					}
				}
				alert(response.message);
			},
			'json'
		);
	};
	
	for(var i=1, base_id=get_random_id(); i<=args.max_rating; ++i)
	{
		var element = $('<acronym></acronym>');
		
		if ($.isArray(args.img_captions)) element.attr('title', args.img_captions[i-1]);
		
		var image = $('<img />');
		
		image.attr('id', base_id+'_'+i);
		
		image.attr('src', ((i <= args.current_rating) ? args.img_on_url : args.img_off_url));
		
		image.attr('class', args.img_class);
		
		image.mouseover(function() {
			ajax_rater_over({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		image.mouseout(function() {
			ajax_rater_out({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		image.click(function() {
			ajax_rater_click({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		element.append(image);
		
		args.target.append(element);
	}
	
	return true;
}





var collapsible_box_exclusive_groups = new Array();

/**
 * Registers a box (that is, a combination of a control element and a collapsible (hideable) element) as
 * 'collapsible'. This adds an onclick event to the control element that will toggle the collapsed state
 * of (i.e. show or hide) the collapsible element.
 * 
 * If you have an onclick event of your own, this will be kept and fired AFTER the toggle event when the
 * control element is clicked.
 * 
 * If you have a group of elements in which only one element may be expanded at a time, you can use the
 * optional .exclusive_group parameter to join together boxes in groups. You may create as many groups as
 * you wish.
 * 
 * If cookies are enabled, box state is kept across page views and sessions.
 * 
 * @param	{bool}		args.defaults_to_expanded							Whether, if no state has been recorded in the user's cookie, to show the box expanded.
 * @param	{string}	args.control_element_id								The dom id of the control element of the box whose state to toggle.
 * @param	{string}	args.control_element_class_expanded					The class to apply to the control element when the box is expanded.
 * @param	{string}	args.control_element_class_collapsed				The class to apply to the control element when the box is collapsed.
 * @param	{string}	args.collapsible_element_id							The dom id of the content element to collapse.
 * @param	{string}	args.collapsible_element_class_expanded				The class to apply to the content element when the box is expanded.
 * @param	{string}	args.collapsible_element_class_collapsed			The class to apply to the content element when the box is collapsed.
 * @param	{string}	args.control_element_tooltip_expanded		(OPT)	A tooltip caption to attach to the control element when expanded.
 * @param	{string}	args.control_element_tooltip_collapsed		(OPT)	A tooltip caption to attach to the control element when collapsed.
 * @param	{string}	args.exclusive_group						(OPT)	A group name in which only one box may be expanded at any one time.
 * @return	{bool}															True on success, false on failure.
 */
function make_collapsible_box(args)
{
	if (is_empty(arguments) || !is_defined(args.control_element_id) || !is_defined(args.collapsible_element_id) || !is_defined(args.control_element_class_expanded) || !is_defined(args.control_element_class_collapsed) || !is_defined(args.collapsible_element_class_expanded) || !is_defined(args.collapsible_element_class_collapsed))
	{
		trigger_error("Core::make_collapsible_box(): Not enough arguments provided. I need to know at least the control element to add an onclick event to ('control_element_id'), the element to collapse or expand when the control element is clicked ('collapsible_element_id'), and the CSS classes to to toggle between ('control_element_class_expanded', 'control_element_class_collapsed', 'collapsible_element_class_expanded', and 'collapsible_element_class_collapsed')");
		return false;
	}
	
	// Always make sure we don't try to fiddle with the DOM before it's finished loading:
	$(document).ready(function() {
		
		// Attach a collapse state toggle to the on click event of the control element:
		$('#'+args.control_element_id).click( function(e) { toggle_collapsible_box(args); } );
		
		// Make sure the box state is restored:
		restore_collapsible_box_state(args);
	});
	
	return true;
}




/**
 * Expands a collapsed box, or collapses an expanded box. If 'exclusive_group' is defined, then only one of the boxes in
 * exclusive_group is allowed to be open at any one time. Upon expanding one, the rest are automatically collapsed.
 * 
 * You wouldn't normally call this function directly, but instead initialize your boxes with make_collapsible_box().
 * 
 * @param	{string}	args.control_element_id								The dom id of the control element of the box whose state to toggle.
 * @param	{string}	args.control_element_class_expanded					The class to apply to the control element when the box is expanded.
 * @param	{string}	args.control_element_class_collapsed				The class to apply to the control element when the box is collapsed.
 * @param	{string}	args.collapsible_element_id							The dom id of the content element to collapse.
 * @param	{string}	args.collapsible_element_class_expanded				The class to apply to the content element when the box is expanded.
 * @param	{string}	args.collapsible_element_class_collapsed			The class to apply to the content element when the box is collapsed.
 * @param	{string}	args.control_element_tooltip_expanded		(OPT)	A tooltip caption to attach to the control element when expanded.
 * @param	{string}	args.control_element_tooltip_collapsed		(OPT)	A tooltip caption to attach to the control element when collapsed.
 * @param	{string}	args.exclusive_group						(OPT)	A group name in which only one box may be expanded at any one time.
 * @return	{bool}															True on success, false on failure.
 */
function toggle_collapsible_box(args)
{
	if (is_empty(arguments) || !is_defined(args.control_element_id) || !is_defined(args.collapsible_element_id) || !is_defined(args.control_element_class_expanded) || !is_defined(args.control_element_class_collapsed) || !is_defined(args.collapsible_element_class_expanded) || !is_defined(args.collapsible_element_class_collapsed))
	{
		trigger_error("Core::toggle_collapsible_box(): Not enough arguments provided. I need to know at least the control element that's been clicked ('control_element_id'), the element to collapse or expand ('collapsible_element_id'), and the CSS classes to to toggle between ('control_element_class_expanded', 'control_element_class_collapsed', 'collapsible_element_class_expanded', and 'collapsible_element_class_collapsed')");
		return false;
	}
	
	var collapse = $('#' + args.control_element_id).is('.' + args.control_element_class_expanded);
	
	if (collapse)
	{
		collapse_collapsible_box(args);
	}
	else
	{
		if (is_defined(args.exclusive_group))
    		for (id in collapsible_box_exclusive_groups[args.exclusive_group])
    			collapse_collapsible_box(collapsible_box_exclusive_groups[args.exclusive_group][id]);
		
		expand_collapsible_box(args);
	}
	
	return false;
}

/**
 * Internal utility functions, you shouldn't call these directly. Use toggle or restore instead.
 */
function collapse_collapsible_box(args)
{
	// Switcharoo:
	$('#' + args.control_element_id).removeClass(args.control_element_class_expanded).addClass(args.control_element_class_collapsed);
	$('#' + args.collapsible_element_id).removeClass(args.collapsible_element_class_expanded).addClass(args.collapsible_element_class_collapsed);
	
	// Store in a domain- and site-wide cookie:
	$.cookie(site_hostname + '_' + args.control_element_id + '_expanded', 'no', {expires:90, path:'/', domain:'.'+site_hostname});
	
	if (is_defined(args.control_element_tooltip_collapsed)) $('#' + args.control_element_id).attr('title', args.control_element_tooltip_collapsed);
}
function expand_collapsible_box(args)
{
	// Switcharoo:
	$('#' + args.control_element_id).removeClass(args.control_element_class_collapsed).addClass(args.control_element_class_expanded);
	$('#' + args.collapsible_element_id).removeClass(args.collapsible_element_class_collapsed).addClass(args.collapsible_element_class_expanded);
	
	// Store in a domain- and site-wide cookie:
	$.cookie(site_hostname + '_' + args.control_element_id + '_expanded', 'yes', {expires:90, path:'/', domain:'.'+site_hostname});
	
	if (is_defined(args.control_element_tooltip_collapsed)) $('#' + args.control_element_id).attr('title', args.control_element_tooltip_expanded);
}
function restore_collapsible_box_state(args)
{
	if (is_defined(args.exclusive_group))
	{
		// Create the exclusive group if it doesn't exist:
		if (!$.isArray(collapsible_box_exclusive_groups[args.exclusive_group]))
			collapsible_box_exclusive_groups[args.exclusive_group] = [];
		
		// Add this box to the group (implicitly if it's not registered already (happens on template reloads, for instance, why is also why we index the grouped boxes using their dom id)):
		collapsible_box_exclusive_groups[args.exclusive_group][args.control_element_id] = args;
	}
	
	var cookie_value		= $.cookie(site_hostname + '_' + args.control_element_id + '_expanded');
	var should_be_expanded	= cookie_value == 'yes' || (cookie_value == null && args.defaults_to_expanded);
	var is_expanded			= $('#' + args.control_element_id).is('.' + args.control_element_class_expanded);
	
	if (is_defined(args.control_element_tooltip_collapsed) && !is_expanded)	$('#' + args.control_element_id).attr('title', args.control_element_tooltip_collapsed);
	if (is_defined(args.control_element_tooltip_expanded) && is_expanded)	$('#' + args.control_element_id).attr('title', args.control_element_tooltip_expanded);
	
	if (should_be_expanded != is_expanded) toggle_collapsible_box(args);
}




function iAjax() {
  var xmlHttp;
    
  try { xmlHttp = new XMLHttpRequest(); } 
  catch (e) { 
    try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } 
    catch (e) { 
      try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } 
      catch (e) { alert("Your browser does not support AJAX!"); return false; }
    }    
  }
 
  return xmlHttp;
}


/**
 * Handle commenting deletion and saving. 
 * @param id	int			Comment id key ("challenge code")
 * @param arg	string		Either save or delete
 **/
function handle_comment(id, arg) {
  xmlHttp = iAjax();

  xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState == 4) {
      document.getElementById("returnstatus").innerHTML = xmlHttp.responseText;
    } 
  } 

  var output = "/html/shadowbox_handle_comment.php?challenge_code=" + id + "&comment_destiny=" + arg;
  xmlHttp.open("GET", output, true); 
  xmlHttp.send(null);
}

