// $Id: betslipbody.js,v 1.1.2.253 2010-08-06 16:35:33 darmitag Exp $
// This file contains javascript functions that are required by the betslip.
// These functions may only be used once the betslip has been opened.
//
// The naming convention is that functions and globals in this file should be
// prefixed with "BS_".

// minified CVS tag version
var dummy = '$Id: betslipbody.js,v 1.1.2.253 2010-08-06 16:35:33 darmitag Exp $';

function BS_Fraction(num, den) {
	this.num = num;
	this.den = den;

	this.Equals = function(num, den) {
		return (this.num == num && this.den == den);
	}

	this.Decimal = function() {
		if (this.den == 0) return Number.NaN;
		return this.num / this.den;
	}
}
function BS_Seln() {
	this.price = new BS_Fraction(null, null);
	this.prev_price = new BS_Fraction(null, null);

	this.hcap = null;
	this.prev_hcap = null;
	this.hcap_str = null;
}
function BS_Cookie() {
	this.leg           = new Object();
	this.leg_nums      = new Array();
	this.combi_type    = new Array();
	this.combi         = new Array();
	this.esc_grouping  = '';
}
function BS_ErrorMessage(id, msg) {
	this.id      = id;
	this.message = msg;
}

// Global hash containing the cookie info related to the betslip
var SELN   = new Array();
var COOKIE = new BS_Cookie();
var BS_ERR_MSG = new Array();


// Given a string and an object, replace all instances of the form
// $varname in the string with the value of the object property named
// varname, or the empty string if none exists.
//
// e.g. js_subst("$greet $name", {greet: "hello", name: "world"})
//      => "hello world".
//
// A valid varname consists of a letter followed by zero or more
// alphanumeric (or underscore) characters. "{" and "}" can be used to
// terminate a variable name - e.g. "${foo}bar".
// Invalid varnames are left alone - so e.g. $100 would not be replaced.
//
// Handy for really, really complicated translations.
//
function BS_js_subst (str, obj) {

	var out;
	var mtch;
	var i, mS, mE;
	var vname;

	out = "";
	i = 0;
	vname_re = /\$\{?([A-Za-z]\w*)\}?/g;

	while ((mtch = vname_re.exec(str)) != null)
	{
		mS = mtch.index;
		out = out + str.substring(i, mS);
		vname = mtch[1];
		if (obj && obj[vname]) {
			out = out + obj[vname];
			if (vname == "price" && obj['price_type'] && obj['price_type'] == 'G' &&
				obj['g_price_checked'] && obj['g_price_checked'] == 'Y' && document.getElementById('allow_gp_js').value != 'N') {
				out = out + ' (' + betslipbody.msg.BS_GUARANTEED_PRICE + ')';
			}
		}
		i = vname_re.lastIndex;
	}
	out = out + str.substring(i);

	return out;
}


// Polls for status updates on the bir bet
var bir_url             = null;
var bir_req_id          = null;
var bir_poll_interval   = null;
var bir_req_poll_int    = null;
var bir_req_tout_int    = null;
var playing_bir_confirm = 0;
var bir_is_timeout      = 0;
var bad_resp_count      = 0;
var fast_bet            = true;

function BIR_auto_poll(_bir_req_id, poll_interval, timeout) {

	// Set the BIR_REQ_ID cookie and ensure the
	//  POSSIBLE_PLACED_BET cookie is cleared
	set_cookie("BIR_REQ_ID",_bir_req_id, "", "/");
	set_cookie("POSSIBLE_PLACED_BET", "", "", "/");
	playing_bir_confirm = 1;

	BS_open_betslip();
	
	if (location.protocol == "https:")
		bir_url           = tld_secure;
	else 
		bir_url           = tld;

	var fastbet_cookie  = get_cookie(betslipbody.cookie_info.BET_FAST_BET_COOKIE_NAME);
	// We don't have previous data in the cookie
	if (fastbet_cookie == null || fastbet_cookie == '') {
		fast_bet = false;
	}

	bir_req_id        = _bir_req_id;
	bir_poll_interval = poll_interval;
	bir_is_timeout    = 0;

	//If someone try to do something with the betslip while
	//processing a bir bet, another interval/timeout is created
	//so we need to clear the previous ones
	if (bir_req_tout_int != null) clearTimeout(bir_req_tout_int);
	if (bir_req_poll_int != null) clearInterval(bir_req_poll_int);

	bir_req_tout_int  = setTimeout('BIR_timeout()',timeout);
	bir_req_poll_int  = setInterval('BIR_poll_bet_status(fast_bet)',bir_poll_interval);
}

function BIR_timeout() {
	bir_is_timeout = 1;
        clearInterval(bir_req_poll_int);
        set_cookie("BIR_REQ_ID","", "", "/");
        BS_open_betslip();
        BS_display_error(betslipheader.msg.BS_TIMEOUT_TITLE, betslipheader.msg.BS_TIMEOUT_TXT);
        bad_resp_count = 0;
}

// Handles the bir delay for the bir confirmation page
function BIR_poll_bet_status(fast_bet){

	var url = bir_url + '?action=get_bir_req_status&bir_req_id=' + bir_req_id;

	if (fast_bet) {
		url = url + '&bir_fast_bet=1';
	}

	try {
		new Ajax.Request(url, {
			method: 'get',
			onException: function(object,exception) {
				set_cookie("BIR_REQ_ID","", "", "/");
				BS_display_error(betslipheader.msg.BS_ERROR_TITLE, betslipheader.msg.BS_ERROR_TXT + ' (1)' );
				return false;
			},
			onSuccess: function(transport) {
				var ret_arr = transport.responseText.match(/^([A-Z]+):?(.*)/);
	
				if (ret_arr == null) {
					// Allow one null response through - this means a navigation at the
					//  same time as an ajax reqest shouldnt mess things up.
					if (bad_resp_count > 0) {
						clearTimeout(bir_req_tout_int);
						bir_req_tout_int = null;
						set_cookie("BIR_REQ_ID","", "", "/");
						var ret = "BAD_RESPONSE - " + transport.responseText;
						bad_resp_count = 0;
					} else {
						var ret = "PENDING";
						bad_resp_count++;
					}
				} else {
					var ret  = ret_arr[1];
					var html = transport.responseText.replace(/^([A-Z]+):?/, "");
				}
	
				if (ret == 'PENDING') {
					// Do nothing
					return false;
				} else if (ret == 'RECEIPT') {
					clearInterval(bir_req_poll_int);
					clearTimeout(bir_req_tout_int);
					playing_bir_confirm = 0;
					bir_req_tout_int = null;
					bir_req_poll_int = null;
					set_cookie("BIR_REQ_ID","", "", "/");
					document.getElementById('betSlipBodyMembrane').innerHTML = html;
					vsize_betslip();
					bad_resp_count = 0;
					enable_fastbet_buttons_upd_cookie();
					
					BS_refresh_my_bets();
					
					return false;
				} else if (ret == 'OVERRIDE') {
					clearInterval(bir_req_poll_int);
					clearTimeout(bir_req_tout_int);
					playing_bir_confirm = 0;
					bir_req_tout_int = null;
					bir_req_poll_int = null;
					set_cookie("BIR_REQ_ID","", "", "/");	
					document.getElementById('betSlipBodyMembrane').innerHTML = html;
					vsize_betslip();
					bad_resp_count = 0;
					return false;
				} else if (ret == 'TIMEOUT') {
					clearInterval(bir_req_poll_int);
					clearTimeout(bir_req_tout_int);
					playing_bir_confirm = 0;
					bir_req_tout_int = null;
					bir_req_poll_int = null;
					set_cookie("BIR_REQ_ID","", "", "/");
					BS_open_betslip();
					BS_display_error(betslipheader.msg.BS_TIMEOUT_TITLE, betslipheader.msg.BS_TIMEOUT_TXT);
					bad_resp_count = 0;
					return false;
				} else {
					clearInterval(bir_req_poll_int);
					clearTimeout(bir_req_tout_int);
					playing_bir_confirm = 0;
					bir_req_tout_int = null;
					bir_req_poll_int = null;
					set_cookie("BIR_REQ_ID","", "", "/");
					BS_display_error(betslipheader.msg.BS_ERROR_TITLE, betslipheader.msg.BS_ERROR_TXT);
					bad_resp_count = 0;
					return false;
				}
			}
		});
	}

	catch(err) {
		// Do nothing if it fails - means we have navigated away before
		//  Ajax returned so just quietly catch the error.
	}
}


// Setup BIR price updates
function BIR_auto_price_upd() {
	if (push_enabled) {
		BIR_auto_price_upd_ls();
	} else {
		BIR_auto_price_upd_bx();
	}
}

// Setup BIR price updates (BIR XML)
function BIR_auto_price_upd_bx() {
	if (!$('bir_selns') || $('bir_selns').value == "") return;
	var poll_interval = betslipbody.bir.poll_interval * betlive_poll_factor;
	if (betslipbody.bir.interval_id == -1 && poll_interval > betlive_min_poll_interval) {
		betslipbody.bir.interval_id = setInterval('BIR_check_price_upd()', poll_interval);
	}
}



// Handles the bir price update for selections 'in running' (BIR XML)
function BIR_check_price_upd() {

	// Check for price updates only if there is selections in running
	// If the poll_interval is to small, do not check for price updates
	var poll_interval = betslipbody.bir.poll_interval * betlive_poll_factor;
	if (   !($('bir_selns') && $('bir_selns').value != "")
		|| poll_interval <= betlive_min_poll_interval) {
		clearInterval(betslipbody.bir.interval_id);
		betslipbody.bir.interval_id = -1;
		return false;
	}

	var url = betslipbody.bir.URL + "&seln_ids=" + $("bir_selns").value;

	new Ajax.Request(url , {
	  method: 'get',
	  onException: function(req, exception) {
	  },
	  onSuccess: function(transport) {
		var json = transport.responseText.evalJSON();
		// Update the betslip selections with the new price a/o new hcap/index
		var price_changed = false;
		var hcap_changed = false;
		var js_seln = '';
		var bs_seln = '';
		if (!json.selection) {
			return;
		}
		for (var index = 0, len = json.selection.length; index < len; ++index) {
			js_seln = json.selection[index];
			bs_seln = SELN[js_seln.id];
			if (!bs_seln.price.Equals(js_seln.lp_num, js_seln.lp_den)) {

				//Update previous prices
				bs_seln.prev_price.num = bs_seln.price.num;
				bs_seln.prev_price.den = bs_seln.price.den;

				bs_seln.price.num = js_seln.lp_num;
				bs_seln.price.den = js_seln.lp_den;
				price_changed = true;

			}

			if (bs_seln.hcap != js_seln.hcap) {
				//Update previous hcap/index
				bs_seln.prev_hcap = bs_seln.hcap;

				bs_seln.hcap = js_seln.hcap;
				bs_seln.hcap_str = js_seln.hcap_value;
				hcap_changed = true;

			}

			
		}
		if (price_changed || hcap_changed) {
			BS_selns_changed();
		}
	  }
	});
}

// Betslip BIR Selection Updates (LiveServ version)

// The LiveServ channels to which the betslip is subscribed.
var _BS_BIR_channels = [];

// Last message id at the time initial state was received.
var _BS_BIR_last_msg_id = "";

// Information about BIR selns and their ancestors.
var _BS_BIR_Selcn = {};
var _BS_BIR_EvMkt = {};
var _BS_BIR_Event = {};

// Do arrays a and b (of primitive types) contain the same elements
// (regardless of order + duplicates)?
function _BS_BIR_simple_arrays_eq_setwise(a, b) {

	if (a.length != b.length) return false;
	var inA = {};
	var inB = {};
	for (var i = 0; i < a.length; i++) {
		inA["k" + a[i]] = true;
	}
	for (var i = 0; i < b.length; i++) {
		var el = b[i];
		if (!inA["k" + el]) {
			return false;
		}
		inB["k" + el] = true;
	}
	for (var i = 0; i < a.length; i++) {
		if (!inB["k" + a[i]]) {
			return false;
		}
	}

	return true;
}

// Setup BIR price updates (LiveServ).
// NB: This can be called more than once during the lifetime
// of the page since the betslip can be updated via AJAX. We
// take special care to avoid doing unnecessary re-subs, but
// I do wonder if it would have been simpler to deregister
// (i.e. unsubscribe) before reloading the betslip, rather
// than try to be so clever.
function BIR_auto_price_upd_ls() {

	var new_msg_id = "";
	var el = document.getElementById("bir_msg_id");
	if (el) {
		new_msg_id = el.value;
	}
	var new_channels = _BS_BIR_get_push_channels();

	var channels_same = _BS_BIR_simple_arrays_eq_setwise
	                      (_BS_BIR_channels, new_channels);
	var msg_id_lower  = (new_msg_id < _BS_BIR_last_msg_id);
	if (channels_same && (!msg_id_lower || !new_channels.length)) {
		return;
	}

	_BS_BIR_last_msg_id = new_msg_id;
	_BS_BIR_channels    = new_channels;

	ps_connect_register
	  ("betslip", _BS_BIR_got_push_msg, _BS_BIR_channels, _BS_BIR_last_msg_id);

	return;
}


// Determine push channels to which we must subscribe,
// and also build up some drilldown arrays we'll need in
// order to handle push messages efficiently.
function _BS_BIR_get_push_channels() {

	_BS_BIR_Selcn = {};
	_BS_BIR_EvMkt = {};
	_BS_BIR_Event = {};

	var el = $('bir_selns');
	if (!el || el.value == "") return [];

	var bir_selns = $('bir_selns').value.split("|");
	if (!bir_selns.length) return [];

	// Information about selections is stored by single index,
	// not by ev_oc_id. We only care about the BIR ones.

	for (var i = 0; i < bir_selns.length; i++) {
		var ev_oc_id = bir_selns[i];
		_BS_BIR_Selcn["s" + ev_oc_id] = {};
	}

	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		var el = document.getElementById("ev_oc_ids_sgl_" + leg_num);
		if (!el) continue;
		var sgl_selns = el.value;
		if (_BS_BIR_Selcn["s" + sgl_selns] == undefined) continue;
		var ev_oc_id = sgl_selns;
		var suspended_on_server =
		  ("Y" == document.getElementById("suspended_" + leg_num).value);
		// no point subscribing if suspended on server;
		// we won't be able to re-activiate it.
		if (suspended_on_server) continue;
		var ev_mkt_id = document.getElementById("ev_mkt_id_sgl_" + leg_num).value;
		var ev_id     = document.getElementById("ev_id_sgl_" + leg_num).value;
		var fb_result = document.getElementById("fb_result_sgl_" + leg_num).value;
		_BS_BIR_Selcn["s" + sgl_selns] =
		  {ev_mkt_id: ev_mkt_id, ev_id: ev_id, leg_num: leg_num,
		   fb_result: fb_result, status: "A", bettable: true};
		if (_BS_BIR_EvMkt["m" + ev_mkt_id] == undefined) {
			_BS_BIR_EvMkt["m" + ev_mkt_id] =
			  {ev_id: ev_id, selcns: [ev_oc_id], status: "A"};
		} else {
			_BS_BIR_EvMkt["m" + ev_mkt_id].selcns.push(ev_oc_id);
		}
	}

	// Set up event hash.

	for (var key in _BS_BIR_EvMkt) {
		var ev_mkt_id = key.substr(1);
		var mkt = _BS_BIR_EvMkt[key];
		var ev_id = mkt.ev_id;
		if (_BS_BIR_Event["e" + ev_id] == undefined) {
			_BS_BIR_Event["e" + ev_id] =
			  {evmkts: [ev_mkt_id], status: "A"};
		} else {
			_BS_BIR_Event["e" + ev_id].evmkts.push(ev_mkt_id);
		}
	}

	// Finally, return the channels to which we must subscribe.

	var channels = [];

	for (var key in _BS_BIR_Selcn) {
		var ev_oc_id = key.substr(1);
		var padded_ev_oc_id = ps_connect_lpad_id(ev_oc_id, 10);
		channels.push("sSELCN" + padded_ev_oc_id);
	}
	for (var key in _BS_BIR_EvMkt) {
		var ev_mkt_id = key.substr(1);
		var padded_ev_mkt_id = ps_connect_lpad_id(ev_mkt_id, 10);
		channels.push("sEVMKT" + padded_ev_mkt_id);
	}
	for (var key in _BS_BIR_Event) {
		var ev_id = key.substr(1);
		var padded_ev_id = ps_connect_lpad_id(ev_id, 10);
		channels.push("sEVENT" + padded_ev_id);
	}

	return channels;
}

