
/*
 * Live Betting Currently Live Market Static JS (using Orbis Push Server)
 */

// ensure CVS revision number survives minification
var dummy = '$Id: lb_cur.js,v 1.1.6.5 2010-04-07 14:46:52 mcarey Exp $';

/* -------- PUBLIC API -------- */

/*
 * Initialise the Currently Live Market Area.
 * Params:
 *   event       = initial state of event
 *   evmkt       = initial state of market
 *   selcns      = initial state of selections
 *   init_msg_id = last push message id at time initial state was queried
 *   cfg         = config item object; see _lb_cur_cfg for details.
 */
function lb_cur_init(event, evmkt, selcns, init_msg_id, cfg) {

	if (_lb_cur_first_time) {
		_lb_cur_store_templates();
	}

	_lb_cur_ev_id       = event.ev_id;
	_lb_cur_ev_mkt_id   = evmkt.ev_mkt_id;

	for (var f in cfg) _lb_cur_cfg[f] = cfg[f];

	// Reset list of selections.

	_lb_cur_selcn_hash = {};

	// Pretend that the initial state we've just been given has been received
	// as push server messages - but don't generate HTML until ...

	_lb_cur_got_EVENT(event, true);
	_lb_cur_got_EVMKT(evmkt, true);

	for (var i = 0; i < selcns.length; i++) {
		_lb_cur_got_SELCN(selcns[i], true);
	}

	// ... now.

	_lb_cur_repaint();

	// Subscribe to the event itself, the market itself, and the market's children.

	var padded_ev_id     = ps_connect_lpad_id(_lb_cur_ev_id);
	var padded_ev_mkt_id = ps_connect_lpad_id(_lb_cur_ev_mkt_id);
	var channels = [
	  "sEVENT" + padded_ev_id,
	  "sEVMKT" + padded_ev_mkt_id,
	  "SEVMKT" + padded_ev_mkt_id
	];
	ps_connect_register
	  ("lb_cur", _lb_cur_got_msg, channels, init_msg_id);

	_lb_cur_first_time = false;

	return;
}

/* -------- HTML TEMPLATES and ELEMENT IDs -------- */

/*
 * These are the element ids (or prefixes thereof) with special meaning.
*/

var _lb_cur_area_id                  = "currently-live-market"
var _lb_cur_selcn_id_prefix          = "lb_cur_selcn_";
var _lb_cur_price_class_name         = "odds";
var _lb_cur_price_con_id_prefix      = "lb_cur_price_con_";
var _lb_cur_price_class_name         = "two";
var _lb_cur_stake_id_prefix          = "lb_cur_stake_";

// A generic object to access the hashes in this file.
function lb_cur_object () {
	// Do nothing. We're only interested in the subclasses.
	return;
}

lb_cur_object.prototype.getConfig = function() {

	return _lb_cur_cfg;
}

lb_cur_object.prototype.gotPushData = function(identifier, data) {

	switch(identifier) {
		case "PRICE":
			_lb_cur_got_PRICE(data);
			break;
		case "SELCN":
			_lb_cur_got_SELCN(data);
			break;
		case "MHCAP":
			_lb_cur_got_MHCAP(data);
			break;
		case "EVMKT":
			_lb_cur_got_EVMKT(data);
			break;
		case "EVENT":
			_lb_cur_got_EVENT(data);
			break;
		default:
			// Unknown message.
	}

	return;
}

// Create a generic object to access the hashes in this file. This is the handler.
var lb_cur_obj = new lb_cur_object();

// Create a new comparsion object.
var lb_cur_cmp_objects = new function () {};
lb_cur_cmp_objects = new push_hash_cmp_objects();

// We must overwrite cmpSelcns.
lb_cur_cmp_objects.cmpSelcns = function (selcnA, selcnB) {

	// Display order.
	var d = selcnA.disporder - selcnB.disporder;

	if (d) {
		return d;
	}

	// Price (no price comes after any price).
	if (selcnA.lp_num && !selcnB.lp_num) {
		return -1;
	} else if (!selcnA.lp_num && selcnB.lp_num) {
		return +1;
	} else if (selcnA.lp_num && selcnB.lp_num) {

		o = (selcnA.lp_num * selcnB.lp_den) - (selcnA.lp_den * selcnB.lp_num);

		if (o) {
			return o;
		}
	}

	// Name.
	if (selcnA.name < selcnB.name) {
		return -1;
	} else if (selcnA.name > selcnB.name) {
		return + 1;
	}

	// ID.
	return selcnA.ev_oc_id - selcnB.ev_oc_id;
}

