/*
 * $Id: coupon.js,v 1.75 2011-07-04 18:18:58 pmiller Exp $
 */
// Arrays containing the coupons for state-keeping
SA_coupon_ids = new Array();
SA_coupons = new Array();

// The values from the last request - default to requesting any available coupons
if (typeof(SA_moreCouponsFetchParams) == "undefined") {
	SA_moreCouponsFetchParams = {"criteria" : "all", "value" : "n/a"};
}

// States for coupon deletion
var SA_deleted_coupon_ids = new Array();
SA_currentlyDeletingCoupons = false;

// Bind some initialization code to be executed when the page finishes loading
SA_ajax_init = function () {
	SA_request_broker = SA_singleton_request_broker.instance();
	r = SA_request_broker;

	r.BETSLIP_PRI = 1;
	r.COUPONS_PRI = 1;
	r.UPDATE_BALS = 2;
}

$(function() {
		SA_ajax_init();
		});

StatisticsQ = new Array();

// Performs an AJAX request in order to retrieve coupon statistics
function SA_request_statistics(sort, saxgame_id){

	// Since the RQbroker API is serves callbacks at the same order as when theyt where called then 
	//  We may have a FIFO queue to store the xgame_ids of the games for which we request statistics
	StatisticsQ.push(saxgame_id);

	url = "?action=GoStats"
		+"&sort="+sort
		+"&saxgame_id="+saxgame_id;

	var r = SA_request_broker;
	r.send( {
		'priority'        : r.COUPONS_PRI,
		'method'          : 'GET',
		'url'             : url,
		'callback'        : function(response) {

					// pop the saxgame_id from the Q upon execution
					var saxgame_id = StatisticsQ.shift();


					if (!$('#cpn_' + saxgame_id + ' div.stats div.inner')[0]) return;
					// store the response
					$('#cpn_' + saxgame_id + ' div.stats div.inner')[0].innerHTML = response.responseText;

					// error checking
					if ($('#cpn_' + saxgame_id + ' div.stats div.inner input[name=has_error]').val() == '1'){
						// No stats found or any other server error
						$('#cpn_' + saxgame_id + ' div.stats div.inner').addClass('servererror');
					} else {
						// visualise the stats
						var el = $('#cpn_' + saxgame_id + ' div.stats').find('div.predictions table.s1');
						renderStats(el);
					}

					// attach event listeners to the newly acquired html
					$('#Body div.flyOver a.flyClose').click(function(e) {
						e.preventDefault();
						$('div.flyWrap').removeClass('open');
					});
		
					$('#Body div.game div.stats select.fixtureDrop').change(function(){
						var target = 'div.predictions table.' + $(this).attr('value');
						var el = $(this).parents('div.stats').find(target);
						renderStats(el);
					});
	
					$('#Body div.game div.stats div.inner .action').click(function(e){
						e.preventDefault();
						$(this).blur();

						$(this).parents('div.flyOver').toggleClass('details');
				        });
			            },
		'debug'           : true,
		'timeoutmsecs'    : SA_REQUEST_TIMEOUT,
		'timeoutcallback' : function(){
					var saxgame_id = StatisticsQ.shift();
					$('#cpn_' + saxgame_id + ' div.stats div.inner h4').removeClass('loading');
					$('#cpn_' + saxgame_id + ' div.stats div.inner h4').addClass('error');
					$('#cpn_' + saxgame_id + ' div.stats div.inner h4')[0].innerHTML = $('input[name=sa_stats_err_msg]').val();
				    }
	} );

}

// Adds more coupons of the prevously selected type
function SA_moreCoupons() {
	SA_doneDeletingCoupons();

	if (!SA_betslip_old_coupons) {
		var criteria = SA_moreCouponsFetchParams.criteria;
		var value = SA_moreCouponsFetchParams.value;
		var num_to_add = SA_calcNumNewCoupons();
		
		SA_get_coupons_by_criteria(criteria, value, num_to_add, false);
	}
}



function SA_returnToCoupons() {
	if (SA_betslip_old_coupons) {
		SA_get_coupons_by_criteria("id", SA_betslip_old_coupons,SA_MAX_COUPONS_HARD,1);
		SA_moreCouponsFetchParams = SA_betslip_old_get_state;

		$("#SA_MoreCouponsButton").show();
		$("#SA_BackToCouponsButton").hide();

		SA_betslip_old_coupons = null;
		SA_betslip_old_get_state = null;
	}
}

// Calculate how many coupons to add, taking into account the soft and hard
// coupon count limits.
function SA_calcNumNewCoupons(target_num) {
	var num_coupons = SA_coupon_ids.length;

	if (!target_num) {
		var target_num = SA_MAX_COUPONS_SOFT;
	}
	
	var cur_limit = SA_MAX_COUPONS_SOFT;
	if (num_coupons >= cur_limit) {
		// We've reached the soft limit
		cur_limit = SA_MAX_COUPONS_HARD;
	}
	if (num_coupons >= cur_limit) {
		// We've reached the hard limit
		SA_startDeletingCoupons();
	}
	var num_to_add = target_num;
	if (num_coupons + num_to_add > cur_limit) {
		num_to_add = cur_limit - num_coupons;
	}

	return num_to_add;
}

// Enters "delete mode" - grays out all the coupons and displays a control to
// remove them from the page.
function SA_startDeletingCoupons() {
	if (SA_currentlyDeletingCoupons) {
		printfire("Warning: Coupon deletion process already started.");
		return;
	}
	printfire("Coupon deletion process started.");
	showPanelNoEvent($("#panelMaximumLimit"), close);
	$("#Body div.game").addClass("close");
	$("#Body div.game > a.closeCoupon").show();
	$("#Body div.game div.disabler div.inner").css("opacity", 0.5);
	SA_currentlyDeletingCoupons = true;
}

// Enough coupons have been deleted - exit "delete mode".
function SA_doneDeletingCoupons() {
	if (!SA_currentlyDeletingCoupons) {
		printfire("Warning: Coupon deletion process not running.");
		return;
	}
	printfire("Coupon deletion process finished.");

	$("#Body div.game").removeClass("close");
	$("#Body div.game > a.closeCoupon").hide();
	SA_currentlyDeletingCoupons = false;
}

// A coupon has been deleted - add it to the internal memory of deleteed
// coupons (if enabled) and check whether we've deleted enough coupons.
function SA_couponDeleted(saxgame_id) {
	if (SA_AVOID_DELETED_COUPONS) {
		SA_deleted_coupon_ids.push(saxgame_id);
	}
	if (SA_coupon_ids.length <= SA_MAX_COUPONS_SOFT) {
		SA_doneDeletingCoupons();
	} else {
		printfire((SA_coupon_ids.length - SA_MAX_COUPONS_SOFT)+" coupons still need to be deleted.");
	}
}

// Retrieves a given coupon from SA_coupons and passes a button click event to
// it.
function SA_buttonClickWrapper(saxgame_id,x,y) {
	var thisCoupon = SA_coupons['cpn_'+saxgame_id];
	thisCoupon.buttonClick(x,y);
}

// Retrieves a given coupon from SA_coupons and reCalculates it.
function SA_calculateCoupon(saxgame_id) {
	var thisCoupon = SA_coupons['cpn_'+saxgame_id];
	thisCoupon.permutationFormula();
}


// Clear the given coupon of selections 
// Wraps SA_Coupon.clear()
// container is a DOM reference to the coupon's outer div (cpn_[saxgame_id])
// This is used to choose the correct javascript object to work on
function SA_clearCoupon(event, container) {

	var coup_index = container[0].id;
	var thisCoupon = SA_coupons[coup_index];
	thisCoupon.clear();
}

// Choose a random set of selections for the given coupon
// Wraps SA_Coupon.luckyDip()
// container is a DOM reference to the coupon's outer div (cpn_[saxgame_id])
// This is used to choose the correct javascript object to work on
function SA_luckyDip(event, container) {

	var coup_index = container[0].id;
	var thisCoupon = SA_coupons[coup_index];
	thisCoupon.luckyDip();
}


// Remove all coupons
function SA_remove_all_coupons() {
	while(SA_coupon_ids.length > 0) {
		SA_coupons['cpn_'+SA_coupon_ids[0]].remove();
	}
}

// A queue containing the saxgame ids used when calling SA_get_linked_coupons
var SA_linked_id_queue = new Array();

// Gets coupons linked to the given game row and adds them to the page. These 
// are inserted into #coupon_container and initialized with JSON data. Any
// existing coupons are moved but not readded (this logic is done after the 
// AJAX call returns)
SA_get_linked_coupons =  function(saxgame_id, row_id) {
	if (SA_coupon_ids.length >= SA_MAX_COUPONS_HARD) {
		// We've reached the hard limit
		// so can't add more coupons until some are removed
		SA_startDeletingCoupons();
	} else {
		var theme = "";
		switch (SA_moreCouponsFetchParams.criteria) {
			case "theme_code":
			case "theme":
				theme = SA_moreCouponsFetchParams.value;
				break;
			case "sortandtheme":
				theme = SA_moreCouponsFetchParams.value.split("|")[1];
				break;
		}

		url = "?action=GoPoolsLinkedCoupons"
			+"&row_id="+row_id
			+"&saxgame_id="+saxgame_id
			+"&theme="+theme;

		SA_linked_id_queue.push(saxgame_id);

		var r = SA_request_broker;
		r.send( {
			'priority': r.COUPONS_PRI,
			'method'  : 'GET',
			'url'     : url,
			'callback': SA_handle_linked_coupons_response,
			'debug'   : true
		} );
	}
}

// Handler for the response to SA_get_linked_coupons
SA_handle_linked_coupons_response = function(response) {
	response_sections = eval(response.responseText);

	var num_coupons = SA_coupon_ids.length;
	var coupons_to_add = Math.floor(response_sections.length/2);

	if ((num_coupons + coupons_to_add) > SA_MAX_COUPONS_HARD) {
		// Adding these coupons would put us over the hard limit
		// so display modal window asking to remove some
		SA_startDeletingCoupons();
	} else {
		// Get the correct saxgame_id from our queue
		var saxgame_id = SA_linked_id_queue.shift();

		for (var response_idx in response_sections) {
			try {
				thisResponse = response_sections[response_idx];
				if (thisResponse.couponData) {

					// The JS initialization data and new HTML are both in the JSON response
					couponData = thisResponse.couponData;

					if (couponData.status != "OK") {
						printfire("Linked coupon request returned status ERROR");
						continue;
					}

					var newHTML = str_trim(thisResponse.couponHTML, " \n\t");;
				
					// Head off to insert the HTML into the page, coupon
					// initialization is finished off in
					// SA_continue_linked_coupons_response
					couponData.callerId = saxgame_id;
					if (document.getElementById('cpn_'+couponData.saxgame_id)) {
						SA_coupons['cpn_'+couponData.saxgame_id].moveAfter(SA_coupons['cpn_'+saxgame_id]);
						continue;
					}
					SA_delayedInnerHTML(couponData, newHTML, SA_continue_linked_coupons_response);


				}
			} catch (e) {
				printfire(e);
			}
		}
	}
}

// Continues from the asyncInnerHTML call in SA_handle_linked_coupons_response
SA_continue_linked_coupons_response = function(couponData, htmlFragment) {
	try {
		var saxgame_id = couponData.saxgame_id;

		if (!SA_coupons['cpn_'+saxgame_id]) {
			coup_cont = document.getElementById("coupon_container");
		//	coup_cont.appendChild(htmlFragment.childNodes[0].childNodes[0]);

			// Initialize the JS state
			var coupon = SA_initCoupon(couponData);
			coupon.jQueryInit();
		}

		SA_coupons['cpn_'+saxgame_id].moveAfter(SA_coupons['cpn_'+couponData.requestInfo.target_saxgame_id]);

	} catch (e) {
		printfire(e);
	}
}

function SA_reset_menu_selection(menuItem) {
	SA_jquery_cached("#TreeView").find(".selected").removeClass("selected");
	SA_jquery_cached("#TreeView").find(".current").removeClass("current");
	$(menuItem).parent("li").addClass("current");
}

SA_get_coupons_by_bet_num = function(bet_num) {
	SA_get_coupons_by_criteria("bet_num",bet_num,1,1);
}