// Called when we get a message from the Push server
function _BS_BIR_got_push_msg(msg) {
	
	// Dont try and update the betslip while its in knightrider mode or on the receipt.
	if (bir_req_tout_int != null || BS_is_on_receipt()) {
		return
	}

	var data = {};

	if (msg.subject_level == "sPRICE" || msg.subject_level == "sSELCN") {
		var ev_oc_id = msg.subject_id;
		if (_BS_BIR_Selcn["s" + ev_oc_id]) {
			eval("data = " +  msg.data + ";");
			_BS_BIR_got_push_selcn(ev_oc_id, data);
		}
	} else if (msg.subject_level == "sEVMKT" || msg.subject_level == "sMHCAP") {
		var ev_mkt_id = msg.subject_id;
		if (_BS_BIR_EvMkt["m" + ev_mkt_id]) {
			eval("data = " +  msg.data + ";");
			_BS_BIR_got_push_evmkt(ev_mkt_id, data);
		}
	} else if (msg.subject_level == "sEVENT") {
		var ev_id = msg.subject_id;
		if (_BS_BIR_Event["e" + ev_id]) {
			eval("data = " +  msg.data + ";");
			_BS_BIR_got_push_event(ev_id, data);
		}
	} else {
		// presumably this msg wasn't intended for us.
		return;
	}

	return;
}

// Called when we get a selection/price update from LiveServ
function _BS_BIR_got_push_selcn(ev_oc_id, data) {

	var bs_seln = SELN[ev_oc_id];
	if (!bs_seln) return;

	var bir_selcn = _BS_BIR_Selcn["s" + ev_oc_id];
	if (!bir_selcn) return;

	var bir_evmkt = _BS_BIR_EvMkt["m" + bir_selcn.ev_mkt_id];
	var bir_event = _BS_BIR_Event["e" + bir_selcn.ev_id];
	if (!bir_evmkt | !bir_event) return;

	var found_change = false;

	if (data.lp_num != undefined) {

		var lp_num = data.lp_num;
		var lp_den = data.lp_den;

		if (!bs_seln.price.Equals(lp_num, lp_den)) {

			//Update previous prices
			bs_seln.prev_price.num = bs_seln.price.num;
			bs_seln.prev_price.den = bs_seln.price.den;

			bs_seln.price.num = lp_num;
			bs_seln.price.den = lp_den;

			found_change = true;

		}

	}

	// Figure out whether selection was and is now bettable.

	var orig_bettable = bir_selcn.bettable;
	if (data.status) {
		bir_selcn.status = data.status;
	}
	bir_selcn.bettable = 
	  bir_selcn.status == "A" &&
	  bir_evmkt.status == "A" &&
	  bir_event.status == "A";

	if (bir_selcn.bettable != orig_bettable) {
		_BS_BIR_got_status_chg(ev_oc_id, bir_selcn.bettable);
	}

	if (found_change) {
		BS_selns_changed();
	}

	return;
}

// Called when we get a market/handicap update from LiveServ
function _BS_BIR_got_push_evmkt(ev_mkt_id, data) {

	var bir_evmkt = _BS_BIR_EvMkt["m" + ev_mkt_id];
	if (!bir_evmkt) return;

	var found_change = false;

	if (data.raw_hcap != undefined) {

		var raw_hcap    = data.raw_hcap;
		var hcap_values = data.hcap_values;

		var ev_oc_ids = _BS_BIR_EvMkt["m" + ev_mkt_id].selcns;
		if (!ev_oc_ids || !ev_oc_ids.length) return;

		for (var i = 0; i < ev_oc_ids.length; i++) {
			var ev_oc_id = ev_oc_ids[i];
			var bs_seln = SELN[ev_oc_id];
			var fb_result = _BS_BIR_Selcn["s" + ev_oc_id].fb_result;
			if (!bs_seln || !fb_result) continue;
			var new_hcap     = raw_hcap;
			var new_hcap_str = hcap_values[fb_result];
			if (new_hcap == undefined || new_hcap_str == undefined) continue;
			if (new_hcap != bs_seln.hcap) {
				bs_seln.prev_hcap = bs_seln.hcap;
				bs_seln.hcap     = new_hcap;
				bs_seln.hcap_str = new_hcap_str;
				found_change = true;
			}
		}

	}

	if (found_change) {
		BS_selns_changed();
	}

	// If a status change has occurred, fire the selection level
	// message handler for each selection in our event - it should
	// check the event's status and act accordingly.

	if (data.status && data.status != bir_evmkt.status) {
		bir_evmkt.status = data.status;
		var ev_oc_ids = _BS_BIR_EvMkt["m" + ev_mkt_id].selcns;
		for (var i = 0; i < ev_oc_ids.length; i++) {
			var ev_oc_id = ev_oc_ids[i];
			_BS_BIR_got_push_selcn(ev_oc_id, {});
		}
	}

	return;
}


// Called when we get an event update from LiveServ
function _BS_BIR_got_push_event(ev_id, data) {

	var bir_event = _BS_BIR_Event["e" + ev_id];

	// If a status change has occurred, fire the selection level
	// message handler for each selection in our event - it should
	// check the event's status and act accordingly.

	if (data.status && data.status != bir_event.status) {
		bir_event.status = data.status;
		var ev_mkt_ids = _BS_BIR_Event["e" + ev_id].evmkts;
		for (var i = 0; i < ev_mkt_ids.length; i++) {
			var ev_mkt_id = ev_mkt_ids[i];
			var ev_oc_ids = _BS_BIR_EvMkt["m" + ev_mkt_id].selcns;
			for (var j = 0; j < ev_oc_ids.length; j++) {
				var ev_oc_id = ev_oc_ids[j];
				_BS_BIR_got_push_selcn(ev_oc_id, {});
			}
		}
	}

	return;
}

// Update the betslip prices in the cookie/page as a result of a BIR msg.
function BS_selns_changed() {

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		// skip multi-part legs
		if (COOKIE.leg[leg_num].selections.split('c').length != 1) continue;

		var sgl  = COOKIE.leg[leg_num];
		var seln = SELN[sgl.selections];

		// skip suspended selections
		var susp = document.getElementById('suspended_' + leg_num);
		if (susp != null && susp.value == 'Y') continue;

		//Price has changed
		if (!seln.price.Equals(sgl.lp_num, sgl.lp_den)) {

			//Update previous prices
			sgl.prev_lp_num = seln.prev_price.num;
			sgl.prev_lp_den = seln.prev_price.den;

			sgl.lp_num = seln.price.num;
			sgl.lp_den = seln.price.den;

			// update price on the betslip

			var disp_price = format_price(sgl.lp_num,sgl.lp_den);

			document.getElementById('txt_sgl_live_price_' + leg_num).innerHTML = disp_price;

			var el_num = document.getElementById('price_num_sgl_' + leg_num);
			var el_den = document.getElementById('price_den_sgl_' + leg_num);

			var old_num = el_num.value;
			var old_den = el_den.value;

			el_num.value = sgl.lp_num;
			el_den.value = sgl.lp_den;

			_BS_adjust_pot_rtn(leg_num, old_num, old_den, sgl.lp_num, sgl.lp_den);

			// Determine whether the odds have lengthened
			var diff = (sgl.lp_den * old_num) - (sgl.lp_num * old_den);

			// Show appropriate price change icon.
			var prc_icon_longer  = document.getElementById('img_price_chg_longer_'  + leg_num);
			var prc_icon_shorter = document.getElementById('img_price_chg_shorter_' + leg_num);

			if (diff > 0) {
				prc_icon_longer.style.display  = 'none';
				prc_icon_shorter.style.display = '';
			} else if (diff < 0) {
				prc_icon_longer.style.display  = '';
				prc_icon_shorter.style.display = 'none';
			} else {
				prc_icon_longer.style.display  = 'none';
				prc_icon_shorter.style.display = 'none';
			}
		}

		//Hcap/index has changed (TODO: is this check correct - see old push branch?)
		if (seln.hcap != sgl.hcap_value && seln.hcap_str != null && seln.hcap_str != "") {

			//Update hcap/index
			sgl.prev_hcap_value = seln.prev_hcap;
			sgl.hcap_value = seln.hcap;

			//Update the hcap/idx value on the betslip
			document.getElementById('txt_sgl_oc_desc_short_hcap_' + leg_num).innerHTML = seln.hcap_str;
			document.getElementById('txt_sgl_oc_desc_hcap_' + leg_num).innerHTML = seln.hcap_str;
			document.getElementById('inf_sgl_oc_desc_hcap_' + leg_num).innerHTML = seln.hcap_str;

			// Show appropriate hcap change icon.on the betslip
			var hcap_icon_longer  = document.getElementById('img_hcap_chg_longer_'  + leg_num);
			var hcap_icon_shorter = document.getElementById('img_hcap_chg_shorter_' + leg_num);

			if (sgl.prev_hcap_value < sgl.hcap_value) {
				hcap_icon_longer.style.display  = '';
				hcap_icon_shorter.style.display = 'none';
			} else if (sgl.prev_hcap_value > sgl.hcap_value) {
				hcap_icon_longer.style.display  = 'none';
				hcap_icon_shorter.style.display = '';
			} else {
				hcap_icon_longer.style.display  = 'none';
				hcap_icon_shorter.style.display = 'none';
			}
		}
	}

	//Update the cookie
	BS_store_hash_cookie();

	BS_upd_potential_winnings();
}


// Adjust the unit stake potential return fields for a single leg due to a
// price change that the client knows about but the server did not when the
// unit stake pot. rtn. was calculated. Does *not* update the displayed
// potential returns - caller must call BS_upd_potential_winnings for that.
function _BS_adjust_pot_rtn(leg_num, old_num, old_den, new_num, new_den) {

	var el_win = document.getElementById("pot_rtn_win_sgl_" + leg_num);
	var el_plc = document.getElementById("pot_rtn_plc_sgl_" + leg_num);

	// NB: we rely on this being stored unrounded...

	var pot_win = parseFloat(el_win.value);
	var pot_plc = parseFloat(el_plc.value);

	var old_dec = 1.0 + (parseFloat(old_num) / parseFloat(old_den));
	var new_dec = 1.0 + (parseFloat(new_num) / parseFloat(new_den));

	var factor = (new_dec / old_dec);

	pot_win = pot_win * factor;
	pot_plc = pot_plc * factor;

	el_win.value = pot_win;
	el_plc.value = pot_plc;

	if (BS_get("mult_inclusion","SGL",leg_num)) {

		// This selection appears in multiples. We'll need to update their
		// unit stake potential returns too.
		// Unfortunately, we can only apply the factor to multiples where the leg
		// appears in all the lines - which in practice means multiples with one
		// line. We'll have to set the others as incalculable.

		var el_mul_win, el_mul_plc;
		var pot_mul_win, pot_mul_plc;

		var multi_types = BS_get_multi_types();
		for (i = 0; i < multi_types.length; i++) {

			var type_name = multi_types[i];
			var num_lines = BS_get("num_lines", type_name);

			// XXX arguably we should check that the single is in fact in this
			// particular multiple - but we can assume it is from the mult incl
			// checkbox for now.

			var el_mul_win = document.getElementById("pot_rtn_win_type_" + type_name);
			var el_mul_plc = document.getElementById("pot_rtn_plc_type_" + type_name);

			var pot_mul_win = parseFloat(el_mul_win.value);
			var pot_mul_plc = parseFloat(el_mul_plc.value);

			if (num_lines == 1) {
				pot_mul_win = pot_mul_win * factor;
				pot_mul_plc = pot_mul_plc * factor;
			} else {
				pot_mul_win = 0.0;
				pot_mul_plc = 0.0;
			}

			el_mul_win.value = pot_mul_win;
			el_mul_plc.value = pot_mul_plc;

		}

	}

}


// Has a leg been suspended - either server-side or client-side?
// (the latter can occur as a result of a LiveServ msg)
function BS_is_suspended(leg_num) {

	var susp_el = document.getElementById('suspended_' + leg_num);
	if (susp_el && susp_el.value == 'Y') {
		return true;
	}

	var selcns_el = document.getElementById("ev_oc_ids_sgl_" + leg_num);
	if (selcns_el) {
		var bir_seln = _BS_BIR_Selcn["s" + selcns_el.value];
		if (bir_seln && !bir_seln.bettable) {
			return true;
		}
	}

	return false;
}

// Called when a BIR selection that was not suspended server-side
// changes status as a result of a LiveServ message.
function _BS_BIR_got_status_chg(ev_oc_id, bettable) {

	var bir_seln = _BS_BIR_Selcn["s" + ev_oc_id];
	var leg_num  = bir_seln.leg_num;

	// Gray-out (well, yellow-out really) or un-gray-out selection rows.
	// [we should use the CSS suspended class - but it messes up formatting]

	var row_id = "tr_sgl_" + leg_num + "_1";
	var row_el = document.getElementById(row_id);
	if (row_el) {
		row_el.style.backgroundColor = bettable ? "" : "#ffffd9";
	}

	var row_id = "tr_sgl_" + leg_num + "_2";
	var row_el = document.getElementById(row_id);
	if (row_el) {
		row_el.style.backgroundColor = bettable ? "" : "#ffffd9";
	}

	// Disable/enable stake field (and remove stake if suspended).
	// Removing the stake & disabling will prevent bets being placed.

	var stake_el = document.getElementById("stake_sgl_" + leg_num);
	if (stake_el) {
		stake_el.disabled = !bettable;
		// XXX bit ugly in FF but necessary in IE
		stake_el.style.backgroundColor = bettable ? "" : "#dcdcdc";
		if (!bettable) {
			stake_el.value = "";
			BS_stakes_changed(true);
		}
	}

	// Other input fields apart from the multi inclusion checkbox since
	// it's hard to tell if it was enabled to begin with + it won't hurt
	// to leave it enabled since it hits the server.

	var ew_el = document.getElementById("leg_ew_sgl_" + leg_num);
	if (ew_el) {
		ew_el.disabled = !bettable;
	}

	var pt_el = document.getElementById("price_type_sgl_" + leg_num);
	if (pt_el) {
		pt_el.disabled = !bettable;
	}

	// May need to enable/disable multiples too ...

	if (BS_get('mult_inclusion','SGL',leg_num)) {
		_BS_BIR_upd_multiples_avail();
	}

}

// Client-side suspensions/activations can alter whether multiple
// bets are available.
function _BS_BIR_upd_multiples_avail() {

	var multi_types = BS_get_multi_types();
	if (!multi_types.length) return;

	var multis_ok = true;
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		if (BS_get('mult_inclusion','SGL',leg_num) &&
		    BS_is_suspended(leg_num)) {
			multis_ok = false;
			break;
		}
	}

	for (i = 0; i < multi_types.length; i++) {
		var type_name = multi_types[i];
		var row_el = document.getElementById("tr_multi_type_" + type_name);
		row_el.className = multis_ok ? "boption" : "suspended";
		var stk_el = document.getElementById("stake_type_"  + type_name);
		if (stk_el) {
			stk_el.disabled = !multis_ok;
			// XXX bit ugly in FF but necessary in IE
			stk_el.style.backgroundColor = multis_ok ? "" : "#dcdcdc";
			if (!multis_ok) {
				stk_el.value = "";
			}
		}
	}

	if (!multis_ok) {
		BS_stakes_changed(true);
	}

}

function BS_set_price_change_alerts_pref(status) {
	if (status == true) {
		set_pref('BETSLIP_ALERTS', 'All');
	}
}

function BS_set_hcap_change_alerts_pref(status) {
	if (status == true) {
		set_pref('BP_HP_IDX_ALERTS', 'All');
	}
}

// Purely used to round the number to 2 dp
// Use for calculations, and not for display
function BS_round(n) {
	return Math.round(n * 100) / 100;
}


// function called on loading of the standard betslip
function BS_betslip_onload() {

	// build a hash of the cookie
	BS_build_hash_cookie();

	// Rearrange legs if necessary.
	// We give the browser a chance to finish resizing first.
	window.setTimeout("BS_rearrange_legs()",10);

	// update total stake
	BS_upd_total_stake();

	// update potential winnings
	BS_upd_potential_winnings();

	// update maximum bets
	BS_upd_max_bet();

	// check for bir price updates
	BIR_auto_price_upd();

	BS_upd_cookie();

	if(document.forms['betslipCount'].bet_count.value == 0){
		//BS_close_betslip();
	}
}