/*
 * These are the templates used to construct and update the live betting
 * currently live area. See tmpl.js for syntax details.
 */
function _lb_cur_store_templates() {

	// The Currently Live Market.

	tmpl_store("lb_cur_evmkt", ''
	+ '<ul>'
	+ '<li class="<%IF !bettable%>suspended-market<%END%>">'
	+ '<%IF _lb_cur_cfg.is_financial%>'
	+ '<h2 class="head1">'
	+ '  <a href="<%tld%>?action=go_betlive_financials_home&amp;ev_id=<%_lb_cur_ev_id%>" title="<%tmpl_esc_html(name)%>"><%name_trunc%></a>'
	+ '</h2>'
	+ '<h2 class="head2"><%XL CURR_LIVE_FINANCIAL_LEVEL%></h2>'
	+ '<%ELSE%>'
	+ '<h2 class="head1">'
	+ '  <a href="<%tld%>?action=go_betlive_event&amp;ev_id=<%_lb_cur_ev_id%>" title="<%tmpl_esc_html(name)%>"><%name_trunc%></a>'
	+ '</h2>'
	+ '<%END%>'
	+ '<h2 class="head3"><%XL ODDS%></h2>'
	+ '<h2 class="head4"><%XL BET_NOW%></h2>'
	+ '</li>'
	+ '</ul>'
	);

	// A Currently Live Selection.

	tmpl_store("lb_cur_selcn", ''
	+'	<ul class="greyrow">'
	+ '<%IF _lb_cur_cfg.is_financial%>'
	+ '<li class="one" <%IF !bettable%>title="<%XL CURR_LIVE_SUSP_SELN%>" style="color:#ccc"<%END%>>'
	+ '<span title="<%tmpl_esc_html(name)%>" class="txt-left" style="float:left"><%name_trunc%></span></li>'
	+ '<li class="four" <%IF !bettable%>title="<%XL CURR_LIVE_SUSP_SELN%>" style="color:#ccc"<%END%>><%hcap_value%></li>'
	+ '<%ELSE%>'
	+ '<li class="one" <%IF !bettable%>title="<%XL CURR_LIVE_SUSP_SELN%>" style="color:#ccc"<%END%>>'
	+ '<span title="<%tmpl_esc_html(name)%>" class="txt-left" style="float:left"><%name_trunc%></span>'
	+ '<%IF (hcap_value != "")%>'
	+ '<span class="txt-right" style="float:right; margin-right:6px"><%hcap_value%></span>'
	+ '<%END%>'
	+ '</li>'
	+ '<%END%>'
	+ '<li id="lb_cur_price_con_<%ev_oc_id%>" class="two txt-cntr">'
	+ '<a href="javascript:void(0)" onclick="lb_cur_add_bet(<%ev_oc_id%>)">'
	+ '<%price_str%>'
	+ '</a>'
	+ '</li>'
	+ '<li class="three">'
	+ '<input id="lb_cur_stake_<%ev_oc_id%>" class="odds" type="text" onkeypress="if (event.keyCode==13) {lb_cur_add_bet(<%ev_oc_id%>)}" onchange="BS_stake_changed(this.id, false)" value="" />'
	+ '<a href="javascript:void(0)" onclick="lb_cur_add_bet(<%ev_oc_id%>)" class="bet">Bet</a>'
	+ '</li>'
	+ '</ul>'
	);

	return;
}

/* ---------- PRIVATE --------- */

/* Default config items: (can be overriden by lb_cur_init call) */

var _lb_cur_cfg = {
  is_financial       : false,
  lang               : "en",
  price_str_type     : "ODDS",
  price_str_type_ah  : "DECIMAL",
  price_str_type_hl  : "DECIMAL",
  price_str_sep      : "-",
  highlight_duration : 4000,
  max_selcns         : 3,
  evmkt_name_length  : 20,
  selcn_name_length  : 23,
	reload_url         : ""
};

