/**
 *		Version:	1.0.0
 */

/*#	Check if a TVListings object and some if its properties already
	exists and if not initialise them	*/
if (!TVListings) {	var TVListings = {};}

if (!TVListings.properties) {	
	TVListings.properties = {};
	TVListings.properties.params = {};
	TVListings.properties.cookie_prefix = "dnitvl_";
	TVListings.properties.defaults = {};	
} else {	
	if (!TVListings.properties.cookie_prefix) {
		TVListings.properties.cookie_prefix = "dnitvl_";
	}	
	if (!TVListings.properties.params) {
		TVListings.properties.params = {};
	}
}


/* Service Location & Info Defaults */
	TVListings.appname = "TV Schedule";
	TVListings.servlet_address = "/dni-tvlistings/";
	TVListings.listings_address = "/tv-schedule/";
	TVListings.image_path = "/tier0/images/common/dnitvl";
	TVListings.style_suffix = "";
	

/**
 * Adds a parameter to be used and retreived from various sources
 * @param {String} name 	Name to use for the TVListings.properties.param key
 * @param {Object} regex	Optional. Regular expression to override the default
 * @param {Integer} pos		Optional. Regex group from the match method to use
 */
TVListings.properties.add_parameter = function (name, pos, regex) {
	//create an object to hold the parameter's settings
	var param = {'name': name.toLowerCase()};
	
	//if custom regex for the get_url_parameter method then assign that
	if (regex) { param.regex = regex; param.pos = pos;}
	
	//add it
	this.params[name] = param;
}

/**
 * Uses the information in the TVListings.properties object's params property to retrieve information from the current URL
 * @param {Object} name		Name of the TVListings.properties.params key
 * @param {String} url		Optional. Specify a URL to get the information frmo instead of the current URL
 */
TVListings.properties.get_url_parameter = function (name, url) {
	var param = this.params[name];
	if (!url) var url = document.location.href;
	
	//if custom regex has been supplied then use that, otherwise use default regex
	if (param.regex) {
		var url_regex = RegExp(param.regex);
		var pos = param.pos;
	} else {
		var url_regex = RegExp('(.*)(\\?|&)' + param.name + '=([^&]*)($|&)(.*)');
		var pos = 3;
	}
	
	//try and get the information from the URL, otherwise return null
	var value = null;	
	try { value = url.match(url_regex)[pos];}
	catch(e) { value = null;}
	
	return value;
}

/**
 * Uses the information in the TVListings.properties object to retrieve cookie information from the browser
 * @param {Object} name		Name of the TVListings.properties.params key
 */
TVListings.properties.get_cookie_parameter = function (name, ovr) {
	if (!ovr) {
		var param = this.params[name];
		
		//if a prefix for settings cookies has been set, use it
		if (this.cookie_prefix) name = this.cookie_prefix + name;
	}

	var cookie_regex = RegExp('(^|.*; )' + name + '=([^;]*).*');

	//try and get the information from document.cookie, otherwise return null
	var value = null;
	try { value = document.cookie.match(cookie_regex)[2];} 
	catch(e) { value = null;}
	
	return value;
}

/**
 * Creates TVListings property param object from a string (kind of like a miroformat)
 * Class Name syntax is: name__value
 * @param {String} str		String to extract information from
 * @param {String} delimit	A delimiter used to seperate properties
 */
TVListings.properties.get_info_from_string = function (str, delimit) {
	if (!delimit) var delimit = " ";
	var isparam = RegExp('(.*)__(.*)');
	var arr = {}
	
	//Create an array of the element's classes
	var str = str.split(delimit);
	//loop through classes and add them to arr if they are parameters
	for (var i=str.length-1,k;k=str[i];i--) {
		var tmp = k.match(isparam);
		if (tmp) {
			arr[tmp[1]] = tmp[2];
		}
	}
	//return the array object of parameters
	return arr;
}

/**
 * Gathers information for a TVListings.properties.params object and returns an array
 */
TVListings.properties.process_parameters = function (params) {
	var value = {};
	var chnl_fix = 0;
	if (!params) var params = this.params;
	if (this.defaults) var ovr = true;
	for (var i in params) { //loop through params
		var j, k = params[i];
		var name = k.name;
		if ((ovr) && (this.defaults[name]) && (this.defaults[name].value !== "") && (this.defaults[name].override)) { //look for a master override
				value[name] = this.defaults[name].value;
		} else if ((j = this.get_url_parameter(name)) && (j.value !== "")) { //next try looking in the url
			value[name] = j;
		} else if ((j = this.get_cookie_parameter(name)) && (j.value !== "")) { //lastly try cookies
			value[name] = j;
		} else if ((ovr) && (this.defaults[name]) && (this.defaults[name].value !== "")) {
			//having failed, try to return a suggested value
			value[name] = this.defaults[name].value;
			if (name == "channel_code") chnl_fix++;
			if (name == "country_code") chnl_fix--;
		}
	}
	
	if ((value.country_code)&&(chnl_fix == 1)) {
		delete value.channel_code;
	}
	
	return value;	
}

/**
 * Sets a cookie for a TVListings.properties.params object
 * @param {Object} arr		Array of key-value pairs, keys corresponding to TVListings.properties.params keys
 * @param {Object} exp		Optional. The expiration date of the cookie, formatted by toGMTString()
 */
TVListings.properties.set_cookie_parameters = function (arr, exp) {
	if (!exp) var exp = new Date(new Date().getFullYear()+20, 1, 1).toUTCString();
	for (i in arr) {
		var k = arr[i];
		var name = this.params[i].name;
		if (this.cookie_prefix) { name = this.cookie_prefix + name;}
		
		//assemble the cookie string
		var value = name + "=" + k + "; ";
		value += "expires=" + exp + "; "; //if an expiry date has been specified add that
		value += "path=/; ";
		
		//set cookie
		document.cookie = value;
	}
}

/**
 * Deletes a cookie for a TVListings.properties.params object
 * @param {Object} arr		Array of key-value pairs, keys corresponding to TVListings.properties.params keys
 */
TVListings.properties.remove_cookie_parameters = function (arr) {
	for (i in arr) {
		var k = arr[i];
		var name = this.params[i].name;
		if (this.cookie_prefix) { name = this.cookie_prefix + name;}
		
		//assemble the cookie string
		var exp = new Date().toUTCString();
		var value = name + "=" + k + ";";
		value += "expires=" + exp + ";; "; //if an expiry date has been specified add that

		//set cookie
		document.cookie = value;
	}
}
/**
 * Adds a TVListings.properties.params object and a value to the current url
 * @param {Object} name		Name of the TVListings.properties.params key
 * @param {Object} val		Value to set the parameter to
 * @param {String} url		Optional. Specify a URL to change instead of the current URL
 */
TVListings.properties.add_url_parameter = function (name, val, url) {
	var param = this.params[name];
	var val = val;
	
	//if no URL was specified use the current URL
	if (!url) url = document.location.href;
	
	var tmp_str = "";
	//check if the current url already has a query string
	if (url.match(RegExp('\\?'))) {
		tmp_str = "&"
	} else { tmp_str = "?"}
	
	//prepare the parameter
	tmp_str += param.name + "=" + val;
		
	//add the parameter
	var value = null;
	value = url + tmp_str;

	return value;
}

/**
 * Changes a TVListings.properties.params object's value in the current URL and returns the new URL
 * @param {Object} name		Name of the TVListings.properties.params key
 * @param {Object} val		Value to change the parameter to
 * @param {String} url		Optional. Specify a URL to change instead of the current URL
 */
TVListings.properties.remove_url_parameter = function (name, url) {
	var param = this.params[name];
	
	//if no URL was specified use the current URL
	if (!url) url = document.location.href;
	
	//if custom regex has been supplied then use that, otherwise use default regex
	if (param.regex) {
		var url_regex = RegExp(param.regex);
		var pos = param.pos;
	} else {
		var url_regex = RegExp('(.*)(\\?|&)' + param.name + '=([^&]*)($|&)(.*)');
		var pos = 3;
	}
	
	var regex_fix = RegExp('(.*)&$');
	
	var count = null, value = null;
	//get the amount of matches the regex returns
	try { count = url.match(url_regex).length-1;}
	catch(e) { count = null;}
	
	//if there were matches found in the URL replace the match at position param.pos
	if ((count !== null) && (count >= 0)) {
		var tmp_str = "";
		for (var i = 1; i <= count; i++) {
			if ((i !== pos) && (i !== pos+1)) { tmp_str += "$" + i;}
		}
		value = url.replace(url_regex, tmp_str).replace(regex_fix, "$1");
	} else {
		value = url;
	}
	
	return value;
}

/**
 * Changes a TVListings.properties.params object's value in the current URL and returns the new URL
 * @param {Object} name		Name of the TVListings.properties.params key
 * @param {Object} val		Value to change the parameter to
 * @param {String} url		Optional. Specify a URL to change instead of the current URL
 */