// Utility function to get variables for a particular bet
// on the betslip (e.g. 4th SGL's stake)
// and do all the necessary validation checks.
// This means we don't have to do the checks each time manually
// and because it's in one place, if we add more checks,
// all calling functions benefit from the change
function BS_get(name, bet_type, index, dflt) {
	var f = document.forms.betSlipInfoForm;

	switch (name) {
		case 'mult_inclusion':
			var id = 'mult_inclusion_' + index;
			var mult_inclusion = (dflt ? dflt : false);
			if (f[id] && f[id].checked) {
				mult_inclusion = true;
			}
			return mult_inclusion;

		case 'leg_type':
			if (bet_type == 'SGL') {
				var id = 'leg_ew_sgl_' + index;
			} else {
				var id = 'leg_ew_type_' + bet_type;
			}

			var leg_type = (dflt ? dflt : 'W');

			if (f[id] && f[id].checked) {
				leg_type = 'E';
			}

			return leg_type;

		case 'ew_factor':
			if (bet_type == 'SGL') {
				var num_id  = 'ew_fac_num_sgl_' + index;
				var den_id  = 'ew_fac_den_sgl_' + index;
			} else {
				var num_id  = 'ew_fac_num_type_' + bet_type;
				var den_id  = 'ew_fac_den_type_' + bet_type;
			}

			var ew_factor = new BS_Fraction(0, 1);
			if (dflt) ew_factor = dflt;

			if (f[num_id] && f[den_id]) {
				var num = parseInt(f[num_id].value);
				var den = parseInt(f[den_id].value);

				if (!isNaN(num) && !isNaN(den)) {
					ew_factor = new BS_Fraction(num, den);
				}
			}

			return ew_factor;

		case 'price':
			if (bet_type == 'SGL') {
				var num_id  = 'price_num_sgl_' + index;
				var den_id  = 'price_den_sgl_' + index;
			} else {
				var num_id  = 'price_num_type_' + bet_type;
				var den_id  = 'price_den_type_' + bet_type;
			}

			var price = new BS_Fraction(0, 1);
			if (dflt) price = dflt;

			if (f[num_id] && f[den_id]) {
				var num = parseInt(f[num_id].value);
				var den = parseInt(f[den_id].value);

				if (!isNaN(num) && !isNaN(den)) {
					price = new BS_Fraction(num, den);
				}
			}
			return price;

		case 'stake':
			throw('use spl instead');

		case 'spl':
			if (bet_type == 'SGL') {
				var id = 'stake_sgl_' + index;
			} else {
				var id = 'stake_type_' + bet_type;
			}

			var ret_val = (dflt ? dflt : 0.0);

			if (f[id]) {
				spl = parseFloat(f[id].value);
				if (!isNaN(spl)) {
					ret_val = spl;
				}
			}

			return ret_val;

		case 'max_payout':

			// Look for element with given name and return value - provided
			// it can be parsed as a float.

			if (bet_type == 'SGL') {
				var id = name + '_sgl_' + index;
			} else {
				var id = name + '_type_' + bet_type;
			}

			var ret_val = (dflt ? dflt : '');

			if (f[id]) {
				var val = parseFloat(f[id].value);
				if (!isNaN(val)) {
					ret_val = val;
				}
			}

			return ret_val;

		default:

			// Look for element with given name and return value.

			if (bet_type == 'SGL') {
				var id = name + '_sgl_' + index;
			} else {
				var id = name + '_type_' + bet_type;
			}

			var ret_val = (dflt ? dflt : '');

			if (f[id]) {
				ret_val = f[id].value;
			}

			return ret_val;
	}
}


// Function to build a hash structure reprsenting the leg and stake cookie.
// This is done each time the page is successfully loaded.
//
// The general structure is as follows:
//   COOKIE.leg_nums                      - list of leg numbers
//   COOKIE.leg[leg_num][leg_param]       - value
//   COOKIE.combi_type                    - list of combi types
//   COOKIE.combi[combi_type][comb_param] - value
//
function BS_build_hash_cookie() {

	var offset = 0;

	// Clear the old cookie hash
	COOKIE = new BS_Cookie();

	// Singles

	var leg_cookie  = get_cookie(betslipbody.cookie_info.BET_LEG_COOKIE_NAME);

	if (leg_cookie == null || leg_cookie == '') {
		return;
	}

	var leg_params = BS_get_leg_format();
	var num_params = leg_params.length;

	// Build up the hash for the legs
	var cookie_data = leg_cookie.split("|");
	if (cookie_data.length % num_params != 0) {

		// Malformed leg cookie, ignore
		// This can be caused by:
		//  - a cookie format change since the last request
		//  - user corrupting the cookie
		// Either way, prevent the user from changing the state of the cookie
		// (since it will not work) by clearing the betslip. The backend
		// server will then handle this scenario.
		BS_clear_bet_slip();
		return false;
	}

	var num_legs = cookie_data.length / num_params;

	offset = 0;
	for (var leg_idx = 0; leg_idx < num_legs; leg_idx++) {

		// Copy the leg params into an object.

		var Leg = new Object();
		for (var param_idx = 0; param_idx < num_params; param_idx++) {
			Leg[leg_params[param_idx]] = cookie_data[offset + param_idx];
		}
		offset += num_params;

		// The COOKIE.leg object is a hash indexed by leg number.
		// Each entry is an object with the leg params as properties.
		// We also keep a list of the leg numbers.

		var leg_num = Leg.leg_num;
		COOKIE.leg[leg_num] = Leg;
		COOKIE.leg_nums.push(leg_num);

		// get info from the cookie about the selections
		var selections   = COOKIE.leg[leg_num].selections.split('c');
		var lp_num       = COOKIE.leg[leg_num].lp_num;
		var lp_den       = COOKIE.leg[leg_num].lp_den;
		var prev_lp_num  = COOKIE.leg[leg_num].prev_lp_num;
		var prev_lp_den  = COOKIE.leg[leg_num].prev_lp_den;

		var hcap         = COOKIE.leg[leg_num].hcap_value;
		var prev_hcap    = COOKIE.leg[leg_num].prev_hcap_value;

		for (var i = 0; i < selections.length; i++) {
			var ev_oc_id = selections[i];

			if (!SELN[ev_oc_id]) {
				SELN[ev_oc_id] = new BS_Seln();
			}

			// remember the price if it's a straight single
			// the same for hcap/index
			if (selections.length == 1) {
				if (lp_num != '' && lp_den != '') {
					SELN[ev_oc_id].price.num      = lp_num;
					SELN[ev_oc_id].price.den      = lp_den;
					SELN[ev_oc_id].prev_price.num = prev_lp_num;
					SELN[ev_oc_id].prev_price.den = prev_lp_den;
				}

				if (hcap != '') {
					SELN[ev_oc_id].hcap      = hcap;
					SELN[ev_oc_id].prev_hcap = prev_hcap;
				}
			}
		}

		if (get_pref('USE_GP') == "Y" && $('g_price_checkbox')) {
			$('g_price_checkbox').checked = true;
		}

	}

	// Multiples (confusingly called combis here)

	var cookie_comb = get_cookie(betslipbody.cookie_info.BET_MULTI_COOKIE_NAME);

	if (cookie_comb == null) {
		return;
	}

	var cookie_data = cookie_comb.split("|");

	var esc_grouping = cookie_data[0];
	var type_info    = cookie_data.slice(1);

	if (type_info.length % 4 != 0) {
		// Malformed stake cookie, ignore
		return;
	}

	// We don't actually do anything with this - but bet placement needs it back.
	COOKIE.esc_grouping = esc_grouping;

	var num_combi = type_info.length / 4;

	offset = 0;
	for (var combi_idx = 0; combi_idx < num_combi; combi_idx++) {

		// Nice improvement would be to make the stake generic in the same way
		// as the leg cookie
		var combi_type = type_info[offset];

		COOKIE.combi_type.push(combi_type);
		COOKIE.combi[combi_type]           = new Array();
		COOKIE.combi[combi_type].stake     = type_info[offset+1];
		COOKIE.combi[combi_type].leg_type  = type_info[offset+2];
		COOKIE.combi[combi_type].num_lines = type_info[offset+3];

		offset += 4;
	}


}


// Get array containing the leg numbers in the COOKIE.leg hash.
//
function BS_get_leg_nums() {
	return COOKIE.leg_nums;
}


// Get array containing all multiple bet types.
//
function BS_get_multi_types() {
	return COOKIE.combi_type;
}


// function to store the cookie hash structure
function BS_store_hash_cookie() {

	var leg_params = BS_get_leg_format();
	var num_params = leg_params.length;

	var cookie_data = new Array();
	var offset = 0;
	var cookie_str = '';

	// Stores the leg cookie
	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		for (var j = 0; j < num_params; j ++) {
			cookie_data[offset + j] = COOKIE.leg[leg_num][leg_params[j]]
		}
		offset += num_params;
	}

	cookie_str = cookie_data.join('|');
	set_cookie(betslipbody.cookie_info.BET_LEG_COOKIE_NAME, cookie_str, "", betslipbody.cookie_info.BET_COOKIE_PATH, "", "");

	cookie_data = new Array(); // Clear the array

	// Stores the stake cookie
	offset = 0;

	cookie_data[offset++] = COOKIE.esc_grouping;

	var combi_type = '';
	for (var i = 0; i < COOKIE.combi_type.length; i++) {
		combi_type              = COOKIE.combi_type[i];
		cookie_data[offset]     = combi_type;
		cookie_data[offset + 1] = COOKIE.combi[combi_type].stake;
		cookie_data[offset + 2] = COOKIE.combi[combi_type].leg_type;
		cookie_data[offset + 3] = COOKIE.combi[combi_type].num_lines;

		offset += 4;
	}

	cookie_str = cookie_data.join('|');
	set_cookie(betslipbody.cookie_info.BET_MULTI_COOKIE_NAME, cookie_str, "", betslipbody.cookie_info.BET_COOKIE_PATH, "", "");

}


// function called whenever an EW checkbox changes state
function BS_do_ew_change(e) {

	BS_update_plural_ew();

	// update cookie - this will pick up the leg type from the checkbox
	BS_upd_cookie();

	BS_upd_total_stake();
	BS_upd_potential_winnings();
	BS_upd_max_bet();

}


// Against which leg maxima "keys" (LP Win, SP Place, Forecast etc.)
// must a given bet leg be checked? Used for max bet calculations.
// Returns a subset of ["L,W","L,P","S,W","S,P","F","T"].
function _mb_keys_for_leg(leg_type, leg_sort, price_type) {

	var prc_prefix;
	var mb_keys = new Array();

	if (price_type == "S") {
		prc_prefix = "S";
	} else {
		prc_prefix = "L";
	}

	if (leg_type == "W" || leg_type == "E") {
		mb_keys.push(prc_prefix + ",W");
	}
	if (leg_type == "P" || leg_type == "E") {
		mb_keys.push(prc_prefix + ",P");
	}

	if (leg_sort == "SF" || leg_sort == "RF" || leg_sort == "CF") {
		mb_keys.push("F");
	} else if (leg_sort == "TC" || leg_sort == "CT") {
		mb_keys.push("T");
	}
	mb_keys.push("SGL");

	return mb_keys;
}


// Get the maximum stake-per-line for each of the given bets.
//
// For ease of testing and debugging, this is a pure function -
// the caller must retrieve the information needed from document or
// cookie(s), and pass it in as follows:
//
// MB should be an associative array with properties:
//   sep_cum_max_stk       - True iff [ob_bet::get_config sep_cum_max_stk]
//                           is not "N". Indicates if LP and SP cum stake is
//                           separate.
//   max,$ev_oc_id,$mb_key - max stake of type $mb_key on selection $ev_oc_id
//                           as in the database, but in the customer's ccy.
//   cum,$ev_oc_id,$mb_key - cumulative stake of type $mb_key on selection
//                           $ev_oc_id
//   There should be entries for every possible $ev_oc_id, $mb_key combination;
//   an mb_key can be one of ["L,W","L,P","S,W","S,P","F","T"].
//
// Bets should be an array containing objects with properties:
//   leg_type         - "W"in, "E"ach Way etc.
//   legs             - Indices in the Legs array of each leg in the bet.
//   grp_sf           - The group stake factor.
//   bt_sf            - The bet type stake factor.
//   lines_per_leg    - Either:
//                        Array of length 1 containing the number of lines
//                        in which each leg appears (assumed to be the same
//                        for each leg).
//                      or:
//                        Array with as many elements as their are legs in the
//                        bet, with each entry representing the number of
//                        lines in which the corresponding leg appears.
//                      NB: lines_per_leg should *not* take into account EWs.
//   bet_type_max     - The max for this bet arising from the bet type alone.
//   spl              - The proposed stake-per-line for this bet.
//
// Legs should be an array containing objects with properties:
//   leg_sort     - "--" = normal, "SF" = straight forecast, etc.
//   price_type   - "L"ive price, "S"tarting price, etc.
//   ev_oc_ids    - List of ev_oc_ids in this leg.
//
// Returns an array with one element per element in the Bets array, such
// that the Nth entry represents the  maximum stake for the Nth bet in the
// Bets array.
//
// As a side effect, we also maintain the _maxima_reduced_by_cross_bet_stakes
// global which will be true iff any maxima for bets with stakes have been
// reduced due to the stakes on other bets.
//

var _maxima_reduced_by_cross_bet_stakes = false;