var _lb_cur_first_time       = true;
var _lb_cur_ev_id            = -1;
var _lb_cur_ev_mkt_id        = -1;
var _lb_cur_event            = null;
var _lb_cur_evmkt            = null;
var _lb_cur_selcn_hash       = {};
var _lb_cur_last_price_shown = {};
var _lb_cur_reload_timer     = null;

/*
 * Because the currently live section is so simple, we just
 * re-create the whole thing if anything changes. After all,
 * browsers are PDQ at rendering HTML!
 * [One might think one could avoid this for price changes,
 *  but these can lead to a re-ordering of the selections].
 */
function _lb_cur_repaint() {

	// Start by assuming we need a reload.

	var need_reload = true;

	// Don't reload if the event itself is still bettable and
	// the market is still displayed (even if all the selections
	// turn out to be suspended). See Bug 6642.

	if (_lb_cur_event.bettable && _lb_cur_evmkt.displayed == 'Y') {
		need_reload = false;
	}

	var html = "";

	// Generate market HTML.
	html += tmpl_play("lb_cur_evmkt", _lb_cur_evmkt);

	// Sort selections according to ordering rules, and
	// mark them all as unpainted.
	var selcns = [];
	for (var ev_oc_id in _lb_cur_selcn_hash) {
		var selcn = _lb_cur_selcn_hash[ev_oc_id];
		selcn.painted = false;
		if (selcn.displayed == "Y") {
			selcns.push(selcn);
		}
	}
	selcns.sort(lb_cur_cmp_objects.cmpSelcns);

	// Generate HTML for first N selections, and mark them as painted.
	var painteds = [];
	for (var i = 0; i < selcns.length && i < _lb_cur_cfg.max_selcns; i++) {
		html += tmpl_play("lb_cur_selcn", selcns[i]);
		selcns[i].painted = true;
		painteds.push(selcns[i]);
		
		// Don't reload if we managed to paint a bettable selection.
		if (selcns[i].bettable) {
			need_reload = false;
		}
	}
	
	if (!need_reload) {

		// Update the page.
		var el = document.getElementById(_lb_cur_area_id);
		el.innerHTML = html;
	
		// (re-)apply price change highlighting
		for (var i = 0; i < painteds.length; i++) {
			var selcn = painteds[i];
			if (selcn.price_dir) {
				_lb_cur_highlight(
					_lb_cur_price_con_id_prefix + selcn.ev_oc_id,
					_lb_cur_price_class_name, selcn.price_dir
				);
			}
		}
	}
	
	_lb_cur_schedule_reload(need_reload);

	return;
}


/*
 * Called whenever we receive any published message from the Push Server.
 */
function _lb_cur_got_msg(msg) {

	bir_got_msg(lb_cur_obj, msg);

	return;
}

// Handle event message.
function _lb_cur_got_EVENT(data, noRepaint) {

	// Is this the one event which we're displaying?

	if (data.ev_id != _lb_cur_ev_id) {
		return;
	}

	// Determine if this is a new item and if not, make a note of
	// some of the original properties of the item. We then copy
	// fields from the message to the item's info object.

	var info = _lb_cur_event;
	var is_new_item = !info;
	var orig_bettable;
	var orig_displayed;

	if (is_new_item) {
		info = data;
		_lb_cur_event = info;
	} else {
		orig_bettable  = info.bettable;
		orig_displayed = info.displayed;
		for (var f in data) {
			info[f] = data[f];
		}
	}

	// Derive additional properties of the item.

	info.bettable = (info.status == "A");

	// Has anything important actually changed?

	if (!is_new_item && !(
	         (orig_bettable   != info.bettable)
	      || (orig_displayed  != info.displayed)
	   )) {
		return;
	}

	// Pretend we got a market message so that the event's bettable property
	// is propagated to the market. Then repaint (unless surpressed).

	if (!is_new_item) {
		_lb_cur_got_EVMKT({ev_mkt_id: _lb_cur_ev_mkt_id}, true);
	}
	if (!noRepaint) _lb_cur_repaint();

	return;
}