// Gets a given number of new coupons of the given type to add to the page.
// These are inserted into #coupon_container and initialized with JSON data.
SA_get_coupons_by_sort = function(sort, num, clear, menuItem) {
	SA_get_coupons_by_criteria("sort",sort,num,clear);
	if (menuItem) {
		SA_reset_menu_selection(menuItem);
	}
}
SA_get_coupons_by_theme = function(theme, num, clear, menuItem) {
	SA_get_coupons_by_criteria("theme",theme,num,clear);
	if (menuItem) {
		SA_reset_menu_selection(menuItem);
	}
}
SA_get_coupons_by_tag = function(tag, num, clear, menuItem) {
	SA_get_coupons_by_criteria("tag",tag,num,clear);
	if (menuItem) {
		SA_reset_menu_selection(menuItem);
	}
}
SA_get_coupons_by_sortandtheme = function(sort, theme, num, clear, menuItem) {
	if (sort != "all") {
		SA_get_coupons_by_criteria("sortandtheme",sort+"|"+theme,num,clear);
	} else {
		SA_get_coupons_by_theme(theme, num, clear);
	}
	if (menuItem) {
		SA_reset_menu_selection(menuItem);
	}
}
SA_get_all_coupons = function(num, clear, menuItem) {
	SA_get_coupons_by_criteria("all","n/a",num,clear);
	if (menuItem) {
		SA_reset_menu_selection(menuItem);
	}
}
SA_get_coupons_by_criteria = function(criteria, value, num, clear) {
	if (clear) {
		SA_remove_all_coupons();
		SA_deleted_coupon_ids = new Array();
		SA_jQueryCacheNoContext = new Array();
		SA_moreCouponsFetchParams.criteria = criteria;
		SA_moreCouponsFetchParams.value = value;
	}

	if (criteria != "id") {
		num = SA_calcNumNewCoupons(num);
	}

	var notString = SA_coupon_ids.join(" ")
		+" " + SA_deleted_coupon_ids.join(" ");

	url = "?action=GoPoolsCouponsOnly"
		+"&by="+criteria
		+"&val="+value
		+"&count="+num
		+"&not={"+notString+"}"
		+"&result_format=in_json"
		+"&expander=1";
	var r = SA_request_broker;
	r.send( {
		'priority'      : r.COUPONS_PRI,
		'method'        : 'GET',
		'url'           : url,
		'callback'      : SA_handle_new_coupons_response,
		'nocache_harsh' : true,
		'debug'         : true
	} );
	
}

// Handler for the response to SA_get_coupons_by_sort
SA_handle_new_coupons_response = function(xmlhttp) {
	var response_sections = null;
	var progress = 0;
		var response_text = xmlhttp.responseText;
		response_text = "("+response_text+")";
	try {
		response_sections = eval(response_text);
		throw("test");
	} catch (e) {
		try {
			response_sections = JSON.parse(xmlhttp.responseText);
		} catch (f) {

			var vDebug = "";
			for (var prop in f) {
				vDebug += "property: "+ prop+ " value: ["+ f[prop]+ "]\n";
			}
			vDebug += "toString(): " + " value: [" + f.toString() + "]";
			alert("Error parsing request: "+vDebug)

		}
	}

	for (var response_idx in response_sections) {
		try {
			var thisResponse = response_sections[response_idx];
			if (thisResponse.couponData) {
				// The JS initialization data and new HTML are both in the JSON response
				var couponData = thisResponse.couponData;

				if (couponData.status != "OK") {
					printfire("Coupon request returned status ERROR");
					continue;
				}

				if (SA_coupons['cpn_'+couponData.saxgame_id]) {
					printfire("Coupon already in page, ignoring");
					continue;
				}

				var newHTML = str_trim(thisResponse.couponHTML, " \n\t");;
				SA_delayedInnerHTML(couponData, newHTML, SA_continue_new_coupons_response);
			}
		} catch (e) {
			printfire(e);
		}
	}
}

// Continues from the SA_delayedInnerHTML call in SA_handle_new_coupons_response
SA_continue_new_coupons_response = function(couponData, htmlFragment) {
	try {
		var saxgame_id = couponData.saxgame_id;

		coup_cont = document.getElementById("coupon_container");

		// Throwback from SA_delayedInnerHTML usage - switching back at some point
		// would be nice
//		coup_cont.appendChild(htmlFragment.childNodes[0].childNodes[0]);

		// Initialize the JS state
		var coupon = SA_initCoupon(couponData);
		coupon.jQueryInit();

	} catch (e) {
		printfire(e);
	}
}

// Generic initialization code for the coupon, handles non-object related
// things, as well as calling the object constructor to deal with other setup.
// Is called on page load for coupons that are initially displayed and also
// when new coupons are loaded in via Ajax requests.
SA_initCoupon = function(couponData) {

	var curCoupon = new SA_Coupon(couponData);

	SA_coupons['cpn_'+curCoupon.game_id] = curCoupon;
	SA_coupon_ids.push(curCoupon.game_id);

	curCoupon.clearAllViews();
	SA_moreCouponsFetchParams = couponData.req_args;

	if (couponData.is_edit_coupon) {
		SA_betslip_obj.populateSelections(curCoupon.game_id,couponData.edit_bet_num,curCoupon.cols,curCoupon.cols*curCoupon.rows)
	} else {
		SA_loadCouponState(curCoupon.game_id);
	}
	curCoupon.loaded = true;

	return curCoupon;

}


// Coupon object
//
// One should be instantiated for each of the coupons displayed on the page.
// They should be held in SA_coupons, a copy of their saxgame_ids is in 
// SA_coupon_ids for quick reference.
SA_Coupon = function(couponData) {
	// Do some standard initializations
	this.init_data  = couponData;
	this.game_id    = couponData.saxgame_id;
	this.game_sort  = couponData.sort;
	this.cols       = couponData.num_cols;
	this.rows       = couponData.num_rows;
	this.max_picks  = couponData.max_picks;
	this.min_picks  = couponData.min_picks;
	this.min_stake  = couponData.min_stake;
	this.cost       = couponData.stake;
	this.rules      = couponData.rules;
	this.local_ccy_sign = couponData.local_ccy_sign;
	this.ccy_code   = "none";
	this.exch_rate  = "0.00";
	this.template_type = couponData.template_type;
	this.array      = new Array();
	this.all        = new Array();
	this.num_picks  = 0;
	this.num_subs   = 1;
	this.loaded     = false;
	this.selected_subs_ele = null;
	this.show_time_zone = couponData.show_time_zone;
	this.close_date_utc = couponData.close_date_utc;

	this.has_balls    = couponData.has_balls;
	this.views        = couponData.game_views;
	this.cur_view     = this.views[0];
	this.has_subviews = couponData.has_subviews;
	this.error_status = null;

	this.has_plans = couponData.has_plans;
	if (this.has_plans) {
		this.plan_ids = new Array();
		this.plans    = new Array();
		for (var i = 0; i < couponData.plans.length; i++) {
			var curPlan         = couponData.plans[i].plan_id;
			this.plan_ids[i]    = curPlan;
			this.plans[curPlan] = couponData.plans[i];
			if (this.plans[curPlan].is_default == "Y") {
				this.plans.defaultPlan = curPlan;
			}
		}

		this.min_picks = this.plans[this.plans.defaultPlan].num_picks;
		this.max_picks = this.plans[this.plans.defaultPlan].num_picks;
		this.cost      = this.plans[this.plans.defaultPlan].stake;
		this.show_ccy  = this.plans[this.plans.defaultPlan].show_ccy;

		// These ccy values are the Sci-Games converted values (e.g. GBP)
		// For example, if the local currency is AUD then the ccy values should be in
		// GBP and the normal cost value will be in AUD
		this.ccy_cost  = this.plans[this.plans.defaultPlan].ccy_stake;
		this.ccy_code  = this.plans[this.plans.defaultPlan].ccy_code;
		this.ccy_sign  = this.plans[this.plans.defaultPlan].ccy_sign;
		this.exch_rate = this.plans[this.plans.defaultPlan].exch_rate;

	}

	// Grab the formatting info for handling multiple views
	this.data_id_format = couponData.data_id_format;

	this.viewStates = new Array();

	for (var view_idx = 0; view_idx < this.views.length; view_idx++) {
		var thisView              = this.views[view_idx];
		this.viewStates[thisView] = new Object();
		var thisState = this.viewStates[thisView];
		thisState.num_picks = 0;

		thisState.selections = new Array(this.rows);
		var selections = thisState.selections;
		// Populate .array to contain selections for this coupon
		for (var i = 0; i < this.rows; i++)
		{
			selections[i] = new Array(this.cols);

			for (var j = 0; j < this.cols; j++) {
				selections[i][j] = 0;
			}
		}

		if (this.has_plans) {
			thisState.plan      = this.plans.defaultPlan;
			thisState.cost      = this.cost;
			thisState.ccy_cost  = this.ccy_cost;
			thisState.ccy_code  = this.ccy_code;
			thisState.exch_rate = this.exch_rate;
		}

		// Valid states:
		// NEW - Hasn't been checked yet - avoids some nasty button-state
		//    caching issues
		// EMPTY - No selections made, fine to change view
		// PART_FILLED - Some selections made, not fine to change view
		// FILLED - All required selections made, sometimes fine to change view
		this.viewStates[thisView].filled = 'NEW';
	}

	// Initialize the all array
	for (var i = 0; i < couponData.num_row_types; i++) {
		for (var j = 0; j < couponData.num_rows; j++) {
			for (var k = 0; k < couponData.num_cols; k++) {
				this.all.push({row:j,col:k});
			}
		}
	}

	if (this.has_subviews) {
		this.subview_ids = couponData.subviews;
		this.subview_states = new Array();
		this.subview_tab = couponData.subview_tab;
		var states = this.subview_states;
		states.current = "A";
		states.prefix = couponData.subview_prefix;
		for (var subview_idx in this.subview_ids) {
			var thisSubView = this.subview_ids[subview_idx];
			states[thisSubView] = this.viewStates[states.prefix+thisSubView];
		}
	}

	this.time_offset = (new Date(couponData.time_now*1000)) - (new Date()) ;

	this.time_close = new Date(couponData.time_close);
	this.advanceTimer();

	this.has_partial_stake = false;
	if (SA_jquery_cached("#"+this.game_id+"_stake_value select.partialStake")[0]) {
		this.has_partial_stake = true;
	}

	// Start with a blank stake
	if (this.game_sort == "TCH") {
		if (this.show_ccy != 0) {
			SA_jquery_cached("#"+this.game_id+'_stake_value .ccy_cost').innerHTML = 
					this.ccy_sign + SA_formatCurrency(this.ccy_cost,true,1);
		} else {
			SA_jquery_cached("#" + this.game_id+'_stake_value .local_ccy')[0].style.display = 'none';
		}
	} else if (!this.has_partial_stake) {
		SA_jquery_cached('#cpn_'+this.game_id + ' .main.toggle.coupon')[0].className += " no_partial_stake ";
	}
	SA_jquery_cached("#"+this.game_id+'_stake_value .cost').innerHTML = 
				this.local_ccy_sign + SA_formatCurrency(this.cost,true,1);

	this.write_coupon_date(this.close_date_utc, "cdate_" + this.game_id, this.game_sort, this.show_time_zone);
}

SA_Coupon.prototype.switchSlipButton = function (action) {
	var game_id    = this.game_id;
	var element_id = 'BetNowBtn_'+game_id;
	if (action == 'hide') {
		document.getElementById(element_id).style.display = 'none';
	} else {
		document.getElementById(element_id).style.display = 'block';
	}

}

SA_Coupon.prototype.clearAllViews = function () {

	// Clear all the views
	var default_view = this.cur_view;
	for (var view_idx = 0; view_idx < this.views.length; view_idx++) {
		this.cur_view = this.views[view_idx];
		this.clear();
	}

	SA_jquery_cached("#Body div.coupon .view.full .cost", this.getElement()).html("");

	this.cur_view = default_view;

	if (this.subviewActive()) {
		var cur_subview = this.subview_ids[0];
		this.subview_states.current = cur_subview;

		this.cur_view = this.subview_states.prefix+cur_subview;
	}
}

// Gives a reference to the containing div element for this coupon
SA_Coupon.prototype.getElement = function() {
	return document.getElementById("cpn_"+this.game_id);
}