function _calc_max_spls(MB, Bets, Legs) {

	var i,j,k;
	var bet_idx, Bet;
	var bet_leg_idxs, Leg, leg_idx;
	var num_lines_per_seln;
	var stake;
	var leg_mb_keys, mb_key, also_mb_key;
	var ev_oc_id;
	var bet_max_spl, bet_max_reduced_xstk;
	var seln_max_stk, cum_stk, tot_stk, our_stk, oth_stk, seln_max_spl;
	var more_tot_stk, more_our_stk;

	var max_spls = new Array();
	var SelnStk = new Object();

	var effective_sf;

	_maxima_reduced_by_cross_bet_stakes = false;

	// We build up in SelnStk the total stake from each bet on each selection,
	// breaking it down by mb_key (e.g. "L,W" for LP Win, "F" for forecast).
	// We also add total entries for each selection across all bets.
	// e.g.
	//   SelnStk[123,S,P,3]     = 100.00
	//   SelnStk[123,S,P,total] = 150.00
	// means that:
	//   Bet 3 is contributing $100 of SP Place stake to ev_oc_id #123,
	//   out of a total of $150 of SP Place stake on that selection.

	// For each bet ...

	for (bet_idx = 0; bet_idx < Bets.length; bet_idx++) {

		Bet = Bets[bet_idx];

		// Skip bets with no stake.
		if (Bet.spl == 0.0) {
			continue;
		}

		bet_leg_idxs = Bet.legs;

		// For each leg of this bet ...

		for (i = 0; i < bet_leg_idxs.length; i++) {
			leg_idx = bet_leg_idxs[i];
			Leg = Legs[leg_idx];
			if (Bet.lines_per_leg.length == 1) {
				num_lines_per_seln = Bet.lines_per_leg[0];
			} else {
				num_lines_per_seln = Bet.lines_per_leg[i];
			}
			stake = Bet.spl * num_lines_per_seln;

			leg_mb_keys =
			  _mb_keys_for_leg(Bet.leg_type, Leg.leg_sort, Leg.price_type);

			// For each mb_key this leg contributes to ...

			for (j = 0; j < leg_mb_keys.length; j++) {
				mb_key = leg_mb_keys[j];

				// For each selection in this leg ...

				for (k = 0; k < Leg.ev_oc_ids.length; k++) {
					ev_oc_id = Leg.ev_oc_ids[k];

					if (!SelnStk[ev_oc_id + "," + mb_key + "," + "total"]) {
						SelnStk[ev_oc_id + "," + mb_key + "," + "total"] = stake;
					} else {
						SelnStk[ev_oc_id + "," + mb_key + "," + "total"] += stake;
					}
					if (!SelnStk[ev_oc_id + "," + mb_key + "," + bet_idx]) {
						SelnStk[ev_oc_id + "," + mb_key + "," + bet_idx] = stake;
					} else {
						SelnStk[ev_oc_id + "," + mb_key + "," + bet_idx] += stake;
					}

				}

			}

		}
	}

	// Now calculate the effective maximum spl for each bet.
	// This will be the smallest spl we are allowed to stake under any mb_key
	// for each selection in each leg of the bet.

	for (bet_idx = 0; bet_idx < Bets.length; bet_idx++) {

		Bet = Bets[bet_idx];

		// The bet itself has a maximum derived from the bet type.

		bet_max_spl = Bet.bet_type_max;
		bet_max_reduced_xstk = false;

		// For each leg in this bet ...

		bet_leg_idxs = Bet.legs;
		for (i = 0; i < bet_leg_idxs.length; i++) {
			leg_idx   = bet_leg_idxs[i];
			Leg = Legs[leg_idx];

			// The caller can supply an array of length 1 for symmetric bets.

			if (Bet.lines_per_leg.length == 1) {
				num_lines_per_seln = Bet.lines_per_leg[0];
			} else {
				num_lines_per_seln = Bet.lines_per_leg[i];
			}

			// For each maxima key to which this bet leg contributes ...

			leg_mb_keys =
			  _mb_keys_for_leg(Bet.leg_type, Leg.leg_sort, Leg.price_type);

			for (j = 0; j < leg_mb_keys.length; j++) {
				mb_key = leg_mb_keys[j];

				// If we've been configured to not treat LP/SP seperately,
				// we must add in the entries for this mb_key also.
				also_mb_key = '';
				if (MB.sep_cum_max_stk == 0) {
					if (mb_key == 'L,W') {
						also_mb_key = 'S,W';
					} else if (mb_key == 'S,W') {
						also_mb_key = 'L,W';
					} else if (mb_key == 'L,P') {
						also_mb_key = 'S,P';
					} else if (mb_key == 'S,P') {
						also_mb_key = 'L,P';
					}
				}

				// For each selection in this leg ...

				for (k = 0; k < Leg.ev_oc_ids.length; k++) {
					ev_oc_id = Leg.ev_oc_ids[k];

					// This is the maximum stake from the DB (but in our ccy).
					seln_max_stk = MB['max,' + ev_oc_id + ',' + mb_key];

					// This is the cumulative stake (from the server).
					cum_stk  = MB['cum,' + ev_oc_id + ',' + mb_key];

					if (also_mb_key != '') {
						cum_stk += MB['cum,' + ev_oc_id + ',' + also_mb_key];
					}

					// We want to know what extra cumulative stake to take
					// into account from the *other* bets on the slip on this
					// seln. This is the same as the total stakes minus our
					// stake (ie the stake from this bet).

					tot_stk = SelnStk[ev_oc_id + "," + mb_key + "," + "total"];
					if (tot_stk == null) {
						tot_stk = 0.0;
					}
					our_stk = SelnStk[ev_oc_id + "," + mb_key + "," + bet_idx];
					if (our_stk == null) {
						our_stk = 0.0;
					}
					if (also_mb_key != '') {
						more_tot_stk = SelnStk[ev_oc_id + ',' + also_mb_key + ',' + 'total'];
						if (more_tot_stk != null) {
							tot_stk += more_tot_stk;
						}
						more_our_stk = SelnStk[ev_oc_id + ',' + also_mb_key + ',' + bet_idx];
						if (more_our_stk != null) {
							our_stk += more_our_stk;
						}
					}
					oth_stk = tot_stk - our_stk;

					// We apply the stake factor for the group to the seln max
					// before deducting the cumulative stakes.

					// note, the max stake as defined for a SGL bet type should *not*
					// be affected by the stake factor! (See PP DevRT #3452)
					if (mb_key != 'SGL') {
						effective_sf = Bet.grp_sf;
					} else {
						effective_sf = 1;
					}

					seln_max_stk =
					  (seln_max_stk * effective_sf) - (cum_stk + oth_stk);

					// Apply the bet type stake factor to the resulting maximum,
					// before dividing it by the number of lines this selection
					// appears in. This (finally) gives us the maximum
					// stake-per-line we can put on this selection from this bet.

					seln_max_spl = seln_max_stk * Bet.bt_sf / num_lines_per_seln;

					if (seln_max_spl < 0.0) {
						seln_max_spl = 0.0;
					}

					if (seln_max_spl < bet_max_spl) {
						bet_max_spl = seln_max_spl;
						// Note whether the max spl has been affected by stakes
						// from other bets on this selection.
						bet_max_reduced_xstk = (oth_stk >= 0.01);
					}

				}

			}

		}

		if (Bet.spl > 0.0 && bet_max_reduced_xstk) {
			_maxima_reduced_by_cross_bet_stakes = true;
		}

		max_spls.push(bet_max_spl);

	}

	return max_spls;
}


// Update and check the maximum bet limits for all bets on the betslip.
// This is surprisingly complicated because PPW want max stakes to be
// enforced across all bets on the betslip, not just on a per-bet basis.
// Returns true if any max bet limits are exceeded and false otherwise.
function BS_upd_max_bet() {

	var i, j;
	var Leg, Bet;
	var leg_num, leg_nums, mult_legs;
	var LegsOnEvOc, ev_oc_ids, ev_oc;
	var leg_idx, leg_sort, legs_on, other_leg_idx, other_leg_sort;
	var stake, spl;
	var max_spls, max_spl;
	var any_exceeded, exceeded;
	var key, elem, max_bet_msg;

	// If the customer isn't logged in then we simply return
	if (!appears_logged_in()) return;

	// And similarly if there are no selections.
	if (!BS_get_leg_nums().length) return;

	// The clever stuff is done by _calc_max_spls; we just need to perform
	// the more tedious task of extracting the information it needs.

	// Extract the detailed max bet info from the page into the MB hash.
	// SB_bet::_bind_max_bet_details should have done most of the work.

	var MB;
	try {
		// Evaluate the contents of the mb_js element.
		// This should create our MB hash.
		eval($('mb_js').value);
		if (MB == null) {
			throw 'no MB hash found';
		}
	} catch (e) {
		throw 'could not interpret mb_js due to ' + e;
	}

	var Legs = new Array();
	var Bets = new Array();

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		Leg = new Object();

		Leg.leg_sort   = COOKIE.leg[leg_num].leg_sort;
		if (Leg.leg_sort == '') {
			Leg.leg_sort = '--';
		}
		Leg.price_type = COOKIE.leg[leg_num].price_type;
		Leg.ev_oc_ids  = document.getElementById("ev_oc_ids_sgl_" + leg_num).value.split(",");

		Legs[Legs.length] = Leg;

	}

	// Add the singles bets first.

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		Bet = new Object();

		Bet.leg_type         = BS_get("leg_type", "SGL" , leg_num);

		// Singles have one leg (obviously)
		Bet.legs             = [ leg_num ];

		Bet.grp_sf           = parseFloat($("grp_sf_sgl_"   + leg_num).value);
		Bet.bt_sf            = parseFloat($("bt_sf_sgl_"    + leg_num).value);
		Bet.bet_type_max     = parseFloat($("type_max_sgl_" + leg_num).value);
		Bet.leg_sort         = COOKIE.leg[leg_num].leg_sort;

		var total_num_lines = BS_get("num_lines", "SGL" , leg_num);
		var num_selns       = COOKIE.leg[leg_num].selections.split('c').length;
		// There's only one leg, so it must appear in all the lines.
		switch (Bet.leg_sort) {
			case 'CF':
				Bet.lines_per_leg = [ total_num_lines * 2 / num_selns ];
				break;
			case 'CT':
				Bet.lines_per_leg =  [ total_num_lines * 3 / num_selns ];
				break;
			case 'RF':
				Bet.lines_per_leg    = [ 2 ];
				break;
			default:
				Bet.lines_per_leg    = [ BS_get("num_lines", "SGL" , leg_num) ];
		}

		stake = BS_get("spl", "SGL", leg_num);
		// a NaN is not greater than zero...
		if (!(stake > 0.0)) {
			stake = 0.0;
		}
		Bet.spl              = stake;

		// Not needed for the calculation, but it'll help us find the
		// field we need to update later.
		Bet.id_key           = leg_num;

		Bets.push(Bet);

	}

	// Add the multiples bets.

	var multi_types = BS_get_multi_types();
	for (i = 0; i < multi_types.length; i++) {

		var type_name = multi_types[i];

		Bet = new Object();

		Bet.leg_type         = BS_get('leg_type', type_name);

		Bet.grp_sf         = parseFloat($("grp_sf_"   + type_name).value);
		Bet.bt_sf          = parseFloat($("bt_sf_"    + type_name).value);
		Bet.bet_type_max   = parseFloat($("type_max_" + type_name).value);

		Bet.lines_per_leg  = $("leg_lines_" + type_name).value.split(",");

		Bet.legs = $("legs_" + type_name).value.split(",");

		spl = BS_get('spl', type_name, null, 0.0);
		// a NaN is not greater than zero...
		if (!(spl > 0.0)) {
			spl = 0.0;
		}
		Bet.spl              = spl;

		// Not needed for the calculation, but it'll help us find the
		// field we need to update later.
		Bet.id_key           = type_name;

		Bets.push(Bet);

	}

	max_spls = _calc_max_spls(MB, Bets, Legs);

	any_exceeded = false;

	for (i = 0; i < max_spls.length; i++) {

		max_spl  = max_spls[i];
		key      = Bets[i].id_key;
		exceeded = false;

		// Round down to two decimal places.
		max_spl = Math.round( max_spl*100 - 0.5 + 1e-6 ) / 100;

		if (Bet.spl > max_spl) {
			exceeded = true;
			any_exceeded = true;
		}

		max_bet_msg = BS_format_stake(max_spl);

		elem = document.getElementById('max_bet_disp_'+key);
		if (elem) elem.innerHTML = max_bet_msg;

		elem = document.getElementById('max_bet_disp_tooltip_'+key);
		if (elem) elem.innerHTML = max_bet_msg;

		if (!isNaN(key)) {
			document.getElementById("max_bet_sgl_" + key).value = max_spl;

			// MAX is short hand for the max bet for this single so replace it
			elem = document.getElementById("stake_sgl_"+key);
			if (elem) {
				if (elem.value == "MAX") {
					BS_upd_sgl_stake(key, max_spl, false);
				}
			}
		} else {
			document.getElementById("max_bet_" + key).value = max_spl;

			// MAX is short hand for the max bet for this type so replace it
			elem = document.getElementById("stake_type_"+key);
			if (elem) {
				if (elem.value == "MAX") {
					BS_upd_type_stake(key, max_spl);
				}
			}
		}

	}

	_BS_upd_max_bet_plural();

	return any_exceeded;
}


// Update the maximum bet shown next to the "plural" bet.
function _BS_upd_max_bet_plural() {

	var i;
	var leg, legs;
	var max_spl, min_max_spl;
	var max_bet_msg, elem, key;

	legs = BS_get_plural_legs();
	if (legs.length < 1) {
		return;
	}

	// We want the smallest max bet amongst the singles in the plural.

	for (i = 0; i < legs.length; i++) {
		leg = legs[i];
		max_spl = parseFloat(document.getElementById("max_bet_sgl_" + leg).value);
		if ( i == 0 || max_spl < min_max_spl ) {
			min_max_spl = max_spl;
		}
	}

	max_bet_msg = BS_format_stake(min_max_spl);

	key = 'plural';

	elem = document.getElementById('max_bet_disp_'+key);
	if (elem) elem.innerHTML = max_bet_msg;

	elem = document.getElementById('max_bet_disp_tooltip_'+key);
	if (elem) elem.innerHTML = max_bet_msg;

	return;
}

// checks that the selections in the betslip are NOT all suspended.
// returns true if at least one selection is valid
// returns false if ALL selections are suspended
function BS_selections_not_all_suspended() {

	var msg      = betslipbody.msg;

	var leg_nums = BS_get_leg_nums();
	var num_susp = 0;

	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		if (BS_is_suspended(leg_num)) num_susp++;
	}

	if (num_susp < leg_nums.length) {
		return true;
	} else {
		BS_display_error(msg.BS_ALL_SELN_SUSP_TITLE, msg.BS_ALL_SELN_SUSP_TEXT);
		return false;
	}

}

// checks if the stake per line for each bet is within the limits,
// returns true if they are ok, false otherwise
// Warning: relies on BS_upd_max_bet having been called first.
function BS_stakes_within_limits() {

	var msg = betslipbody.msg;

	// ensure at least one bet has a stake
	var bet_exists = false;

	// store stake errors
	var err_str = '';

	// Matchedbet info
	var matchedbet_token_id = document.forms[betslipbody.mainForm].elements['matchedbet_token_id'].value;
	var matchedbet_stake = document.forms[betslipbody.mainForm].elements['matchedbet_stake'].value;
	var matchedbet_leg_or_type = document.forms[betslipbody.mainForm].elements['matchedbet_leg_or_type'].value;

	var ccy = document.getElementById('cust_html_ccy').value.unescapeHTML();

	// check singles
	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		var stake   = COOKIE.leg[leg_num].stake;

		if (stake != "" && stake > 0) {
			bet_exists = true;
		}

		var max_bet   = $("max_bet_sgl_"     + leg_num).value;
		var min_bet   = $("min_bet_sgl_"     + leg_num).value;
		var seln_name = $("txt_sgl_oc_desc_disp_" + leg_num).innerHTML + ' ' + $("txt_sgl_oc_desc_hcap_" + leg_num).innerHTML;
		var el = $("txt_sgl_ev_desc_" + leg_num);
		var ev_name;
		if (el != null) {
			ev_name = el.innerHTML;
		} else {
			ev_name = '';
		}

		if (appears_logged_in()) {
			var stake = parseFloat(stake);

			var is_invalid = false;
			if (max_bet >= 0.0 && stake >= 0.0 && stake > max_bet) {
				var max_stake = BS_format_stake(max_bet);

				var high_msg = _maxima_reduced_by_cross_bet_stakes ?
					msg.BS_STAKE_SGL_TOO_HIGH_CROSS : msg.BS_STAKE_SGL_TOO_HIGH;

				err_str += BS_js_subst(high_msg,
					{item: seln_name, amount: max_stake, event: ev_name});
				// Check if this is the result of the matchedbet offer
				if (matchedbet_leg_or_type != "" && matchedbet_leg_or_type == leg_num) {
					var original_stake = stake - matchedbet_stake;
					if (original_stake <= max_bet) {
						err_str += BS_js_subst(msg.BS_STAKE_SGL_MB_TOO_HIGH,
						{matchedbet_stake: matchedbet_stake, ccy: ccy});
					}
					BS_deallocate_matchedbet_token();
				}

				is_invalid = true;
			} else if (min_bet >= 0.0 && stake > 0.0 && stake < min_bet) {
				var min_stake = BS_format_stake(min_bet);
				err_str += BS_js_subst(msg.BS_STAKE_SGL_TOO_LOW,
					{item: seln_name, amount: min_stake, event: ev_name});
				is_invalid = true;
			} else if (stake < 0) {
				err_str += BS_js_subst(msg.BS_STAKE_LESS_THAN_ZERO,
					{item: seln_name});
				is_invalid = true;
			}

			if (is_invalid) {
				err_str += '<br><br>';
			}

		}
	}

	// check multiples

	var multi_types = BS_get_multi_types();
	for (i = 0; i < multi_types.length; i++) {

		type_name = multi_types[i];

		var max_bet   = document.getElementById("max_bet_"+type_name).value;
		var min_bet   = document.getElementById("min_bet_"+type_name).value;
		var stake     = document.getElementById("stake_type_"+type_name).value;
		var type_desc = document.getElementById("type_desc_"+type_name).value;

		if (stake != "" && stake > 0) {
			bet_exists = true;
		}

		if (appears_logged_in()) {
			var stake = parseFloat(stake);

			if (max_bet >= 0.0 && stake >= 0.0 && stake > max_bet) {
				var max_stake = BS_format_stake(max_bet);

				var high_msg = _maxima_reduced_by_cross_bet_stakes ?
					msg.BS_STAKE_TYPE_TOO_HIGH_CROSS : msg.BS_STAKE_TYPE_TOO_HIGH;

				err_str += BS_js_subst(high_msg,
					{item: type_desc, amount: max_stake}) + '<br><br>';
			} else if (min_bet >= 0.0 && stake > 0.0 && stake < min_bet) {
				var min_stake = BS_format_stake(min_bet);
				err_str += BS_js_subst(msg.BS_STAKE_TYPE_TOO_LOW,
					{item: type_desc, amount: min_stake}) + '<br><br>';
			} else if (stake < 0) {
				err_str += BS_js_subst(msg.BS_STAKE_LESS_THAN_ZERO,
					{item: type_desc});
			}
		}

	}

	if (!bet_exists) {
		BS_display_error(msg.BS_STAKE_INVALID, msg.BS_STAKE_INPUT_PROMPT);
		return false;
	}

	if (err_str != '') {
		BS_display_error(msg.BS_STAKE_INVALID, err_str);
		return false;
	}

	// validate freebet usage
	var fb_value = BS_get_freebet_token_value();
	if (fb_value > 0) {
		var fb_ok = false;

		var leg = document.forms[betslipbody.mainForm].elements['freebet_leg_or_type'].value;
		if (leg != "") {
			var stake = BS_get_stake(leg);
			if (stake >= fb_value) {
				fb_ok = true;
			}
		}

		if (!fb_ok) {
			BS_display_error(msg.BS_STAKE_INVALID, msg.BS_FREEBET_ENTIRETY);
			return false;
		}
	}

	return true;
}