// Handle market message.
function _lb_cur_got_EVMKT(data, noRepaint) {

	// Is this the one market which we're displaying?

	if (data.ev_mkt_id != _lb_cur_ev_mkt_id) {
		return;
	}

	// Get parent item info.

	var parent_info = _lb_cur_event;

	// Determine if this is a new item and if not, make a note of
	// some of the original properties of the item. We then copy
	// fields from the message to the item's info object.

	var info = _lb_cur_evmkt;
	var is_new_item = !info;

	var orig_name;
	var orig_bettable;
	var orig_hcap_values;

	if (is_new_item) {
		info = data;
		_lb_cur_evmkt = info;
		info.child_hash = {};
	} else {
		orig_name        = info.name;
		orig_bettable    = info.bettable;
		orig_displayed   = info.displayed;
		orig_hcap_values = info.hcap_values;
		for (var f in data) {
			info[f] = data[f];
		}
	}

	// Derive additional properties of the item.

	info.name = info.names[_lb_cur_cfg.lang];
	info.name_trunc = _lb_cur_truncate(info.name, _lb_cur_cfg.evmkt_name_length, "..");
	info.bettable  = (info.status == "A") && parent_info.bettable;
	if (parent_info.displayed != 'Y') info.displayed = 'N';
	if (info.bet_in_run != 'Y') info.displayed = 'N';

	// Has anything important actually changed?

	if (!is_new_item && !(
	       (orig_name      != info.name      ) ||
	       (orig_bettable  != info.bettable  ) ||
	       (orig_displayed != info.displayed ) ||
	       (_lb_cur_diff_objs_brief(orig_hcap_values, info.hcap_values))
	     )
	   ) {
		return; // No.
	}

	// Pretend we got messages for each selection so that the market's bettable
	// and handicap properties are propagated to the selections. Then repaint.

	if (!is_new_item) {
		for (var ev_oc_id in _lb_cur_selcn_hash) {
			_lb_cur_got_SELCN({
			  ev_oc_id:  ev_oc_id,
			  ev_mkt_id: info.ev_mkt_id
			}, true);
		}
	}

	if (!noRepaint) _lb_cur_repaint();

	return;
}

// Logic from the BIR XML based version certain appears to say:
// "If the market changes from active to suspended, reload the page."
// However, this seems rather crazy; aren't we likely to get the
// cached active version again, then pick up an update which gives us
// the suspended version - causing us to repeatedly reload?
// As a (probably ineffective) safety precaution, let's:
//   - delay a bit before reloading
//   - add a random factor to the delay to stop everyone hitting
//     the appserver at once
//   - cancel the timer if the market re-activates before it fires
function _lb_cur_schedule_reload(need_reload) {

	if (need_reload && _lb_cur_reload_timer == null) {
		var delay = Math.round(4000 + Math.random() * 2000);
		_lb_cur_reload_timer = window.setTimeout("_lb_cur_reload()", delay);
	}

	if (!need_reload && _lb_cur_reload_timer != null) {
		window.clearTimeout(_lb_cur_reload_timer);
		_lb_cur_reload_timer = null;
	}

}

// Reload the currently live area via AJAX - may show different market.
function _lb_cur_reload() {
	if (_lb_cur_cfg.reload_url == "") return;
	// stop listening and unsubscribe from the channels
	ps_connect_deregister("lb_cur");
	// and also from the betradar ones
	lb_betradar_cleanup();
	// inject the new javascript
	new Ajax.Updater('currently_live', _lb_cur_cfg.reload_url, { method: 'get', evalScripts: true });
}


// Handle market handicap message.
function _lb_cur_got_MHCAP(data) {

	// Is this the one market which we're displaying?

	if (data.ev_mkt_id != _lb_cur_ev_mkt_id) {
		return;
	}

	// Get parent & item info.

	var parent_info = _lb_cur_event;
	var info = _lb_cur_evmkt;
	if (!parent_info || !info) return;

	// Copy fields from the message to the item's info object.

	for (var f in data) {
		info[f] = data[f];
	}

	// Pretend we got messages for each selection so that the market's
	// handicap property is propagated to the selections. Then repaint.

	for (var ev_oc_id in _lb_cur_selcn_hash) {
		_lb_cur_got_SELCN({
		  ev_oc_id:  ev_oc_id,
		  ev_mkt_id: info.ev_mkt_id
		}, true);
	}

	_lb_cur_repaint();

	return;
}