// Move the DOM element for this coupon to before that of the given coupon
SA_Coupon.prototype.moveBefore = function (target) {

	target.getElement().parentNode.insertBefore(this.getElement(), target.getElement());
}

// Move the DOM element for this coupon to after that of the given coupon
SA_Coupon.prototype.moveAfter = function (target) {
	thisElement = this.getElement();
	sibling = target.getElement().nextSibling;
	while (
			sibling != null &&
			(sibling.nodeType != 1 || sibling.tagName != "DIV")
			) {
		sibling = sibling.nextSibling;
	}
	if (sibling != null) {
		sibling.parentNode.insertBefore(thisElement, sibling);
	} else {
		thisElement.parentNode.appendChild(thisElement);
	}
}

// Advance the countdown timer displayed on the coupon
SA_Coupon.prototype.advanceTimer = function() {
	

	var time_now = new Date();
	time_now.setMilliseconds(time_now.getMilliseconds() + this.time_offset);

	var secs       = (this.time_close - time_now) / 1000;

	if (secs < 0)
		secs = 0;

	var mins       = secs / 60;
	var hours      = mins / 60;
	var days       = hours / 24;

	var time_left_ele = SA_jquery_cached("#"+this.game_id+'_time_left span');

	if (days > 3) {

		var disp_days = Math.round(days);
		time_left_ele.html(SA_XL_sprintf("SA_DAYS", disp_days));
	} else if (hours >= 1) {

		var disp_hours = Math.floor(hours);
		var disp_mins  = Math.round(mins % 60);
		time_left_ele.html(SA_XL_sprintf("SA_HOURS_AND_MINS", disp_hours, disp_mins));
	} else {

		var disp_mins = Math.floor(mins);
		var disp_secs = Math.round(secs % 60);
		time_left_ele.html(SA_XL_sprintf("SA_MINS_AND_SECS", disp_mins, disp_secs));
	}

	if (secs != 0)
		this.thread = window.setTimeout("SA_coupons['cpn_"+this.game_id+"'].advanceTimer();",1000);
}

// Provides a list of the selections in a string, to be used in bet placement
SA_Coupon.prototype.getSelectionsString = function (view) {
	if (typeof(view) == "undefined") {
		// By default, use the current view
		view = this.cur_view;
	}

	var thisArray = this.viewStates[view].selections;
	var selectionArray = new Array();
	for (var i = 0; i < this.cols * this.rows; i++) {
		curCol = this.all[i].col;
		curRow = this.all[i].row;
		if (thisArray[curRow][curCol]) {
			selectionArray.push(i+1);
		}
	}
	return selectionArray.join("c");
}



// Starts a bet for this coupon - assembles and sends an Ajax request
SA_Coupon.prototype.startBetProcess = function () {

	this.getElement().className += " pending ";

	var plan_id = this.getPlanId();
	var included = 1;
	var reqBit = "";
	var partialStakeOptions = SA_jquery_cached("#"+this.game_id+"_stake_value select.partialStake")[0];
	if (this.game_sort != "TCH" && partialStakeOptions) {
		var partial_stake = partialStakeOptions.options[partialStakeOptions.selectedIndex].value;
	} else {
		var partial_stake = parseFloat("0.00").toFixed(2)
	}

	if (this.subviewActive()) {
		var cost = 0;
		this.placedBetCost = 0;
		for (var i = 0; i < this.subview_ids.length; i++) {
			var prefix = this.subview_states.prefix;
			var selections = this.getSelectionsString(prefix + this.subview_ids[i]);
			var curSubview = this.subview_states[this.subview_ids[i]];
			if (curSubview.filled == "FILLED") {
				if (reqBit != "") {
					reqBit += "_";
				}
				if (this.game_sort == "TCH" && this.show_ccy != 0) {
					cost = curSubview.ccy_cost;
				} else {
					cost = curSubview.cost;
				}
				reqBit += this.game_id
					+SA_BET_PROPERTY_SEPARATOR+parseFloat(cost).toFixed(2)
					+SA_BET_PROPERTY_SEPARATOR+this.num_subs
					+SA_BET_PROPERTY_SEPARATOR+curSubview.plan
					+SA_BET_PROPERTY_SEPARATOR+selections
					+SA_BET_PROPERTY_SEPARATOR+included
					+SA_BET_PROPERTY_SEPARATOR+partial_stake
					+SA_BET_PROPERTY_SEPARATOR+this.ccy_code
					+SA_BET_PROPERTY_SEPARATOR+this.exch_rate;

				this.placedBetCost += parseFloat(cost);
			}
		}
	} else {
		var selections = this.getSelectionsString();
		this.placedBetCost = this.permutationFormula();

		reqBit = this.game_id
			+SA_BET_PROPERTY_SEPARATOR+this.placedBetCost.toFixed(2)
			+SA_BET_PROPERTY_SEPARATOR+this.num_subs
			+SA_BET_PROPERTY_SEPARATOR+plan_id
			+SA_BET_PROPERTY_SEPARATOR+selections
			+SA_BET_PROPERTY_SEPARATOR+included
			+SA_BET_PROPERTY_SEPARATOR+partial_stake
			+SA_BET_PROPERTY_SEPARATOR+this.ccy_code
			+SA_BET_PROPERTY_SEPARATOR+this.exch_rate;
	}
	var request_uri = "?action=GoPoolsBetFootball&bets="+reqBit;

	// Do the Ajax call, picked up in betPlaceCallback
	var game_id = this.game_id;
	var r = SA_request_broker;
	r.send( {
		'priority'        : r.COUPONS_PRI,
		'method'          : 'GET',
		'url'             : request_uri,
		'callback'        : function(req) {
			SA_coupons['cpn_'+game_id].betPlaceCallback(req);
		},
		'debug'           : true,
		'nocache_harsh'   : true,
		'timeoutmsecs'    : SA_REQUEST_TIMEOUT,
		'timeoutcallback' : function(){
						SA_coupons['cpn_'+game_id].betPlaceFailed({
							status:'TIMEOUT', err_head:SA_XL('SA_PLACE_BET_TIMEOUT_ERR_HEADER'),
							err_body:SA_XL('SA_PLACE_BET_TIMEOUT_ERR_BODY')
						});
				    }
	} );

}

// Called when a bet placement completes, checks for errors and runs a
// corresponding function
SA_Coupon.prototype.betPlaceCallback = function (xmlhttp) {
	var response_obj = eval("("+xmlhttp.responseText+")");
	if (response_obj.status == "OK") {
		this.betPlaced(response_obj);
	} else {
		this.betPlaceFailed(response_obj);
	}
	if (updateAccountBalances) {
		updateAccountBalances();
	}
	SA_refreshCouponBetCounts();
}

// Bet placed successfully, inform the user
SA_Coupon.prototype.betPlaced = function (response_obj) {
	var thisEl = this.getElement();
	var paidVal = new Number(response_obj.betValue);
	var freeVal = new Number(response_obj.freeBetValue);
	var receiptNum = new String(response_obj.receiptNum);
	this.getElement().className = this.getElement().className.replace(new RegExp("(^| )pending( |$)"),"");
	this.getElement().className += " confirmed ";
	SA_jquery_cached(".confirmation h4 span", thisEl)[0].innerHTML = this.local_ccy_sign + SA_formatCurrency(paidVal + freeVal, true,1);
	SA_jquery_cached(".confirmation .foot a", thisEl)[1].href = "javascript:openReceipt('DoHist','" + receiptNum + "');";

	this.clear();
	$("#simpleTooltip").remove();
}

// Bet placement failed, inform the user
SA_Coupon.prototype.betPlaceFailed = function (response_obj) {
	var thisEl = this.getElement();
	thisEl.className = thisEl.className.replace(new RegExp("(^| )pending( |$)"),"");
	SA_jquery_cached(".failure .msg h3", thisEl)[0].innerHTML = response_obj.err_head;
	SA_jquery_cached(".failure .msg h4", thisEl)[0].innerHTML = response_obj.err_body;

	thisEl.className += " failed ";

	if (response_obj.status == "INVALID_EXCH_RATE") {
		this.exch_rate = response_obj.exch_rate;
		this.resetLocalCCyValues();
	}

}


// Click handler for the Play Again button - display the coupon again
SA_Coupon.prototype.playAgainClicked = function () {
	this.getElement().className = this.getElement().className.replace(new RegExp("(^| )confirmed( |$)")," ");
}

// Click handler for the Try Again button - display the coupon again
SA_Coupon.prototype.tryAgainClicked = function () {
	this.getElement().className = this.getElement().className.replace(new RegExp("(^| )failed( |$)")," ");
}

// Rest the local currency values with a new exchange rate
SA_Coupon.prototype.resetLocalCCyValues = function () {

	exch_rate = this.exch_rate;
	game_id = this.game_id;

	var views = ["fixtures", "numbers", "full"];

	if (this.has_plans) {
		for (i = 0; i < this.plan_ids.length; i++) {
			var plan_id = this.plan_ids[i];
			var curr_plan = this.plans[plan_id]
			curr_plan.stake = curr_plan.ccy_stake * exch_rate;
			for (j = 0; j < views.length; j++) {
				views[j].cost      = views[j].ccy_cost * exch_rate;
				views[j].exch_rate = exch_rate;
				var stakeQuery = "#plan-" + plan_id + "-" + game_id + "-" + views[j] + "-stakes .local_ccy_value";
				try {
					var element = SA_jquery_cached(stakeQuery)[0];
					element.innerHTML = SA_formatCurrency(curr_plan.stake,true,1);
				} catch (e) {
					// don't really need to do anything as a lot of these won't exist
					// console.log("This element does not exist: " + stakeQuery + " Error: " + e);
				}
			}
		}
	}

	this.cost = this.ccy_cost * exch_rate;
	this.permutationFormula();
}

// NOT IMPLEMENTED
SA_Coupon.prototype.add_to_slip = function() {
	alert("Not done.");
}

// Initializes the JQuery-based events for this coupon.
SA_Coupon.prototype.jQueryInit = function() {
	context = "#cpn_"+this.game_id;

	SA_addHandlers(context);
	SA_jquery_cached(context+" a").simpletooltip();
	SA_jquery_cached(context+".game h3").simpletooltip();
	SA_jquery_cached(context+" th").simpletooltip();
	SA_jquery_cached(context+" input").simpletooltip();
	SA_jquery_cached(context+" button").simpletooltip(); 
}

// Clear all selections for this coupon
SA_Coupon.prototype.clear = function () {

	var thisArray = this.viewStates[this.cur_view].selections;
	this.num_picks = 0;

	for (var i = 0; i < thisArray.length; i++) {
		for (var j = 0; j < thisArray[i].length; j++) {
			this.setCheckbox(i,j,false);
			thisArray[i][j] = 0;
		}
	}


	if (this.subviewActive()) {
		var subview_cost = SA_jquery_cached(
			"form > div.coupon > div.view.full tr.costRow td.cost.c"
			+this.subview_states.current, this.getElement());
		subview_cost.html("");
	}

	this.toggleButton("AddToSlipBtn","disable");
	this.toggleButton("BetNowBtn","disable");

	this.checkCompleted();
	SA_saveCouponState(this.game_id);

}