// function called whenever a price type changes
function BS_do_price_type_change() {

	// update price type in cookie
	BS_upd_cookie();

	// update dependent fields
	BS_upd_total_stake();
	BS_upd_potential_winnings();
	BS_upd_max_bet();
}


// Called when the leg sort dropdown of a single has changed.
function BS_leg_sort_changed(leg_num, leg_sort) {

	var key = 'sgl_' + leg_num + '_' + leg_sort;

	// Look up new values for num lines and leg sort description.

	var num_lines     = document.getElementById('ls_lines_' + key).value;
	var leg_sort_desc = document.getElementById('ls_desc_'  + key).value;

	// Update html / input fields / cookie array accordingly.

	COOKIE.leg[leg_num].leg_sort = leg_sort;
	document.getElementById('num_lines_sgl_' + leg_num).value = num_lines;
	var el = document.getElementById('txt_sgl_mkt_desc_' + leg_num);
	if (el) {
		el.innerHTML = leg_sort_desc;
	}

	// update leg sort in cookie
	BS_upd_cookie();

	// update dependent fields
	BS_upd_total_stake();
	BS_upd_max_bet();
}


// update cookie with any info user may change on betslip
function BS_upd_cookie() {

	// update leg info
	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		// update price type
		COOKIE.leg[leg_num].price_type = BS_get('price_type','SGL',leg_num);

		// SP legs should not record any price values
		if (COOKIE.leg[leg_num].price_type == 'S') {
			COOKIE.leg[leg_num].lp_num = '';
			COOKIE.leg[leg_num].lp_den = '';
			COOKIE.leg[leg_num].prev_lp_num = '';
			COOKIE.leg[leg_num].prev_lp_den = '';
		}

		// update stake
		COOKIE.leg[leg_num].stake      = BS_get('spl','SGL',leg_num);

		// update leg_type
		COOKIE.leg[leg_num].leg_type   = BS_get('leg_type','SGL',leg_num);

		// update multiple inclusions flag
		var mult_incl            = BS_get('mult_inclusion','SGL',leg_num);
		COOKIE.leg[leg_num].mult_incl  = mult_incl ? 'Y': 'N';

		// update USE_GP pref
		update_use_gp();
	}

	//update combi info
	var combi_type = '';
	for (var i = 0; i < COOKIE.combi_type.length; i++) {
		combi_type = COOKIE.combi_type[i];

		// update stake
		COOKIE.combi[combi_type].stake    = BS_get('spl',      combi_type);

		// update combi type
		COOKIE.combi[combi_type].leg_type = BS_get('leg_type', combi_type);
	}

	BS_store_hash_cookie();
}


// Calculate the raw unit stake potential return for a bet.
// Returns a list of the form [ pot_rtn_win, pot_rtn_plc ] where:
//   pot_rtn_win = Estimated win only return from unit stake,
//                 or NaN if unable to calculate.
//   pot_rtn_plc = Estimated place only return from unit stake,
//                 or NaN if unable to calculate.
function _BS_raw_pot_rtn(bet_type, index) {

	var i;
	var leg_num, leg_nums;
	var price_type;
	var pot_rtn_win, pot_rtn_plc;

	if (bet_type == "SGL") {
		leg_nums = [ index ];
	} else {
		leg_nums = document.getElementById("legs_" + bet_type).value.split(",");
	}

	for (i = 0; i < leg_nums.length; i++) {
		leg_num = leg_nums[i];
		price_type = BS_get("price_type", "SGL", leg_num);
		if ( !(price_type == "L" || price_type == "G") ) {
			// Cannot give pot rtn if any of the legs are neither LP nor GP.
			return [ NaN, NaN ];
		}
	}

	pot_rtn_win = parseFloat(BS_get("pot_rtn_win", bet_type, index));
	if ( !(pot_rtn_win > 0.0) ) {
		// Cannot give pot rtn if the server did not provide it.
		pot_rtn_win = NaN;
	}

	pot_rtn_plc = parseFloat(BS_get("pot_rtn_plc", bet_type, index));
	if ( !(pot_rtn_plc > 0.0) ) {
		// Cannot give pot rtn if the server did not provide it.
		pot_rtn_plc = NaN;
	}

	return [ pot_rtn_win, pot_rtn_plc ];
}


// Calculate potential return for a bet.
// Returns an object with properties:
//   pot_rtn              = Potential return from current stake (to 2dp),
//                          or 0.0 if no valid stake,
//                          or NaN if unable to calculate.
//   pot_rtn_msg          = Potential return to show customer.
//   unit_stk_rtn_win     = Unit stake win-only potential return (to 2dp),
//                          or NaN if unable to calculate.
//   unit_stk_rtn_win_msg = Unit stake win-only pot. return to show customer.
function BS_pot_rtn(bet_type, index) {

	var Rtn = new Object();

	var raw_rtns = _BS_raw_pot_rtn(bet_type,index);
	var pot_rtn_win = raw_rtns[0];
	var pot_rtn_plc = raw_rtns[1];

	if ( isNaN(pot_rtn_win) ) {
		// Win-only potential return could not be calculated.
		Rtn.unit_stk_rtn_win     = NaN;
		Rtn.unit_stk_rtn_win_msg = betslipbody.msg.BS_NOT_APPLICABLE;
	} else {
		Rtn.unit_stk_rtn_win     = BS_round(pot_rtn_win);
		Rtn.unit_stk_rtn_win_msg = BS_format_stake(pot_rtn_win);
	}

	var spl = BS_get("spl", bet_type, index);

	if ( !(spl > 0.0) ) {
		// Stake is zero or invalid.
		// Treat this as a zero potential return for calculations, but
		// show N/A to the customer.
		Rtn.pot_rtn     = 0.0;
		Rtn.pot_rtn_msg = betslipbody.msg.BS_NOT_APPLICABLE;
	} else {

		var leg_type = BS_get("leg_type", bet_type, index);

		var pot_rtn = 0.0;
		if (leg_type == "W" || leg_type == "E") {
			pot_rtn += pot_rtn_win;
		}
		if (leg_type == "P" || leg_type == "E") {
			pot_rtn += pot_rtn_plc;
		}

		if ( !(pot_rtn > 0.0) ) {
			// Potential returns could not be calculated.
			Rtn.pot_rtn     = NaN;
			Rtn.pot_rtn_msg = betslipbody.msg.BS_NOT_APPLICABLE;
		} else {
			pot_rtn = pot_rtn * spl;

			// Remove freebet value
			var freebet_value = BS_get_freebet_token_value();
			if (freebet_value) {
				var selected_bet = document.forms[betslipbody.mainForm].elements['freebet_leg_or_type'].value;
				if (
					bet_type     == 'SGL' &&
					selected_bet == index ||
					selected_bet == bet_type
				) {
					var current_stake = BS_get_stake(selected_bet)

					freebet_value = parseFloat(freebet_value);
					current_stake = parseFloat(current_stake);

					if (current_stake > freebet_value) {
						pot_rtn -= freebet_value;
					} else {
						pot_rtn -= current_stake;
					}
				}
			}

			if (betslipbody.cfg.POT_RTN_OBEYS_MAX_PAY != 'N') {
				var max_payout = BS_get("max_payout", bet_type, index);
				if (max_payout > 0.0 && pot_rtn > max_payout) {
					pot_rtn = max_payout;
				}
			}
			Rtn.pot_rtn     = BS_round(pot_rtn);
			var ccy = document.getElementById('cust_html_ccy').value;
			Rtn.pot_rtn_msg = ccy + BS_format_stake(pot_rtn);
		}

	}

	return Rtn;
}


// Update the potential winnings and accumulated odds text.
function BS_upd_potential_winnings() {

	var i;
	var num_sgls;
	var leg_num;
	var Rtn;

	BS_allocate_freebet_token();

	// We show tooltips warning the customer not to take the potential
	// winnings as gospel when they're over this threshold - which should
	// be converted to the customer's currency (XXX but isn't currently)!
	var warn_threshold = parseFloat(betslipbody.cfg.POT_WARN_THRESHOLD);

	// NB: we'll rely on the equalities "x + NaN == NaN" and "!(NaN > 0.0)"
	// when manipulating and testing this.
	var tot_pot_rtn = 0.0;

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		Rtn = BS_pot_rtn("SGL", leg_num);

		tot_pot_rtn += Rtn.pot_rtn;

		var html_pot_win = $('txt_potential_win_' + leg_num);
		if (html_pot_win) {
			html_pot_win.innerHTML = Rtn.pot_rtn_msg;
		}

		var warn_el = $('pot_win_warn_' + leg_num);
		if (warn_el != null) {
			if (Rtn.pot_rtn < warn_threshold) {
				warn_el.style.display = 'none';
			} else {
				warn_el.style.display = '';
			}
		}

	}

	// foreach multiple bet type ...

	var multi_types = BS_get_multi_types();
	for (i = 0; i < multi_types.length; i++) {

		type_name = multi_types[i];

		Rtn = BS_pot_rtn(type_name);

		tot_pot_rtn += Rtn.pot_rtn;

		var html_pot_win = $('txt_potential_win_' + type_name);
		if (html_pot_win) {
			html_pot_win.innerHTML = Rtn.pot_rtn_msg;
		}

		var html_acc_odds = $('txt_acc_odds_' + type_name);
		if (html_acc_odds) {
			html_acc_odds.innerHTML = Rtn.unit_stk_rtn_win_msg;
		}

		var warn_el = $('pot_win_warn_' + type_name);
		if (warn_el != null) {
			if (Rtn.pot_rtn > warn_threshold) {
				warn_el.style.display = '';
			} else {
				warn_el.style.display = 'none';
			}
		}

	}

	var potential_win = $('txtPotentialWin');
	if (potential_win) {
		if (tot_pot_rtn > 0.0) {
			var ccy = document.getElementById('cust_html_ccy').value;
			potential_win.innerHTML = ccy + BS_format_stake(tot_pot_rtn);
		} else {
			potential_win.innerHTML = betslipbody.msg.BS_NOT_APPLICABLE;
		}
	}

	var warn_el = $('pot_win_warn');
	if (warn_el != null) {
		if (tot_pot_rtn > warn_threshold) {
			warn_el.style.display = 'block';
		} else {
			warn_el.style.display = 'none';
		}
	}

	return tot_pot_rtn;
}


// function to format the stake to 2 d.p. Stake is returned
// as a string so should only be used for displaying stake
function BS_format_stake(stake) {

	stake = (Math.round(stake * 100)/100).toString();
	var decimal = stake.indexOf('.');
	if (decimal == -1) {
		stake = stake + ".00";
	} else if (decimal == stake.length - 1) {
		stake = stake + "00";
	} else if (decimal == stake.length - 2) {
		stake = stake + "0";
	} else {
		stake = stake.substr(0,(decimal + 3));
	}

	return stake;
}


// Turn the contents of the given input field into either a monetary value
// in canonical form (e.g. 0.10 not .1) or the empty string if invalid.
// We probably don't want to do this while someone is still typing.
function BS_canonicalise_monetary_input(el) {

	var s, f, c;

	s = el.value;
	c = '';

	if (s.length) {
		f = parseFloat(s);
		if (!isNaN(f)) {
			c = BS_format_stake(f);
		}
	}

	if (c.indexOf("+") != -1 || c.length > 9) {
		alert(betslipbody.msg.BS_STAKE_INVALID);
		c = '';
	}

	if (s != c) {
		el.value = c;
	}

	return c;
}


// Ensure that the given input field contains a monetary value,
// and if not, remove everything from the first bad char onwards.
// It should be reasonable to do this while someone is typing.
function BS_ensure_monetary_input(el) {

	var s    = el.value;
	var re   = /(^[0-9]{0,8}(\.[0-9]{0,2})?)/;
	var mtch = s.match(re);

	var good;
	if (mtch.length) {
		good = mtch[0];
	} else {
		good = '';
	}

	if (good != s) {
		el.value = good;
	}

	return good;
}


// Call this whenever the contents of a stake input box may have changed.
// Optional params:
//   from_kb      - was this change the result of a keyboard input?
//   skip_dep_upd - skip updates of dependent info; caller must call
//                  BS_stakes_changed() himself.
function BS_stake_changed(box_id, from_kb, skip_dep_upd) {

	var el = document.getElementById(box_id);

	if (from_kb) {
		BS_ensure_monetary_input(el);
	} else {
		BS_canonicalise_monetary_input(el);
	}

	if (!skip_dep_upd) {
		// Request that the cookie and anything else derived from the stake
		// (total stake, max bet, potential returns) be updated.
		BS_stakes_changed();

		// If this is a single stake, and it's no longer the same as the
		// plural stake, clear the latter.
		if (box_id.indexOf("_sgl_") != -1) {
			BS_clear_plural_stake_if_not(el.value);
		}
	}
}


// Call when one or more stakes may have changed to request that
// the cookie and anything else derived from the stake (total stake,
// max bet, potential returns) be updated.
// Set force to true to ensure an instant update.
var stk_chg_timer = null;
function BS_stakes_changed(force) {

	// It's important to keep the cookie up-to-date ...

	BS_upd_cookie();

	if (stk_chg_timer) {
		window.clearTimeout(stk_chg_timer);
	}

	if (force) {
		_BS_stakes_changed_delayed();
	} else {
		// .. but recalculating total stakes, max bets and estimated returns
		// is quite expensive - we certainly don't want to do it on every
		// keypress. We'll call BS_stakes_changed_real() once no-one has
		// called BS_stakes_changed() for half a second or so.
		stk_chg_timer = window.setTimeout("_BS_stakes_changed_delayed()", 666);
	}
}


// Called once stake(s) have changed and remained stable for a bit.
function _BS_stakes_changed_delayed() {
	if (stk_chg_timer) {
		window.clearTimeout(stk_chg_timer);
	}
	BS_upd_total_stake();
	BS_upd_potential_winnings();
	BS_upd_max_bet();
}


// Update the stake for a single.
// Optional Params:
//   skip_dep_upd - skip updates of dependent info; caller must call
//                  BS_stakes_changed() himself.
function BS_upd_sgl_stake (leg_num, stk, skip_dep_upd) {

	var box_id = 'stake_sgl_' + leg_num;
	var el = document.getElementById(box_id);

	if (el) {
		el.value = stk;
		BS_stake_changed(box_id, false, skip_dep_upd);
	}

}


// Update the stake for a given multiple bet type.
function BS_upd_type_stake (bet_type, stk) {

	var box_id = 'stake_type_' + bet_type;
	var el = document.getElementById(box_id);

	if (el) {
		el.value = stk;
		BS_stake_changed(box_id, false);
	}

}


// Which legs appear in the "plural" bet?
function BS_get_plural_legs() {
	var el = document.getElementById("legs_plural");
	if (!el || !el.value.length) {
		return [];
	} else {
		var legs = el.value.split(",");
		var keep_legs = [];
		for (var i = 0; i < legs.length; i++) {
			var leg = legs[i];
			if (!BS_is_suspended(leg)) keep_legs.push(leg);
		}
		return keep_legs;
	}
}


// Call this when the stake in the plural stake box has changed.
// from_kb - was this change the result of a keyboard input?
function BS_plural_stake_changed(from_kb) {

	var i;
	var legs, leg;
	var el;
	var spl;

	el = document.getElementById("stake_plural");

	if (from_kb) {
		BS_ensure_monetary_input(el);
	} else {
		BS_canonicalise_monetary_input(el);
	}

	spl = el.value;

	if (spl == '') {
		// XXX In two minds about this ... Perhaps there's a better way?
		// Don't want to blow away the user's stakes for no reason
		// (esp. if we merely tabbed into the box) but on the other
		// hand returning causes slightly odd behaviour: when deleting
		// a plural stake using the backspace key the first digit
		// remains in the sgl stakes.
		// return;
	}

	legs = BS_get_plural_legs();

	for (i = 0; i < legs.length; i++) {
		leg = legs[i];
		// Promise that we'll call BS_stakes_changed ourselves.
		BS_upd_sgl_stake(leg, spl, true);
	}

	BS_stakes_changed();
}


// Clear the plural stake if it is not the same as the given stake.
// Does not update the singles stakes.
function BS_clear_plural_stake_if_not(stk) {

	var el, pl_stk;


	el = document.getElementById("stake_plural");

	if (!el) {
		// may not always be a plural stake box
		return;
	}

	stk = parseFloat(stk);
	pl_stk = parseFloat(el.value);

	if (stk != pl_stk) {
		el.value = '';
	}

}