// Handle selection message.
function _lb_cur_got_SELCN(data, noRepaint) {

	// Does this selection belong to the one market which we're displaying?

	if (data.ev_mkt_id != _lb_cur_ev_mkt_id) {
		return;
	}

	// Get parent item info.

	var parent_info = _lb_cur_evmkt;

	// Determine if this is a new item and if not, make a note of
	// some of the original properties of the item (if needed).
	// We then copy fields from the message to the item's info object.

	var info = _lb_cur_selcn_hash[data.ev_oc_id];
	var is_new_item = !info;

	var orig_lp_num;
	var orig_lp_den;

	if (is_new_item) {
		info = data;
		_lb_cur_selcn_hash[data.ev_oc_id] = info;
	} else {
		orig_lp_num = info.lp_num;
		orig_lp_den = info.lp_den;
		for (var f in data) {
			info[f] = data[f];
		}
	}

	// Derive additional properties of the item.

	info.name = info.names[_lb_cur_cfg.lang];
	info.name_trunc = _lb_cur_truncate(info.name, _lb_cur_cfg.selcn_name_length, "..");
	if ( info.fb_result && 
	     parent_info.hcap_values ) {
		info.hcap_value = parent_info.hcap_values[info.fb_result];
	}
	info.bettable = (info.status == "A") && parent_info.bettable;
	if (parent_info.displayed != 'Y') info.displayed = 'N';

	// Make a note of any price change.

	info.price_dir = 0;
	if ( !is_new_item && 
	     orig_lp_num != "" && orig_lp_den != "" && 
	     info.lp_num != "" && info.lp_den != "" ) {
		info.price_dir = 
		  info.lp_num * orig_lp_den - info.lp_den * orig_lp_num;
	}

	// Cancel/start unhighlight timer.

	if (info.unhighlight_timer) {
		window.clearTimeout(info.unhighlight_timer);
		info.unhighlight_timer = null;
	}
	
	// comment this out due to bug 7291
	//if (info.price_dir != 0) {
	//	info.unhighlight_timer = window.setTimeout(
	//	  "_lb_cur_unhighlight_price_change(" + info.ev_oc_id + ")",
	//	  _lb_cur_cfg.highlight_duration
	//	);
	//}

	info.price_str = get_price_str(
		lb_cur_obj,
		info.lp_num,
		info.lp_den,
		parent_info.lp_avail,
		parent_info.mkt_sort
	);

	// There's no need to repaint if this selection is both
	// not visible now + was not visible on the last repaint.

	var was_visible = (!is_new_item && info.painted);
	var now_visible = _lb_cur_will_be_visible(info);
	if (!was_visible && !now_visible) {
		return;
	}

	if (!noRepaint) _lb_cur_repaint();

	return;
}

// Handle price message.
function _lb_cur_got_PRICE(data) {

	// Find the selection to which it refers.

	var info = _lb_cur_selcn_hash[data.ev_oc_id];

	// Unfortunately, we don't have much choice but to ignore the
	// message if we don't know anything about the selection. This
	// is problematic if our initial state did not include all the
	// selections, since a price change to another selection should
	// move it into the top N selections which we display.

	if (!info) return;

	// Get parent item info.

	var parent_info = _lb_cur_evmkt;

	// Identify direction of price change.

	info.price_dir = 0;
	if ( info.lp_num != "" && info.lp_den != "" && 
	     data.lp_num != "" && data.lp_den != "" ) {
		info.price_dir = 
		  data.lp_num * info.lp_den - data.lp_den * info.lp_num;
	}

	// Cancel/start unhighlight timer.
	// Applying price changes is more difficult on this page, since
	// we have to be careful not to undo them when we repaint.

	if (info.unhighlight_timer) {
		window.clearTimeout(info.unhighlight_timer);
		info.unhighlight_timer = null;
	}
	
	// comment this out due to bug 7291
	//if (info.price_dir != 0) {
	//	info.unhighlight_timer = window.setTimeout(
	//	  "_lb_cur_unhighlight_price_change(" + info.ev_oc_id + ")",
	//	  _lb_cur_cfg.highlight_duration
	//	);
	//}

	// Copy price from the message to the item's info object.

	info.lp_num = data.lp_num;
	info.lp_den = data.lp_den;

	// Derive additional properties of the item.

	info.price_str = get_price_str(
		lb_cur_obj,
		info.lp_num,
		info.lp_den,
		'Y',
		parent_info.mkt_sort
	);

	// There's no need to repaint if this selection is both
	// not visible now + was not visible on the last repaint.

	var was_visible = info.painted;
	var now_visible = _lb_cur_will_be_visible(info);
	if (!was_visible && !now_visible) {
		return;
	}

	_lb_cur_repaint();

	return;
}