TVListings.properties.change_url_parameter = function (name, val, url, del) {
	var param = this.params[name];

	var val = val;
	
	//if no URL was specified use the current URL
	if (!url) url = document.location.href;
	
	//if custom regex has been supplied then use that, otherwise use default regex
	if (param.regex) {
		var url_regex = RegExp(param.regex);
		var pos = param.pos;
	} else {
		var url_regex = RegExp('(.*)(\\?|&)' + param.name + '=([^&]*)($|&)(.*)');
		var pos = 3;
	}
	
	var count = null, value = null;
	//get the amount of matches the regex returns
	try { count = url.match(url_regex).length-1;}
	catch(e) { count = null;}
	
	//if there were matches found in the URL replace the match at position param.pos
	if ((count !== null) && (count >= 0)) {
		var tmp_str = "";
		for (var i = 1; i <= count; i++) {
			if (i == pos) { tmp_str += param.name + "=" + val;}
			else { tmp_str += "$" + i;}
		}
		value = url.replace(url_regex, tmp_str);
	} else { //else add the parameter to the URL
		value = this.add_url_parameter(name, val, url);
	}
	
	return value;
}

/**
 * Creates a URL from an Object Array of paramater key-value pairs like those produced by process_parameters()
 * @param {Array} arr		An array of key-value pairs to use as the query strings
 * @param {Object} url		Optional. A URL to use as a base, instead of the current one
 */
TVListings.properties.get_url = function (arr, url) {
	//if no URL was specified use the current URL
	if (!url) var url = document.location.href;
	
	//remove any query strings
	if ((url.match(RegExp('\\?')))) {
		url = url.match(RegExp('(.*)\\?(.*)'))[1];
	}
	
	//create query string
	var tmp_str = "?";
	for (var i in arr) { tmp_str += i + "=" + arr[i] + "&";}
	tmp_str = tmp_str.replace(RegExp('&$'), "");
	
	//arrange the new url
	var value = url + tmp_str;
		
	return value;
}

/* Listings Controls */