// Called when other EW boxes are altered
function BS_update_plural_ew() {

	var pl_ew_box = document.getElementById('leg_ew_type_plural');
	if (!pl_ew_box) return;

	var all_ew = true;

	var ew_box;
	var leg = BS_get_plural_legs();
	for (var i = 0; i < leg.length; i++) {
		ew_box = document.getElementById('leg_ew_sgl_' + leg[i]);

		if (!ew_box || ew_box.disabled) continue;

		all_ew &= ew_box.checked;
	}

	pl_ew_box.checked = all_ew;
}

// Called when the plural EW checkbox has changed.
function BS_plural_ew_changed() {

	var legs, i, leg;
	var ew;
	var el;

	ew = document.getElementById("leg_ew_type_plural").checked;

	legs = BS_get_plural_legs();
	for (i = 0; i < legs.length; i++) {
		leg = legs[i];
		el = document.getElementById("leg_ew_sgl_" + leg);
		if (el && !el.disabled) {
			el.checked = ew;
		}
	}

	// update cookie - this will pick up the leg types from the checkboxes
	BS_upd_cookie();

	BS_upd_total_stake();
	BS_upd_potential_winnings();
	BS_upd_max_bet();

}

// Updates total stake
function BS_upd_total_stake() {
	var total_stake = document.getElementById("txtTotalStake");
	//Avoiding js errors...
	if (total_stake) {
		total_stake.innerHTML = BS_format_stake(BS_get_total_stake());
	}
}

// Gets total stake taking into account num lines and leg type.
function BS_get_total_stake() {

	var total_stake = 0.0;

	// Add singles stakes.
	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		var stake = BS_get_stake(leg_num);
		if (stake > 0.0) {
			total_stake += stake;
		}
	}

	// Adds any matchedbet stake
	if (parseFloat($('matchedbet_stake')) > 0) {
		total_stake += parseFloat($('matchedbet_stake'));
	}

	var multi_types = BS_get_multi_types();
	for (i = 0; i < multi_types.length; i++) {

		type_name = multi_types[i];

		var stake = BS_get_stake(type_name);
		if (stake > 0.0) {
			total_stake += stake;
		}

	}
	
	return total_stake;
}


// Place the bet(s).
function place_bet() {

	//As we are not in a fast bet clear its stuff
	set_cookie("FASTBET_PROCESS","","","/");
	BS_clear_fastbet_cookie();

	var freebet_cookie      = get_cookie(betslipbody.cookie_info.BET_FREEBET_COOKIE_NAME);
	var lowest_token_value  = document.getElementById("lowest_token_value");
	var freebets_menu       = document.getElementById('lstFreeBets');
	
	// are their tokens the customer is not using
	if (!freebet_cookie && freebets_menu && lowest_token_value) {
		// is the stake greater than or equal to the value of the lowest value unused tokem
		if (BS_get_total_stake() >= lowest_token_value.value) {
			// show the freebets warning and bail
			show_freebet_warning();
			return
		}
	}
	
	place_bet_work();
}

// Place the bet(s).
function place_bet_work() {
	
	// Force the cookie and max bets, pot wins, tot stk to be updated.
	BS_stakes_changed(true);

	// allocate freebet/mathed bet token
	if(!BS_allocate_freebet_token()) {
		// Matchedbet token are not in use
		//BS_allocate_matchedbet_token();
	}

	if (!BS_valid_betslip()) return;

	if (BS_confirm_bet()) {

		if(BS_show_prc_hcap_alerts()) {
			BS_deallocate_matchedbet_token();
		} else {
			// Submit the betslip via http
			//We have to take into account that some overrides could
			//exist, so the BS_show_prc_hcap_alerts cannot detect all
			//the changes.
	                set_cookie("POSSIBLE_PLACED_BET","1","","/");
			BS_submit_betslip('betSlipMainForm',betslipbody.url.CGI_URL);
		}
	} else {
		BS_deallocate_matchedbet_token();
	}
}

function show_freebet_warning() {
	if (document.getElementById('betSlipFreebet')) {
		document.getElementById('betSlipFreebet').style.display = "";
	}
}

function hide_freebet_warning() {
	if (document.getElementById('betSlipFreebet')) {
		document.getElementById('betSlipFreebet').style.display = "none";
	}
}


function force_freebet() {
	var lowest_token_id  = document.getElementById("lowest_token_id");
	if (lowest_token_id != null) {
		BS_update_freebet(lowest_token_id.value);
		BS_update_freebet_menu();
	}
	
	place_bet_work();
}

//Display an alert box in case there have been some updates for
//prices a/o hcap/indexes
function BS_show_prc_hcap_alerts () {

	var new_msgs = new Array();
	var prc_chg_alert = get_pref('BETSLIP_ALERTS'); //3 possible values:
							//ignore lengthened odds, (Better)
							//ignore shortened and lengthened odds (All) or
							//never ignore price changes (Prompt)

	if (prc_chg_alert == null || prc_chg_alert == "") {
		prc_chg_alert = "Better";
	}


	var hcp_chg_alert = get_pref('BP_HP_IDX_ALERTS');	//2 possible values:
								// ignore hcap changes (All)
								// never ignore hcap changes (Prompt)

	if (hcp_chg_alert == null || hcp_chg_alert == "") {
		hcp_chg_alert = "Prompt";
	}


	var alerts_cfg    = betslipbody.cfg.BETSLIP_ALERTS;

	//if ignoring changes or skiping alerts
	if ((hcp_chg_alert == "All" && prc_chg_alert == "All") || alerts_cfg == 'N') return false;

	var is_price_change_alert = false;
	var is_hcap_change_alert = false;

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		// skip multi-part legs
		if (COOKIE.leg[leg_num].selections.split('c').length != 1) continue;

		var sgl  = COOKIE.leg[leg_num];

		// skip suspended selections
		var susp = document.getElementById('suspended_' + leg_num);
		if (susp != null && susp.value == 'Y') continue;

		//Price has changed
		if (sgl.prev_lp_num != null && sgl.prev_lp_num != null && sgl.prev_lp_num != "" && sgl.prev_lp_num != "") {

			var odds_longer = (sgl.lp_num * sgl.prev_lp_den) > (sgl.lp_den * sgl.prev_lp_num);

			// Do not display a price change alert to the customer if
			// price alerts for lengthening odds has been disabled
			if ( !(prc_chg_alert == "Better" && odds_longer) && prc_chg_alert != "All" ){

				// Generate the alert message to display to the customer
				var ev_oc_desc = document.getElementById('txt_sgl_oc_desc_disp_' + leg_num).innerHTML + ' ' +  document.getElementById('txt_sgl_oc_desc_hcap_' + leg_num).innerHTML;

				var msg_id  = 'price_change_message_' + leg_num;
				var msg_txt = betslipbody.msg.BS_BIR_PRICE_UPD_TXT + " " + ev_oc_desc + '<br><br>';
				var msg     = new BS_ErrorMessage(msg_id, msg_txt);
				new_msgs.push(msg);
				is_price_change_alert = true;
			}

			//Remove previous price from the cookie
			// for the next time we attempt to place the bet not appear the alert box
			sgl.prev_lp_num = null;
			sgl.prev_lp_den = null;
			COOKIE.leg[leg_num].prev_lp_num = null;
			COOKIE.leg[leg_num].prev_lp_den = null;

			sgl.displayed_lp_num = sgl.lp_num;
			sgl.displayed_lp_den = sgl.lp_den;
			COOKIE.leg[leg_num].displayed_lp_num = COOKIE.leg[leg_num].lp_num;
			COOKIE.leg[leg_num].displayed_lp_den = COOKIE.leg[leg_num].lp_den;
		}

		//Hcap has changed
		if(sgl.prev_hcap_value != null && sgl.prev_hcap_value != "") {
			var show_alert = true;
			// Check the tolerance for the hcap
			var tolerance = document.getElementById('tolerance_sgl_' + leg_num).value;
			if ( betslipbody.cfg.USE_TOLERANCE && tolerance != null && tolerance != "" ) {

				if ( !((parseFloat(sgl.prev_hcap_value) - parseFloat(tolerance)) > sgl.hcap_value ||
				       (parseFloat(sgl.prev_hcap_value) + parseFloat(tolerance)) < sgl.hcap_value )) {
					// Not show alert
					show_alert = false;
				}
			}

			if (hcp_chg_alert == "Prompt" && show_alert) {
				// Generate the alert message to display to the customer
				var ev_oc_desc = document.getElementById('txt_sgl_oc_desc_disp_' + leg_num).innerHTML + ' ' + document.getElementById('txt_sgl_oc_desc_hcap_' + leg_num).innerHTML;

				var msg_id  = 'hcap_change_message_' + leg_num;
				var msg_txt = betslipbody.msg.BS_BIR_HCAP_UPD_TXT + " " + ev_oc_desc + '<br><br>';
				var msg     = new BS_ErrorMessage(msg_id, msg_txt);
				new_msgs.push(msg);
				is_hcap_change_alert = true;
			}

			//Remove previous hcap from the cookie
			// for the next time we attempt to place the bet not appear the alert box
			sgl.prev_hcap_value = null;
			COOKIE.leg[leg_num].prev_hcap_value = null;
			COOKIE.leg[leg_num].displayed_hcap_value = COOKIE.leg[leg_num].hcap_value;
		}

	}

	//Update the cookie
	BS_store_hash_cookie();

	// Display an update alert
	if ( new_msgs.length  > 0 ) {
		for (var i = 0; i < new_msgs.length; i++) {
			BS_add_error(new_msgs[i]);
		}
		var alert_msg = BS_error_string();

		// Show the disable price/hcap change alerts option
		if (is_price_change_alert || is_hcap_change_alert) {
			if (is_price_change_alert && is_hcap_change_alert) {
				BS_display_error(
					betslipbody.msg.BS_BIR_PRICE_UPD_TITLE,
					alert_msg,
					BS_set_price_change_alerts_pref,
					betslipbody.msg.BS_DO_NOT_SHOW_IN_FUTURE,
					false,
					BS_set_hcap_change_alerts_pref,
					betslipbody.msg.BS_DO_NOT_SHOW_IN_FUTURE_HCAP
				);
			} else if (is_price_change_alert){
				BS_display_error(
					betslipbody.msg.BS_BIR_PRICE_UPD_TITLE,
					alert_msg,
					BS_set_price_change_alerts_pref,
					betslipbody.msg.BS_DO_NOT_SHOW_IN_FUTURE
				);
			} else {
				BS_display_error(
					betslipbody.msg.BS_BIR_PRICE_UPD_TITLE,
					alert_msg,
					BS_set_hcap_change_alerts_pref,
					betslipbody.msg.BS_DO_NOT_SHOW_IN_FUTURE_HCAP
				);
			}
		} else {
			BS_display_error(betslipbody.msg.BS_BIR_PRICE_UPD_TITLE, alert_msg);
		}
		return true;
	}
	return false;
}


// Display confirmation popup summarising the bets on the betslip.
// Returns true if the user wishes to place the bet, false otherwise.
//
function BS_confirm_bet() {

	var msg = "";

	var ccy = document.getElementById('cust_html_ccy').value.unescapeHTML();

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		var spl = BS_get("spl", "SGL", leg_num);
		if (!(spl > 0.0)) {
			continue;
		}

		var Bet = new Object();

		// These get substituted into the translation.
		if (parseInt($('matchedbet_leg_or_type').value) == leg_idx) {
			Bet.has_matchedbet_stake = true;
			spl -= $('matchedbet_stake').value;
		} else {
			Bet.has_matchedbet_stake = false;
		}


		Bet.stake            = ccy + BS_format_stake(spl);
		Bet.total            = ccy + BS_format_stake(BS_get_stake(leg_num));
		Bet.lines            = BS_get("num_lines", "SGL", leg_num);
		Bet.name             = $("txt_sgl_oc_desc_disp_" + leg_num).innerHTML.unescapeHTML() + ' ' + $("txt_sgl_oc_desc_hcap_" + leg_num).innerHTML.unescapeHTML();
		Bet.price_type       = BS_get("price_type", "SGL", leg_num);
		Bet.matchedbet_stake = ccy + $('matchedbet_stake').value;

		if ($('g_price_checkbox') && $('g_price_checkbox').checked || !$('g_price_checkbox')) {
			Bet.g_price_checked = 'Y';
		} else {
			Bet.g_price_checked = 'N';
		}

		var pt_el = $("price_type_sgl_" + leg_num);
		if (pt_el.options) {
			// The price type is a drop down; get the text of the
			// selected option.
			Bet.price = pt_el.options[pt_el.selectedIndex].text;
		} else {
			var txt_el;
			if ( (txt_el = $("txt_sgl_live_price_" + leg_num)) ||
			     (txt_el = $("txt_sgl_nonlive_price_" + leg_num)) ) {
				Bet.price = txt_el.innerHTML.unescapeHTML();
			} else {
				Bet.price = '??';
			}
		}

		var xlation;
		var leg_type    = BS_get("leg_type", "SGL", leg_num);
		if (leg_type == "W") {
			if (Bet.lines == 1) {
				// Operator can use a nice simple message for a win
				// single with one line.
				if (Bet.has_matchedbet_stake) {
					xlation = betslipbody.msg.BS_CONFIRM_MB_SGL_WIN;
				} else {
					xlation = betslipbody.msg.BS_CONFIRM_SGL_WIN;
				}
			} else {
				// Operator will probably need a more complex message for a
				// to win single with multiple lines (e.g. reverse forecast).
				if (Bet.has_matchedbet_stake) {
					xlation = betslipbody.msg.BS_CONFIRM_MB_SGL_COMB;
				} else {
					xlation = betslipbody.msg.BS_CONFIRM_SGL_COMB;
				}
			}
		} else {
			// Different message for Each Way single. We'll need to show terms
			// and double the number of lines too.
			Bet.lines *= 2;
			Bet.terms = $("txt_sgl_ew_desc_" + leg_num).innerHTML.unescapeHTML();
			if (Bet.has_matchedbet_stake) {
				xlation = betslipbody.msg.BS_CONFIRM_MB_SGL_EW;
			} else {
				xlation = betslipbody.msg.BS_CONFIRM_SGL_EW;
			}
			xlation = betslipbody.msg.BS_CONFIRM_SGL_EW;
		}

		msg += BS_js_subst(xlation, Bet) + "\n\n";

	}

	// Multiples.
	var multi_types = BS_get_multi_types();
	for (i = 0; i < multi_types.length; i++) {

		bet_type = multi_types[i];

		var spl = BS_get("spl", bet_type);
		if (!(spl > 0.0)) {
			continue;
		}

		var Bet = new Object();

		// These get substituted into the translation.
		Bet.stake       = ccy + BS_format_stake(spl);
		Bet.total       = ccy + BS_format_stake(BS_get_stake(bet_type));
		Bet.lines       = BS_get("num_lines", bet_type);
		Bet.type        = $('txt_comb_desc_' + bet_type).innerHTML.unescapeHTML();

		var xlation;
		var leg_type    = BS_get("leg_type", bet_type);
		if (leg_type == "W") {
			xlation = betslipbody.msg.BS_CONFIRM_MUL_WIN;
		} else {
			// It doesn't make sense to show ew terms for multiples
			// (well, I suppose we could if they're all the same).
			// But we do need to double the number of lines.
			Bet.lines *= 2;
			xlation = betslipbody.msg.BS_CONFIRM_MUL_EW;
		}

		msg += BS_js_subst(xlation, Bet) + "\n\n";

	}

	msg += BS_js_subst( betslipbody.msg.BS_CONFIRM_TOTAL,
	        {total: ccy + $('txtTotalStake').innerHTML.unescapeHTML()} ) + "\n";

	// get freebet value
	var freebet = 0.0;
	var token_id = $('freebet_token_id').value;
	if (token_id != "") {
		if ($(token_id + '_value')) {
			freebet = $(token_id + '_value').value;
		}
		msg += BS_js_subst( betslipbody.msg.BS_CONFIRM_FREEBET_TOTAL,
	               {total: ccy + freebet} ) + "\n";
	}

	msg += betslipbody.msg.BS_CONFIRM_BET;

	return confirm(msg);
}


// Generic function updating the betslip.
//
function BS_upd_betslip () {
	BS_load_betslip(betslipbody.url.CGI_URL + '?action=GoBetSlip','POST');
}