// Pick random selections for this coupon
SA_Coupon.prototype.luckyDip = function() {

	this.clear();
	var thisArray = this.viewStates[this.cur_view].selections;

	// This will need more "versions" for other coupon types, all current types
	// (and most types in general) use a standard "one per line" algorithm, so
	// this is default for now.
	switch (this.template_type) {
		// A "lottery" style pick - randomly pick rows and select the first ball
		// in each picked row. Works best for Nx1 games (eg: Generic type)
		case "TCH":
		case "generic":
			var row_nums = new Array(this.rows);
			for (var i = 0; i < this.rows; i++) {
				row_nums[i] = i;
			}
			var picks = new Array();
			for (var i = 0; i < this.min_picks; i++) {
				var thisNum = Math.floor(Math.random()*row_nums.length);
				picks.push(row_nums.splice(thisNum,1));
				this.setCheckbox(picks[i],0,true);
				thisArray[picks[i]][0] = 1;
				this.num_picks++;
			}
			break;

		// Final 4 method used on live site
		case "FN4":
			var NTEAMS  = 32; //32 is the fixed number of teams per 
							  // betslip)
			do {
				// 0  <= rand0 && rand1 < 16
				// 16 <= rand2 && rand3 < 32 
				var rand0 = Math.round(( (NTEAMS/2) - 1)*Math.random());
				var rand1 = Math.round(( (NTEAMS/2) - 1)*Math.random());
				var rand2 = Math.round(( (NTEAMS/2) - 1)*Math.random()) +NTEAMS/2;
				var rand3 = Math.round(( (NTEAMS/2) - 1)*Math.random()) +NTEAMS/2;
			} while ( (rand0==rand1) || (rand0==rand2) || (rand0==rand3 ) || 
				  (rand1==rand2) || (rand1==rand3) || 
				  (rand2==rand3)
				);

			// Update the coupon index
			var picks = new Array();
			picks.push(rand0);
			picks.push(rand1);
			picks.push(rand2);
			picks.push(rand3);
			
			for (var i = 0; i < 4; i++) {
				SA_buttonClickWrapper(this.game_id,picks[i],0);
			}
			
			break;
			
		// Score 3 picking method - randomly choose two selections per line
		case "S3":
			var NSTAGES  = 10;  //0,1,2,3,4+ -- 0,1,2,3,4+ (goals)

			// Update the coupons array with the random numbers ##

			for(var row = 0; row < this.rows; row++) {
				// 0 <= rand0 < 5
				var rand0 = Math.round(((NSTAGES/2) -1)*Math.random());
				// 5 <= rand0 < 10
				var rand1 = Math.round(((NSTAGES/2) -1)*Math.random()) + NSTAGES/2;
				// Update the html checkboxes 
				SA_buttonClickWrapper(this.game_id,row,rand0);
				SA_buttonClickWrapper(this.game_id,row,rand1);	
			}

			// Is this cost check really desired???
			if (this.cost == 1 && this.num_picks < this.max_picks) {
				var cur_selns = this.getSelectionsString();
				for (var num_attempts = 0; num_attempts < 100; num_attempts++) {
					var seln = Math.round(Math.random()*(this.cols*this.rows-1));

					var seln_exists_expr = new RegExp("(c|^)"+(seln+1)+"(c|$)");
					if (!cur_selns.match(seln_exists_expr)) {
						var row = this.all[seln].row;
						var col = this.all[seln].col;
						thisArray[row][col] = 1;
						this.num_picks++;
						this.setCheckbox(row,col,true);
						break;
					}

				}
			}
			break;
			
		// Standard picking method - randomly choose one selection per line
		default:
			for (var row = 0; row < this.rows; row++) {
				var col = Math.round((this.cols-1)*Math.random());
				this.setCheckbox(row,col,true);
				thisArray[row][col] = 1;
				this.num_picks++;
			}

			// Is this cost check really desired???
			if (this.cost == 1 && this.num_picks < this.max_picks) {
				var cur_selns = this.getSelectionsString();
				for (var num_attempts = 0; num_attempts < 100; num_attempts++) {
					var seln = Math.round(Math.random()*(this.cols*this.rows-1));

					var seln_exists_expr = new RegExp("(c|^)"+(seln+1)+"(c|$)");
					if (!cur_selns.match(seln_exists_expr)) {
						var row = this.all[seln].row;
						var col = this.all[seln].col;
						thisArray[row][col] = 1;
						this.num_picks++;
						this.setCheckbox(row,col,true);
						break;
					}

				}
			}
			break;
	}
	this.checkCompleted();
	SA_saveCouponState(this.game_id);
}

// Accepts a click event for one of the checkboxes for this coupon
SA_Coupon.prototype.buttonClick = function (x,y) {
	var thisArray = this.viewStates[this.cur_view].selections;

	if(thisArray[x][y] == 1) {
		this.num_picks--;
	} else {
		this.num_picks++;
	}

	var value = thisArray[x][y];

	if (value == 1) {
		this.setCheckbox(x,y,false);
		value = 0;
	} else if ( value == 0) {
		this.setCheckbox(x,y,true);
		value = 1;
	}

	thisArray[x][y] = value;

	if (!this.checkMaxPerms())  {
		this.setCheckbox(x,y,false);
		thisArray[x][y] = 0;
	}

	this.checkCompleted();
	
	if (!this.checkMaxPerms()) {
		this.num_picks--;
		return false;
	}

	SA_saveCouponState(this.game_id);

	return true;

}

// Checks whether too many selections have been made.
SA_Coupon.prototype.checkMaxPerms = function () {
	// If we're in a subview, maximum selections are handled within
	// permutationFormula(), so don't enforce anything here
	if (this.subviewActive()) {
		return true;
	}

	var thisArray = this.viewStates[this.cur_view].selections;

	var permArray = new Array();

	var numPerSection = 0;
	var doRowLimit = this.rules.use_limit_per_section;
	if  (doRowLimit) {
		var sectMaxSelns = this.rules.limit_per_section;
		var sectNumRows  = thisArray.length / this.rules.section_count;
	}
	
	var totalChecked = 0;
	for (var i = 0; i < thisArray.length; i++) {
		if (i % sectNumRows == 0)
			numPerSection = 0;

		numSelected = 0;

		for (var j = 0; j < thisArray[i].length; j++) {
			if (thisArray[i][j] == 1) {
				numSelected++;
				numPerSection++;
			}

			if (doRowLimit && numPerSection > sectMaxSelns) {
				if (this.rules.limit_per_section_msg != null) {
					var message = this.rules.limit_per_section_msg;
				} else {
					var xlation;
					if (sectNumRows == 1) {
						xlation = "SA_MAX_SELS_PER_GROUP_SIN";
					} else {
						xlation = "SA_MAX_SELS_PER_GROUP_PLU";
					}
					message = SA_XL_sprintf(xlation, sectMaxSelns);
				}
				alert(message);
				return false;
			}

			permArray[i] = numSelected;
			totalChecked += numSelected;
		}

	}

	var numSels = 1;

	if (this.cols != 1) {
		for (var i = 0; i < thisArray.length; i++) {
			numSels = numSels + permArray[i];
		}
	} else {
		numSels = totalChecked;
	}

	if (numSels > this.max_picks) {
		alert(SA_XL("SA_TOO_MANY_SELECTIONS"));
		return false;
	}

	return true;

}

// Checks whether the coupon has the required number of selections.
// If it does, calls permutationFormula.
SA_Coupon.prototype.checkCompleted = function () {
	var thisArray = this.viewStates[this.cur_view].selections;

	var completed = 1;

	for (var i = 0; i < thisArray.length; i++) {
		var isRowSelected = 0;

		for (var j = 0; j < thisArray[i].length; j++) {
			if(thisArray[i][j] == 1) {
				isRowSelected = 1;
				break;
			}
		}

		if (isRowSelected == 0)  {
			completed = 0;
			break;
		}
	}

	this.permutationFormula();

}

// Get the selected plan
SA_Coupon.prototype.getPlanId = function () {

	if (!this.has_plans) {
		return "";
	}

	// Find the selected plan id.
	return this.viewStates[this.cur_view].plan;
}

// Get tje number of subscriptions
SA_Coupon.prototype.getNumSubs = function () {

	if (this.game_sort == 'TCH') {
		// Find the selected plan id.
		var radio_name = 'subs-'+this.game_id+'-length';
		for (var i = 0; i < document.getElementsByName(radio_name).length; i++) {
			if (document.getElementsByName(radio_name)[i].checked) {
				var weeks = document.getElementsByName(radio_name)[i].value;
				return weeks;
 			}
		}
	}    

    // Default value.
    return 1;

}

// Recalculates the current stake of this coupon
// N.B. Calculates the stake of a 1 subscription bet
SA_Coupon.prototype.permutationFormula = function () {
	var thisArray = this.viewStates[this.cur_view].selections;

	var permCount = 0;
	var totalSelections = 0;

	if (this.cols == 1) {
		var numSelected = 0;
		for (var i = 0; i < this.rows; i++) {
			if (thisArray[i][0] == 1) {
				numSelected++;
			}
		}

		totalSelections = numSelected;

		if (numSelected >= this.min_picks) {
			// n = current number of selections
			// m = minimum number of selections
			// permutations = n! / (m! * (n-m)!)
			permCount = SA_factorial(numSelected) / 
				(
					SA_factorial(this.min_picks) * 
					SA_factorial(numSelected - this.min_picks)
				);
		}
	} else if (this.template_type == "S3") {
		// For the S3 coupon we have the selections of 2 teams in one row
		// To calculate correctly the permutation count we split the number
		// of selections for each team into an array. After calculating 
		// the number of selections for each team then we proceed to calculate
		// the permutation count
		
		permCount++;
		// Array to store the number of selections for each team
		var numSelectedArray = new Array();
		//Number of possible selections for a team 0,1,2,3,4+
		var half_columns =  this.cols/2 ;
		var sel_index    = 0;
		
		for (var i = 0; i < this.rows; i++) {
			var numSelected = 0;

			//Check the selections for the first team in row i
			for (var j = 0; j < half_columns; j++) {
				if (thisArray[i][j] == 1) {
					numSelected++;
				}
			}
			//Store number of selections of first team into array
			numSelectedArray[sel_index]= numSelected;
			sel_index++;
			var numSelected = 0;
			//Check the selections for the second team in row i
			for (var j = half_columns; j < this.cols; j++) {
				if (thisArray[i][j] == 1) {
					numSelected++;
				}
			}
			//Store number of selections of second team into array
			numSelectedArray[sel_index]=numSelected;
			sel_index++;
		}
		// Go through the number of selections for each team and calculate
		// the permutation count for the coupon selections
		for (var i = 0; i<numSelectedArray.length; i++) {
			permCount= permCount * numSelectedArray[i];
		}
		
	} else {
		permCount++;
		for (var i = 0; i < this.rows; i++) {
			var numSelected = 0;

			for (var j = 0; j < this.cols; j++) {
				if (thisArray[i][j] == 1) {
					numSelected++;
				}

			}
			totalSelections += numSelected;
			permCount = permCount * numSelected;
		}
	}

	// Set the stake values
	// This depends on whether we're a TCH bet or if we have partial stake values
	var partialStakeOptions = SA_jquery_cached("#"+this.game_id+"_stake_value select.partialStake")[0];
	if (this.game_sort == "TCH") {
		var permValue  = permCount * this.ccy_cost;
		var ccyTotalStake = permValue * this.num_subs;
		var totalStake = ccyTotalStake * this.exch_rate;
	} else if (this.has_partial_stake && partialStakeOptions) {
		var partialStake = partialStakeOptions.options[partialStakeOptions.selectedIndex].value;
		var permValue  = permCount * partialStake;
		var totalStake = permValue * this.num_subs;
	} else {
		var permValue  = permCount * this.cost;
		var totalStake = permValue * this.num_subs;
		var ccyTotalStake = totalStake * this.exch_rate;
	}

	if (totalSelections > this.max_picks) {
		permValue = 0;
	}

	this.updateValidationText();

	if (this.subviewActive()) {
		var subview_cost = SA_jquery_cached("div.coupon div.view.full tr.costRow td.cost.c"+this.subview_states.current, this.getElement());
	}

	var plan_btn = SA_jquery_cached("fieldset.planRadios div.flyWrap a.flyBtn.action", this.getElement());
	var plan_radios = SA_jquery_cached("input.plan_radio", this.getElement());
	
	var old_state = this.viewStates[this.cur_view].filled;

	var bet_allowed = false;
	if (permValue >= this.min_stake) {
		this.viewStates[this.cur_view].filled = "FILLED";
		plan_btn.addClass("inactive");
		plan_radios.attr("disabled", "disabled");

		if (this.subviewActive()) {
			if (this.show_ccy != 0) {
				subview_cost.html(this.ccy_sign + SA_formatCurrency(ccyTotalStake,true,1));
			} else {
				subview_cost.html(this.local_ccy_sign + SA_formatCurrency(totalStake,true,1));
			}
		}
		bet_allowed = true;
	} else {
		if (totalSelections > 0 ) {
			plan_radios.attr("disabled", "disabled");
			this.viewStates[this.cur_view].filled = "PART_FILLED";
			plan_btn.addClass("inactive");
		} else {
			plan_radios.removeAttr("disabled");
			this.viewStates[this.cur_view].filled = "EMPTY";
			plan_btn.removeClass("inactive");
		}
		if (this.subviewActive()) {
			subview_cost.html("");
		}
	}

	if (this.subviewActive()) {
		// This is used for TCH full view
		var totalPermValue = 0;
		var ccyTotalPermStake = 0;
		for (var i = 0; i < this.subview_ids.length; i++) {
			var curSubview = this.subview_states[this.subview_ids[i]];
			if (curSubview.filled == "FILLED") {
				if (this.game_sort == "TCH") {
					ccyTotalPermStake += parseFloat(curSubview.ccy_cost);
					totalPermValue = parseFloat(ccyTotalPermStake * this.exch_rate);
				} else {
					totalPermValue += parseFloat(curSubview.cost);
				}
			}
		}

		SA_jquery_cached("#"+this.game_id+"_stake_value .cost")[0].innerHTML = 
					this.local_ccy_sign + SA_formatCurrency(totalPermValue,true,1);

		if (this.game_sort == "TCH") {
			SA_jquery_cached("#"+this.game_id+"_stake_value .ccy_cost")[0].innerHTML = 
						this.ccy_sign + SA_formatCurrency(ccyTotalPermStake,true,1);
		}

	} else {
		// Used in TCH: Numbers view
		SA_jquery_cached("#"+this.game_id+"_stake_value .cost")[0].innerHTML = 
						this.local_ccy_sign + SA_formatCurrency(totalStake,true,1);
		if (this.game_sort == "TCH") {
			SA_jquery_cached("#"+this.game_id+"_stake_value .ccy_cost")[0].innerHTML = 
						this.ccy_sign + SA_formatCurrency(ccyTotalStake,true,1);
		}
	}

	// Manipulate the Stake/Min Stake text on the coupon footer
	// No selections, display Min Stake: XX
	if (!bet_allowed && (totalSelections == 0 ) ){
		if (this.game_sort == "TCH") {
			SA_jquery_cached("#"+this.game_id+"_stake_value .stake_legend")[0].innerHTML = "Min Stake:";
			SA_jquery_cached("#"+this.game_id+"_stake_value .ccy_cost")[0].innerHTML = 
					this.ccy_sign + SA_formatCurrency(this.ccy_cost,true,1);
			SA_jquery_cached("#"+this.game_id+"_stake_value .cost")[0].innerHTML = 
					this.local_ccy_sign + SA_formatCurrency(this.cost,true,1);
		} else {
			// This is the minimum bet stake
			SA_jquery_cached("#"+this.game_id+"_stake_value .cost")[0].innerHTML = 
					this.local_ccy_sign + SA_formatCurrency(0,true,1);
		}
		SA_jquery_cached("#"+this.game_id+"_time_left")[0].style.display = "block";
		SA_jquery_cached("#"+this.game_id+"_legend")[0].style.display = "none";
	// Some selections but not enough to place a bet, display Min Stake: 0
	} else if (!bet_allowed && (totalSelections > 0)) {
		if (this.game_sort == "TCH") {
			SA_jquery_cached("#"+this.game_id+"_stake_value .stake_legend")[0].innerHTML = "Stake:";
		}
	}

	if (this.game_sort != "TCH") {
		SA_jquery_cached("#"+this.game_id+"_stake_value .lineCount")[0].innerHTML = permCount + " lines @ ";
	}

	// ANDREW: What does this check do?
	if (old_state == this.viewStates[this.cur_view].filled) {
		return permValue;
	}


	if (this.subviewActive()) {
		var states = this.checkSubviewStates();
		if (states == "CORRECT_AND_EMPTY" ||
				states == "ALL_CORRECT") {
				bet_allowed = true;
		}
		if (states == "CORRECT_AND_EMPTY") {
			this.selectNextEmptySubview();
		}
	}

	// If a bet has been properly prepared, then we'll prepare the messages as such
	// For example: Dipslay/Hide the Game and Closes/Cover your odds message
	if (bet_allowed) {
		if (this.game_sort == "TCH") {
			SA_jquery_cached("#"+this.game_id+"_stake_value .stake_legend")[0].innerHTML = "Stake:";
		} else {
			SA_jquery_cached("#"+this.game_id+"_time_left")[0].style.display = "none";
			SA_jquery_cached("#"+this.game_id+"_legend")[0].style.display = "block";
		}

		// Turn your buttons on
		this.toggleButton("AddToSlipBtn","enable");
		this.toggleButton("BetNowBtn","enable");
	} else {
		SA_jquery_cached("#"+this.game_id+"_time_left")[0].style.display = "block";
		SA_jquery_cached("#"+this.game_id+"_legend")[0].style.display = "none";

		// Turn your buttons off
		this.toggleButton("AddToSlipBtn","disable");
		this.toggleButton("BetNowBtn","disable");
	}

	return permValue;
}