/*
 * Will a selection be visible?
 */
function _lb_cur_will_be_visible(selcn) {
	if (selcn.displayed != 'Y') return false;
	var lesser_selcns = 0;
	for (var ev_oc_id in _lb_cur_selcn_hash) {
		var other_selcn = _lb_cur_selcn_hash[ev_oc_id];
		if (other_selcn.displayed != 'Y') continue;
		if (lb_cur_cmp_objects.cmpSelcns(other_selcn, selcn) < 0) {
			lesser_selcns++;
		}
		if (lesser_selcns >= _lb_cur_cfg.max_selcns) {
			return false;
			break;
		}
	}
	return true;
}


// Unhighlight price change.
function _lb_cur_unhighlight_price_change(ev_oc_id) {

	var info = _lb_cur_selcn_hash[ev_oc_id];
	info.price_dir = 0;

	_lb_cur_highlight(
	  _lb_cur_price_con_id_prefix + ev_oc_id,
	  _lb_cur_price_class_name, 0
	);

	return;
}


/*
 * Highlight a change to an element by giving it base_class + "_i"
 * if dir is negative, or base_class + "_d" if positive, or unhighlight
 * it if dir is zero. This version DOES NOT use a timer to unhighlight.
 */
function _lb_cur_highlight(id, base_class, dir) {

	var el = document.getElementById(id);
	if (!el) return;

	if (dir < 0) {
		highlight_class = base_class + "_d";
	} else if (dir > 0) {
		highlight_class = base_class + "_i";
	} else {
		highlight_class = base_class;
	}

	var classes = el.className.split(" ");
	var found = false;
	for (var i = 0; i < classes.length; i++) {
		if (    classes[i] == base_class
		     || classes[i] == base_class + "_d"
		     || classes[i] == base_class + "_i" ) {
			classes[i] = highlight_class;
			found = true;
			break;
		}
	}
	if (!found) {
		classes.push(highlight_class);
	}
	el.className = classes.join(" ");

	return;
}


/*
 * Add a bet on a currently live selection to the slip.
 */
function lb_cur_add_bet(ev_oc_id, stake, fast_bet) {

	if (!stake) {
		var el = document.getElementById(_lb_cur_stake_id_prefix + ev_oc_id);
		if (el) stake = el.value;
	}

	var selcn = _lb_cur_selcn_hash[ev_oc_id];
	var evmkt = _lb_cur_evmkt;

	BS_set_leg("selections",  ev_oc_id);
	BS_set_leg("price_type",  "L");
	BS_set_leg("stake",       stake);
	BS_set_leg("lp_num",      selcn.lp_num);
	BS_set_leg("lp_den",      selcn.lp_den);
	BS_set_leg("hcap_value",  evmkt.raw_hcap);
	BS_set_leg("bir_index",   evmkt.bir_index);
	BS_set_leg("market_tags", "");

	// Check fast_bet param
	var fb = typeof fast_bet === 'boolean' && fast_bet;

	BS_go_bet(fb);

	return;
}

// Truncate string to s length len if needed, appending suffix to indicate
// truncation has occurred. The length of the suffix is taken into account.
function _lb_cur_truncate(s, len, suffix) {
	if (s.length <= len) return s;
	var keep = len - suffix.length;
	return s.substr(0, keep >= 0 ? keep : 0) + suffix;
}

// Do two objects differ in any way?
function _lb_cur_diff_objs_brief(a, b) {
	for (var f in a) {
		if (b[f] == undefined) return true;
	}
	for (var f in b) {
		if (a[f] == undefined) return true;
	}
	for (var f in a) {
		if (b[f] == undefined) continue;
		var va = a[f]; var vb = b[f];
		if (typeof va != typeof vb) {
			return true;
		} else if (typeof va == "object") {
			if (_lb_cur_diff_objs_brief(va, vb)) return true;
		} else if (va != vb) {
			return true;
		}
	}
	return false;
}