// validates login details and sets them in the main betslip
// form so that they are submitted with the bet details
function BS_login_and_place_bet() {

	// Force the cookie and max bets, pot wins, tot stk to be updated.
	BS_stakes_changed(true);

	if (!BS_valid_betslip()) return;

	var user = document.getElementById('bsUsername').value;
	var pass = document.getElementById('bsPassword').value;

	var user_dflt = document.getElementById('username_default').value;
	var pass_dflt = document.getElementById('password_default').value;


	if (user == "" || user == user_dflt) {
		BS_display_error(betslipbody.msg.BS_INVALID_USERNAME,betslipbody.msg.BS_ENTER_VALID_USERNAME,'','','','','','',true);
		return;
	}

	if (pass == "" || pass == pass_dflt) {
		BS_display_error(betslipbody.msg.BS_INVALID_PASSWORD,betslipbody.msg.BS_ENTER_VALID_PASSWORD,'','','','','','',true);
		return;
	}

	// set these in the main form
	var bs_form = document.forms['betSlipMainForm'];

	// copy details from login form to hidden elements in betSlipMainForm
	bs_form.elements['username'].value = user;
	bs_form.elements['pwd'].value = pass;

	BS_allocate_freebet_token();

	if (!BS_valid_betslip()) return;

	// prevent the onsubmit from being fired
	bs_form.onsubmit = "";

	if (BS_confirm_bet()) {
		// Submit the betslip via http
		bs_form.submit();
	}
}


// Is the betslip valid? i.e. the bets be placed?
function BS_valid_betslip() {

	// are there any selections on the slip
	if (BS_get_leg_nums().length == 0) {
		BS_display_error(betslipbody.msg.BS_NO_SELECTIONS_TITLE,
			betslipbody.msg.BS_NO_SELECTIONS_TEXT);
		return false;
	}

	// check whether the selections are suspended
	if(!BS_selections_not_all_suspended()) {
		return false;
	}

	// validate stakes
	if(!BS_stakes_within_limits()) {
		return false;
	}

	return true;
}


// Given a singles leg number or bet type identifier, return the
// total stake (taking into account number of lines and leg type)
// on the leg/type. Returns NaN if stake is empty or invalid.
function BS_get_stake(leg_or_type) {

	var stake;

	var spl;
	if (isNaN(leg_or_type)) {
		 spl = BS_get("spl", leg_or_type);
	} else {
		 spl = BS_get("spl", "SGL", leg_or_type);
	}
	if (isNaN(spl)) {
		return NaN;
	}

	var num_lines;
	if (isNaN(leg_or_type)) {
		 num_lines = BS_get("num_lines", leg_or_type);
	} else {
		 num_lines = BS_get("num_lines", "SGL", leg_or_type);
	}

	stake = spl * num_lines;

	var leg_type;
	if (isNaN(leg_or_type)) {
		 leg_type = BS_get("leg_type", leg_or_type);
	} else {
		 leg_type = BS_get("leg_type", "SGL", leg_or_type);
	}

	if (leg_type == "E") {
		stake = stake * 2;
	}

	return stake;
}

function BS_close_error_fast_bet(keep_fast_bet_cookie) {

	// Close error
	BS_close_error();

	// and clean fast bet cookie
	if (typeof keep_fast_bet_cookie == 'undefined' || keep_fast_bet_cookie == null || keep_fast_bet_cookie != '1') {
		BS_clear_fastbet_cookie();
	
		//Re-enable fast bet buttons
		enable_fastbet_buttons_upd_cookie();
	}


}



// Function to call when betslip error pane is closed.
// Set in BS_display_error() ; Called in BS_close_js_error()
var BS_error_close_function1 = '';
var BS_checkbox_function1 = '';
var BS_error_close_function2 = '';
var BS_checkbox_function2 = '';
var BS_error_keep_fastbet = '';

// Display the betslip error pane
function BS_display_error(title, text, func1, checkBoxMsg1, checkBoxStatus1, func2, checkBoxMsg2, checkBoxStatus2, keepFastbet) {
	if (func1) {
		BS_error_close_function1 = func1;
	} else {
		BS_error_close_function1 = '';
	}
	if (func2) {
		BS_error_close_function2 = func2;
	} else {
		BS_error_close_function2 = '';
	}

	if (checkBoxMsg1) {
		checkBoxStatus1 = checkBoxStatus1 == true;
		document.getElementById('betSlipJSErrorCheckbox1').checked = checkBoxStatus1;
		document.getElementById('betSlipJSErrorCheckboxText1').innerHTML = checkBoxMsg1;
		document.getElementById('betSlipJSErrorCheckboxDiv1').style.display = '';
		BS_checkbox_function1    = BS_error_close_function1;
		BS_error_close_function1 = BS_hide_checkbox1;
	}
	if (checkBoxMsg2) {
		checkBoxStatus2 = checkBoxStatus2 == true;
		document.getElementById('betSlipJSErrorCheckbox2').checked = checkBoxStatus2;
		document.getElementById('betSlipJSErrorCheckboxText2').innerHTML = checkBoxMsg2;
		document.getElementById('betSlipJSErrorCheckboxDiv2').style.display = '';
		BS_checkbox_function2    = BS_error_close_function2;
		BS_error_close_function2 = BS_hide_checkbox2;
	}

	if (keepFastbet) {
		BS_error_keep_fastbet = keepFastbet == true;
	} else {
		BS_error_keep_fastbet = false;
	}

	// update title
	var title_elem = document.getElementById('betSlipJSErrorTitle');
	title_elem.innerHTML = title;

	// update text
	var text_elem = document.getElementById('betSlipJSErrorText');
	text_elem.innerHTML = text;

	// make the error pane appear
	document.getElementById('betSlipJSError').style.display = "";
}

function BS_hide_checkbox1() {
	document.getElementById('betSlipJSErrorCheckboxDiv1').style.display = 'none';
	if (BS_checkbox_function1 != '') {
		BS_checkbox_function1(document.getElementById('betSlipJSErrorCheckbox1').checked);
	}
}
function BS_hide_checkbox2() {
	document.getElementById('betSlipJSErrorCheckboxDiv2').style.display = 'none';
	if (BS_checkbox_function2 != '') {
		BS_checkbox_function2(document.getElementById('betSlipJSErrorCheckbox2').checked);
	}
}

// Close the betslip error pane
function BS_close_error() {
	if (document.getElementById('betSlipBodyError')) {
		document.getElementById('betSlipBodyError').style.display = "none";
	}
}


function BS_close_topup(fastbet) {
	//Re-enable fast bet buttons
	enable_fastbet_buttons_upd_cookie();

	BS_load_betslip(betslipbody.url.CGI_URL + '?action=GoBetSlip&from_topup_fastbet=' + fastbet);

}


function BS_close_error_and_bet() {

	BS_close_error();

	//BS_submit_betslip('betSlipConfirmChanges','');
	try {
	
		BS_update_override();
	
		// Remove previous price and hcpa from the cookie
		// for the next time we attempt to place the bet so as to not show the alert box
	
		// foreach leg ...
		var leg_nums = BS_get_leg_nums();
		for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
			var leg_num = leg_nums[leg_idx];
			COOKIE.leg[leg_num].prev_lp_num = null;
			COOKIE.leg[leg_num].prev_lp_den = null;
		
			COOKIE.leg[leg_num].displayed_lp_num = COOKIE.leg[leg_num].lp_num;
			COOKIE.leg[leg_num].displayed_lp_den = COOKIE.leg[leg_num].lp_den;
		
			COOKIE.leg[leg_num].prev_hcap_value = null;
		
			COOKIE.leg[leg_num].displayed_hcap_value = COOKIE.leg[leg_num].hcap_value;
		}
	
		//Update the cookie
		BS_store_hash_cookie();

		place_bet();
	} catch(e) {
		setTimeout('place_bet();',2000);
		BS_upd_betslip();
	};

	return false;
}

function BS_close_error_and_fast_bet() {

	BS_close_error();

	/**
	 * Get info from the fast_bet cookie */
	var fastbet_cookie  = get_cookie(betslipbody.cookie_info.BET_FAST_BET_COOKIE_NAME);
	// We don't have previous data in the cookie
	if (fastbet_cookie == null || fastbet_cookie == '') {
		BS_clear_fastbet_cookie();
		return;
	}

	var leg_params = BS_get_leg_format();
	var num_params = leg_params.length;

	var cookie_data = fastbet_cookie.split("|");
	if (cookie_data.length % num_params != 0) {
		// Malformed leg cookie, ignore
		BS_clear_fastbet_cookie();
		return false;
	}

	// Check we have only 1 leg
	if ((cookie_data.length / num_params) != 1) {
		BS_clear_fastbet_cookie();
		return false;
	}

	// Copy the leg params into an object.
	// These are all the params we got from the cookie (based on the leg format cookie):
	// stamp, leg_sort, price_type, lp_num, lp_den, hcap_value, prev_lp_num, prev_lp_den,
	// prev_hcap_value, ocv_id, bir_index, banker, bet_origin, market_tags, selections,
	// leg_num, disporder, leg_type, stake, mult_incl, displayed_lp_num, displayed_lp_den, displayed_hcap
	// We will assume we have only 1 selection on the selections param.
	var Leg = new Object();
	for (var param_idx = 0; param_idx < num_params; param_idx++) {
		Leg[leg_params[param_idx]] = cookie_data[param_idx];
	}

	// We can clear the cookie now that we have all the info we need
	BS_clear_fastbet_cookie();

	/**
	 * Get info from the overrides */
	var lp_num_override     = document.forms['overridesInfo'].elements[1].value;
	var lp_den_override     = document.forms['overridesInfo'].elements[2].value;
	var hcap_value_override = document.forms['overridesInfo'].elements[4].value;

	/*
	 * Decide which values we'll use when re-placing the fast bet
	 * We only need the following parameters to regenerate the leg:
	 *   selections, price_type, stake, lp_num, lp_den, hcap_value,
	 *   bir_index and market tags */
	var ev_oc_id    = Leg.selections;
	var price_type  = Leg.price_type;
	var stake       = Leg.stake;
	var lp_num      = Leg.lp_num;
	var lp_den      = Leg.lp_den;
	var hcap_value  = Leg.hcap_value;
	var bir_index   = Leg.bir_index;
	var market_tags = Leg.market_tags;

	if (hcap_value_override != '' && hcap_value != hcap_value_override) {
		hcap_value = hcap_value_override;
	}

	var price = new BS_Fraction(lp_num,lp_den);
	if (lp_num_override != '' && lp_den_override != '' && !price.Equals(lp_num_override, lp_den_override)) {
		lp_num = lp_num_override;
		lp_den = lp_den_override;
	}

	//Place again the fast bet
	BS_set_leg("selections",  ev_oc_id);
	BS_set_leg("price_type",  price_type);
	BS_set_leg("stake",       stake);
	BS_set_leg("lp_num",      lp_num);
	BS_set_leg("lp_den",      lp_den);
	BS_set_leg("hcap_value",  hcap_value);
	BS_set_leg("bir_index",   bir_index);
	BS_set_leg("market_tags", market_tags);

	BS_go_bet(true);

	return;
}

// Close the betslip javascript error pane
function BS_close_js_error() {

	// Clear the old messages
	BS_ERR_MSG = new Array();

	document.getElementById('betSlipJSError').style.display = "none";
	if (BS_error_close_function1 != '') {
		eval(BS_error_close_function1);
	}
	if (BS_error_close_function2 != '') {
		eval(BS_error_close_function2);
	}

	// Clean fast bet stuff
	var fb_process = get_cookie('FASTBET_PROCESS');
	if (!BS_error_keep_fastbet && fb_process && fb_process == '1') {
		BS_clear_fastbet_cookie();

		//Re-enable fast bet buttons
		enable_fastbet_buttons_upd_cookie();
	}

}

// Add a BS_ErrorMessage to the slip error message box,
// ignores duplicates using the id
function BS_add_error(msg) {
	for (var i = 0; i < BS_ERR_MSG.length; i++) {
		if (msg.id == BS_ERR_MSG[i].id) return;
	}
	BS_ERR_MSG.push(msg);
}

// Returns the concatenated message from the BS_ERR_MSG array
function BS_error_string() {
	var error_string = '';
	for (var i = 0; i < BS_ERR_MSG.length; i++) {
		error_string = error_string.concat(BS_ERR_MSG[i].message);
	}
	return error_string;
}

// Display the confirmation window for making a deposit
function BS_do_confirm_deposit() {

	// topup amount will always be at least the minimum deposit
	var topup_amount = document.getElementById('topup_amount').value;
	var ccy_symbol   = document.getElementById('cust_html_ccy').value;
	var min_dep      = document.getElementById('min_dep').value;
	var dep_only     = parseInt(document.getElementById('dep_only').value);

	// any deposit amount can be made if we're on the deposit
	// only page
	if (!dep_only) {
		min_dep = topup_amount;
	}

	// check if user is going to deposit minimum required
	// or custom amount
	if (document.getElementById('rbMinDeposit').checked) {

		var amount = topup_amount
	} else if (document.getElementById('rbChooseAmount').checked) {

		var amount = document.getElementById('txtDepositAmount').value;
		// check the custom amount is at least the minimum
		if (parseFloat(amount) < parseFloat(min_dep)) {
			var error_text = BS_js_subst(betslipbody.msg.BS_QDEP_TOO_LOW_TEXT,
				{ccy: ccy_symbol, amount: min_dep});
			BS_display_error(betslipbody.msg.BS_QDEP_TOO_LOW_TITLE, error_text,'','','','','','',true);
			return
		}
	}

	if (!BS_valid_deposit_fields()) {
		return;
	}

	// update hidden deposit field with amount to be deposited
	document.getElementById('dep_amount').value = BS_format_stake(amount);

	// Just do the deposit w/o confirming if configured to do so.
	var qd_no_confirm = document.getElementById('qd_no_confirm').value;
	if (qd_no_confirm == 1) {
		return BS_do_deposit();
	}

	// update confirmation window with amount and then display it
	document.getElementById('topupAmountConfirm').innerHTML = ccy_symbol + BS_format_stake(amount);
	document.getElementById('confirm_topup').style.display = '';

}

// Validates items on the deposit page, returns 1
// if all ok, 0 otherwise
function BS_valid_deposit_fields() {

	var msg = betslipbody.msg;

	// check a password has been entered (unless the password field doesn't
	// exist, in which case we assume it's not necessary)

	if ( document.getElementById('dep_password') != null &&
	     document.getElementById('dep_password').value == '' ) {
		BS_display_error(msg.BS_QDEP_NO_PASSWORD_TITLE, msg.BS_QDEP_NO_PASSWORD_TEXT,'','','','','','',true);
		return 0;
	}

	// check a valid cvv2 number has been entered
	var re_cc = /^\d{3,4}$/;
	var re_ntlr = /^\d{6}$/;

	switch(document.getElementById('cpm').value) {
		case 'CC':
			// Do not check for CV2 number as laser card do not have this
			// The back end will do this check
			return 1;

		case 'NTLR':
			if (!re_ntlr.test(document.getElementById('ntlr_secureidBS').value)){
				BS_display_error(msg.BS_QDEP_NO_NETELLER_TITLE, msg.BS_QDEP_NO_NETELLER_TITLE,'','','','','','',true);
				return 0;
			}
			break;
		default:
			BS_display_error(msg.BS_QDEP_UNKNOWN, msg.BS_QDEP_UNKNOWN,'','','','','','',true);
			return 0;
	}

	return 1
}



// Close confirm deposit panel
function BS_close_confirm_deposit(fast_bet) {

	document.getElementById('confirm_topup').style.display = 'none';

	if (fast_bet == 1) {
		// clean fast bet cookie
		BS_clear_fastbet_cookie();

		//Re-enable fast bet buttons
		enable_fastbet_buttons_upd_cookie();
	}

}



// Handle making deposit
function BS_do_deposit() {
	var f = document.forms['betSlipDepositForm'];
	f.submit();
}


// The topup wait page has loaded.
//
function BS_topup_wait_loaded(launch_3ds) {
	if (launch_3ds) {
		// Annoyingly we can't just submit the form because Firefox
		// doesn't allow us to target an HTTPS form into an HTTP window.
		// Even if it did, there are still some awkward cases such as
		// the main window no longer existing. So we'll construct a URL
		// from the form and rely on BS_open_in_main to do some magic.
		// XXX This does mean that it'll be a GET request rather than
		// a POST - but the CV2 number is no longer present, so I think
		// we're OK. It doesn't actually make the payment either.
		var df = document.forms.launch3DSForm;
		// It would be nice to use the action _attribute_ of the form,
		// but it's fiddly getting it (and not the element) cross-browser.
		var url = betslipheader.url.MONEY_SCGI + '?';
		for (var i = 0; i < df.elements.length; i++) {
			var el = df.elements[i];
			url = url + el.name + '=' + encodeURIComponent(el.value) + '&';
		}
		BS_open_in_main(url);
	}
}