// Function for toggling certain buttons on or off
SA_Coupon.prototype.toggleButton = function (buttonName, action) {
	var query = "#"+buttonName+"_"+this.game_id;
	var button = SA_jquery_cached(query)[0];
	if (button) {
		if ((!action && !button.disabled) || action == "enable") {
			button.disabled = false;
			button.className = "";
		} else {
			var disabled = document.createAttribute("disabled");
			disabled.value = "disabled";
			button.disabled = true;
			button.className = "disabled";
			button.attributes.setNamedItem(disabled);
		}
	}
}

// Removes this coupon from the page and JS object stores
SA_Coupon.prototype.remove = function () {
	clearTimeout(this.thread);

	var id_idx = -1;
	for (var i = 0; i < SA_coupon_ids.length; i++) {
		if (SA_coupon_ids[i] == this.game_id) {
			id_idx = i;
			break;
		}
	}

	SA_coupon_ids.splice(id_idx,1);
	delete SA_coupons['cpn_'+this.game_id];
	var element = this.getElement();
	element.parentNode.removeChild(element);
	SA_flush_DOM_cache(this.game_id);
}


// Handles a click event from a coupon's close/remove button.
// Removes the coupon from the page, calls for a check on whether enough coupons have been deleted and deals with some 
SA_Coupon.prototype.removeCouponClicked = function () {
	var saxgame_id = this.game_id;
	this.remove();
	SA_couponDeleted(saxgame_id);
	$("#simpleTooltip").remove();
}



// Sets a checkbox value across all views
//
// This is goverened by the "data_id_format" element in the JSON object. If
// there are different views, the has_format_type flag should be set to true
// and the format_types array should be populated with a set of values be
// inserted when looking up the HTML IDs of the containers for the views.
// The IDs are be formatted as:
//
// DATA_<view>_<saxgame_id>_<x>_<y>
SA_Coupon.prototype.setCheckbox = function (x, y, value) {
	var curView = this.cur_view;
	if (curView == "default") {
		var viewString = "";
	} else {
		var viewString = curView+"_";
	}

	if (this.data_id_format.has_format_type) {
		formats = this.data_id_format.format_types;
		for (var i = 0; i < formats.length; i++) {
			id = 'DATA_'+viewString+formats[i]+'_'+this.game_id+'_'+x+'_'+y;
			SA_cachedElementById(id).checked = value;
		}
	} else {
		var id = 'DATA_'+viewString+this.game_id+'_'+x+'_'+y;
		var element = SA_cachedElementById(id);
		if (element) {
			element.checked = value;
		} else {
			printfire(id+" not found");
		}
	}

	if (this.has_balls) {
		var context = "";
		if (this.cur_view != "default") {
			context = "."+this.cur_view+" ";
		}
		
		var jq_query = "sBall_"+viewString+this.game_id+"_"+x+"_"+y
		var element = $(SA_cachedElementById(jq_query));
		if (value) {
			element.addClass("selected");
		} else {
			element.removeClass("selected");
		}
	}
}

// Click handler for the classic pools coupon's full view. Makes sure the
// correct subview is being used.
//
// Is called by a wrapper in the jquery js file - might be a bit finnicky about
// changes to the id format.
SA_Coupon.prototype.tchFullViewClick = function (subview, x, y) {
	var selection_is_valid = true;
	if (this.subview_states.current != subview) {
		selection_is_valid = this.switchSubView(subview);
	}
	if (selection_is_valid) {
		this.buttonClick(x, y);
		return true;
	} else {
		return false;
	}
	// Hide the Add to Betslip button
}

// Selects a different subview (eg: a new column in TCH's full view).
// no_view_change stops any change of selected view occuring
SA_Coupon.prototype.switchSubView = function (subview, no_view_change) {
	if (!this.subviewActive()) {
		var firstSubView = this.subview_states.prefix + this.subview_ids[0];
		this.changeView(firstSubView);
	}
	if (this.viewStates[this.cur_view].filled != "PART_FILLED") {
		this.viewStates[this.cur_view].num_picks = this.num_picks;
		this.num_picks = this.subview_states[subview].num_picks;
		if (!no_view_change) {
			this.cur_view = this.subview_states.prefix+subview;
		}
		this.subview_states.current = subview;
		var ele = SA_jquery_cached(".coupon .view.full", this.getElement());
		for (var subview_idx in this.subview_ids) {
			ele.removeClass("c"+this.subview_ids[subview_idx]);
		}
		ele.addClass("c"+subview);

		var ele1 = SA_jquery_cached(".coupon .view.full .planRadios legend", this.getElement());
		
		var retVal = this.switchPlan(this.subview_states[subview].plan, true);
		var plan_name = this.plans[this.subview_states[subview].plan].name;
		ele1.text(SA_XL_sprintf("SA_COLUMN_PLAN_HEADER", this.subview_states.current, plan_name));
		return retVal;
	} else {
		alert(SA_XL_sprintf("SA_PARTIAL_COLUMN_WARNING", this.subview_states.current));
		return false;
	}
}

// Switch the current view to use a new plan.
// force stops the function from rejecting a change should there be too many
// selections for the new plan.
SA_Coupon.prototype.switchPlan = function (plan_id, force) {
	var newPlan = this.plans[plan_id];
	var success = true;

	if (this.num_picks > newPlan.num_picks) {
		if (!force) {
			return false;
		}
		success = false;
	}

	this.min_picks = newPlan.num_picks;
	this.max_picks = newPlan.num_picks;
	this.cost      = newPlan.stake;
	this.ccy_cost  = newPlan.ccy_stake;
	var thisState  = this.viewStates[this.cur_view];
	thisState.plan = plan_id;
	thisState.cost = this.cost;
	thisState.ccy_cost = this.ccy_cost;
	
	var tab = this.cur_view;
	if (this.subviewActive()) {
		tab = this.subview_tab;
	}
	var checkboxes = SA_jquery_cached("#plan-"+plan_id+"-"+this.game_id+"-"+tab);
	if (checkboxes.length > 0) {
		checkboxes[0].checked = true;
	} else {
		SA_jquery_cached("#cpn_"+this.game_id+" .view.full .planRadios input:radio")
			.each(function (index, element) {
				element.checked = false;
			});
	}

	var ele1 = SA_jquery_cached(".view.full .planRadios legend", this.getElement());
	ele1.text(SA_XL_sprintf("SA_COLUMN_PLAN_HEADER", this.subview_states.current, this.plans[plan_id].name));

	this.updateSubscriptionPrice();

	this.permutationFormula();
	return success;
}

// Update the subscriptions div with the correct prices
// depending on the selected plan. i.e.
//   50 comps @ 1.00 (8 from 10) == £50
//   50 comps @ 2.75 (8 from 11) == £137.50
//   etc
SA_Coupon.prototype.updateSubscriptionPrice = function (){
	nsubs = $('#cpn_' + this.game_id + ' div.subs input:radio');
	for(var i=0; i<nsubs.length ; i++) {
		formatted_value = SA_formatCurrency(nsubs[i].value*this.cost,1,1)
		$('#subs_cost_' + this.game_id + '_' + nsubs[i].value).text(formatted_value);
	}
}