TVListings.controls = {} //most things here require JQuery because I'm lazy

	/* Reminders Functionality */
	TVListings.controls.reminders = {}
	TVListings.controls.reminders.messages = {}
	TVListings.controls.reminders.when = 1;
	
	TVListings.controls.reminders.init = function () {
		var form = $(document.getElementById("listings-reminder"));
		var submit = $(document.getElementById("listings-reminder-submit"));
		var days = document.getElementById("listings-reminder-days");
		var msgs = TVListings.properties.get_info_from_string(submit.attr("alt"), "%%");
		this.messages.success = msgs.success;
		this.messages.failed = msgs.failed;
		this.messages.invalid_email = msgs.invalid_email;
		this.messages.help = submit.parent().children(".hint").html();
		
		form.submit(function(){
			TVListings.controls.reminders.submit();
			return false;
		});
		
		if (days) {
			this.dropdown(days);
		}
		//remove remniders for programmes which start within 24 hours
		this.remove_old_reminders();
		
		//CLICK TO ACTIVATE REMINDER LINK
		this.addClickToActivate();
		//GENERATE UA LOGIN AND REGISTER LINKS
        this.addUALinks();
	}
	
	/**
	 * Function that activates the email input for reminders when the user is logged in
	 */
	TVListings.controls.reminders.addClickToActivate = function() {
        $("#click-to-activate").click(function(){
			$("#listings-reminder-email").removeClass().removeAttr("readonly");
			return false;
		});
    }
	
	/**
     * Function that generate the links to UA
     */
    TVListings.controls.reminders.addUALinks = function() {
		var loginURL = "/ua-fe/login.shtml?returnUrl=" + escape(location);
		var registerURL = "/ua-fe/register-step-1.shtml?returnUrl=" + escape(location);
        $("#ua-login").attr("href",loginURL);
		$("#ua-register").attr("href",registerURL);
    }
	
	/**
	 * Validates email addresses (from ap3 send-to-friend, except accepts 4 character TLDs)
	 * @param {Object} address		Email address to validate
	 */
	TVListings.controls.reminders.validate_email = function(address) {
		var invalid = /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/;
		var valid = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
		if (!invalid.test(address) && valid.test(address)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Takes an array of elements and returns an array of their values.
	 * @param {Object} arr		Array of checkbox DOM Elements
	 */
	TVListings.controls.reminders.get_checkbox_values = function (arr) {
		var value = [];
		for (var i=arr.length-1;i>=0;i--) {
			if (arr[i].checked) {
				value.push(arr[i].value);
			}
		}
		return value;
	}
	
	/**
	 * Requires JQuery. Hides status messages.
	 */
	TVListings.controls.reminders.hide_messages = function () {
		$("#listings-reminder-msg").fadeOut("slow").html("");
	}
	
	/**
	 * Requires JQuery. Displays success message.
	 */
	TVListings.controls.reminders.success = function (msg) {
		this.hide_messages();
		if (!msg) var msg = this.messages.success;
		$("#listings-reminder-msg").css("opacity", "0").html(msg).fadeIn("slow");
	}
	
	/**
	 * Requires JQuery. Displays error message.
	 */
	TVListings.controls.reminders.failed = function (msg) {
		this.hide_messages();
		if (!msg) var msg = this.messages.failed;
		$("#listings-reminder-msg").css("opacity", "0").html(msg).fadeIn("slow");
	}
	
	/**
	 * Gather reminder information from the page and return a properties object
	 * @param {Object} name		Name attribute of the reminder checkboxes which is also used as 'Name' -email and -when
	 * 							for when-to-send and email address inputs
	 */
	TVListings.controls.reminders.get_reminder_properties = function (name) {
		try {
			//Get checkboxes and check if they are checked before extracting schedule ids
			var checkboxes = document.getElementsByName(name);
			var sids = this.get_checkbox_values(checkboxes);
			
			//If no checkboxes are checked do nothing
			if (sids.length < 1) {
				this.failed(this.messages.help);
				return false;
			}
			
			//Get channel code from the listings properties
			var chnl_code = TVListings.properties.get_info_from_string(
				$("#listings-channel-controls li.current").attr("class")
			).channel_code;
	
			
			//Get the when to send and email address values
			var when = TVListings.controls.reminders.when;
			var email = document.getElementsByName(name + "-email")[0].value;
			
			//validate email and return properties object if successfull
			if ((!this.validate_email(email))) {
				this.failed(this.messages.invalid_email);
				return false;
			} else {
				var value = {}
				value.sids = sids;
				value.chnl_code = chnl_code
				value.when = when;
				value.email = email;
				return value;
			}
		} catch (e) {
			this.failed();
			return false;
		}
	}
	
	/**
	 * Generates and returns xml as a string for programme reminder information arrays
	 * @param {Object} arr		Array of program reminder information generated by the get_reminder_properties method
	 */
	TVListings.controls.reminders.get_reminders_xml = function (arr) {
		try {
			if ((!arr.email)||(arr.sids.length<1)||(!arr.when)) {
				this.failed(this.messages.help);
				return false;
			}
			//Create top-level reminders element
			var xml = document.createElement("reminders");
			
			//Create channel-code element
			var chnl_code = document.createElement("channel-code");
			chnl_code.innerHTML = arr.chnl_code;
			xml.appendChild(chnl_code);
			
			//Create email element and add to reminders element
			var email = document.createElement("email");
			email.innerHTML = arr.email;
			xml.appendChild(email);
	
			//Create schedule-item-id elements for checked programmes and add to reminders element
			for (var i=arr.sids.length-1,k;k=arr.sids[i];i--) {
				var tmp_elem = document.createElement("scheduled-item");
				tmp_elem.innerHTML = k;
				xml.appendChild(tmp_elem);
			}
			
			//Create when-to-send element
			var when = document.createElement("reminder-offset");
			when.innerHTML = arr.when;
			xml.appendChild(when);
			
			//Create a dummy parent element so that innerHTML contains the reminders root element as well
			var dummy = xml;
			var xml = document.createElement("dummy");
			xml.appendChild(dummy);
			
			//Add xml element and return the complete reminders xml as a string
			return '<?xml version="1.0" encoding="UTF-8"?>' + xml.innerHTML;
		} catch(e) {
			this.failed();
			return false;
		}
	}
	
	TVListings.controls.reminders.remove_old_reminders = function () {
		var date = new Date();
		var threshold = date.getTime() + 86400000;
		var hidden = 0;
		
		var checkboxes = document.getElementsByName("listings-reminder");
		var checkboxes_len = checkboxes.length;
		for (var i=checkboxes.length-1;((i !== -1) && (checkboxes[i].alt) && (i>=0));i--) {
			var checkbox = checkboxes[i];
			var this_datetime = TVListings.properties.get_info_from_string(checkbox.alt).datetime;
			var this_date_obj = TVListings.misc.date.get_date_object(
				this_datetime.substring(0,8),
				this_datetime.substring(8)
			);
			if ((this_date_obj.getTime() < threshold)) {
				var jq_Checkbox = $(checkbox);
				jq_Checkbox.parent().parent().addClass("no-reminders");
				jq_Checkbox.parent().children().remove();
				hidden++;
			}
		}
		if (hidden == checkboxes_len) {
			$(".listings-page-controls.reminders-form").remove();
		}
	}
	
	/**
	 * Requires JQuery. Sets reminders.
	 */
	TVListings.controls.reminders.submit = function () {
		var properties = this.get_reminder_properties("listings-reminder");
		if ((properties)&&(typeof(properties) == "object")) {
			var xml = this.get_reminders_xml(properties);
			if (xml) {
				var url = TVListings.servlet_address + "reminder";
				$.ajax({
					'url': url, 'data': {'message':xml},
					'type': "POST",
					'success': function () { TVListings.controls.reminders.success();},
					'error': function () { TVListings.controls.reminders.failed();}
				})
			}
		}
		return false;
	}
	
	TVListings.controls.reminders.dropdown = function (elem) {
		if (typeof(elem.height) !== "function") elem = $(elem);
		
		var toggle = document.createElement("span");
		toggle.className = "controls_toggle_button";
		$(toggle).insertAfter(elem);
		
		$(document).click(function (event) {
				if ((($(event.target).parents("#listings-reminder-days").length == 0)
				  && (elem.parent().parent().hasClass("expanded")))
				    || (event.target.className == "controls_toggle_button")) {
						elem.parent().parent().toggleClass("expanded");
						elem.parent().toggleClass("expanded");
				}
		})
		
		elem.children("li:not(.default-menu-item)").click(
			function () {
				TVListings.controls.reminders.when = this.getElementsByTagName("strong")[0].innerHTML;
				$(this).parent().children().filter(".current").removeClass("current");
				$(this).addClass("current");
				$(this).parent().parent().removeClass("expanded");
				$(this).parent().parent().parent().removeClass("expanded");
				$(this).siblings(".default-menu-item").children("strong").html($(this).text());
			}
		);
		
		elem.children("li").hover(
			function () { $(this).addClass("visible");},
			function () { $(this).removeClass("visible");}
		);
	}
	
	/* Other Controls */

	/**
	 * Saves the value of a checkbox in a cookie as the default country code. Removes on second click.
	 * @param {Object} elem		Control's main ul element
	 */
	TVListings.controls.remember_country = function (elem) {
		if (elem) {
			if (typeof(elem.height) !== "function") elem = $(elem);
			elem.change(
				function () {
					if (this.checked !== false) {
						TVListings.properties.set_cookie_parameters({
							'country_code': this.value
						});
					} else {
						TVListings.properties.remove_cookie_parameters({
							'country_code': ""
						});
					}
				}
			)
		}
	}
	
	/**
	 * Turns a listings single-level list into functional controls
	 * @param {Object} elem		Control's main ul element
	 */
	TVListings.controls.apply_single_level = function (elem, del_) {
		if (del_) var del = del_;
		var list_items = $(elem.getElementsByTagName("li"));
		
		//loop through li elements
		list_items.each (function () {
					
			//extract parameter information from class names
			this.info = TVListings.properties.get_info_from_string(this.className);
			
			//on click update the current page with the new parameter information
			$(this).click( function () {
				if (del) {
					for (var i in del) {
						TVListings.main.stage.url = TVListings.properties.remove_url_parameter(i,TVListings.main.stage.url);
					}
				}
				
				//update the stage URL with new info
				for (var i in this.info) {
					if (i !== "ss") {
						TVListings.main.stage.url = TVListings.properties.change_url_parameter(
							i, this.info[i], TVListings.main.stage.url, del
						);
					}
				}
				//go to new URL
				document.location.href = TVListings.main.stage.url;
				return false;
			});
		});
	}
	
	/**
	 * Turns a listings multi-level list into functional controls
	 * @param {Object} elem		Control's main ul element
	 */
	TVListings.controls.apply_multi_level = function (elem, del, max_height) {
		
		//get all descendant li elements
		var list_items = $(elem.getElementsByTagName("li")).filter(":not(.menu-item-spacer)").filter(":not(.first-item-spacer)");
		
		//On hover show sub menus
		$(elem).hoverIntent({
			'sensitivity': 8,
			'interval': 100,
			'timeout': 250,
			'over': function () {
				TVListings.controls.datepicker.close();
				$(this).parent().css("z-index", "3");
				TVListings.controls.show_submenu(this, max_height);
			},
			'out': function () {
				TVListings.controls.hide_submenu(this, max_height);
				$(this).parent().css("z-index", "1");
			}
		});
		
		$(elem).click(function () {
			TVListings.controls.hide_submenu(this, max_height);
			$(this).parent().css("z-index", "1");
		})
		
		$(elem.parentNode).hoverIntent({
			'sensitivity': 5,
			'interval': 50,
			'over': function () { TVListings.controls.show_submenu(this);},
			'out': function () { TVListings.controls.hide_submenu(this);}
		});
		
		list_items.hover(
			function () { TVListings.controls.show_submenu(this);},
			function () { TVListings.controls.hide_submenu(this);}
		);
		
		list_items.filter(".current")
		list_items = list_items.filter(":not(.current)").filter(":not(.default-menu-item)");
		

		//loop through li elements
		list_items.each (function () {
					
			//extract parameter information from class names
			this.info = TVListings.properties.get_info_from_string(this.className);
			
			//Check if the object has properties
			var has_properties = false;
			
			for (var f in this.info) {
				has_properties = true;
			}
			
			if (has_properties == true) {
				
				//on click update the current page with the new parameter information
				$(this).click( function () {
					if (del) {
						for (var i in del) {
							TVListings.main.stage.url = TVListings.properties.remove_url_parameter(i,TVListings.main.stage.url);
						}
					}
					
					//if we have only a country being changed and not a channel then make sure channel_code is not
					//in the URL so that it does not override it
					if ((this.info.country_code)&&(!this.info.channel_code)) {
						TVListings.main.stage.url = TVListings.properties.remove_url_parameter(
							'channel_code',TVListings.main.stage.url
						);
					}
					
					//update the stage URL with new info
					for (var i in this.info) {
						if (i !== "ss") {
							TVListings.main.stage.url = TVListings.properties.change_url_parameter(
								i, this.info[i], TVListings.main.stage.url, del
							);
						}
					}
					//go to new URL
					document.location.href = TVListings.main.stage.url;
				})
			}
		});
	}
	
	TVListings.controls.show_submenu = function (elem, max_height) {
		//if elem is not a JQuery object create one
		if (typeof(elem.height) !== "function") elem = $(elem);
		
		if (max_height) {
			elem.css('height', max_height + "px").css('overflow', "auto");			
		}

		elem.addClass("visible");
	}
	
	TVListings.controls.hide_submenu = function (elem, max_height) {
		//if elem is not a JQuery object create one
		if (typeof(elem.height) !== "function") elem = $(elem);
		
		elem.removeClass("visible");
		
		if (max_height) {
			elem.css('overflow', "visible").css('height', "auto");
		}
	}
	
	TVListings.controls.widget = function () {
		$("#listings-widget-region-control").change(function () {

			//Get selected option
			var opt = $("#listings-widget-region-control option:selected")[0];

			//Get the current page settings
			var settings = TVListings.properties.process_parameters();
			
			//Change the country code
			settings.country_code = opt.value;
			delete settings.channel_code;
			
			TVListings.main.get_listings(settings);
		})
		
		$("#listings-widget-link").click(function () {
	
			if (TVListings.listings_address !== "") {
				//Get the current page settings
				var settings = TVListings.properties.get_info_from_string(this.className);
				
				//Create URL using these settings
				var url = TVListings.properties.get_url(settings, TVListings.listings_address);
				
				document.location.href = url;
			}
		})
	}
	
	TVListings.controls.datepicker = {}
	TVListings.controls.datepicker.event_match = ".dpTable";
	TVListings.controls.datepicker.picker = null;
	TVListings.controls.datepicker.input = null;
	
	TVListings.controls.datepicker.init = function () {
		this.input = document.getElementById('dnitvl_datepicker_input');
		
		var trans = {}
		$("#datepicker-translations *").each(function () {
			var arr = TVListings.properties.get_info_from_string(this.className);
			if (arr.name) {
				trans[arr.name] = [];		
				for (var i=1,k=0;k=arr[i];i++) {
					trans[arr.name].push(k);
				}
			}
		});
		
		for (var i=trans.months.length-1,k;k=trans.months[i];i--) {
			DatePicker.monthArrayLong[i] = k;
			DatePicker.monthArrayMed[i] = k.slice(3);
			DatePicker.monthArrayShort[i] = k.slice(3);
		}
		
		if ((trans.days.length >= 6)&&(trans.days_short.length >= 6)) {
			DatePicker.dayArrayLong[0] = trans.days[6];
			DatePicker.dayArrayMed[0] = trans.days.pop(6);
			DatePicker.dayArrayShort[0] = trans.days_short.pop(6);
		}
		
		for (var i=0,k;k=trans.days[i];i++) {
			DatePicker.dayArrayLong[i+1] = k;
			DatePicker.dayArrayMed[i+1] = k;
			DatePicker.dayArrayShort[i+1] = trans.days_short[i];
		}
		
		
		var date_arr = TVListings.properties.get_info_from_string(this.input.className);
		var sarr = DatePicker.splitDateString(date_arr.date_range_start);
		var earr = DatePicker.splitDateString(date_arr.date_range_end);
		DatePicker.startDate = {
			'd': sarr[0],
			'm': sarr[1],
			'y': sarr[2]
		}
		
		DatePicker.endDate = {
			'd': earr[0],
			'm': earr[1],
			'y': earr[2]
		}
		
		DatePicker.displayDatePicker(TVListings.controls.datepicker.input.id,this);
		DatePicker.displayDatePicker(TVListings.controls.datepicker.input.id,this);
		
		this.picker = document.getElementById('dnitvl_datepicker');
		
		var dp_unfocus;
		
		var toggle_dp = function () {
			DatePicker.displayDatePicker(TVListings.controls.datepicker.input.id,this);
		}
		
		$(this.input).click(toggle_dp);
		
		$(document).click(function (event) {
			if (($(event.target).parents(TVListings.controls.datepicker.event_match).length == 0)
					&& (event.target.id !== TVListings.controls.datepicker.input.id)) {
				if ((TVListings.controls.datepicker.picker)
						  &&(TVListings.controls.datepicker.picker.style.display !== "none")) {
					toggle_dp();
				}
			}
		})
		

	}
	
	TVListings.controls.datepicker.open = function () {
		if ((this.picker)&&(this.picker.style.display == "none")) {
			DatePicker.displayDatePicker(TVListings.controls.datepicker.input.id,this.input)
		}
	}
	TVListings.controls.datepicker.close = function () {
		if ((this.picker)&&(this.picker.style.display !== "none")) {
			DatePicker.displayDatePicker(TVListings.controls.datepicker.input.id,this.input)
		}
	}
	
	
	
/* Retrieve Listings */

//Check if TVListings object already exists in the page
//if (!TVListings) var TVListings = {};
TVListings.request = {};

/**
 * Executes actions just before a request is made. To be used by the beforeSend option of $.ajax (JQuery)
 * @param {Object} data		Array of key-value pairs like those produced by TVListings.parameters.process_parameters()
 * @param {Object} request	XMLHttpRequest Object which is about to be sent	
 */
TVListings.request.sent = function(url, arr, request) {  //can be used for displaying a loading message etc.
	if (arr.type !== "widget") {
		var message = document.createElement("div");
		
		var image = new Image();
		image.src = TVListings.image_path + "/loading.gif";
		image.alt = "loading";
		image.width = "24";
		image.height = "24";
		
		var image_container = document.createElement("p");
		image_container.className = "listings-loading-image";
		image_container.appendChild(image);
		
		var title = document.createElement("h2");
		title.id = "listings-title";
		title.innerHTML = TVListings.appname;
		message.appendChild(title);
		
		message.appendChild(image_container);
		
		message.className = "listings-loading " + arr.type + "-view";
		message.id = "listings";
		var body = document.getElementById("dni-listings");
		body.innerHTML = "";
		body.appendChild(message);
	}
}


/**
 * If a request fails, this function can be called to handle it.
 * @param {Object} request		XMLHTTPRequest Object of the request which failed
 * @param {Object} status		Status Object
 * @param {Object} exception	Exception object if one was thrown
 * @param {Object} arr			Array of key-value pairs from the request like those produced by TVListings.parameters.process_parameters()
 */
TVListings.request.failed = function (response, status, exception, arr) {
	if (arr.type !== "widget") {
		var resp = response.responseXML.documentElement;
		
		var error_msg = document.createElement("p");
		error_msg.innerHTML = resp.textContent||resp.innerText;
		
		var error = document.createElement("div");
		
		var title = document.createElement("h2");
		title.id = "listings-title";
		title.innerHTML = TVListings.appname;
		
		error.appendChild(title);
		error.appendChild(error_msg);
		
		error.className = "listings-error-fatal " + arr.type + "-view";
		error.id = "listings";
		var body = document.getElementById("dni-listings");
		body.innerHTML = "";
		body.appendChild(error);
	}
}

/**
 * Requires JQuery. Makes a GET request to the specified url
 * containing data based on the TVListings.parameters.params objects
 * @param {String} url		URL to make GET request to
 * @param {Object} arr		Array of key-value pairs like those produced by TVListings.parameters.process_parameters()
 */
TVListings.request.get_listings = function (url, arr) {
	if (TVListings.style_suffix) arr.style += TVListings.style_suffix;
	var CASTGC;
	if (CASTGC = TVListings.properties
				.get_cookie_parameter("CASTGC", true)) {
		arr.CASTGC = CASTGC;
	}
	$.ajax({
		'url': url, 'data': arr,
		'dataType': "html",
		'type': "GET",
		'beforeSend': function(request){
			TVListings.request.sent(url, arr, request);
		},
		'success': function(data, status){
			TVListings.request.process_listings(data, status, arr);
		},
		'error': function(request, status, exception){
			TVListings.request.failed(request, status, exception, arr);
		}
	})
}

/**
 * After a successful request, this function can be called to handle the returned listings data.
 * @param {Object} response		Reponse object from requst
 * @param {Object} status		Status Object
 * @param {Object} arr			Array of key-value pairs used in the request
 */
TVListings.request.process_listings = function (response, status, arr) {
	if (arr.type !== "widget") { //Main tv listings application things
		var old = document.getElementById("listings-reminder");
		
		if (old) delete old; //remove old listings content
		var body = document.getElementById("dni-listings");

		body.innerHTML = response; //insert new listings content
		$("#listings").attr("class", arr.type + "-view");
		
		//enable reminder functionality
		TVListings.controls.reminders.init();
		
		//enable IE fixes
		TVListings.misc.ui.ie_fixes();
		
		//enable "rememebr my country"
		var remember = document.getElementById("listings-controls-remember");
		TVListings.controls.remember_country(remember);
		
		TVListings.reporting.exec_queue();
		//FACEBOOK INTEGRATION
		TVListings.facebook.init();
		view_specific_calls();
				
	} else {
		document.getElementById("dni-listings").innerHTML = response;
		$("#listings")[0].className = arr.type + "-view";
		view_specific_calls();
	}
	
	function view_specific_calls ()	 {
		switch(arr.type) { //things which need to be called specifically for each view
			case "day":
				//enable listings tabs
				TVListings.controls.apply_single_level(
					document.getElementById("listings-controls-tabs")
				);
			
				//enable shorter descriptions
				TVListings.misc.ui.elipsis_descriptions("1.25em");
				
				//enable controls
				var region_control = document.getElementById("listings-region-controls");
				var channel_control = document.getElementById("listings-channel-controls");
				
				if (region_control) {
					TVListings.controls.apply_multi_level(
						region_control
					);
				}
				
				if (channel_control) {
					TVListings.controls.apply_multi_level(
							channel_control
					);
				}
				TVListings.controls.datepicker.init();
				
				break;
			case "series":
				//enable listings tabs
				TVListings.controls.apply_single_level(
					document.getElementById("listings-controls-tabs"),
					{'page': true, 'series_id': true}
				);
				
				//enable series titlebar fix
				TVListings.misc.ui.series_title_fix();
				
				//enable shorter descriptions
				TVListings.misc.ui.elipsis_descriptions("1.25em");
				
				//enable controls
				var region_control = document.getElementById("listings-region-controls");
				var channel_control = document.getElementById("listings-channel-controls");
				
				if (region_control) {
					TVListings.controls.apply_multi_level(
							region_control,
						{'series_id': true, 'page': true}
					);
				}
				
				if (channel_control) {
					TVListings.controls.apply_multi_level(
							channel_control,
						{'series_id': true, 'page': true}
					);
				}
				
				TVListings.controls.apply_multi_level(
					document.getElementById("listings-series-controls"),
					{'page': true}, "400"
				);
				
				break;
			case "week":
				//enable listings tabs
				TVListings.controls.apply_single_level(
					document.getElementById("listings-controls-tabs"),
					{'time_slot': true}
				);
				
				//enable tooltips
				if (($.tooltip) && ($(window).height)) TVListings.misc.ui.week_tooltips();
				
				//enable controls
				var region_control = document.getElementById("listings-region-controls");
				var channel_control = document.getElementById("listings-channel-controls");
				
				if (region_control) {
					TVListings.controls.apply_multi_level(
						region_control,
						{'date': true, 'time_slot': true}
					);
				}
				
				if (channel_control) {
					TVListings.controls.apply_multi_level(
						channel_control
					);
				}
				TVListings.controls.apply_multi_level(
					document.getElementById("listings-time-controls")
				);
				
				TVListings.controls.datepicker.init();
				
				break;
			case "widget":
				//enable region control
				TVListings.controls.widget();
			
				//enable tooltips
				$("#tooltip .body dl").remove();
				if (($.tooltip) && ($(window).height)) TVListings.misc.ui.widget_tooltips();
				
				//enable shorter descriptions
				TVListings.misc.ui.elipsis_descriptions("2.8em", true);
				
				break;
		}
	}
}

/* Main listings-specific request creation functions */
TVListings.main = {}

/**
 * Object to stage a parameter key-value object and urls before making a request. Completely optional to use.
 */
if (!TVListings.main.stage) {
	TVListings.main.stage ={};
	TVListings.main.stage.url = document.location.href;
	TVListings.main.stage.params = TVListings.properties.process_parameters();
}

/**
 * Specific implementation of TVListings which makes a request for a listings view if the type key exists in the Array
 * @param {Object} arr		Array of key-value pairs used in the request. Must include 'type' for the request to go through
 */
TVListings.main.get_listings = function (arr) {

	if (!arr.type) arr.type = "day";
	TVListings.reporting.initReportingParams(arr);
	
	switch(arr.type) {
		case "day": this.get_day_listings(arr);
			break;
		case "week": this.get_week_listings(arr);
			break;
		case "series": this.get_series_listings(arr);
			break;
		case "widget": this.get_widget_listings(arr);
			break;
	}
}

TVListings.main.get_week_listings = function (arr) {
	
	var url = TVListings.servlet_address + "GetScheduleByWeek";
	arr.style = "week";

	if (!arr.date) {
		var time = new Date();
		var date = TVListings.misc.date.pad_number(time.getDate().toString(), 2);
		var month = time.getMonth()+1;
		month = TVListings.misc.date.pad_number(month.toString(), 2)
		var year = time.getFullYear().toString();
		var f_date =  date + month + year;
		
		arr.date = f_date;	
		TVListings.main.stage.params.date = {'value': f_date};
		TVListings.main.stage.url = TVListings.properties.change_url_parameter(
			'date',
			date + month + year,
			TVListings.main.stage.url
		)
	}
	if (!arr.time_slot) {
		var time_slot = TVListings.misc.date.generate_offset_time();
		
		arr.time_slot = time_slot;
		TVListings.main.stage.params.time_slot = {'value': f_date};
		TVListings.main.stage.url = TVListings.properties.change_url_parameter(
			'time_slot',
			time_slot,
			TVListings.main.stage.url
		)
	}
	
	//work out current time slot here if not specified
	TVListings.request.get_listings(url, arr);
	
}

TVListings.main.get_day_listings = function (arr) {
	
	var url = TVListings.servlet_address + "GetScheduleByBroadcastDate";
	arr.style = "day";
	
	if (!arr.date) {
		
		var time = TVListings.misc.date.generate_request_date(
			TVListings.main.stage.params.ss
		);
		
		var date = time.getDate();

		var month = time.getMonth()+1;
		var year = time.getFullYear().toString();

		date = TVListings.misc.date.pad_number(date.toString(), 2);
		month = TVListings.misc.date.pad_number(month.toString(), 2);
		
		arr.date =  date + month + year;
	}
	
	TVListings.request.get_listings(url, arr);
	
}

TVListings.main.get_series_listings = function (arr) {
	
	var url = TVListings.servlet_address + "GetSeriesSchedule";
	arr.style = "series";
	
	if (arr.series_id) {
		//work out current broadcast day and time here if not specified
		TVListings.request.get_listings(url, arr);
	}
	
}

TVListings.main.get_widget_listings = function (arr) {
	
	var url = TVListings.servlet_address + "GetScheduleByTime";
	arr.style = "widget";
	if (!arr.date_time) {
		
		//Get Date and Time information
		var time = new Date();
		var date = time.getDate();
		var month = time.getMonth()+1;
		var year = time.getFullYear();
		var hours = time.getHours();
		var minutes = time.getMinutes();
		
		//Add zero padding to numbers
		date = TVListings.misc.date.pad_number(date.toString(), 2)
		month = TVListings.misc.date.pad_number(month.toString(), 2)
		hours = TVListings.misc.date.pad_number(hours.toString(), 2)
		minutes = TVListings.misc.date.pad_number(minutes.toString(), 2)
		
		//Update properties
		arr.date_time =  date + month + year + hours + minutes;
	}
	
	TVListings.request.get_listings(url, arr);
	
}

/**
 * TV Listings configurations. Call these to activate listings application modes.
 */
TVListings.init = {};

TVListings.init.main = function () { //main tv listings application page
	
	var geocode = TVListings.misc.get_geocode();
	if (geocode) {
		TVListings.properties.defaults['geocode'] = {'value': geocode};
	}
	
	$(document).ready(function(){
		
		TVListings.properties.add_parameter('type');
		TVListings.properties.add_parameter('geocode');
		TVListings.properties.add_parameter('country_code');
		TVListings.properties.add_parameter('channel_code');
		TVListings.properties.add_parameter('series_id');
		TVListings.properties.add_parameter('time_slot');
		TVListings.properties.add_parameter('page');
		TVListings.properties.add_parameter('date');
		
		var settings = TVListings.properties.process_parameters();
		
		TVListings.main.get_listings(settings);
		
	});
	
}

TVListings.init.widget = function () { //"what's on now" widget

		TVListings.properties.add_parameter('type');
		TVListings.properties.add_parameter('geocode');
		TVListings.properties.add_parameter('country_code');
		TVListings.properties.add_parameter('channel_code');
		TVListings.properties.add_parameter('date_time');
			
		TVListings.properties.defaults['type'] = {'value': "widget", "override": true};
		
		var settings = TVListings.properties.process_parameters();
		TVListings.main.get_listings(settings);

}

/***/


/* Other Functionality */
TVListings.misc = {};

	/* Reporting Functionality */
	TVListings.reporting = { params:{} }
	TVListings.reporting.func_queue = []
	
		/**
		 * Returns a Dictionary List of information about the current listings page
		 * 
		 */
		
		/* Example
	
			var listings_info = TVListings.misc.get_page_info();
			
			var info = {
				country: listings_info.country,
				channel: listings_info.channel,
				page: listings_info.page,
				series: listings_info.series
			}
	
		 */
		TVListings.reporting.get_page_info = function () {
			
			return this.params;
		}
		
		TVListings.reporting.initReportingParams = function(arr) {
			
			var params = this.params = {}
			params.pageType = arr.type;
			params.pageTitle = $("#listings-controls-tabs .current").text();
			params.country = $("#listings-region-controls .current").text();
			params.channel = $("#listings-channel-controls .current").text();		
			params.series = $("#listings-series-controls .current").text();
			params.series_id = arr.series_id || null;
		}
		
		TVListings.reporting.exec_queue = function () {
			for (var i=this.func_queue.length-1,k;k=this.func_queue[i];i--) {
				k();
			}
		}
	
		
		
	/* Akamai Geo-targeting */
		
		TVListings.misc.get_geocode = function () {
			/*  */ var geocode = "US"; //
			if (geocode.indexOf("$") == -1) {
				TVListings.misc.get_geocode = function() {
					return geocode;
				}
			} else {
				TVListings.misc.get_geocode = function () { return false }
			}
		}

		
		
		
	/* General Date and time functions */
	TVListings.misc.date = {}
	
		/**
		Convert a string to a JavaScript Date object.
		*/
		TVListings.misc.date.get_date_object = function (dateString, timeString, dateFormat) {
			var dateVal, dArray, d, m, y;	 
			if (!dateFormat) var dateFormat = " ";
			try {
				dArray = this.split_date_string(dateString);				
				if (dArray) {
				  switch (dateFormat) {
					case "dmy" :
					  d = parseInt(dArray[0], 10);
					  m = parseInt(dArray[1], 10) - 1;
					  y = parseInt(dArray[2], 10);
					  break;
					case "ymd" :
					  d = parseInt(dArray[2], 10);
					  m = parseInt(dArray[1], 10) - 1;
					  y = parseInt(dArray[0], 10);
					  break;
					case "mdy" :
					default :
					  d = parseInt(dArray[0], 10);
					  m = parseInt(dArray[1], 10) - 1;
					  y = parseInt(dArray[2], 10);
					  break;
				  }
				  
				  if (timeString) {
				  	var tArray = [timeString.substring(0, 2), timeString.substring(2, 4)];
				  	dateVal = new Date(y, m, d, tArray[0], tArray[1]);
				  } else {
				  	dateVal = new Date(y, m, d);
				  }
				} else if (dateString) {
				  dateVal = new Date(dateString);
				} else {
				  dateVal = new Date();
				}
			} catch(e) {
				dateVal = new Date();
			}
			return dateVal;
		}
		
		/**
		Try to split a date string into an array of elements, using common date separators.
		If the date is split, an array is returned; otherwise, we just return false.
		*/
		TVListings.misc.date.split_date_string = function (dateString) {
			var dArray;
			if (dateString.indexOf(" / ") >= 0)
				dArray = dateString.split(" / ");
			else if (dateString.indexOf(".") >= 0)
				dArray = dateString.split(".");
			else if (dateString.indexOf("-") >= 0)
				dArray = dateString.split("-");
			else if (dateString.indexOf("\\") >= 0)
				dArray = dateString.split("\\");
			else
				dArray = [dateString.substring(0,2), dateString.substring(2,4), dateString.substring(4,8)];
				
			return dArray;
		}


		/**
		 * Takes an offset in seconds (found in the XML now)
		 * and returns a JS date object set to the correct schedule
		 * date.
		*/
		TVListings.misc.date.generate_request_date = function (offset) {
		    var now = new Date();
		
		    if (this.get_day_in_seconds(now) < offset) {
		        return (new Date(now.getTime() - 86000000));
		    } else {
		        return now;
		    }
		
		}
		
		/**
		 * Takes a date and returns the number of seconds that
		 * have expired on that dates current day
		 */
		TVListings.misc.date.get_day_in_seconds = function (date) {
		    return date.getTime() % 86000;
		}
	
		/**
		 * Generates an offset time period from the current local time
		 */
		TVListings.misc.date.generate_offset_time = function () {
		    var now = new Date().getHours();
		    return now - (now % 2);
		}
	
		/**
		 * Adds zero-padding to a number.
		 * @param {String}  num		Number to pad as a string
		 * @param {Integer} pad		Amount of characters to pad until
		 */
		TVListings.misc.date.pad_number = function(num, pad){
			var padding = "";
			for (var i=0;i<pad-num.length;i++) { padding += "0";}
			num = (padding + num).slice("-" + pad);
			return num;
		}
	
	/* Random UI functions */
	TVListings.misc.ui = {}
	
		
		/* IE Fixes */
		/**
		 * Requires JQuery
		 */
		TVListings.misc.ui.ie_fixes = function () {
			var pctrl = $("ul.listings-page-controls>li.pagination>ol")
			pctrl.children("li:first-child").addClass("first-child");
			pctrl.children("li:first-child").addClass("last-child");
			
			$(".week-view table.listings-results tbody tr").each(function () {
				$(this).children("td:first").addClass("first-child");
				$(this).children("td:last").addClass("last-child");
			});
		}
	
		/* Tooltips */
		/**
		 * Implementation Specific. Initialise Weekly Listings tooltips.
		 */
		TVListings.misc.ui.week_tooltips = function () {
			var programme = $(".week-view table.listings-results tbody td.listings-programme:not(.listings-filler)");
			programme.each( function () {
				var self = $(this);
				var tooltip = TVListings.misc.ui.create_tooltip(
					self.children("dl").children("dt").text(),
					self.children("dl").children("dd.start-time").html(),
					self.children("dl").children("dd.description").text(),
					"week"
				)
				TVListings.misc.ui.attach_tooltip(this, tooltip, "week");

				self.hover(
					function(){ self.addClass("hover")},
					function(){ self.removeClass("hover")}
				)
			});
		}
		
		/**
		 * Implementation Specific. Initialise Widget Listings tooltips.
		 */
		TVListings.misc.ui.widget_tooltips = function () {
			var programme = $(".widget-view table.listings-widget-results tbody td.listings-programme").not(":has(.listings-error-nodata)");
			programme.each(function(){
				var self = $(this);
				var tooltip = TVListings.misc.ui.create_tooltip(
					self.children("dl").children("dt").text(),
					self.children("dl").children("dd.start-time").html(),
					self.children("dl").children("dd.description").text(),
					"widget"
				)
				TVListings.misc.ui.attach_tooltip(this, tooltip, "widget");
			});
		}
		
		/**
		 * Requires JQuery. Creates a basic TV Listings style tooltip element.
		 * @param {Object} title	DOM Element or text node to use as title
		 * @param {Object} stitle	DOM Element or text node to use as sub-title
		 * @param {Object} content	DOM Element or text node to use as content
		 */
		TVListings.misc.ui.create_tooltip = function(title, stitle, content, type){
			var elem = $(document.createElement("dl"));
			var i1 = $(document.createElement("dt"));
			var i2 = $(document.createElement("dd")).addClass("sub-title");
			var title = i1.append($(document.createElement("strong")).append(title));
			var stitle = i2.append(stitle);
			elem.append(title).append(stitle);
			
			var blank = RegExp('^( |)$');
			
			if ((!content.match(blank))&&(content !== null)) {
				var i3 = $(document.createElement("dd")).addClass("content");
				var content = i3.append(content);
				elem.append(content);
			}
			elem.addClass("js_tooltip");
			if (type) elem.addClass(type);
			return elem;
		}
		
		/**
		 * Requires JQuery. Requires Jquery Tooltip plugin. Creates a semi-opaque tooltip from 'tooltip' attached to 'elem'.
		 * @param {Object} elem		DOM Element to attach tooltip to
		 * @param {Object} tooltip	DOM Element to use as tooltip
		 */	
		TVListings.misc.ui.attach_tooltip = function (elem, tooltip, type) {
			if (!type) var type = " generic";
			
			var elem = $(elem);
			elem.tooltip({
				track: true,
				delay: 400,
				extraClass: "js_tooltip " + type,
				bodyHandler: function(){
				return tooltip;
				}
			});
			elem.mouseover(function () { $("#tooltip").css("opacity", ".95")})
		}
		
		/* Programme Descriptions */
		/**
		 * Requires JQuery. Implementation specific.
		 */
		TVListings.misc.ui.elipsis_descriptions = function (height, no_toggle) {
			if ($) {
				var series_titles = $("td.listings-programme dl dt");
				var descriptions = $(".listings-programme .description");
				descriptions.each(function () { $(this).wrapInner(document.createElement("span"))});
				descriptions = descriptions.children("span");
			}
			if (series_titles) {
				if (descriptions) { descriptions.each(function(){ var t = function(elem){
					var parent = $(elem).parent();
					if (parent.height()) {
						parent.css("height", height); //set the description dd
						elem.fullHeight = elem.offsetHeight;
						if (parent.height() < elem.fullHeight) {
							//if the full description is long than 1 line, add a toggle button and shorten
							TVListings.misc.ui.toggle_elipsis(elem);
							if (!no_toggle) {
								var link = document.createElement('a');
								link.className = "desc_toggle";
								if ($.browser["msie"]) { $(link).css("zoom", "1");}
								$(link).click(function(){ TVListings.misc.ui.toggle_button(this);});
								parent.siblings("dt").append(link);
							}
						} else { //set the height back
							parent.css("height", "auto");
						}
					}};t(this);})
				}
			}
		}
		
		/**
		 * Figures out how many characters can fit on one line and removes the excess
		 * @param {Object} elem		Element directly containing text to cut down
		 * @param {Object} cut		Amount of characters to remove at a time
		 */
		TVListings.misc.ui.cut_description = function (elem) {
			var len = Math.ceil(elem.offsetWidth / ($.jqem.current()/2.7));
			elem.innerHTML = elem.innerHTML.substring(0, len-4);
			var elipsis = document.createElement("span");
			elipsis.className = "elipsis";
			elipsis.innerHTML = " &hellip;";
			elem.appendChild(elipsis);
		}
		
		/**
		 * Implementation specific.
		 */
		TVListings.misc.ui.reset_description = function (elem) { 
			if (elem.fullText) { elem.innerHTML = elem.fullText; elem.fullText = null;}
		}	
		
		/**
		 * Implementation specific.
		 */
		TVListings.misc.ui.toggle_elipsis = function (elem) {
			if (elem) {
				if (elem.fullText == null) { elem.fullText = elem.innerHTML; this.cut_description(elem);
				} else { this.reset_description(elem)}
			}
		}
		
		/**
		 * Requires JQuery. Implementation specific.
		 */
		TVListings.misc.ui.toggle_button = function (elem) {
			//Get relevant jQuery elements
			var parent = $(elem).parent().parent().parent().parent();
			var desc = $(elem).parent().siblings(".description");
			var desc_inner = desc.children("span")[0];
			
			//Check for show/hide classes
			var regex = RegExp('(^|.* )(expanded)($| .*)');
			var match = parent[0].className.match(regex);
			
			if ((match !== null)) {
				// if expanded, hide description and animate
				parent.toggleClass("expanded");
				desc.animate({height: "1.25em"}, 150, "linear",
					function () {
						TVListings.misc.ui.toggle_elipsis(desc_inner);
					}
				);
			} else {
				// if hidden, show description and animate
				var full_height = desc_inner.fullHeight;
				parent.toggleClass("expanded");
				TVListings.misc.ui.toggle_elipsis(desc_inner);
				desc.animate({height: full_height}, 250, "linear");
				
			}
		}
		
		/**
		 * Requires JQuery. Implementation Specific.
		 */
		TVListings.misc.ui.series_title_fix = function () {
			var title_height = $(".series-view table.listings-results thead th.title span").height();
			var ctrls = $(".series-view #listings-page-controls-top");
			var ctrls_height = ctrls.height();
			var padding = Math.ceil((title_height - Math.ceil(ctrls_height/1.2))) + 6;
			ctrls.css("padding-bottom", padding + "px");
		}
		
/**
 * FACEBOOK FEED
 */
TVListings.facebook = {};
TVListings.facebook.init = function(){
	
	if ((!window.$TORA)||(!$TORA("USER").getAuthInfo("LOGGED_IN_ON_FB"))) {
		return;
	}
	
	var facebookFeedConfig = $("#facebook-feed-configuration *");
	if(facebookFeedConfig.length == 0){
		//NO FACEBOOK INFO - DO NOTHING
		return;
	}
	var facebookInfo = {};
    facebookFeedConfig.each(function () {
		var info = TVListings.properties.get_info_from_string(this.value,"%%");
		facebookInfo[this.className] = info;		
    });
	
	TVListings.facebook.facebookInfo = facebookInfo;
	TVListings.facebook.feed_link();
}

TVListings.facebook.feed_link = function () {		
	function createLinkElement(HAS_AIRED, programmeElement, programmeDate){
		var elem = document.createElement("dd");
		elem.className = "facebook-link-item";
		
		var img = document.createElement("img");
	    img.setAttribute("src", "/resources/ua/images/ua_fb_login_logo.gif");
		
		var facebookInfo = TVListings.facebook.facebookInfo;
		var feedText = facebookInfo.feedLink.textBeforePresent;
		if(HAS_AIRED){
			feedText = facebookInfo.feedLink.textBeforePast;
		}
		var text = document.createTextNode(" " + feedText + " ");
						
		var link = document.createElement("a");
		link.setAttribute("href", "#");
		link.onclick = function(){
			var _HAS_AIRED = HAS_AIRED;
			var _programmeElement = programmeElement;
			var _programmeDate = programmeDate;
			return TVListings.facebook.feed_link_onClick(_HAS_AIRED, _programmeElement, _programmeDate);
		    };
		link.innerHTML = facebookInfo.feedLink.linkText;
		
		elem.appendChild(img);
		elem.appendChild(text);
		elem.appendChild(link);
		
		return elem;
	}
	
	function getTime(className){
       var itemDatetime = TVListings.properties.get_info_from_string(className).datetime;
	   var itemDatetimeObj = TVListings.misc.date.get_date_object(
	                            itemDatetime.substring(0,8),
	                            itemDatetime.substring(8)
	                            );
	   return itemDatetimeObj;
	}
	
	var now = new Date();
	
	//FOR EVERY ROW WITH PROGRAMMES
	$(".listings-results tbody tr").each(function(){
		//FIND THE PROGRAMME TIME
		$(".listings-programme", this).each(function(){
			var className = this.className;
			if(className.indexOf("datetime") == -1){
				//NOT A VALID ROW
				return;
			}
			var itemDatetime = getTime(className);
			var HAS_AIRED = true;
			if(itemDatetime > now){
				HAS_AIRED = false;
			}
			//APPEND FACEBOOK FEED LINK
			$("dl", this).after(createLinkElement(HAS_AIRED, this, itemDatetime));
	    });
	});
}
TVListings.facebook.feed_link_onClick = function (HAS_AIRED, programmeElement, programmeDate) {
	var requestProperties = TVListings.properties.process_parameters();
	var facebookInfo = TVListings.facebook.facebookInfo;
	var pad = TVListings.misc.date.pad_number
	
	function toString(element){
		if(!element){
			return "";
		}
		var dummy = document.createElement("div");
		dummy.appendChild(element);
		return dummy.innerHTML;
	}
	
	function formatDate(date){
		return [pad(date.getDate()+"",2),pad((date.getMonth() + 1)+"",2),1900 + date.getYear()].join("/");
	}
	
	function formatTime(date){
		return [pad(date.getHours()+"", 2),pad(date.getMinutes()+"",2)].join(":");
	}
	
	function getChannelPart(){
		return facebookInfo.site.description;
	}
	
	function getShowPart(programmeElement, seriesViewLink){
		if(requestProperties.type != "week"){
			programmeElement = $(programmeElement).parent();
		}
		
		//GET SHOW NAME FROM HTML
		var showPart = $("dl dt",programmeElement).text();
		//SHOW LINK DEFAULTS TO SERIES VIEW
		var showLink = seriesViewLink;
		
		if(!showPart || showPart.length == 0){
			//GET EPISODE TITLE NAME
			showPart = $("dl dd.episode-title",programmeElement).text();
		}
		//GET PROMO LINK IF ANY
		$("ul.programme_promo_links li a:first",programmeElement).each(function(){
			//IF THERE IS A PROMO => LINK TO IT
			showLink = this.href;
		});
		
		var link = document.createElement("a");
		link.setAttribute("href",showLink);
		link.innerHTML =  showPart;
		return {value: showPart,
		        link:  showLink};
	}
	
	function getSeriesViewLink(programmeElement){
		//DEFAULTS TO CURRENT PAGE (IF SERIES VIEW)
		var href = window.location;
		if(requestProperties.type != "series"){
			//GET LINK TO SERIES VIEW FROM HTML
			href = $("dl dt a:first",programmeElement)[0].href;
		}
		return href;
	}
	
	function createBody(programmeElement, programmeDate, lineText){
		
		return lineText
				.replace("${date}",formatDate(programmeDate))
				.replace("${time}",formatTime(programmeDate));
	}
	
	function createImage(programmeElement, imageLink){
		var imageSrc = facebookInfo.site.logo;
		if(requestProperties.type != "series"){
			//ANY OTHER VIEW
			$("dd.promo-image img",programmeElement).each(function(){
				//THERE IS ONLY ONE
				imageSrc = this.src;	
			});
		}else{
			//FOR SERIES VIEW
			$("table thead th.title img").each(function(){
				//THERE IS ONLY ONE
				imageSrc = this.src;
			})
		}
		
		return {url: imageSrc,
		        target: imageLink};
	}

	function process(HAS_AIRED, programmeElement, programmeDate){
		var seriesViewLink = getSeriesViewLink(programmeElement);
		var showPart = getShowPart(programmeElement, seriesViewLink);
		var channelPart = getChannelPart();
		
		var caption = facebookInfo.feedContent.headingPresent;
		var line1Text = facebookInfo.feedContent.line1PresentText;
		var line1LinkText = facebookInfo.feedContent.line1PresentLink;
		if(HAS_AIRED){
			caption = facebookInfo.feedContent.headingPast;
			line1Text = facebookInfo.feedContent.line1PastText;
			line1LinkText = facebookInfo.feedContent.line1PastLink;
		}
		caption = caption
				.replace("${show}",showPart.value)
				.replace("${channel}",channelPart);
		
		var bodyString = createBody(programmeElement, programmeDate, line1Text);
		
		var feedContent = {
			title: showPart.value,
			url: showPart.link,
			caption: "%%USER%% " + caption + ".",
			body: bodyString + ".",
			images: [createImage(programmeElement,showPart.link)],
			actions: [{type: line1LinkText,
					text: showPart.value, url: seriesViewLink}]
		};
		$TORA("USER").publishStory(feedContent);
		return false;
	}
	
	TVListings.facebook.feed_link_onClick =  process;
	
	return process(HAS_AIRED, programmeElement, programmeDate);
}

	
/* ***** DATEPICKER FROM HERE ****** */
	
		
// $Id: this.js 5899 2007-03-19 17:38:37Z bashford $

var DatePicker = {
	name: "DatePicker",
	datePickerDivID: "dnitvl_datepicker",
	iFrameDivID: "dnitvl_datepicker_iframe",
	
	dayArrayShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
	dayArrayMed: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
	dayArrayLong: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	monthArrayShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
	monthArrayMed: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
	monthArrayLong: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	defaultDateSeparator: " / ",        // common values would be "/" or "."
	defaultDateFormat: "dmy",        // valid values are "mdy", "dmy", and "ymd"
		
	displayDatePicker: function (dateFieldName, displayBelowThisObject, dtFormat, dtSep) {
		this.targetDateField = document.getElementsByName(dateFieldName).item(0);
		if (this.targetDateField == null) {
			this.targetDateField = document.getElementById(dateFieldName);
		}
		
		// if we weren't told what node to display the datepicker beneath, just display it
		// beneath the date field we're updating
		if (!displayBelowThisObject)
			this.displayBelowThisObject = this.targetDateField;
		else
			this.displayBelowThisObject = displayBelowThisObject;
		
		
		
		// if a date separator character was given, update the dateSeparator variable
		if (dtSep)
			this.dateSeparator = dtSep;
		else
			this.dateSeparator = this.defaultDateSeparator;
		
		// if a date format was given, update the dateFormat variable
		if (dtFormat)
			this.dateFormat = dtFormat;
		else
			this.dateFormat = this.defaultDateFormat;
		
		this.drawDatePicker(this.targetDateField);
	},
	
	drawDatePicker: function (targetDateField, x, y) {
	  var dt = this.getFieldDate(targetDateField.value );
	 
		if (!document.getElementById(this.datePickerDivID)) {
			var newNode = document.createElement("div");
			newNode.setAttribute("id", this.datePickerDivID);
			newNode.setAttribute("class", "dpDiv");
			newNode.setAttribute("style", "visibility: hidden;");
			document.body.appendChild(newNode);
		}
	 
	  // move the datepicker div to the proper x,y coordinate and toggle the visiblity
	  var pickerDiv = document.getElementById(this.datePickerDivID);
	  pickerDiv.style.position = "absolute";
	  pickerDiv.style.visibility = (pickerDiv.style.visibility == "visible" ? "hidden" : "visible");
	  pickerDiv.style.display = (pickerDiv.style.display == "block" ? "none" : "block");
	  pickerDiv.style.left = ($(this.targetDateField).offset().left) + 'px';
	  pickerDiv.style.top = ($(this.targetDateField).offset().top + 20) + 'px';
	  pickerDiv.style.zIndex = 10000;
	 
	  // draw the datepicker table
	  this.refreshDatePicker(targetDateField.name, dt.getFullYear(), dt.getMonth(), dt.getDate());
	},
	
	/**
	This is the function that actually draws the datepicker calendar.
	*/
	refreshDatePicker: function (dateFieldName, year, month, day) {
	  // if no arguments are passed, use today's date; otherwise, month and year
	  // are required (if a day is passed, it will be highlighted later)
		var thisDay = new Date();
		
		//Some TV Listings Alterations 
		var hideForwardButton = false;
		var hideBackButton = false;
 
		if ((year >= this.startDate["y"])) {
			if ((year == this.startDate["y"]) && (month == this.startDate["m"]-1)) 
				var startDay = this.startDate["d"];
			else 
				var startDay = -100;
		} else {
				var startDay = this.startDate["d"];
				day = this.startDate["d"];
				month = this.startDate["m"]-1;
				year = this.startDate["y"];
				document.getElementsByName(dateFieldName)[0].value = this.getDateString(new Date(year, month, day));
		}
		
		if ((year <= this.endDate["y"])) {
			if ((year == this.endDate["y"]) && (month == this.endDate["m"]-1)) 
				var endDay = this.endDate["d"];
			else 
				var endDay = 100;
		} else {
				var endDay = this.endDate["d"];
				day = this.endDate["d"];
				month = this.endDate["m"]-1;
				year = this.endDate["y"];
				document.getElementsByName(dateFieldName)[0].value = this.getDateString(new Date(year, month, day));
		}
		
		if ((month >= 0) && (year > 0)) {
			thisDay = new Date(year, month, 1);
		} else {
			day = thisDay.getDate();
			thisDay.setDate(1);
		}
		
		if ((year==this.startDate["y"]) && (month)==this.startDate["m"]-1) { hideBackButton = true;}
		if ((year==this.endDate["y"]) && (month)==this.endDate["m"]-1) { hideForwardButton = true;}
	
		
		// the calendar will be drawn as a table
		// you can customize the table elements with a global CSS style sheet,
		// or by hardcoding style and formatting elements below
		var crlf = "\r\n";
		var TABLE = "<table cols=7 class='dpTable'>" + crlf;
		var xTABLE = "</table>" + crlf;
		var TR = "<tr class='dpTR'>";
		var TR_title = "<tr class='dpTitleTR'>";
		var TR_days = "<tr class='dpDayTR'>";
		var TR_todaybutton = "<tr class='dpTodayButtonTR'>";
		var xTR = "</tr>" + crlf;
		var TD = "<td class='dpTD' onMouseOut='this.className=\"dpTD\";' onMouseOver=' this.className=\"dpTD dpTDHover\";' ";
		var TD_title = "<div class='dpTitleTD'>";
		var TD_buttons = "<span class='dpButtonTD'>";
		var TD_todaybutton = "<td colspan=7 class='dpTodayButtonTD'>";
		var TD_days = "<td class='dpDayTD'>";
		var TD_selected = "<td class='dpTD dpDayHighlightTD' onMouseOut='this.className=\"dpTD dpDayHighlightTD\";' onMouseOver='this.className=\"dpTD dpDayHighlightTD dpTDHover\";' ";    // leave this tag open, because we'll be adding an onClick event
		var xTD = "</td>" + crlf;
		var DIV_title = "<div class='dpTitleText'>";
		var DIV_selected = "<div class='dpDayHighlight'>";
		var xDIV = "</div>";
		
		var TR_title = TR_title + "<td class='dpTitle' colspan='7'>";
		var TD_active = "<td class='dpTD active' onMouseOut='this.className=\"dpTD active\";' onMouseOver=' this.className=\"dpTD dpTDHover active\";' ";
		var TD_blank = "<td>"
		var TD_selected_active = "<td class='dpTD active dpDayHighlightTD' onMouseOut='this.className=\"dpTD active dpDayHighlightTD\";' onMouseOver='this.className=\"dpTD active dpTDHover\";' ";
		var TD_buttons_bck = "<span class='dpButtonTD back'>";
		var TD_buttons_fwd = "<span class='dpButtonTD forward'>";
		var SPAN = "<span>";
		var xSPAN = "</span>" + crlf;
		
		// start generating the code for the calendar table
		var html = TABLE;
	 
		// this is the title bar, which displays the month and the buttons to
		// go back to a previous month or forward to the next month
		html += TR_title;
		if (!hideBackButton) html += TD_buttons_bck + this.getButtonCode(dateFieldName, thisDay, -1, "&lt;") + xSPAN;
		html += TD_title + DIV_title + this.monthArrayLong[ thisDay.getMonth()] + " " + thisDay.getFullYear() + xDIV + xDIV;
		if (!hideForwardButton) html += TD_buttons_fwd + SPAN + this.getButtonCode(dateFieldName, thisDay, 1, "&gt;") + xSPAN;
		html += xTR;
	 
		// this is the row that indicates which day of the week we're on
		html += TR_days;
		for (i=0;i<this.dayArrayShort.length;i++)
			html += TD_days + SPAN + this.dayArrayShort[i] + xSPAN + xTD;
			html += xTR;
	 
		// now we'll start populating the table with days of the month
		html += TR;
	 
		// first, the leading blanks
		for (i = 0; i < thisDay.getDay(); i++)
			html += TD_blank + SPAN + "&nbsp;" + xSPAN + xTD;
	 
		// now, the days of the month
		do {
			var dayNum = thisDay.getDate();
			
			var TD_onclick_active = " onclick=\"" + this.name + ".updateDateField('" + dateFieldName + "', '" + this.getDateString(thisDay) + "');\">";			
			var TD_onclick = " onclick=\"return false;\">";			
			
			if ((startDay<=dayNum)&&(dayNum<=endDay)) {
				var TD_ = TD_active;
				var TD_selected_ = TD_selected_active;
				var TD_onclick_ = TD_onclick_active;
			} else {
				var TD_ = TD;
				var TD_selected_ = TD_selected;
				var TD_onclick_ = TD_onclick;
			}
			
			if (dayNum == day)
			  html += TD_selected_ + TD_onclick_ + SPAN + DIV_selected + dayNum + xDIV + xSPAN + xTD;
			else
			  html += TD_ + TD_onclick_ + SPAN + dayNum + xSPAN + xTD;
			// if this is a Saturday, start a new row
			if (thisDay.getDay() == 6)
			  html += xTR + TR;
			
			// increment the day
			thisDay.setDate(thisDay.getDate() + 1);
		} while (thisDay.getDate() > 1)
	 
		// fill in any trailing blanks
		if (thisDay.getDay() > 0) {
			for (i = 6; i >= thisDay.getDay(); i--)
			  html += TD_blank + SPAN + "&nbsp;" + xSPAN + xTD;
		}
		html += xTR;
	 
		// add a button to allow the user to easily return to today, or close the calendar
		var today = new Date();
		var todayString = "Today is " + this.dayArrayMed[today.getDay()] + ", " + this.monthArrayMed[ today.getMonth()] + " " + today.getDate();
		html += TR_todaybutton + TD_todaybutton;
		html += "<button class='dpTodayButton' onClick='" + this.name + ".refreshDatePicker(\"" + dateFieldName + "\");'>this month</button> ";
		html += "<button class='dpTodayButton' onClick='" + this.name + ".updateDateField(\"" + dateFieldName + "\");'>close</button>";
		html += xTD + xTR;
	 
		// and finally, close the table
		html += xTABLE;
	 
		document.getElementById(this.datePickerDivID).innerHTML = html;
		// add an "iFrame shim" to allow the datepicker to display above selection lists
	},

	getButtonCode: function (dateFieldName, dateVal, adjust, label) {
	  var newMonth = (dateVal.getMonth () + adjust) % 12;
	  var newYear = dateVal.getFullYear() + parseInt((dateVal.getMonth() + adjust) / 12);
	  if (newMonth < 0) {
		newMonth += 12;
		newYear += -1;
	  }
	  return "<button class='dpButton' onClick='" + this.name + ".refreshDatePicker(\"" + dateFieldName + "\", " + newYear + ", " + newMonth + ");'>" + label + "</button>";
	},
	
	getDateString: function (dateVal) {
		var dayString = "00" + dateVal.getDate();
		var monthString = "00" + (dateVal.getMonth()+1);
		dayString = dayString.substring(dayString.length - 2);
		monthString = monthString.substring(monthString.length - 2);
	 
		switch (this.dateFormat) {
			case "dmy" :
			  return dayString + this.dateSeparator + monthString + this.dateSeparator + dateVal.getFullYear();
			case "ymd" :
			  return dateVal.getFullYear() + this.dateSeparator + monthString + this.dateSeparator + dayString;
			case "mdy" :
			default :
			  return monthString + this.dateSeparator + dayString + this.dateSeparator + dateVal.getFullYear();
		}
	},
	
	/**
	Convert a string to a JavaScript Date object.
	*/
	getFieldDate: function (dateString) {
		var dateVal, dArray, d, m, y;	 
		try {
			dArray = this.splitDateString(dateString);
			if (dArray) {
			  switch (this.dateFormat) {
				case "dmy" :
				  d = parseInt(dArray[0], 10);
				  m = parseInt(dArray[1], 10) - 1;
				  y = parseInt(dArray[2], 10);
				  break;
				case "ymd" :
				  d = parseInt(dArray[2], 10);
				  m = parseInt(dArray[1], 10) - 1;
				  y = parseInt(dArray[0], 10);
				  break;
				case "mdy" :
				default :
				  d = parseInt(dArray[1], 10);
				  m = parseInt(dArray[0], 10) - 1;
				  y = parseInt(dArray[2], 10);
				  break;
			  }
			  dateVal = new Date(y, m, d);
			} else if (dateString) {
			  dateVal = new Date(dateString);
			} else {
			  dateVal = new Date();
			}
		} catch(e) {
			dateVal = new Date();
		}
		return dateVal;
	},
		
	/**
	Try to split a date string into an array of elements, using common date separators.
	If the date is split, an array is returned; otherwise, we just return false.
	*/
	splitDateString: function (dateString) {
		var dArray;
		if (dateString.indexOf(" / ") >= 0)
			dArray = dateString.split(" / ");
		else if (dateString.indexOf(".") >= 0)
			dArray = dateString.split(".");
		else if (dateString.indexOf("-") >= 0)
			dArray = dateString.split("-");
		else if (dateString.indexOf("\\") >= 0)
			dArray = dateString.split("\\");
		else
			dArray = [dateString.substring(0,2), dateString.substring(2,4), dateString.substring(4,8)];
			
		return dArray;
	},
	
	updateDateField: function (dateFieldName, dateString) {
		var targetDateField = document.getElementsByName(dateFieldName).item(0);
		if (targetDateField == null) {
			targetDateField = document.getElementById(dateFieldName);
		}
		if (dateString) targetDateField.value = dateString;
	 	
		
		var pickerDiv = document.getElementById(this.datePickerDivID);
		pickerDiv.style.visibility = "hidden";
		pickerDiv.style.display = "none";
	 
		this.targetDateField.focus();
	 
		var date_arr = this.splitDateString(dateString);
		var listings_date = date_arr[0] + '' + date_arr[1] + '' + date_arr[2];
		document.location.href = TVListings.properties.change_url_parameter("date", listings_date);
	}

}