// Delete specific legs - takes one or more leg numbers as arguments.
function BS_delete_legs(vargs) {
	var legs = new Array();
	for (var i = 0; i < arguments.length; i++) {
		var leg_num = arguments[i];
		if (leg_num == null) {
			// It's useful to allow the last arg to be null (saves worrying
			// about trailing commas in TP_LOOPS)...
			continue;
		}
		legs.push(leg_num);
	}
	BS_submit_betslip('betSlipDeleteLegForm','','legs',legs.join('|'));
}


var retain_sels = false;
// Close the betslip and simulate the set a flag to retain
//  selections
function BS_close_receipt_bir() {
	retain_sels = true;
	BS_close_receipt();
}

// Close the receipt
function BS_close_receipt() {

	// clear the bet receipt cookie
	set_cookie(
	  betslipbody.cookie_info.BET_RECEIPT_COOKIE_NAME, '',
	  '', betslipbody.cookie_info.BET_COOKIE_PATH, '', '');

	var is__BS = 0


	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		if (COOKIE.leg[leg_num].is__BS == 1) {
			is__BS = 1;
			break;
		}
	}

	if ($('chkRetainSelections') && $('chkRetainSelections').checked) {
		retain_sels = true;
	}

	// check if we should clear betslip selections
	if (retain_sels) {
		// refresh slip
		BS_load_betslip(betslipbody.url.CGI_URL + '?action=GoBetSlip&retain=Y&is__BS='+is__BS, 'POST');
	} else {
		BS_clear_bet_slip();
	}

	// reset the retain_sels flag
	retain_sels = false;

	enable_fastbet_buttons_upd_cookie();
}



// If a freebet token has been selected, allocate it to a bet
//
function BS_allocate_freebet_token() {

	var mainf = document.forms[betslipbody.mainForm];
	if (!mainf) return false;
	
	// Unselect any freebets
	mainf.elements['freebet_leg_or_type'].value = '';
	mainf.elements['freebet_token_id'].value = '';

	var f = document.forms[betslipbody.freebetsForm];
	if (!f) return false;

	var ct_id = f.elements['lstFreeBets'].value;
	if (ct_id == '') return false;

	// Get the value of any freebet token used
	var freebet_value = BS_get_freebet_token_value();
	if (!freebet_value) return false;

	var valid_legs = f.elements['valid_leg_or_type_' + ct_id];
	if (!valid_legs) return false;

	// Find the highest staked bet
	var selected_bet = 'none';
	var highest_stake = 0;

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		if (COOKIE.leg[leg_num].stake > highest_stake) {
			selected_bet  = leg_num;
			highest_stake = COOKIE.leg[leg_num].stake;
		}
	}

	// Check multis
	var combi_type;
	var combi;
	for (var i = 0; i < COOKIE.combi_type.length; i++) {
		combi_type = COOKIE.combi_type[i]
		combi      = COOKIE.combi[combi_type];
		if (combi.stake > highest_stake) {
			selected_bet  = combi_type;
			highest_stake = combi.stake;
		}
	}

	// Get a default
	if (selected_bet == 'none') {
		if (leg_nums.length > 0) {
			selected_bet = 0;
			BS_upd_sgl_stake(selected_bet, freebet_value);
		} else if (COOKIE.combi_type.length > 0) {
			selected_bet = COOKIE.combi_type[0].combi_type;
			BS_upd_type_stake(selected_bet, freebet_value);
		} else {
			// Shouldn't get here
			return false;
		}
	}

	document.forms[betslipbody.mainForm].elements['freebet_leg_or_type'].value = selected_bet;
	document.forms[betslipbody.mainForm].elements['freebet_token_id'].value = ct_id;

	return true;
}


function BS_deallocate_matchedbet_token() {

	var matchedbet_token_id = document.forms[betslipbody.mainForm].elements['matchedbet_token_id'].value;
	var matchedbet_stake = document.forms[betslipbody.mainForm].elements['matchedbet_stake'].value;
	var matchedbet_leg_or_type = document.forms[betslipbody.mainForm].elements['matchedbet_leg_or_type'].value;

	if (matchedbet_token_id != "") {
		// update the selection stake to its original value (before adding the
		//matchedbet offer)

		var stake = BS_get_stake(matchedbet_leg_or_type);
		stake -= matchedbet_stake;
		BS_upd_sgl_stake (matchedbet_leg_or_type, stake, false);

		document.forms[betslipbody.mainForm].elements['matchedbet_token_id'].value = "";
		document.forms[betslipbody.mainForm].elements['matchedbet_stake'].value = "";
		document.forms[betslipbody.mainForm].elements['matchedbet_leg_or_type'].value = "";
	}
}



// Update the cookie which holds the value of the selected freebet token
//
function BS_update_freebet(val) {
	set_cookie(betslipbody.cookie_info.BET_FREEBET_COOKIE_NAME, val);
	BS_allocate_freebet_token();
	BS_upd_potential_winnings();
}



// Clear the cookie which holds the value of the selected freebet token
//
function BS_clear_freebet_cookie() {
	set_cookie(betslipbody.cookie_info.BET_FREEBET_COOKIE_NAME, '');
}

// Clear the cookie which holds the selection for the fast bet
//
function BS_clear_fastbet_cookie() {
	set_cookie(betslipbody.cookie_info.BET_FAST_BET_COOKIE_NAME,"","","/");
}

// If there is a value in the freebet cookie, force that item to be selected
// in the freebets drop-down menu.
// If the menu is not present then wipe the cookie.
function BS_update_freebet_menu() {
	var val = get_cookie(betslipbody.cookie_info.BET_FREEBET_COOKIE_NAME);
	if (!val) return;

	var menu = document.getElementById('lstFreeBets');

	if (!menu) {
		BS_clear_freebet_cookie();
		return;
	}

	// Loop over the menu items, and select one if it matches the value
	for (var i=0; i<menu.options.length; i++) {
		if (menu.options[i].value == val) {
			menu.options[i].selected = 'selected';
			return;
		}
	}

	// If option is no longer available then clear the cookie
	BS_clear_freebet_cookie();
}


// Gets the value of any freebet token used
function BS_get_freebet_token_value() {
	var f = document.forms[betslipbody.freebetsForm];
	if (!f) return null;

	var ct_id = f.elements['lstFreeBets'].value;
	if (ct_id == '') return null;
	var freebet_value = f.elements['value_'+ct_id].value;
	return freebet_value;
}


// Rearrange the order in which legs appear on the slip (if necessary).
// Intended to be called shortly after betslip load once resizing has
// finished and the cookie hash has been built.
//
// Business rule is that new legs are moved to the top of the slip if the
// selections section has scrollbars. However, we don't move scombi legs
// or legs that appear in scombis.
//
function BS_rearrange_legs() {

	// This behaviour can be configured off.

	if (betslipbody.cfg.BET_ADD_LEGS_AT != 'AUTO') {
		return;
	}

	// See if the betslip selections section has scrollbars.

	var elem = $('betSelectionsScroll');
	var has_scrollbars =
	  (elem != null) && (elem.clientHeight < elem.scrollHeight - 4);

	if (!has_scrollbars) {
		// Nothing to do.
		return;
	}

	// Find new legs - i.e. ones that have just been added to the slip.
	// However, if scombi parts follow the displayed order of the legs
	// then we don't want selections that are in scombis (otherwise the
	// order of the selns may not reflect their order in e.g. a Forecast).
	// We never move scombis themselves, either.

	var new_legs = new Array();
	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];
		var el = $('is_new_sgl_' + leg_num);
		if (el == null || el.value != '1') {
			continue;
		}
		if ($('is_scombi_sgl_' + leg_num).value == '1') {
			continue;
		}
		if ( betslipbody.cfg.BET_SCOMBI_PART_ORDER == 'DISP' &&
		     $('in_scombi_sgl_' + leg_num).value == '1' ) {
			continue;
		}
		new_legs.push(leg_num);
	}

	if (!new_legs.length) {
		// Nothing to do.
		return;
	}

	// Move the new legs to the top of the slip.
	// Because each leg goes to the top, we have to do this in reverse order.

	for (var i = new_legs.length - 1; i >= 0; i--) {
		BS_move_leg_to_top(new_legs[i], true);
	}

	// Update the cookie accordingly to ensure the ordering remains across
	// requests.

	BS_upd_cookie_leg_disporders();

	return
}


// Move the given leg to the top of the singles section.
// Returns true if the leg was moved successfully.
//
function BS_move_leg_to_top (leg_num, skip_cookie_upd) {

	var row_ids = BS_get_row_ids_for_leg(leg_num);

	if (!row_ids.length) {
		// No rows found.
		return false;
	}

	var first_row_in_leg = $(row_ids[0]);
	var tbody = first_row_in_leg.parentNode;

	// Skip over the plural row (if any).

	var first_row_in_table = tbody.firstChild;
	if (first_row_in_table.id = 'tr_plural') {
		first_row_in_table = first_row_in_table.nextSibling;
	}

	if (first_row_in_leg == first_row_in_table) {
		// Row already at top.
		return false;
	}

	// Loop over the rows, removing them from the tbody and inserting them
	// before the first row in the tbody.

	for (var i = 0; i < row_ids.length; i++) {
		row = $(row_ids[i]);
		tbody.removeChild(row);
		tbody.insertBefore(row, first_row_in_table);
	}

	if (!skip_cookie_upd) {
		BS_upd_cookie_leg_disporders();
	}

	return true;
}


// Get an array of the ids in the doc of the table row(s) which represent
// the leg with the given leg number.
//
function BS_get_row_ids_for_leg (leg_num) {

	var row_ids = new Array();
	var idx = 1;

	while (true) {
		var row_id = 'tr_sgl_' + leg_num + '_' + idx;
		if ($(row_id) == null) {
			break;
		}
		row_ids.push(row_id);
		idx++;
	}

	return row_ids;
}


// Update the leg cookie with disporders based on the order the legs appear
// in the document.
//
function BS_upd_cookie_leg_disporders() {

	// Loop over the rows in the selections table, using the ids to
	// figure out which leg they represent and thus building up a
	// list of the leg nums in the order they appear in the table.

	var found_leg_nums = new Array();

	var tbody = $('betSelections').getElementsByTagName("tbody")[0];
	var rows  = tbody.getElementsByTagName("tr");

	for (var i = 0; i < rows.length; i++) {
		var row_id = rows[i].id;

		var mtch = row_id.match(/^tr_sgl_([0-9]+)_1$/);

		if (mtch) {
			leg_num = mtch[1];
			found_leg_nums.push(leg_num);
		}

	}

	// It's possible we didn't find all legs (eg scombis).
	// We'll default the legs to have a higher disporder to cater for this.

	var not_found_disporder = found_leg_nums.length;

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		COOKIE.leg[leg_num].disporder = not_found_disporder;
	}

	// Now go back and assign disporders sequentially from zero for the
	// legs we did find.

	var disporder = 0;
	for (var leg_idx = 0; leg_idx < found_leg_nums.length; leg_idx++) {
		var leg_num = found_leg_nums[leg_idx];
		COOKIE.leg[leg_num].disporder = disporder++;
	}

	//Update the cookie
	BS_store_hash_cookie();

	return
}


function BS_update_override () {

	var nb_selns      = document.forms['overridesInfo'].elements.length;

	for (var i=0; i < nb_selns; i+=8) {
		var ev_oc_id   = document.forms['overridesInfo'].elements[i].value;
		var lp_num     = document.forms['overridesInfo'].elements[i+1].value;
		var lp_den     = document.forms['overridesInfo'].elements[i+2].value;
		var hcap_str   = document.forms['overridesInfo'].elements[i+3].value;
		var hcap_value = document.forms['overridesInfo'].elements[i+4].value;
		var leg_num    = document.forms['overridesInfo'].elements[i+5].value;
		var desc       = document.forms['overridesInfo'].elements[i+6].value;
		var desc_short = document.forms['overridesInfo'].elements[i+7].value;


		if (!SELN[ev_oc_id]) {
			SELN[ev_oc_id] = new BS_Seln();
		}

		var seln = SELN[ev_oc_id];
		if (lp_num != '' && lp_den != '' && !seln.price.Equals(lp_num, lp_den)) {
			seln.price.num = lp_num;
			seln.price.den = lp_den;
		} else {
			seln.price.num = document.getElementById('price_num_sgl_' + leg_num).value;
			seln.price.den = document.getElementById('price_den_sgl_' + leg_num).value;
		}
		if (hcap_value != '' && seln.hcap != hcap_value) {
			seln.hcap = hcap_value;
		} else {
			seln.hcap = document.getElementById('hcap_sgl_' + leg_num).value;
		}

		var sgl  = COOKIE.leg[leg_num];

		if (hcap_str != '' && sgl.hcap_value != hcap_value) {
			sgl.prev_hcap_value = sgl.hcap_value;
			sgl.hcap_value = hcap_value;
			//Update txt_sgl_oc_desc_short_leg_num as well
			//try {
			document.getElementById('txt_sgl_oc_desc_short_disp_' + leg_num).innerHTML = desc_short + ' ';
			document.getElementById('txt_sgl_oc_desc_short_hcap_' + leg_num).innerHTML = hcap_str;
			document.getElementById('txt_sgl_oc_desc_disp_' + leg_num).innerHTML = desc + ' ';
			document.getElementById('txt_sgl_oc_desc_hcap_' + leg_num).innerHTML = hcap_str;
			document.getElementById('inf_sgl_oc_desc_disp_' + leg_num).innerHTML = desc + ' ';
			document.getElementById('inf_sgl_oc_desc_hcap_' + leg_num).innerHTML = hcap_str;
			//} catch (e) {};

			// Show appropriate hcap change icon.on the betslip
			var hcap_icon_longer  = document.getElementById('img_hcap_chg_longer_'  + leg_num);
			var hcap_icon_shorter = document.getElementById('img_hcap_chg_shorter_' + leg_num);



			if (sgl.prev_hcap_value < sgl.hcap_value) {
				hcap_icon_longer.style.display  = '';
				hcap_icon_shorter.style.display = 'none';
			} else {
				hcap_icon_longer.style.display  = 'none';
				hcap_icon_shorter.style.display = '';
			}
		}
		sgl.prev_hcap_value = null;
		COOKIE.leg[leg_num].prev_hcap_value = null;
	}

	// foreach leg ...
	var leg_nums = BS_get_leg_nums();
	for (var leg_idx = 0; leg_idx < leg_nums.length; leg_idx++) {
		var leg_num = leg_nums[leg_idx];

		// skip multi-part legs
		if (COOKIE.leg[leg_num].selections.split('c').length != 1) continue;

		var sgl  = COOKIE.leg[leg_num];
		var seln = SELN[sgl.selections];

		if (seln.price.Equals(sgl.lp_num, sgl.lp_den)) continue;

		// skip suspended selections
		var susp = document.getElementById('suspended_' + leg_num);
		if (susp != null && susp.value == 'Y') {
			continue;
		}

		sgl.lp_num = seln.price.num;
		sgl.lp_den = seln.price.den;

		// update price on the betslip

		var disp_price = format_price(sgl.lp_num,sgl.lp_den);


		if ( document.getElementById('txt_sgl_live_price_' + leg_num) != null ) {
			document.getElementById('txt_sgl_live_price_' + leg_num).innerHTML = disp_price;
		}

		var el_num = document.getElementById('price_num_sgl_' + leg_num);
		var el_den = document.getElementById('price_den_sgl_' + leg_num);

		var old_num = el_num.value;
		var old_den = el_den.value;

		el_num.value = sgl.lp_num;
		el_den.value = sgl.lp_den;

		_BS_adjust_pot_rtn(leg_num, old_num, old_den, sgl.lp_num, sgl.lp_den);

		// Determine whether the odds have lengthened
		var odds_longer = (sgl.lp_num * old_den) > (sgl.lp_den * old_num);

		// Show appropriate price change icon.
		var prc_icon_longer  = document.getElementById('img_price_chg_longer_'  + leg_num);
		var prc_icon_shorter = document.getElementById('img_price_chg_shorter_' + leg_num);
		if (odds_longer) {
			prc_icon_longer.style.display  = '';
			prc_icon_shorter.style.display = 'none';
		} else {
			prc_icon_longer.style.display  = 'none';
			prc_icon_shorter.style.display = '';
		}
	}

	//Update the cookie
	BS_store_hash_cookie();

	//And potential winnings
	BS_upd_potential_winnings();
}


function update_use_gp() {
	if ( ($('g_price_checkbox') && $('g_price_checkbox').checked) ||
		 (!$('g_price_checkbox')) ) {
		document.getElementById('use_gp').value = true;
	} else {
		document.getElementById('use_gp').value = false;
	}
}