// Switches the active view - changes state keeping functionality, display
// is handled elsewhere
SA_Coupon.prototype.changeView = function (newView) {

	if (newView == this.cur_view) {
		// We're already using this view, no need to change
		return false;

	} else if (this.has_subviews) {
		// If we're working with subviews, we still might not need to switch
		var subview_match = this.subview_states.prefix+"*";
		var new_is_subview = newView.match(subview_match)
		var old_is_subview = this.cur_view.match(subview_match);
		if (new_is_subview && old_is_subview) {
			return false;
		}
	}

	if (this.num_picks > 0 ||
			(this.subviewActive() && this.checkSubviewStates() != "ALL_EMPTY")
			) {

		var confirmed = confirm(SA_XL("SA_CHANGE_VIEW_LOSE_SELECTIONS"));
		if (!confirmed) {
			return false
		}

		this.clearAllViews();
	}

	SA_jquery_cached("form", this.getElement()).each(function (index, element) {
			element.reset();
			});

	this.num_subs = 1;

	this.viewStates[this.cur_view].num_picks = this.num_picks;
	this.num_picks = this.viewStates[newView].num_picks;

	// Make sure we're on the default subview, if we have them
	if (this.subviewActive()) {
		var def_subview = this.subview_ids[0];
		this.switchSubView(def_subview, true);
	}

	if (this.has_plans) {
		this.permutationFormula();
		this.switchPlan(this.plans.defaultPlan);
		var tab = this.cur_view;
		if (this.subviewActive()) {
			tab = this.subview_tab;
		}
		SA_jquery_cached("#plan-"+this.plans.defaultPlan+"-"+this.game_id+"-"+tab)[0].checked = true;
	}
	
	return true;

}

// Checks to see if a subview is currently being used as the active view.
// Will always return false if subviews are not used in this coupon type
SA_Coupon.prototype.subviewActive = function () {
	if (this.has_subviews) {
		var match = this.subview_states.prefix+"*";
		// Wrap the current view in a string object so IE doesn't complain
		var retval = new String(this.cur_view).match(match);
		return retval;
	} else {
		return false;
	}
}

// Updates the validation text by the Add to Slip button (currently only used
// on TCH coupons)
SA_Coupon.prototype.updateValidationText = function () {
	// Replace with logic passed via JSON:
	if (this.game_sort != "TCH") {
		return;
	}


	var cpn_ele = this.getElement();

	var picks_left = this.min_picks - (this.num_picks)
	var newValElement;
	var xLation = "";
	var plural_sel = false;
	if (picks_left > 0) {
		if (picks_left == 1) {
			xLation = "SA_MORE_SELS_FOR_ENTRY_SIN";
		} else {
			xLation = "SA_MORE_SELS_FOR_ENTRY_PLU";
			plural_sel = true;
		}
	} else if (picks_left < 0) {
		if (picks_left == -1) {
			xLation = "SA_LESS_SELS_FOR_ENTRY_SIN";
		} else {
			xLation = "SA_LESS_SELS_FOR_ENTRY_PLU";
			plural_sel = true;
		}
	} else {
		xLation = "SA_MORE_SELS_FOR_ENTRY_NONE";
	}
	if (plural_sel == true) {
		var text = SA_XL_sprintf(xLation, Math.abs(picks_left), this.ccy_sign, Number(this.ccy_cost));
	} else {
		var text = SA_XL_sprintf(xLation, this.ccy_sign, Number(this.ccy_cost));
	}
	var validationTextElement = SA_cachedElementById("validation_"+this.game_id);
	if (!validationTextElement) {
		validationTextElement = document.createElement("h5");
		validationTextElement.id = "validation_"+this.game_id;
		validationTextElement.className = "validation";
		
		//$(".box .footer .validation", cpn_ele).remove();
		$(validationTextElement).insertBefore(SA_jquery_cached("form.box > div.footer > div.clr", cpn_ele));
//var footerSelCount = SA_jquery_cached(".remainingSels", newValElement);
	}
	validationTextElement.innerHTML = text;

	if (this.subviewActive()) {

		// Set the text above and below the selectiont to match the correct
		// column being selected
		var ele1 = SA_jquery_cached(".coupon .view.full .planRadios span.columnName", cpn_ele);
		var ele2 = SA_jquery_cached("span.columnName", newValElement);
		ele1.add(ele2).text(this.subview_states.current);
	}

	var price_span = SA_jquery_cached("span.stake", newValElement);
	if (this.show_ccy != 0) {
		price_span.html(this.ccy_sign + SA_formatCurrency(this.ccy_cost,true,1));
	} else {
		price_span.html(this.local_ccy_sign + SA_formatCurrency(this.cost,true,1));
	}
}

// Checks the states of each subview for validity.
// Returns one of:
// * ALL_CORRECT       - All subviews are filled correctly (Bet possible)
// * CORRECT_AND_EMPTY - Some subviews are filled correctly, all others are
//                       empty (Bet possible)
// * ALL_EMPTY         - No selections have been made in any subviews (No bet)
// * INVALID           - One or more subview is incorrectly filled (No bet)
SA_Coupon.prototype.checkSubviewStates = function() {
	var num_filled = 0;

	// Loop through all subviews.
	for (var i = 0; i < this.subview_ids.length; i++) {
		var this_subview_id = this.subview_ids[i];
		var this_subview = this.subview_states[this_subview_id];
		switch (this_subview.filled) {
			case "FILLED":
				num_filled++;
				break;

			// Return if we've got an badly filled subview, nothing else we
			// find changes the result
			case "PART_FILLED":
				return "INVALID";
		}
	}

	if (num_filled == this.subview_ids.length) {
		return "ALL_CORRECT";
	} else if (num_filled > 0) {
		return "CORRECT_AND_EMPTY";
	} else {
		return "ALL_EMPTY";
	}
}

// Makes the next empty subview active. Called once a subview is filled in.
SA_Coupon.prototype.selectNextEmptySubview = function () {
	for (var i = 0; i < this.subview_ids.length; i++) {
		var this_subview_id = this.subview_ids[i];
		var this_subview = this.subview_states[this_subview_id];
		if (this_subview.filled == "EMPTY") {
			this.switchSubView(this_subview_id);
			return true;
		}
	}
	return false;
}

// Update the number of subscriptions being used for the current view.
SA_Coupon.prototype.updateSubscriptionCount = function () {
	// We want to remember the element, so we can default back later
	this.selected_subs_ele = $(".flyOver.subs input:radio:checked", this.getElement())[0];
	this.num_subs = this.selected_subs_ele.value;
	this.permutationFormula();
}

// Resets the selection for the "subscriptions" menu
SA_Coupon.prototype.resetSubscriptionCountSelection = function () {
	// We want to remember the element, so we can default back later
	if (this.selected_subs_ele == null) {
		this.selected_subs_ele = SA_jquery_cached(".flyOver.subs input:radio", this.getElement())[0];
	}
	this.selected_subs_ele.checked = true;
	this.num_subs = this.selected_subs_ele.value;
}

// Utility functions


// Formats a given number into a currency-style string, eg: £1
// addDecimal will add a "cent"-style figure after the number, eg: £1.99
function SA_formatCurrency(num, addDecimal, noCurSymbol) {

	var num = num.toString().replace(/\£|&pound;|\,/g,'');

	if (isNaN(num)) num = "0";

	var sign = (num == (num = Math.abs(num)));
	num = Math.floor(num * 100 + 0.50000000001);
	var cents = num % 100;
	num = Math.floor(num / 100).toString();

	if (cents < 10) cents = "0" + cents;

	for (var i = 0; i < Math.floor((num.length - (1 + i)) / 3); i++) {
		num = num.substring(0, num.length - (4 * i + 3)) + ',' +
		num.substring(num.length - (4 * i + 3));
	}

	if (typeof(noCurSymbol) != "undefined" && noCurSymbol) {
		currency = '';
	} else {
		currency = '&pound;';
	}

	if (typeof(addDecimal) != "undefined" && !addDecimal) {
		return (((sign)?'':'-') + currency + num);
	} else {
		return (((sign)?'':'-') + currency + num + '.' + cents);
	}
}


// Calculates the factorial value of a number
// eg: num = 3, returns 3*2*1=6
function SA_factorial(num) {
	if (num == 0)
		return 1;
	else if (num < 0)
		throw("Number type not supported: "+num);
	else
		return num*(SA_factorial(num-1));
}





// Stores DOM references cached by SA_cachedElementById
SA_cachedElementIds = new Array();

// A cached version of document.getElementById
// Stores the results of each call in SA_cachedElementIds.
// Warning: The cache doesn't get updated if the element is removed from the
// DOM. This might cause troubles if you replace elements with .innerHTML
SA_cachedElementById = function (id) {
	if (!SA_cachedElementIds[id]) {
		var return_val = document.getElementById(id);
		if (return_val == null) {
			return null;
		}
		SA_cachedElementIds[id] = return_val;
	}
	return SA_cachedElementIds[id];
}





// A cache array for jQuery calls given no context.
SA_jQueryCacheNoContext = new Array();

// This function allows for the caching of jquery DOM traversal calls - when a
// query is performed, it stores the result by the context and query text.
// Arguments:
// * query - a jQuery format query to run, eg: "table.contents th"
// * context - (Optional) a DOM element representing the root for where this
//             query should be performed. Default is the same as that of
//             jQuery, ie: the document root.
SA_jquery_cached = function(query, context) {

	var origquery = query;

	// If we weren't given a context, use the global store.
	if (!context) {
		context = SA_jQueryCacheNoContext;
	}

	// If we haven't got a cache for this element, make one.
	if (!context.SA_jQ_Cache) {
		context.SA_jQ_Cache = new Array();
	}

	// If the cache doesn't contain this call, perform the casll and save the
	// result.
	if (typeof(context.SA_jQ_Cache[query]) == "undefined") {
		var query_result = null;
		if (context == SA_jQueryCacheNoContext) {
			query_result = $(origquery);
		} else {
			query_result = $(origquery, context);
		}
		if (query_result.length > 0) {
			context.SA_jQ_Cache[query] = query_result;
		} else {
			return query_result;
		}
	}

	// Return our cache entry
	return context.SA_jQ_Cache[query];
}

// Uncomment this line to completely disable jquery caching:
//SA_jquery_cached = $;


// Flushes the DOM cache (SA_jquery_cached and SA_cachedElementById) for a
// given coupon
function SA_flush_DOM_cache (saxgame_id) {
	if (!saxgame_id) {
		SA_cachedElementIds = new Array();
		SA_jQueryCacheNoContext = new Array();
		return;
	}
	var id_regex = new RegExp("(_|#)"+saxgame_id+"(_|$| )");
	for (id in SA_cachedElementIds) {
		if (id.match(id_regex)) {
			delete SA_cachedElementIds[id];
		}
	}
	for (qry in SA_jQueryCacheNoContext.SA_jQ_Cache) {
		if (qry.match(id_regex)) {
			delete SA_jQueryCacheNoContext.SA_jQ_Cache[qry];
		}
	}
}


function SA_XL (code) {
	if (!SA_XLs[code]) {
		return code;
	}
	return SA_XLs[code];
}
function SA_XL_sprintf() {
	if (arguments.length < 1) {
		return "";
	}
	var args = arguments;
	args[0] = SA_XL(args[0]);
	return sprintf(args);
}




/**
 * sprintf() for JavaScript v.0.4
 *
 * Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
 * Thanks to David Baird (unit test and patch).
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */

function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }

// Modified to take its arguments as an array, to avoid an eval
function sprintf (arguments) {
  var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
  while (f) {
    if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
    else if (m = /^\x25{2}/.exec(f)) o.push('%');
    else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
      if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
      if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
        throw("Expecting number but found " + typeof(a));
      switch (m[7]) {
        case 'b': a = a.toString(2); break;
        case 'c': a = String.fromCharCode(a); break;
        case 'd': a = parseInt(a); break;
        case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
        case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
        case 'o': a = a.toString(8); break;
        case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
        case 'u': a = Math.abs(a); break;
        case 'x': a = a.toString(16); break;
        case 'X': a = a.toString(16).toUpperCase(); break;
      }
      a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
      c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
      x = m[5] - String(a).length;
      p = m[5] ? str_repeat(c, x) : '';
      o.push(m[4] ? a + p : p + a);
    }
    else throw ("Huh ?!");
    f = f.substring(m[0].length);
  }
  return o.join('');
}





/*
 * From:
 * http://james.padolsey.com/javascript/asynchronous-innerhtml/
 *
 * Writes innerHTML in an async manner, gives a smoother feel when writing huge
 * amounts of HTML into the page.
 */
function SA_delayedInnerHTML(couponData, HTML, callback) {
	// Create an element to hold our data and a separated document to hold the
	// element
    var temp = document.createElement('div');
    var frag = document.createDocumentFragment();

	frag.appendChild(temp);

	// Change the contents of our element to be our new HTML
    temp.innerHTML = HTML;

	document.getElementById("coupon_container").appendChild(temp.childNodes[0]);


	// Throwback from SA_delayedInnerHTML usage - switching back at some point
	// would be nice:
	//
	// Send the browser away to process the document changes without locking
	// things up
    (function(){
//        if(temp.firstChild){
//            frag.appendChild(temp.firstChild);
//            setTimeout(arguments.callee, 0);
//        } else {
            callback(couponData, frag);
//        }
    })();
}

// Add bets to the bet slip cookie.
SA_Coupon.prototype.addToSlip = function (action,bet_num) {

	var bet_properties = new Array();

	var partialStakeOptions = SA_jquery_cached("#"+this.game_id+"_stake_value select.partialStake")[0];
	if (this.game_sort != "TCH" && partialStakeOptions) {
		var partial_stake = partialStakeOptions.options[partialStakeOptions.selectedIndex].value;
	} else {
		var partial_stake = parseFloat("0.00").toFixed(2)
	}

	if (this.subviewActive()) {
		bet_properties['saxgame_id']    = "";
		bet_properties['cost']          = "";
		bet_properties['subs']          = "";
		bet_properties['plan_id']       = "";
		bet_properties['selections']    = "";
		bet_properties['included']      = "";
		bet_properties['partial_stake'] = "";
		bet_properties['ccy_code']      = "";
		bet_properties['exch_rate']     = "";

		for (var i = 0; i < this.subview_ids.length; i++) {
			
			var separator = "|";
			if (i == 0) separator = "";
			var prefix = this.subview_states.prefix;

			var thisSubview = this.subview_states[this.subview_ids[i]];

			// Move to the next entry if this one isn't ready to use
			if (thisSubview.filled != "FILLED") continue;

			bet_properties['saxgame_id'] += separator+this.game_id;

			if (this.game_sort == "TCH" && this.show_ccy != 0) {
				bet_properties['cost']       += separator+thisSubview.ccy_cost;
			} else {
				bet_properties['cost']       += separator+thisSubview.cost;
			}
			bet_properties['subs']          += separator+this.getNumSubs();
			bet_properties['plan_id']       += separator+thisSubview.plan;
			bet_properties['selections']    += separator+this.getSelectionsString(prefix+this.subview_ids[i]);
			bet_properties['included']      += separator+1;
			bet_properties['partial_stake'] += separator+partial_stake;
			bet_properties['ccy_code']      += separator+this.ccy_code;
			bet_properties['exch_rate']     += separator+this.exch_rate;

		}

	} else {
		// Get required data for each subview.
		bet_properties['saxgame_id']    = this.game_id;
		bet_properties['cost']          = this.permutationFormula().toFixed(2);
		bet_properties['subs']          = this.getNumSubs();
		bet_properties['plan_id']       = this.getPlanId();
		bet_properties['selections']    = this.getSelectionsString();
		bet_properties['included']      = 1;
		bet_properties['partial_stake'] = partial_stake;
		bet_properties['ccy_code']      = this.ccy_code;
		bet_properties['exch_rate']     = this.exch_rate;
	}

	if (action == 'edit') {
		var handler = 'GoPoolsEditSlip';
		bet_properties['bet_num'] = bet_num;
	} else {
		var handler = 'GoPoolsAddToSlip';
		bet_properties['bet_num'] = '';
	}

	// *************************************************************
	// It is Important that the order of these items is NOT changed!
	// *************************************************************
	// Construct key-value pairs.
	var url = "?action="+handler
	+"&saxgame_id="    + bet_properties['saxgame_id']
	+"&cost="          + bet_properties['cost']
	+"&num_subs="      + bet_properties['subs']
	+"&plan_id="       + bet_properties['plan_id']
	+"&selections="    + bet_properties['selections']
	+"&included="      + bet_properties['included']
	+"&partial_stake=" + bet_properties['partial_stake']
	+"&ccy_code="      + bet_properties['ccy_code']
	+"&exch_rate="     + bet_properties['exch_rate']
	+"&bet_num="       + bet_properties['bet_num']
	;
	// *************************************************************


	// Pretty safe to assume this is only going to be an issue for additions.
	if (action == 'add') {
		// Test the size of the cookie and only assign where it can fit.
		var cookie_contents = getCookie(SA_BETSLIP_COOKIE_NAME);

		if (cookie_contents == null) {
			var cur_cookie_length = 0;
		} else {
			var cur_cookie_length = cookie_contents.length;
		}

		var cookie_addition = SA_BETSLIP_SEPARATOR + bet_properties.join('|');
		var new_length = cur_cookie_length + cookie_addition.length;

		// E.g. 2kb of ASCII. Can realistically increase up to around 2045 - some browsers stop at
		// 2046/2047. In the unlikely even this ever becomes an issue we may consider creating
		// further cookies to store the information.
		if (new_length > SA_COOKIE_MAX_SIZE) {
			alert('Betslip size limit reached. Selection not added.');
			return;
		}
	}

	var target_game_id = this.game_id;

	if (action == 'edit') {
		//SA_get_all_coupons(SA_calcNumNewCoupons(), true);
		document.getElementById("AddToSlipBtn_"+this.game_id).onclick = function () { SA_coupons['cpn_'+target_game_id].addToSlip("add"); };

		SA_get_coupons_by_criteria("id", SA_betslip_old_coupons,SA_MAX_COUPONS_HARD,1);
		SA_moreCouponsFetchParams = SA_betslip_old_get_state;

		SA_betslip_old_coupons = null;
		SA_betslip_old_get_state = null;
	
		$("#SA_MoreCouponsButton").show();
		$("#SA_BackToCouponsButton").hide();
	}

	var r = SA_request_broker;
	r.send( {
		'priority'        : r.COUPONS_PRI,
		'method'          : 'GET',
		'url'             : url,
		'callback'        : SA_betsAddToSlipCallback,
		'debug'           : true,
		'nocache_harsh'   : true,
		'timeoutmsecs'    : SA_REQUEST_TIMEOUT,
		'timeoutcallback' : function(){
						showPanelNoEvent($("#panelPlaceBetsTimeout"), true);
				    }
	} );

	if (action != 'edit') {
		this.clearAllViews();
	}
	$("#simpleTooltip").remove();

}


/*
 * Outputs all useful+available state into a string.
 * See SA_saveCouponState.
 */
SA_Coupon.prototype.getCookieSelectionString = function () {
	var selection_string = "";
	var selection_sets = new Array();
	var views = this.views;
	for (var i = 0; i < views.length; i++) {
		var thisViewName = views[i];
		var thisView = this.viewStates[thisViewName];
		var plan_string = "";
		if (this.has_plans) {
			plan_string = "p:"+thisView.plan+"|";
		}
		if (thisView.filled != "EMPTY" && thisView.filled != "NEW") {
			selection_sets.push( thisViewName+"|"+plan_string+this.getSelectionsString(thisViewName) );
		}
	}
	selection_string = selection_sets.join("|");
	var state_string = "c:"+this.game_id + "|" + selection_string;
	return state_string;
}


/*
 * Reads the state information held in the given string and loads it.
 * See SA_loadCouponState.
 */
SA_Coupon.prototype.loadStateFromString = function (state_string) {
	var sections = state_string.split("|");

	if (sections.length < 3) {
		printfire("String doesn't contain enough information to be useful - skipping.");
		return;
	}

	this.clearAllViews();

	var cpn_id_section = sections.shift();
	var game_id = cpn_id_section.substring(2);


	/*
	 * Flow of this loop should be roughly as follows:
	 *
	 * 1. If state is "start", read in a view name, set state to
	 *    "got_view_name" then start the loop again.
	 * 2. Check to see if the entry starts with "p:" - if it does, store the
	 *    rest of the value as a plan id and start the loop again.
	 * 3. If we've got this far, we should have a set of selections. Split them
	 *    by c, store them as selections, then start the loop again.
	 */

	var old_view = this.cur_view;
	var current_view = null;
	var loop_state = "start";
	while (sections.length > 0) {
		var cur_section = sections.shift();
		if (loop_state == "start") {

			var newTab = cur_section;
			if (this.has_plans) {
				var prefix = this.subview_states.prefix;
				if (newTab.substr(0,prefix.length) == prefix) {
					newTab = this.subview_tab;
					this.switchSubView(cur_section.substr(prefix.length));
				} else {
					this.changeView( cur_section );
				}
			} else {
				this.changeView( cur_section );
			}
			this.cur_view = cur_section;

			this.changeTab( newTab, true );
			current_view = this.viewStates[cur_section];
			loop_state = "got_view_name";
		} else if (cur_section.substring(0,2) == "p:") {
			this.switchPlan(cur_section.substring(2));
		} else {
			var selections = cur_section.split("c");
			for (var i = 0; i < selections.length; i++) {
				var cur_sel = selections[i];
				cur_sel--;
				var y = cur_sel % this.cols;
				var x = Math.floor(cur_sel / this.cols);
				this.setCheckbox(x,y,true);
				current_view.selections[x][y] = 1;
				//this.buttonClick(x,y);
			}

			this.permutationFormula();
			loop_state = "start";
		}
	}
	//this.cur_view = old_view;

}



/*
 * Switch tabs within this coupon - only handles visual changes.
 * If quick is true, doesn't bother animating
 */
SA_Coupon.prototype.changeTab = function (newClass, quick) {
	this.checkCompleted();

	var game = this.getElement();
	var coupon = $(game).find("div.coupon");
	var width = $("#Body .games .c1x1").css("width");
	var height = $("#Body .games .c1x1").css("height");
	
	if (newClass == "full") {

		this.switchSlipButton('hide');
		$(game).addClass("full");
		width = $("#Body .games .c3x2").css("width");
		height = $("#Body .games .c3x2").css("height");
	} else {
		this.switchSlipButton('show');
		$(game).removeClass("full");
	}

	$(coupon).removeClass("fixtures numbers full").addClass(newClass);
	if (quick) {
		$(game).css("width", width);
		$(game).css("height", height);
	} else {
		expandCoupon(game, width, height);
	}
}



SA_COUPON_STATE_COOKIE_NAME = "SA_CPN_STATE";

SA_COUPON_MAX_COOKIE_SIZE = "2047";



/*
 * Checks to see if the given coupon exists in the state cookie.
 */
SA_couponStateExists = function(xgame_id) {
	var cookie_data = getCoupon(SA_COUPON_STATE_COOKIE_NAME);
	var coupon_states = cookie_data.split("\n");
	var prefix = "c:"+xgame_id+"|";
	for (var i = 0; i < coupon_states.length; i++) {
		if (coupon_states[i].substr(0,prefix.length) == prefix) {
			return true;
		}
	}
	return false;
}



/*
 * Removes the given coupon from the state cookie.
 */
SA_deleteCouponState = function (xgame_id) {
	var cookie_data = getCookie(SA_COUPON_STATE_COOKIE_NAME);
	if (cookie_data == null) return false;
	var coupon_states = cookie_data.split("\n");
	var prefix = "c:"+xgame_id+"|";
	var changed = false;
	for (var i = 0; i < coupon_states.length; i++) {
		if (coupon_states[i] == "" ||
				prefix == coupon_states[i].substr(0,prefix.length)) {
			coupon_states.splice(i,1);
			changed = true;
			i--;
		}
	}
	if (!changed) {
		return false;
	}
	cookie_data = coupon_states.join("\n");
	setCookie(SA_COUPON_STATE_COOKIE_NAME, cookie_data, null, "/");
}



/*
 * Saves the state for the given coupon into the state cookie.
 * Cleans out old stuff if we're running low on space.
 *
 * This always adds the current coupon to the end of the cookie - it NEVER
 * updates something in-place. This has the useful side-effect of letting us
 * tell which coupons haven't been used recently - they're the first ones in
 * the cookie.
 */
SA_saveCouponState = function (xgame_id) {
	var cpn_obj = SA_coupons['cpn_'+xgame_id];

	// If we've not finished loading yet, stop trying!
	if (!cpn_obj) return false;
	if (!cpn_obj.loaded) return false;

	SA_deleteCouponState(xgame_id);
	var cookie_data = getCookie(SA_COUPON_STATE_COOKIE_NAME);
	if (cookie_data == null) cookie_data = "";
	else cookie_data += "\n";
	cookie_data += cpn_obj.getCookieSelectionString();
	cookie_data = SA_cleanupCookieData(cookie_data);
	setCookie(SA_COUPON_STATE_COOKIE_NAME, cookie_data, null, "/");
}



/*
 * Loads the state for the given coupon from the state cookie.
 */
SA_loadCouponState = function (xgame_id) {
	if (!SA_coupons['cpn_'+xgame_id]) return false;
	var cookie_data = getCookie(SA_COUPON_STATE_COOKIE_NAME);
	if (cookie_data == null) return false;
	var lines = cookie_data.split("\n");
	for (var i = 0; i < lines.length; i++) {
		if (lines[i].indexOf("|") == lines[i].lastIndexOf("|")) {
			continue;
		}
		var data = lines[i].split("|");
		if (data[0] == "c:"+xgame_id) {
			SA_coupons['cpn_'+xgame_id].loadStateFromString(lines[i]);
		}
	}
}



/*
 * Check to see if the cookie's too long - if it is, start deleting data we
 * don't want/need. The order of deleted data is:
 *
 * 1. Blank lines
 * 2. Lines with only one segment (ie: data for a coupon with no selections)
 * 3. Old lines (ie: beginning with the first line of the string, as new items
 *    are appended rather than inserted)
 *
 * Deleting stops when we're back down below SA_COUPON_MAX_COOKIE_SIZE.
 */
SA_cleanupCookieData = function (data) {
	// If we're already short enough, don't bother processing
	if (data.length < SA_COUPON_MAX_COOKIE_SIZE) return data;

	var lines = data.split("\n");
	var datalength = data.length;

	// 1. Blank lines
	for (var i = 0; i < lines.length; i++) {
		if (lines[i] == "") {
			datalength -= lines.splice(i,1).length;
			i--;
			
			if (datalength < SA_COUPON_MAX_COOKIE_SIZE) return lines.join("\n");
		}
	}

	// 1. Blank lines
	for (var i = 0; i < lines.length; i++) {
		if (lines[i].indexOf("|") == lines[i].lastIndexOf("|")) {
			datalength -= lines.splice(i,1).length;
			i--;
			if (datalength < SA_COUPON_MAX_COOKIE_SIZE) return lines.join("\n");
		}
	}

	// 1. Blank lines
	while (lines.length > 0) {
		datalength -= lines.shift().length;
		if (datalength < SA_COUPON_MAX_COOKIE_SIZE) return lines.join("\n");
	}
}


function SA_refreshCouponBetCounts() {

	var cookie_content = getCookie("BET_COUNTS");
	if (cookie_content == null)
		return;
	var lines = cookie_content.split("|");

	for (var i = 0; i < lines.length; i++) {
		var parts = lines[i].split(" ");
		var game = parts[0];
		var sub_count = parts[1];

		if (SA_coupons['cpn_'+game] && sub_count > 0) {
			$("#cpn_"+game).addClass("submitted");
			$("#submit_count_"+game).text(sub_count);
		} else {
			$("#cpn_"+game).removeClass("submitted");
		}
	}
}


// Outputs close date in user's timezone
SA_Coupon.prototype.write_coupon_date = function(utc_seconds, span_id, gamesort, show_timezone) {
	// Get time
	var local = new Date(utc_seconds*1000);
	// Split time
	var Y = local.getFullYear();
	var M = local.getMonth() + 1;
	var D = local.getDate();
	var h = local.getHours();
	var m = local.getMinutes();
	// Add zeroes
	M = M < 10 ? "0" + M : M;
	D = D < 10 ? "0" + D : D;
	h = h < 10 ? "0" + h : h;
	m = m < 10 ? "0" + m : m;
	// Write time
	var str = D + "/" + M + "/" + Y; 
	if (gamesort == "TCH") {
		str += " " + h + ":" + m;
		if (show_timezone) {
			var offset = local.getTimezoneOffset()/-60;
			if (offset > 0)
				str += " (UTC+" + offset + ")";
			else if (offset < 0)
				str += " (UTC" + offset + ")";
			else
				str += " (UTC)";
		}
	}
	SA_jquery_cached("#" + span_id)[0].innerHTML = str;
}




/*
    This section of code taken from public domain work at: http://www.JSON.org/json2.js
    2010-03-20
    See http://www.JSON.org/js.html
        
		JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

*/

if(!this.JSON){this.JSON={};

var jsonParse = (function () {
  var number
      = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
  var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
      + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
  var string = '(?:\"' + oneChar + '*\")';

  // Will match a value in a well-formed JSON file.
  // If the input is not well-formed, may match strangely, but not in an unsafe
  // way.
  // Since this only matches value tokens, it does not match whitespace, colons,
  // or commas.
  var jsonToken = new RegExp(
      '(?:false|true|null|[\\{\\}\\[\\]]'
      + '|' + number
      + '|' + string
      + ')', 'g');

  // Matches escape sequences in a string literal
  var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');

  // Decodes escape sequences in object literals
  var escapes = {
    '"': '"',
    '/': '/',
    '\\': '\\',
    'b': '\b',
    'f': '\f',
    'n': '\n',
    'r': '\r',
    't': '\t'
  };
  function unescapeOne(_, ch, hex) {
    return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
  }

  // A non-falsy value that coerces to the empty string when used as a key.
  var EMPTY_STRING = new String('');
  var SLASH = '\\';

  // Constructor to use based on an open token.
  var firstTokenCtors = { '{': Object, '[': Array };

  var hop = Object.hasOwnProperty;

  return function (json, opt_reviver) {
    // Split into tokens
    var toks = json.match(jsonToken);
    // Construct the object to return
    var result;
    var tok = toks[0];
    var topLevelPrimitive = false;
    if ('{' === tok) {
      result = {};
    } else if ('[' === tok) {
      result = [];
    } else {
      // The RFC only allows arrays or objects at the top level, but the JSON.parse
      // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
      // at the top level.
      result = [];
      topLevelPrimitive = true;
    }

    // If undefined, the key in an object key/value record to use for the next
    // value parsed.
    var key;
    // Loop over remaining tokens maintaining a stack of uncompleted objects and
    // arrays.
    var stack = [result];
    for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
      tok = toks[i];

      var cont;
      switch (tok.charCodeAt(0)) {
        default:  // sign or digit
          cont = stack[0];
          cont[key || cont.length] = +(tok);
          key = void 0;
          break;
        case 0x22:  // '"'
          tok = tok.substring(1, tok.length - 1);
          if (tok.indexOf(SLASH) !== -1) {
            tok = tok.replace(escapeSequence, unescapeOne);
          }
          cont = stack[0];
          if (!key) {
            if (cont instanceof Array) {
              key = cont.length;
            } else {
              key = tok || EMPTY_STRING;  // Use as key for next value seen.
              break;
            }
          }
          cont[key] = tok;
          key = void 0;
          break;
        case 0x5b:  // '['
          cont = stack[0];
          stack.unshift(cont[key || cont.length] = []);
          key = void 0;
          break;
        case 0x5d:  // ']'
          stack.shift();
          break;
        case 0x66:  // 'f'
          cont = stack[0];
          cont[key || cont.length] = false;
          key = void 0;
          break;
        case 0x6e:  // 'n'
          cont = stack[0];
          cont[key || cont.length] = null;
          key = void 0;
          break;
        case 0x74:  // 't'
          cont = stack[0];
          cont[key || cont.length] = true;
          key = void 0;
          break;
        case 0x7b:  // '{'
          cont = stack[0];
          stack.unshift(cont[key || cont.length] = {});
          key = void 0;
          break;
        case 0x7d:  // '}'
          stack.shift();
          break;
      }
    }
    // Fail if we've got an uncompleted object.
    if (topLevelPrimitive) {
      if (stack.length !== 1) { throw new Error("uncompleted object (topLevelPrimitive) at level "+stack.length); }
      result = result[0];
    } else {
      if (stack.length) { throw new Error("uncompleted object (NO topLevelPrimitive) at level"+stack.length); }
    }

    if (opt_reviver) {
      // Based on walk as implemented in http://www.json.org/json2.js
      var walk = function (holder, key) {
        var value = holder[key];
        if (value && typeof value === 'object') {
          var toDelete = null;
          for (var k in value) {
            if (hop.call(value, k) && value !== holder) {
              // Recurse to properties first.  This has the effect of causing
              // the reviver to be called on the object graph depth-first.

              // Since 'this' is bound to the holder of the property, the
              // reviver can access sibling properties of k including ones
              // that have not yet been revived.

              // The value returned by the reviver is used in place of the
              // current value of property k.
              // If it returns undefined then the property is deleted.
              var v = walk(value, k);
              if (v !== void 0) {
                value[k] = v;
              } else {
                // Deleting properties inside the loop has vaguely defined
                // semantics in ES3 and ES3.1.
                if (!toDelete) { toDelete = []; }
                toDelete.push(k);
              }
            }
          }
          if (toDelete) {
            for (var i = toDelete.length; --i >= 0;) {
              delete value[toDelete[i]];
            }
          }
        }
        return opt_reviver.call(holder, key, value);
      };
      result = walk({ '': result }, '');
    }

    return result;
  };
})();


JSON.parse = jsonParse;
}



/****************************************************************************
 * jQuery 1.3.x plugin to shorten styled text to fit in a block, appending
 * an ellipsis ("...", &hellip;, Unicode: 2026) or other text.
 * (Only supports ltr text for now.)
 *
 * Created by M. David Green (www.mdavidgreen.com) in 2009. Free to use for
 * personal or commercial purposes under MIT (X11) license with no warranty
 *
 * Heavily modified/simplified/improved by Marc Diethelm (http://web5.me/).
 *
****************************************************************************/


(function ($) {

	$.fn.textTruncate = function() {

		var userOptions = {};
		var args = arguments; // for better minification
		var func = args.callee // dito; and much shorter than $.fn.textTruncate

		if ( args.length ) {

			if ( args[0].constructor == Object ) {
				userOptions = args[0];
			} else if ( args[0] == "options" ) {
				return $(this).eq(0).data("options-truncate");
			} else {
				userOptions = {
					width: parseInt(args[0]),
					tail: args[1]
				}
			}
		}

		this.css("visibility","hidden"); // Hide the element(s) while manipulating them

		// apply options vs. defaults
		var options = $.extend({}, func.defaults, userOptions);


		/**
		 * HERE WE GO!
		 **/
		return this.each(function () {

			var $this = $(this);
			$this.data("options-truncate", options);

			/**
			 * If browser implements text-overflow:ellipsis in CSS and tail is "...", use it!
			 **/
			if ( options.tail == "..." && func._native ) {

				this.style[func._native] = "ellipsis";
				/*var css_obj = {}
				css_obj[func._native] = "ellipsis";
				$this.css(css_obj);*/
				$this.css("visibility","visible");

				return true;
			}

			var width = options.width || $this.parent().width();

			var text = $this.text();
			var textlength = text.length;
			var css = "padding:0; margin:0; border:none; font:inherit;";
			var $table = $('<table style="'+ css +'width:auto;zoom:1;position:absolute;"><tr style="'+ css +'"><td style="'+ css +'white-space:nowrap;">' + options.tail + '</td></tr></table>');
			var $td = $("td", $table);
			$this.html( $table );

			var tailwidth = $td.width();
			var targetWidth = width - tailwidth;

			$td.text( text );

			if ($td.width() > width) {
				if ( options.tooltip ) {
					$this.attr("title", text);
				}

				while ($td.width() >= targetWidth ) {
					textlength--;
					$td.html($td.html().substring(0, textlength)); // .html(val) is faster than .text(val) and we already used .text(val) to strip/escape html
				}

				text = $.trim( $td.html() );
				$this.html( text + options.tail );

			} else {
				$this.html( text );
			}

			this.style.visibility = "visible";

			return true;
		});

		return true;

	};


	var css = document.documentElement.style;
	var _native = false;

	if ( "textOverflow" in css ) {
		_native = "textOverflow";
	} else if ( "OTextOverflow" in css ) {
		_native = "OTextOverflow";
	}

	$.fn.textTruncate._native = _native;


	$.fn.textTruncate.defaults = {
		tail: "&hellip;",
		tooltip: true
	};

})(jQuery);


