/**
 * --------------------------------------------------------------------
 * jQuery-Plugin "visualize"
 * by Scott Jehl, scott@filamentgroup.com
 * http://www.filamentgroup.com
 * Copyright (c) 2009 Filament Group 
 * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
 * 	
 * --------------------------------------------------------------------
 * 	
 * 	Andy Moreno added percentages to key labels
 * 	
 * --------------------------------------------------------------------
 */
(function($) { 
$.fn.visualize = function(options, container){
	return $(this).each(function(){
		//configuration
		var o = $.extend({
			type: 'bar', //also available: area, pie, line
			width: $(this).width(), //height of canvas - defaults to table height
			height: $(this).height(), //height of canvas - defaults to table height
			appendTitle: true, //table caption text is added to chart
			title: null, //grabs from table caption if null
			appendKey: true, //color key is added to chart
			colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'],
			textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS
			parseDirection: 'x', //which direction to parse the table data
			pieMargin: 20, //pie charts only - spacing around pie
			pieLabelPos: 'inside',
			lineWeight: 4, //for line and area - stroke weight
			barGroupMargin: 10,
			barMargin: 1, //space around bars in bar chart (added to both sides of bar)
			yLabelInterval: 30 //distance between y labels
		},options);
		
		//reset width, height to numbers
		o.width = parseFloat(o.width);
		o.height = parseFloat(o.height);
		
		
		var self = $(this);
		
		//function to scrape data from html table
		function scrapeTable(){
			var colors = o.colors;
			var textColors = o.textColors;
			var tableData = {
				dataGroups: function(){
					var dataGroups = [];
					if(o.parseDirection == 'x'){
						self.find('tr:gt(0)').each(function(i){
							dataGroups[i] = {};
							dataGroups[i].points = [];
							dataGroups[i].color = colors[i];
							if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
							$(this).find('td').each(function(){
								dataGroups[i].points.push( parseFloat($(this).text()) );
							});
						});
					}
					else {
						var cols = self.find('tr:eq(1) td').size();
						for(var i=0; i<cols; i++){
							dataGroups[i] = {};
							dataGroups[i].points = [];
							dataGroups[i].color = colors[i];
							if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
							self.find('tr:gt(0)').each(function(){
								dataGroups[i].points.push( $(this).find('td').eq(i).text()*1 );
							});
						};
					}
					return dataGroups;
				},
				allData: function(){
					var allData = [];
					$(this.dataGroups()).each(function(){
						allData.push(this.points);
					});
					return allData;
				},
				dataSum: function(){
					var dataSum = 0;
					var allData = this.allData().join(',').split(',');
					$(allData).each(function(){
						dataSum += parseFloat(this);
					});
					return dataSum
				},	
				topValue: function(){
						var topValue = 0;
						var allData = this.allData().join(',').split(',');
						$(allData).each(function(){
							if(parseFloat(this,10)>topValue) topValue = parseFloat(this);
						});
						return topValue;
				},
				bottomValue: function(){
						var bottomValue = 0;
						var allData = this.allData().join(',').split(',');
						$(allData).each(function(){
							if(this<bottomValue) bottomValue = parseFloat(this);
						});
						return bottomValue;
				},
				memberTotals: function(){
					var memberTotals = [];
					var dataGroups = this.dataGroups();
					$(dataGroups).each(function(l){
						var count = 0;
						$(dataGroups[l].points).each(function(m){
							count +=dataGroups[l].points[m];
						});
						memberTotals.push(count);
					});
					return memberTotals;
				},
				yTotals: function(){
					var yTotals = [];
					var dataGroups = this.dataGroups();
					var loopLength = this.xLabels().length;
					for(var i = 0; i<loopLength; i++){
						yTotals[i] =[];
						var thisTotal = 0;
						$(dataGroups).each(function(l){
							yTotals[i].push(this.points[i]);
						});
						yTotals[i].join(',').split(',');
						$(yTotals[i]).each(function(){
							thisTotal += parseFloat(this);
						});
						yTotals[i] = thisTotal;
						
					}
					return yTotals;
				},
				topYtotal: function(){
					var topYtotal = 0;
						var yTotals = this.yTotals().join(',').split(',');
						$(yTotals).each(function(){
							if(parseFloat(this,10)>topYtotal) topYtotal = parseFloat(this);
						});
						return topYtotal;
				},
				totalYRange: function(){
					return this.topValue() - this.bottomValue();
				},
				xLabels: function(){
					var xLabels = [];
					if(o.parseDirection == 'x'){
						self.find('tr:eq(0) th').each(function(){
							xLabels.push($(this).html());
						});
					}
					else {
						self.find('tr:gt(0) th').each(function(){
							xLabels.push($(this).html());
						});
					}
					return xLabels;
				},
				yLabels: function(){
					var yLabels = [];
					yLabels.push(bottomValue); 
					var numLabels = Math.round(o.height / o.yLabelInterval);
					var loopInterval = Math.ceil(totalYRange / numLabels) || 1;
					while( yLabels[yLabels.length-1] < topValue - loopInterval){
						yLabels.push(yLabels[yLabels.length-1] + loopInterval); 
					}
					yLabels.push(topValue); 
					return yLabels;
				}			
			};
			
			return tableData;
		};
		
		
		//function to create a chart
		var createChart = {
			pie: function(){	
				
				canvasContain.addClass('visualize-pie');
				
				if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); }	
						
				var centerx = Math.round(canvas.width()/2);
				var centery = Math.round(canvas.height()/2);
				var radius = centery - o.pieMargin;				
				var counter = 0.0;
				var toRad = function(integer){ return (Math.PI/180)*integer; };
				var labels = $('<ul class="visualize-labels"></ul>')
					.insertAfter(canvas);

				//draw the pie pieces
				$.each(memberTotals, function(i){
					var fraction = (this <= 0 || isNaN(this))? 0 : this / dataSum;
					if (fraction != 0){ //Added by Andy Moreno
						ctx.beginPath();
						ctx.moveTo(centerx, centery);
						ctx.arc(centerx, centery, radius, 
							counter * Math.PI * 2 - Math.PI * 0.5,
							(counter + fraction) * Math.PI * 2 - Math.PI * 0.5,
			                false);
				        ctx.lineTo(centerx, centery);
				        ctx.closePath();
				        ctx.fillStyle = dataGroups[i].color;
				        ctx.fill();
					}
			        // draw labels
			       	var sliceMiddle = (counter + fraction/2);
			       	var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius +  radius / 5;
			        var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance));
			        var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance));
			        var leftRight = (labelx > centerx) ? 'right' : 'left';
			        var topBottom = (labely > centery) ? 'bottom' : 'top';
			        var labeltext = $('<span class="visualize-label">' + Math.round(fraction*100) + '%</span>')
			        	.css(leftRight, 0)
			        	.css(topBottom, 0);
			        var label = $('<li class="visualize-label-pos"></li>')
			       			.appendTo(labels)
			        		.css({left: labelx, top: labely})
			        		.append(labeltext);	
			        labeltext
			        	.css('font-size', radius / 8)		
			        	.css('margin-'+leftRight, -labeltext.width()/2)
			        	.css('margin-'+topBottom, -labeltext.outerHeight()/2);
			        	
			        if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); }	
			      	counter+=fraction;
				});
			},
			
			line: function(area){
			
				if(area){ canvasContain.addClass('visualize-area'); }
				else{ canvasContain.addClass('visualize-line'); }
			
				//write X labels
				var xInterval = canvas.width() / (xLabels.length -1);
				var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
					.width(canvas.width())
					.height(canvas.height())
					.insertBefore(canvas);
				$.each(xLabels, function(i){ 
					var thisLi = $('<li><span>'+this+'</span></li>')
						.prepend('<span class="line" />')
						.css('left', xInterval * i)
						.appendTo(xlabelsUL);						
					var label = thisLi.find('span:not(.line)');
					var leftOffset = label.width()/-2;
					if(i == 0){ leftOffset = 0; }
					else if(i== xLabels.length-1){ leftOffset = -label.width(); }
					label
						.css('margin-left', leftOffset)
						.addClass('label');
				});

				//write Y labels
				var yScale = canvas.height() / totalYRange;
				var liBottom = canvas.height() / (yLabels.length-1);
				var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
					.width(canvas.width())
					.height(canvas.height())
					.insertBefore(canvas);
					
				$.each(yLabels, function(i){  
					var thisLi = $('<li><span>'+this+'</span></li>')
						.prepend('<span class="line"  />')
						.css('bottom',liBottom*i)
						.prependTo(ylabelsUL);
					var label = thisLi.find('span:not(.line)');
					var topOffset = label.height()/-2;
					if(i == 0){ topOffset = -label.height(); }
					else if(i== yLabels.length-1){ topOffset = 0; }
					label
						.css('margin-top', topOffset)
						.addClass('label');
				});

				//start from the bottom left
				ctx.translate(0,zeroLoc);
				//iterate and draw
				$.each(dataGroups,function(h){
					ctx.beginPath();
					ctx.lineWidth = o.lineWeight;
					ctx.lineJoin = 'round';
					var points = this.points;
					var integer = 0;
					ctx.moveTo(0,-(points[0]*yScale));
					$.each(points, function(){
						ctx.lineTo(integer,-(this*yScale));
						integer+=xInterval;
					});
					ctx.strokeStyle = this.color;
					ctx.stroke();
					if(area){
						ctx.lineTo(integer,0);
						ctx.lineTo(0,0);
						ctx.closePath();
						ctx.fillStyle = this.color;
						ctx.globalAlpha = .3;
						ctx.fill();
						ctx.globalAlpha = 1.0;
					}
					else {ctx.closePath();}
				});
			},
			
			area: function(){
				createChart.line(true);
			},
			
			bar: function(){
				
				canvasContain.addClass('visualize-bar');
			
				//write X labels
				var xInterval = canvas.width() / (xLabels.length);
				var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
					.width(canvas.width())
					.height(canvas.height())
					.insertBefore(canvas);
				$.each(xLabels, function(i){ 
					var thisLi = $('<li><span class="label">'+this+'</span></li>')
						.prepend('<span class="line" />')
						.css('left', xInterval * i)
						.width(xInterval)
						.appendTo(xlabelsUL);
					var label = thisLi.find('span.label');
					label.addClass('label');
				});

				//write Y labels
				var yScale = canvas.height() / totalYRange;
				var liBottom = canvas.height() / (yLabels.length-1);
				var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
					.width(canvas.width())
					.height(canvas.height())
					.insertBefore(canvas);
				$.each(yLabels, function(i){  
					var thisLi = $('<li><span>'+Math.round(this)+'%</span></li>')
						.prepend('<span class="line"  />')
						.css('bottom',liBottom*i)
						.prependTo(ylabelsUL);
						var label = thisLi.find('span:not(.line)');
						var topOffset = label.height()/-2;
						if(i == 0){ topOffset = -label.height(); }
						else if(i== yLabels.length-1){ topOffset = 0; }
						label
							.css('margin-top', topOffset)
							.addClass('label');
				});

				//start from the bottom left
				ctx.translate(0,zeroLoc);
				//iterate and draw
				for(var h=0; h<dataGroups.length; h++){
					ctx.beginPath();
					var linewidth = (xInterval-o.barGroupMargin*2) / dataGroups.length; //removed +1 
					var strokeWidth = linewidth - (o.barMargin*2);
					ctx.lineWidth = strokeWidth;
					var points = dataGroups[h].points;
					var integer = 0;
					for(var i=0; i<points.length; i++){
						var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2;
						xVal += o.barGroupMargin*2;
						
						ctx.moveTo(xVal, 0);
						ctx.lineTo(xVal, Math.round(-points[i]*yScale));
						integer+=xInterval;
					}
					ctx.strokeStyle = dataGroups[h].color;
					ctx.stroke();
					ctx.closePath();
				}
			}
		};
	
		//create new canvas, set w&h attrs (not inline styles)
		var canvasNode = document.createElement("canvas"); 
		canvasNode.setAttribute('height',o.height);
		canvasNode.setAttribute('width',o.width);
		var canvas = $(canvasNode);
			
		//get title for chart
		var title = o.title || self.find('caption').text();
		
		//create canvas wrapper div, set inline w&h, append
		var canvasContain = (container || $('<div class="visualize" role="img" aria-label="Chart representing data from the table: '+ title +'" />'))
			.height(o.height)
			.width(o.width)
			.append(canvas);

		//scrape table (this should be cleaned up into an obj)
		var tableData = scrapeTable();
		var dataGroups = tableData.dataGroups();
		var allData = tableData.allData();
		var dataSum = tableData.dataSum();
		var topValue = tableData.topValue();
		var bottomValue = tableData.bottomValue();
		var memberTotals = tableData.memberTotals();
		var totalYRange = tableData.totalYRange();
		var zeroLoc = o.height * (topValue/totalYRange);
		var xLabels = tableData.xLabels();
		var yLabels = tableData.yLabels();
								
		//title/key container
		if(o.appendTitle || o.appendKey){
			var infoContain = $('<div class="visualize-info"></div>')
				.appendTo(canvasContain);
		}
		
		//append title
		if(o.appendTitle){
			$('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain);
		}
		
		//append key
		// Changed by Andy Moreno
		if(o.appendKey){
			var newKey = $('<ul class="visualize-key"></ul>');
			var selector = (o.parseDirection == 'x') ? 'tr:gt(0) th' : 'tr:eq(0) th' ;
			if (o.type == 'bar'){
				self.find(selector).each(function(i){
					$('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ $(this).text() +'</span></li>')
						.appendTo(newKey);
				});
			} else {
			self.find(selector).each(function(i){
				$('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ $(this).text() +' ('+Math.round( dataGroups[i].points) +'%)</span></li>')
					.appendTo(newKey);
			});
			}
			newKey.appendTo(infoContain);
		};		
		
		//append new canvas to page
		
		if(!container){canvasContain.insertAfter(this); }
		if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.initElement(canvas[0]); }	
		
		//set up the drawing board	
		var ctx = canvas[0].getContext('2d');
		
		//create chart
		createChart[o.type]();
		
		//clean up some doubled lines that sit on top of canvas borders (done via JS due to IE)
		$('.visualize-line li:first-child span.line, .visualize-line li:last-child span.line, .visualize-area li:first-child span.line, .visualize-area li:last-child span.line, .visualize-bar li:first-child span.line,.visualize-bar .visualize-labels-y li:last-child span.line').css('border','none');
		if(!container){
		//add event for updating
		canvasContain.bind('visualizeRefresh', function(){
			self.visualize(o, $(this).empty()); 
		});
		}
	}).next(); //returns canvas(es)
};
})(jQuery);

/*
 * Treeview 1.4 - jQuery plugin to hide and show branches of a tree
 * 
 * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
 * http://docs.jquery.com/Plugins/Treeview
 *
 * Copyright (c) 2007 Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.treeview.min.js,v 1.2 2010-04-14 13:30:08 jcheese Exp $
 *
 */;(function($){$.extend($.fn,{swapClass:function(c1,c2){var c1Elements=this.filter('.'+c1);this.filter('.'+c2).removeClass(c2).addClass(c1);c1Elements.removeClass(c1).addClass(c2);return this;},replaceClass:function(c1,c2){return this.filter('.'+c1).removeClass(c1).addClass(c2).end();},hoverClass:function(className){className=className||"hover";return this.hover(function(){$(this).addClass(className);},function(){$(this).removeClass(className);});},heightToggle:function(animated,callback){animated?this.animate({height:"toggle"},animated,callback):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();if(callback)callback.apply(this,arguments);});},heightHide:function(animated,callback){if(animated){this.animate({height:"hide"},animated,callback);}else{this.hide();if(callback)this.each(callback);}},prepareBranches:function(settings){if(!settings.prerendered){this.filter(":last-child:not(ul)").addClass(CLASSES.last);this.filter((settings.collapsed?"":"."+CLASSES.closed)+":not(."+CLASSES.open+")").find(">ul").hide();}return this.filter(":has(>ul)");},applyClasses:function(settings,toggler){this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event){toggler.apply($(this).next());}).add($("a",this)).hoverClass();if(!settings.prerendered){this.filter(":has(>ul:hidden)").addClass(CLASSES.expandable).replaceClass(CLASSES.last,CLASSES.lastExpandable);this.not(":has(>ul:hidden)").addClass(CLASSES.collapsable).replaceClass(CLASSES.last,CLASSES.lastCollapsable);this.prepend("<div class=\""+CLASSES.hitarea+"\"/>").find("div."+CLASSES.hitarea).each(function(){var classes="";$.each($(this).parent().attr("class").split(" "),function(){classes+=this+"-hitarea ";});$(this).addClass(classes);});}this.find("div."+CLASSES.hitarea).click(toggler);},treeview:function(settings){settings=$.extend({cookieId:"treeview"},settings);if(settings.add){return this.trigger("add",[settings.add]);}if(settings.toggle){var callback=settings.toggle;settings.toggle=function(){return callback.apply($(this).parent()[0],arguments);};}function treeController(tree,control){function handler(filter){return function(){toggler.apply($("div."+CLASSES.hitarea,tree).filter(function(){return filter?$(this).parent("."+filter).length:true;}));return false;};}$("a:eq(0)",control).click(handler(CLASSES.collapsable));$("a:eq(1)",control).click(handler(CLASSES.expandable));$("a:eq(2)",control).click(handler());}function toggler(){$(this).parent().find(">.hitarea").swapClass(CLASSES.collapsableHitarea,CLASSES.expandableHitarea).swapClass(CLASSES.lastCollapsableHitarea,CLASSES.lastExpandableHitarea).end().swapClass(CLASSES.collapsable,CLASSES.expandable).swapClass(CLASSES.lastCollapsable,CLASSES.lastExpandable).find(">ul").heightToggle(settings.animated,settings.toggle);if(settings.unique){$(this).parent().siblings().find(">.hitarea").replaceClass(CLASSES.collapsableHitarea,CLASSES.expandableHitarea).replaceClass(CLASSES.lastCollapsableHitarea,CLASSES.lastExpandableHitarea).end().replaceClass(CLASSES.collapsable,CLASSES.expandable).replaceClass(CLASSES.lastCollapsable,CLASSES.lastExpandable).find(">ul").heightHide(settings.animated,settings.toggle);}}function serialize(){function binary(arg){return arg?1:0;}var data=[];branches.each(function(i,e){data[i]=$(e).is(":has(>ul:visible)")?1:0;});$.cookie(settings.cookieId,data.join(""));}function deserialize(){var stored=$.cookie(settings.cookieId);if(stored){var data=stored.split("");branches.each(function(i,e){$(e).find(">ul")[parseInt(data[i])?"show":"hide"]();});}}this.addClass("treeview");var branches=this.find("li").prepareBranches(settings);switch(settings.persist){case"cookie":var toggleCallback=settings.toggle;settings.toggle=function(){serialize();if(toggleCallback){toggleCallback.apply(this,arguments);}};deserialize();break;case"location":var current=this.find("a").filter(function(){return this.href.toLowerCase()==location.href.toLowerCase();});if(current.length){current.addClass("selected").parents("ul, li").add(current.next()).show();}break;}branches.applyClasses(settings,toggler);if(settings.control){treeController(this,settings.control);$(settings.control).show();}return this.bind("add",function(event,branches){$(branches).prev().removeClass(CLASSES.last).removeClass(CLASSES.lastCollapsable).removeClass(CLASSES.lastExpandable).find(">.hitarea").removeClass(CLASSES.lastCollapsableHitarea).removeClass(CLASSES.lastExpandableHitarea);$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings,toggler);});}});var CLASSES=$.fn.treeview.classes={open:"open",closed:"closed",expandable:"expandable",expandableHitarea:"expandable-hitarea",lastExpandableHitarea:"lastExpandable-hitarea",collapsable:"collapsable",collapsableHitarea:"collapsable-hitarea",lastCollapsableHitarea:"lastCollapsable-hitarea",lastCollapsable:"lastCollapsable",lastExpandable:"lastExpandable",last:"last",hitarea:"hitarea"};$.fn.Treeview=$.fn.treeview;})(jQuery);/*
 * $Id: main.js,v 1.48 2011-06-27 13:35:02 pmiller Exp $
 * SingleAccount main.js
 * Copyright (c) 2010 Orbis Technology Ltd. All rights reserved.
 */


$(function() {
	
	css_browser_selector(navigator.userAgent);
	
	if ($('table.piechart').length > 0){
		$('table.piechart')
			.visualize({type: 'pie', appendKey: false, appendTitle: false, pieMargin: 0, height: 64, width: 64, colors: ['#99cc00','#3399ff','#e22018']})
			.trigger('visualizeRefresh');	
	}
	SA_addHandlers();

	if (!$('html').hasClass('ie6')){
		$("a").simpletooltip();
		$("i").simpletooltip();
		$("th").simpletooltip();
		$("button").simpletooltip();
		$("span.tooltip").simpletooltip();
		$("div.game > form > div.header > h3").simpletooltip();
		$("div.coupon input").simpletooltip();
	}
});

/*
 * Gets the coupon that contains the given element.
 */
function getCouponForElement(ele) {
	return SA_coupons[$(ele).parents(".game")[0].id];
}

/*
 * Context specifies as a string where to look for elements to apply the click
 * handlers to. Defaults to the root element if not specified.
 * Example: addHandlers("#cpn_452") will add handlers to coupon 452's
 * children.
 */
function SA_addHandlers(context) {
	if (!context)
		context = "";
	// Add event listeners for coupon stats
	//
	//console.log("SA_addHandlers("+context+")");
	$(context + ' div.flyBtnWrap > a.flyBtn').click(function(e) {
		e.preventDefault();
		$(this).blur();
		if ($(this).hasClass('betslip_trigger') && SA_betslip_obj.checkBetSlipCount(true) == 0) {
			alert("You currently do not have any bets on your bet slip.");
		}
		if ($(this).hasClass('betslip_trigger') && SA_betslip_obj.checkBetSlipCount(true) > 0 || !$(this).hasClass('betslip_trigger')) {
			if (!$(this).hasClass('inactive')){
				if ($(this).parents('#Body div.flyWrap').hasClass('open')){
					$('#Body div.flyWrap').removeClass('open');
					if (($('html').hasClass('ie6') || $('html').hasClass('ie7')) && $(this).parents('#Body div.game').hasClass('submitted')){
						$(this).parents('#Body div.game').find('#Body div.submittedCounter b').show();
					}
				} else {
					$('#Body div.flyWrap').removeClass('open');
					if (($('html').hasClass('ie6') || $('html').hasClass('ie7')) && $(this).parents('#Body div.game').hasClass('submitted')){
						$(this).parents('#Body div.game').find('#Body div.submittedCounter b').hide();
						
					}
					$(this).parents('#Body div.flyWrap').addClass('open');
					if ($(this).parents('#Body div.flyWrap').hasClass('stats') && $(this).parents('div.stats').find('div.predictions table.s1').length > 0){
						var el = $(this).parents('div.stats').find('div.predictions table.s1');
						renderStats(el);
					}
				}
			}
		}
	});

	var start_time = new Date().getTime();
	$(context + ' div.flyBtnWrap > div.flyOver a.flyClose').click(function(e) {
		e.preventDefault();
		if (($('html').hasClass('ie6') || $('html').hasClass('ie7')) && $(this).parents('#Body div.game').hasClass('submitted')){
			$(this).parents('#Body div.game').find('#Body div.submittedCounter b').show();
		}
		$('#Body div.flyWrap').removeClass('open');
	});

	var end_time = new Date().getTime();
	$(context + ' .stats a').click( function() {
		if ( $(this).siblings('input[name=hasStats]').val() != 'true' ) {
			$(this).siblings('input[name=hasStats]').attr('value', 'true');
			SA_request_statistics ( $(this).siblings('input[name=sort]'    )  .val() ,
						$(this).siblings('input[name=saxgame_id]').val() );
		}
	});

	// Matches the last 2 (underscore separated) chunks 
	$(context + " form.box.TCH div.view.full div.wrap > table > tbody > tr > td > input:checkbox").click(function (e) {
		// Regex to pull the row and column values for this checkbox to let us
		// wrap the click into the standard javascript call
		var coupon = getCouponForElement(this);
		var prefix = coupon.subview_states.prefix;
		idRegex = new RegExp("^DATA_"+prefix+"([^_]+)_.*_([0-9]+)_([0-9]+)$");
		var match = this.id.match(idRegex);
		var subview = match[1];
		var x = match[2];
		var y = match[3];
		if (!coupon.tchFullViewClick(subview, x, y)) {
			e.preventDefault();
		}
	});

	$(context + " a.subsAcceptBtn").click(function (e) {
		e.preventDefault();
		getCouponForElement(this).updateSubscriptionCount();
		$("#Body div.flyWrap").removeClass("open");
	});
	$(context + " a.subsCancelBtn").click(function (e) {
		e.preventDefault();
		getCouponForElement(this).resetSubscriptionCountSelection();
		$("#Body div.flyWrap").removeClass("open");
	});
	$(context + " table.subs a.flyClose").click(function (e) {
		e.preventDefault();
		$(".flyWrap").removeClass("open");
	});
	/* To be extended by Orbis */
	$(context + ".game fieldset input:radio").click(function(e) {

		var sourceInfo = this.id.match(".*-(.*)-(.*)-.*");
		var success = false;
		if (sourceInfo != null) {
			var plan_id = sourceInfo[1];

			var success = getCouponForElement(this).switchPlan(plan_id);

		}
		if (!success) {
			printfire("Invalid ID for source element.");
			e.preventDefault();
		}
	});
	$(context + ".game button:reset").click(function(e) {

		e.preventDefault();
		$(this).parents('form')[0].reset();
	});

	// This code will cause the betslip button to flash for a few seconds. It
	// could be used when a bet is added to the betslip. Currently it is *NOT* used.
	$("#Body div.game div.footer button.addToSlip").click(function(e) {
		e.preventDefault();
		
		if (!$('#Body div.betslip').hasClass('open')){
			var el = $('#Body div.betslip div.flyBtnWrap a');
			$(el).animate({
				opacity: 0.1
			}, 150, function() {
				$(el).animate({
					opacity: 1
				}, 150, function() {
					$(el).animate({
						opacity: 0.1
					}, 150, function() {
						$(el).animate({
							opacity: 1
						}, 150);
					});
				});
			});
		}
	});
	
	/* Interface controls */
	$(context + " a.switch").click(function(e) {
		e.preventDefault();
		
		var el = '#' + $(this).attr('name');
		
		$(this).parents('.switch').hide();
		$(el).show();
	});
	$(context + " .tooltip").hover(function(e) {
		$(this).find("span").fadeIn("fast");
	}, function(){
		$(this).find("span").hide();
	});
	$(context + " div.flyWrap div.plans select").click(function(e) {
		$(this).parents("form.common").find("label").removeClass("active");
		$(this).parents("p").find("label").addClass("active");
		var select_type_input = $(this).parents("div.inner").find("input[name=plan_select_type]")[0];
		var select_name = $(this).parents("p").find("select")[0].name;
		select_type_input.value = select_name;
	});
	$(context + " div.flyWrap div.plans p.butts a.loadPlanButton").click(function (e) {
		printfire(e);
		e.preventDefault();
		$("div.flyWrap").removeClass("open");
		this.blur();
		var select_type_input = $(this).parents("div.inner").find("input[name=plan_select_type]")[0];
		var select_type = select_type_input.value;
		var parent = $(this).parents("div.inner");
		var search_string = "select[name="+select_type+"]";
		var plan_select = parent.find(search_string)[0];
		var success = getCouponForElement(this).switchPlan(plan_select.value);
	});
	$(context + " div.flyWrap div.plans p.butts a.cancelPlanSelButton").click(function (e) {
		printfire(e);
		e.preventDefault();
		$("div.flyWrap").removeClass("open");
		this.blur();
	});
/*	$(context + " .classicpools .full input").click(function(e) {

		$(this).parents("div.view").find("table").each(function(){
			if (!$(this).hasClass('plans')){
				$(this).removeClass();
			}
		});
		$(this).parents("div.view").find("table").removeClass();
		$(this).parents("div.view").find("table").addClass($(this).parents("td").attr('class'));
	});*/
	$(context + " a.btnExpand").click(function(e) {
		e.preventDefault();
		$(this).blur();
		
		var parent = $(this).parents("div.game");
		var width = $("#Body div.ghost.c1x1").css("width");
		var height = $("#Body div.ghost.c1x1").css("height");
		
		if (!$(parent).hasClass("full")){
			if ($(parent).hasClass("expandTo2x1")){
				width = $("#Body div.ghost.c2x1").css("width");
				height = $("#Body div.ghost.c2x1").css("height");
			} else if ($(parent).hasClass("expandTo3x1")){
				width = $("#Body div.ghost.c3x1").css("width");
				height = $("#Body div.ghost.c3x1").css("height");
			} else if ($(parent).hasClass("expandTo2x2")){
				width = $("#Body div.ghost.c2x2").css("width");
				height = $("#Body div.ghost.c2x2").css("height");
			} else if ($(parent).hasClass("expandTo3x2")){
				width = $("#Body div.ghost.c3x2").css("width");
				height = $("#Body div.ghost.c3x2").css("height");
			}
			$(parent).addClass("full");
		} else {
			$(parent).removeClass("full");
		}
		expandCoupon(parent, width, height);
		
		$(this).text($(this).text() == 'Expand' ? 'Minimise' : 'Expand');
	});
	$(context + ".game ul.views a").click(function(e) {
		switch_view_tab(e, this);
	});
	$(context + ".game ul.tabs a").click(function(e) {
		e.preventDefault();
		$(this).blur();
		
		$(this).parents("ul").find("li").removeClass("current");
		$(this).parents("li").addClass("current");
		
		var el = 'div.coupon';
		if ($(this).hasClass('rules')){
			el = 'div.rules';
		}
		$(this).parents("form").find("div.toggle").hide();
		$(this).parents("form").find(el).show();
		// Orbis change: hide coupon controls
		if ($(this).hasClass("rules")) {
			$(this).parents("form").find("div.footer").hide();
		} else {
			$(this).parents("form").find("div.footer").show();
		}
	});
	$(context + ".game li.actionLuckyDip a").click(function(e) {
		e.preventDefault();
		SA_luckyDip(e, $(this).parents("div.game"));
	});
	$(context + ".game li.actionReset a").click(function(e) {
		e.preventDefault();
		SA_clearCoupon(e, $(this).parents("div.game"));
	});

	$(context + ".game div.stats select.fixtureDrop").change(function(){
		var target = "div.predictions table." + $(this).attr('value');
		var el = $(this).parents("div.stats").find(target);
		renderStats(el);
	});
	$(context + ".game div.stats > div.flyBtnWrap > a.action").click(function(e){
		e.preventDefault();
		$(this).blur();
		
		$(this).parents('.flyOver').toggleClass('details');
	});

/*	$(context + ".hideConfirmation").click(function(e) {
		e.preventDefault();
		$(this).parents("div.confirmation").hide('fast');
	});
	$(context + "#HideAlert").click(function(e) {
		e.preventDefault();
		$("#Alert").hide('fast');
	});*/
	if (typeof(SA_fixIE6) != "undefined") {
		SA_fixIE6(context);
	}
	if (typeof(SA_refreshCouponBetCounts) != "undefined")
		SA_refreshCouponBetCounts();
}

function renderStats(el){
	$('.visualize').remove();

	if ($(el).parent().hasClass('pie')){
		$(el)
			.visualize({type: 'pie', appendTitle: false, pieMargin: 0, pieLabelPos: 'inside', height: 70, width: 70, colors: ['#e22018','#3399ff','#ffcc00','#99cc00','#cc00cc', '#3300cc']});
	} else {
		$(el)
			.visualize({type: 'bar', appendTitle: true, height: 60, width: 210, barGroupMargin: 1, barMargin: 1, colors: ['#3399ff','#3399ff','#3399ff','#3399ff','#3399ff','#e22018','#e22018','#e22018','#e22018','#e22018']});
	}
}

function expandCoupon(el, width, height){
	$(el).animate({
		width: width,
		height: height
	}, "fast", "swing" );
}

function showPanel(e, panel, close){
	e.preventDefault();
	$(panel).modal({
		overlay:40,
		close:close,
		onOpen: function (dialog) {
			dialog.overlay.fadeIn('fast', function () {
				dialog.container.fadeIn('fast');
				dialog.data.fadeIn('fast');
			});
		},
		onClose: function (dialog) {
			dialog.data.fadeOut('slow');
			dialog.container.fadeOut('fast', function () {
				dialog.overlay.fadeOut('fast', function () {
					$.modal.close();
				});
			});
		}
	});
}
function showPanelNoEvent(panel, close) {
	$(panel).modal({
		overlay:40,
		close:close,
		onOpen: function (dialog) {
			dialog.overlay.fadeIn('fast', function () {
				dialog.container.fadeIn('fast');
				dialog.data.fadeIn('fast');
			});
		},
		onClose: function (dialog) {
			dialog.data.fadeOut('slow');
			dialog.container.fadeOut('fast', function () {
				dialog.overlay.fadeOut('fast', function () {
					$.modal.close();
				});
			});
		}
	});
}

function luckyDip(e, form){
}
function makeSelected(e){
	
}
/*
CSS Browser Selector v0.3.5 (Feb 05, 2010)
Rafael Lima (http://rafael.adm.br)
http://rafael.adm.br/css_browser_selector
License: http://creativecommons.org/licenses/by/2.5/
Contributors: http://rafael.adm.br/css_browser_selector#contributors

Altered by Andy Moreno to spit out Firefox 3.0 separately
*/
function css_browser_selector(u){var ua = u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1;},g='gecko',w='webkit',s='safari',o='opera',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.0')?g+' ff3 ff3_0':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?'mobile':is('iphone')?'iphone':is('ipod')?'ipod':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win':is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js']; c = b.join(' '); h.className += ' '+c; return c;}; css_browser_selector(navigator.userAgent);
/**
*
*	simpleTooltip jQuery plugin, by Marius ILIE
*	visit http://dev.mariusilie.net for details
*
**/
(function($){ $.fn.simpletooltip = function(){
	return this.each(function() {
		var text = $(this).attr("title");
		if(text.length > 0) {
			$(this).hover(function(e){
				var tipX = e.pageX + 12;
				var tipY = e.pageY + 12;
				$(this).attr("title", ""); 
				$("body").append("<div id='simpleTooltip' style='position: absolute; z-index: 100; display: none;'>" + text + "</div>");
				if($.browser.msie) var tipWidth = $("#simpleTooltip").outerWidth(true)
				else var tipWidth = $("#simpleTooltip").width()
				$("#simpleTooltip").width(tipWidth);
				$("#simpleTooltip").css("left", tipX).css("top", tipY).fadeIn("fast");
			}, function(){
				$("#simpleTooltip").remove();
				$(this).attr("title", text);
			});
			$(this).mousemove(function(e){
				var tipX = e.pageX + 12;
				var tipY = e.pageY + 12;
				var tipWidth = $("#simpleTooltip").outerWidth(true);
				var tipHeight = $("#simpleTooltip").outerHeight(true);
				if(tipX + tipWidth > $(window).scrollLeft() + $(window).width()) tipX = e.pageX - tipWidth;
				if($(window).height()+$(window).scrollTop() < tipY + tipHeight) tipY = e.pageY - tipHeight;
				$("#simpleTooltip").css("left", tipX).css("top", tipY).fadeIn("fast");
			});
		}
	});
}})(jQuery);


function switch_view_tab(e,ele) {
	e.preventDefault();
	$(ele).blur();
	
	var game = $(ele).parents("div.game");
	var cpn_object = getCouponForElement(ele);
	var li = $(ele).parents("li");
	var newClass = "";
	var newView = "";
	var quick = 0;

	if ($(li).hasClass("full")) {
		newClass = "full";
	} else if ($(li).hasClass("numbers")) {
		newClass = "numbers";
	} else {
		newClass = "fixtures";
	}

	if (newClass == "full") {
		newView = "full_A";
	} else {
		newView = newClass;
	}

	var view_changed = cpn_object.changeView(newView);

	if (!view_changed) {
		return;
	}

	cpn_object.cur_view = newView;
	if ($('html').hasClass('ie6')) quick = 1;
	cpn_object.changeTab(newClass,quick);
}

/*
 * $Id: office_base.js,v 1.4 2011-07-13 11:58:40 gpeterso Exp $
 * Copyright (c) 2005 Orbis Technology Ltd. All rights reserved.
 *
 * OpenBet Office
 * Base Javascript
 *
 * Original source, openbet office shared javascript: base.js
 * Id: base.js,v 1.43 2010-04-15 14:46:50 cnagy Exp
 */

if(window.cvsID) {
	cvsID('base', '$Id: office_base.js,v 1.4 2011-07-13 11:58:40 gpeterso Exp $', 'office');
}

document.tocMinus = null;
document.tocPlus  = null;


if(document.Package) {
	document.Package.provide('office', 'base');
}


/**********************************************************************
 * Browser
 *********************************************************************/

// sniff out problem browsers
function SniffBrowser()
{
	var a = (this.agt = navigator.userAgent.toLowerCase()),
	i;

	this.ns4      = document.layers;

	this.op5      = a.indexOf("Opera 5") !== -1 || a.indexOf("Opera/5") !== -1;
	this.op6      = a.indexOf("Opera 6") !== -1 || a.indexOf("Opera/6") !== -1;
	this.op8      = a.indexOf("Opera 8") !== -1 || a.indexOf("Opera/8") !== -1;
	this.op9      = a.indexOf("Opera 9") !== -1 || a.indexOf("Opera/9") !== -1;
	this.op       = this.op5 || this.op6 || this.op8 || this.op9;

	this.safari   = a.indexOf("safari") !== -1;

	this.ie       = a.indexOf("msie") !== -1;
	this.ie7      = a.indexOf("msie 7") !== -1;
	this.ie9      = a.indexOf("msie 9") !== -1;
	this.mac_ie   = this.mac && this.ie;

	this.chrome   = a.indexOf("chrome") !== -1;
	this.webkit   = a.indexOf("applewebkit") !== -1;

	this.gecko    = a.indexOf("gecko") !== -1;
	this.ffox     = a.indexOf("firefox") !== -1;
	this.ffox3    = a.indexOf("firefox/3") !== -1;
	this.ffox35   = a.indexOf("firefox/3.5") !== -1;
	this.ffox36   = a.indexOf("firefox/3.6") !== -1;

	// major version only
	this.ffox_ver = (i = a.indexOf("firefox/")) !== -1 ? parseFloat(a.substr(i + 8)) : NaN;

	this.mac      = a.indexOf("macintosh") !== -1;
	this.linux    = a.indexOf("linux") !== -1;
	this.windows  = a.indexOf("windows") !== -1;

	this.lin_ff = this.linux && this.ffox;
	this.win_ff = this.windows && this.ffox;
	this.mac_ff = this.mac && this.ffox;
}
var browser = new SniffBrowser();



// IE specific APIs...
if(!browser.ie || browser.op) {

	// swap node
	function swapNode(_node, _base)
	{
		var p = _node.parentNode,
			s = _node.nextSibling;

		if(!this.parentNode) {
			if(typeof _base == 'undefined') return null;
		}
		else {
			_base = this;
		}

		_base.parentNode.replaceChild(_node, _base);
		p.insertBefore(_base, s);

		return _base;
	}


	// remove node
	function removeNode(_node)
	{
		if(!this.parentNode) {
			if(typeof _node == 'undefined') return null;
		}
		else {
			_node = this;
		}

		_node.parentNode.removeChild(_node);
		return _node;
	}

	// Safari 2.0.* does not support prototype on Node, or allow Node to be modifed,
	// therefore, use functions directly
	if(self.Node && self.Node.prototype) {
		Node.prototype.swapNode = swapNode;
		Node.prototype.removeNode = removeNode;
	}
}



// Array.indexOf for IE and any other browser that doesn't support it.
if(!Array.prototype.indexOf) {

	Array.prototype.indexOf = function(_elt)
	{
		var len = this.length,
		from = Number(arguments[1]) || 0;

		from = (from < 0) ? Math.ceil(from) : Math.floor(from);
		if (from < 0) from += len;

		for (; from < len; from++) {
			if (from in this && this[from] === _elt) return from;
		}

		return -1;
	};
}



/**********************************************************************
 * DOM
 *********************************************************************/

// get object for the different types of browsers
function getObject(_objectId)
{
	if(typeof _objectId === 'undefined' || _objectId === null) return null;

	if(document.getElementById && document.getElementById(_objectId)) {
		return document.getElementById(_objectId);
	}
	if(document.all && document.all(_objectId)) {
		return document.all(_objectId);
	}
	if(document.layers && document.layers[_objectId]) {
		return getObjNN4(document, _objectId);
	}

	return null;
}



// find an object (NS4 only)
function getObjNN4(_obj, _name)
{
	var x = _obj.layers,
	foundLayer,
	i = 0
	len = x.length,
	tmp;

	for(; i < len; i++) {
		if(x[i].id == _name) {
			foundLayer = x[i];
		}
		else if(x[i].layers.length) {
			tmp = get_objNN4(x[i], _name);
		}
		if(tmp) {
			foundLayer = tmp;
		}
	}

	return foundLayer;
}



// get the style object for the different types of browsers
function getStyleObject(_elem)
{
	if(typeof _elem === 'string') _elem = getObject(_elem);
	if(typeof _elem !== 'object' || _elem === null) return null;

	return _elem.style;
}



// get a css rule
function getCSSRule(_href, _selectorText)
{
	var i = 0,
	len = document.styleSheets.length,
	sheets, txt, rules, j, len2;

	_selectorText = _selectorText.toLowerCase();

	for(; i < len; i++) {
		sheets = document.styleSheets[i];
		if(sheets.href === null && _href !== document.cgiURL) continue;                     /* Safari/Firefox3 has no internal style url */
		else if(
			((sheets.href === null || !sheets.href.length) && _href === document.cgiURL) || /* Safari/IE has no internal style url */
			sheets.href.indexOf(_href) > -1                                                 /* Firefox2 internal syle has href == cgi URL */
		) {
			rules = sheets.cssRules ? sheets.cssRules : sheets.rules;

			for(j = 0, len2 = rules.length; j < len2; j++) {
				txt = rules[j].selectorText.toLowerCase();
				if(txt.indexOf(_selectorText) > -1) {
					return rules[j];
				}
			}
		}
	}

	return null;
}



// change style class of an element
function changeClass(_elem, _class)
{
	if(typeof _elem === 'string') _elem = getObject(_elem);
	if(typeof _elem !== 'object' || _elem === null) return;

	if(browser.op5 || browser.op6) _elem.style.className = _class;
	else _elem.className = _class;
}



// add a className to an element if it's not already present
function addClass(_elem, _class)
{
	if(typeof _elem === 'string') _elem = getObject(_elem);
	if(typeof _elem !== 'object' || _elem === null) return;

	var c;

	if(browser.op5 || browser.op6) {
		if((c = _elem.style.className).indexOf(_class) == -1) {
			_elem.style.className = [c, ' ', _class].join('');
		}
	}
	else {
		if((c = _elem.className).indexOf(_class) == -1) {
			_elem.className = [c, ' ', _class].join('');
		}
	}
}



// remove a class from an element
function removeClass(_elem, _class)
{
	if(typeof _elem === 'string') _elem = getObject(_elem);
	if(typeof _elem !== 'object' || _elem === null) return;

	var re = new RegExp(['\\b', _class, '\\b'].join(''));
	if(browser.op5 || browser.op6) {
		_elem.style.className = _elem.style.className.replace(re, "");
	}
	else {
		_elem.className = _elem.className.replace(re, "");
	}
}



// hide or show a page element
function changeObjectDisplay(_objectId, _newDisplay)
{
	var styleObject = getStyleObject(_objectId, document);

	if(styleObject !== null) {
		styleObject.display = _newDisplay;
		return true;
	}

	return false;
}



// find out if an element is currently displayed
function isDisplayed(_elem)
{
	if(typeof _elem === 'string') _elem = getObject(_elem);
	if(typeof _elem !== 'object' || _elem === null) return false;

	return _elem.clientWidth > 0;
}



// hide or show a page element
function changeObjectVisibility(_objectId, _newVisibility)
{
	var styleObject = getStyleObject(_objectId, document);

	if(styleObject !== null) {
		styleObject.visibility = _newVisibility;
		return true;
	}

	return false;
}



// move an element to a new set of X + Y co-ordinates
function moveXY(_obj, _x, _y)
{
	var obj = getStyleObject(_obj);
	if(obj === null) {
		return;
	}

	_x = parseInt(_x);
	_y = parseInt(_y);

	if(browser.ns4) {
		obj.top = _y;
		obj.left = _x;
	}
	else if(browser.op5) {
		obj.pixelTop = _y;
		obj.pixelLeft = _x;
	}
	else {
		obj.top = [_y, 'px'].join('');
		obj.left = [_x, 'px'].join('');
	}
}



// resize an element to a new width & height
function resizeXY(_obj, _x, _y)
{
	var obj = getStyleObject(_obj);
	if(obj === null) {
		return;
	}

	_x = parseInt(_x);
	_y = parseInt(_y);

	if(browser.ns4) {
		obj.height = _y;
		obj.width = _x;
	}
	else if(browser.op5) {
		// TODO! I don't know what to put here
	}
	else {
		obj.height = [_y, 'px'].join('');
		obj.width = [_x, 'px'].join('')
	}
}



// Add a form variable
function insertInputObj(_form, _type, _id, _name, _value)
{
	var doc = _form.ownerDocument;

	if(browser.ie && !browser.ie9){
		inputObj = doc.createElement(['<input name=\"', _name, '\">'].join(''));
	} else {
		inputObj = doc.createElement("input");
		inputObj.name = _name;
	}

	inputObj.type  = _type;
	inputObj.id    = _id;
	inputObj.value = _value;

	_form.appendChild(inputObj);
}



// insert some HTML on the end of an element
function insertHtml(_element, _html)
{
	if(browser.ie || browser.op8) {
		_element.insertAdjacentHTML("beforeEnd", _html);
	}
	else {
		var r = document.createRange(),
		parseNode;

		r.setStartBefore(_element);
		parsedNode = r.createContextualFragment(_html);
		_element.appendChild(parsedNode);
	}
}



// Recursively get all the text of a tag.
// i.e., running it on <span>Click <a href="#">here</a> to continue</span>
// returns Click here to continue
//
// It is best to trim the final result for leading and trailing whitespace
// NodeTypes: 3 is a text node, 1 is a normal element node
function getNodeText(_node)
{
	if(typeof _node !== 'object' && !(_node = getObject(_node))) return '';

	var result = new Array(),
	children = _node.childNodes,
	i = 0,
	len = children.length,
	nodeType;

	for(; i < len; i++) {

		nodeType = children[i].nodeType;
		if(nodeType === 3) {
			result[result.length] = children[i].nodeValue;
		} else if(nodeType === 1) {
			result[result.length] = getNodeText(children[i]);
		}
	}

	return result.join('');
}



// get all the nodes with an id and add to a hash
function getNodeIds(_node, _o)
{
	if(typeof _o === 'undefined') _o = new Object();
	if(typeof _node === 'undefined' || !_node) return _o;

	var i = 0,
	children = _node.childNodes,
	len = children.length,
	node;

	if(typeof _node.id !== 'undefined' && _node.id.length) _o[_node.id] = _node;

	for(; i < len; i++) {
		node = children[i];
		if(node.childNodes.length) getNodeIds(node, _o);

		if(typeof node.id !== 'undefined' && node.id.length) _o[node.id] = node;
	}

	return _o;
}



// add/set text to a node
function addText(_node, _text, _add_nbsp)
{
	var t = typeof _text === 'string' ? _text : _text.join("");

	// empty string, then add non-breaking space (hex)
	// - IE does not render any styles if empty cell
	if(browser.ie && !t.length && (typeof _add_nbsp === 'undefined' || _add_nbsp)) t = '\u00a0';

	if(_node.childNodes.length) _node.firstChild.data = t;
	else _node.appendChild(document.createTextNode(t));
}



// a general function to delete all children of a tag
function removeAllChildren(parent)
{
	var children = parent.childNodes,
	i = 0,
	length = children.length,
	child;

	for(; i < length; i++) {

		// always remove the first child
		child = children[0];
		child.removeNode(true);
	}
}



/**********************************************************************
 * Events
 *********************************************************************/

// get event pageY/clientY
function getEventY(_e)
{
	_e = typeof _e === 'undefined' ? window.event : _e;
	return _e.pageY ? _e.pageY : _e.clientY;
}



// get event pageX/clientX
function getEventX(_e)
{
	_e = typeof _e === 'undefined' ? window.event : _e;
	return _e.pageX ? _e.pageX : _e.clientX;
}



/**********************************************************************
 * Misc
 *********************************************************************/

// set a anchor target to 'officePane' frame if present, else sets to '_top'
function officePaneTarget(_anchorId)
{
	var anchor = getObject(_anchorId);
	if(anchor) {
		anchor.target = parent.parent && parent.parent.officePane
			? 'officePane' : '_top';
	}
}



// setup inheritance between a superclass and a subclass
function setupInheritance(_super, _sub)
{
	_sub.prototype = new _super();
	_sub.prototype.conscructor = _sub;
	_sub.superclass = _super.prototype;
}



// parse int
function toInt(_str, _pm)
{
	if(ckInteger(_str, _pm, 0, 0)) {
		var i = 0,
		len = _str.length;

		while(i < len && _str.substring(i, 1) === '0') i++;
		return parseInt(_str.substring(i, len));
	}

	return _str;
}



// string format
// usage: Stirng.format('text {0} {1} {2}', arg1, arg2, arg3,....)
if(!String.format) {
	String.format = function( text )
	{
		if(arguments.length <= 1) return text;

		// decrement to move to the second argument in the array
		var tokenCount = arguments.length - 2,
		token = 0;

		for(; token <= tokenCount; token++) {
			text = text.replace(new RegExp( ["\\{", token, "\\}"].join(''), "gi" ),
								arguments[token + 1]);
		}

		return text;
	};
}



/* Build an associated array.
 * Sets default options within the array
 *
 *   _def    - default options
 *   _opt    - assocuiated array; default none
 *   returns - _opt or _def if _opt is not defined
 */
function associatedArray(_def, _opt)
{
	if(typeof _opt == 'undefined') return _def;

	for(var i in _def) if(typeof _opt[i] === 'undefined') _opt[i] = _def[i];
	return _opt;
}
/* $Id: request_broker.js,v 1.8 2010-07-16 12:13:58 jcheese Exp $
 * Copyright (c) 2010 Orbis Technology Ltd. All rights reserved.
 *
 * The Request Broker provides prioritised asynchronous AJAX queues. Each priority is
 * assigned a queue (associated array indexed by the priority) that serialises the requests. The
 * requests are still asynchronous, but ordered!
 *
 * The broker will handle any errors with the request before calling the user supplied 'callback'.
 *
 * Use the broker in environments which you cannot control the ordering of the AJAX requests.
 *
 * Original source, openbet office shared javascript: request_broker.js
 * Id: request_broker.js,v 1.2 2010-04-15 14:46:50 cnagy Exp
 */

if(window.cvsID) {
	cvsID('request_broker', '$Id: request_broker.js,v 1.8 2010-07-16 12:13:58 jcheese Exp $', 'office');
}



/*
 * To avoid multiple brokers being instantiated, this class is wrapped in a
 * simple Singleton Pattern implementation.
 */
SA_singleton_request_broker = function() {
	throw "Cannot be instantiated";
}

/*
 * Instantiates RequestBroker for use - ensures that only one instantiation occurs.
 */
SA_singleton_request_broker.instance = function () {
	if (this._instance) {
		return this._instance;
	}

	
/**********************************************************************
 * BEGIN PRIVATE OBJECT DEFINITION
 *********************************************************************/



/**********************************************************************
 * RequestBroker
 *********************************************************************/

var RequestBroker = function (_opt)
{
	this._init(_opt);
}



/* Constructor
 *
 *   _opt - optional arguments (associated array)
 *          'url'           - server url; default null (must be supplied per send request)
 *          'indicator_cb'  - indicator/animated-GIF callback function; default null
 *                            signature:
 *                               function name(_enable) { ... }
 *                            where
 *                               _enable - if true then should start animated GIF, else stop
 *          'max_priority'  - maximum priority; default 10
 *                            keep this maximum small, as can impact performance
 */
RequestBroker.prototype._init = function(_opt)
{
	var def = {
		'url'         : null,
		'indicator_cb': null,
		'max_priority': 10
	};

	_opt = associatedArray(def, _opt);

	// check arguments
	if(_opt.url !== null && typeof _opt.url !== 'string') {
		RequestBroker._error(['invalid url \'', _opt.url, '\''].join(''));
	}
	if(_opt.indicator_cb !== null && typeof _opt.indicator_cb !== 'function') {
		RequestBroker._error('invalid indicator callback');
	}
	if(typeof _opt.max_priority !== 'number' || _opt.max_priority < 0) {
		RequestBroker._error(['invalid max priority \'', _opt.max_priority, '\''].join(''));
	}


	this.queue        = {};
	this.busy         = false;
	this.req_num      = 1;
	this.url          = _opt.url
	this.indicator_cb = _opt.indicator_cb;
	this.max_priority = _opt.max_priority;
};



/* Send a request.
 *
 * The request might not be sent immediately if busy, but will be queued. Duplicate request will
 * ignored.
 *
 *          _opt - optional arguments (associated array)
 *                 'priority' - request pririty; default 0
 *                              the request is added to the queue indexed by the priority, lowest priority
 *                              takes precedence
 *          'url'             : server url; default this.url
 *                              if not supplied will use url set on construction
 *          'method'          : HTTP method (GET|POST); default 'POST'
 *          'action'          : POST action argument; default null
 *                              only applicable if POSTing an associated array
 *          'post'            : post/get arguments; default null
 *                              can be supplied as a string, e.g. '&name=value&name1=value1...' or as an
 *                              associated array (name value pairs)
 *                              if supplying an associated array, then action must be supplied
 *          'async'           : Asynchronous request; default true
 *                              if non-async, the request is not queued and is sent immeditatly, regadless
 *                              of priority and queue size
 *          'callback'        : request callback; default null
 *                              called on successully reciving an AJAX response
 *                              signature:
 *                                   function name(_response) { ... }
 *                              where:
 *                                   _response    AJAX response object
 *          'debug'           : debug mode; default false
 *                              logs messages to firebug if enabled (printfire must be enabled)
 *          'nocache'         : Generates a random number on the url of the request in order to prevent caching
 *          'nocache_harsh'   : Where the req_num is the same because the URL is unchanged but parameters in it
 *                              now have alternative context due other actions we append a datestring to the
 *                              URL.
 *          'timeoutmsecs'    : if supplied it will invalidate the request after N msecs. This means that
 *                              should a response arrive after that time it will be ignored. IF a 
 *                              timeoutcallback is supplied it will be executed at that point.
 *          'timeoutcallback' : If supplied along with timeoutmsecs this callback will be executed upon timeout
 */
RequestBroker.prototype.send = function(_opt)
{

	var def = {
		'priority'        : 0,
		'url'             : this.url,
		'method'          : 'POST',
		'action'          : null,
		'post'            : null,
		'async'           : true,
		'callback'        : null,
		'debug'           : false,
		'nocache'         : false,
		'nocache_harsh'   : false,
		'timeoutmsecs'    : null,
		'timeoutcallback' : null
	},
	c, a;

	_opt = associatedArray(def, _opt);

	// check arguments
	var methods = ['url', 'method', 'callback'];
	
	for(var curmethod = 0; curmethod < methods.length; curmethod++) {
		if(_opt[(c = methods[curmethod])] === null || !_opt[c].length) {
			RequestBroker._error(['invalid ', c].join(''));
		}
	}
	if(typeof _opt.callback !== 'function') {
		RequestBroker._error('invalid callback');
	}
	if(!/^POST|GET$/.test((_opt.method = _opt.method.toUpperCase()))) {
		RequestBroker._error(['invalid method \'', _opt.method, '\''].join(''));
	}
	if(_opt.method === 'POST') {
		if(_opt.post === null) {
			RequestBroker._error(['post argument is not supplied for POST method']);
		}
		if(typeof _opt.post === 'object' && (_opt.action === null || !_opt.action.length)) {
			RequestBroker._error(['action argument is not supplied for POST object']);
		}
	}
	if(
		typeof _opt.priority !== 'number' ||
		_opt.priority < 0 ||
		_opt.priority > this.max_priority
	) {
		RequestBroker._error(['invalid priority \'', _opt.priority, '\''].join(''));
	}
	if(typeof _opt.async !== 'boolean') {
		RequestBroker._error(['invalid asyncronous flag \'', _opt.async, '\''].join(''));
	}
	if(typeof _opt.debug !== 'boolean') {
		RequestBroker._error(['invalid debug flag \'', _opt.debug, '\''].join(''));
	}
	if(_opt.debug && !window.printfire && typeof window.printfire === 'function') {
		_opt.debug = false;
	}


	// convert post arguments if supplied as an object
	if(_opt.method === 'POST' && typeof _opt.post === 'object') {
		_opt.post = RequestBroker.build_post(_opt.action, _opt.post);
	}


	// non-async request sent immediately
	if(!_opt.async) {
		this._send(null, _opt);
		return;
	}


	// add arguments to queue, indexed by priority
	var queue = this.queue,
	i = 0,
	p = _opt.priority,
	len;

	if(typeof queue[p] === 'undefined') queue[p] = [];

	// -check if the request is already within the queue
	_opt.checksum = [_opt.url, _opt.method, _opt.post].join('|');
	for(a = queue[p], len = a.length; i < len; i++) {
		if(a[i].checksum === _opt.checksum) {
			if(_opt.debug) {
				printfire('RequestBroker.send: ignorning duplicate request ', _opt.checksum);
			}
			return;
		}
	}

	if (_opt.nocache || _opt.nocache_harsh) {
		var msg = [];
		msg[msg.length] = _opt.url;
		if (_opt.url.search("\\?") >= 0) {
			msg[msg.length] = '&';
		} else {
			msg[msg.length] = '?';
		}
		msg[msg.length] = 'no_cache=';
		if (_opt.nocache) {
			msg[msg.length] = this.req_num;
		} else {
			 msg[msg.length] = new Date().getTime();
		}

		_opt.url = msg.join('');
	}

	var self = this;

	if ( (_opt.timeoutmsecs) && (_opt.timeoutmsecs > 0) ) {
		_opt.timeoutId = setTimeout( function(){ self._to(_opt) }, _opt.timeoutmsecs);
	}

	// -push request to queue
	a[a.length] = _opt;


	// send
	this._send(_opt.debug);
};

/* Private function to handle TimeOuts
 *
 *
*/
RequestBroker.prototype._to = function(_req)
{
	// invalidate both the timeoutId and the callback
	_req.timeoutId = false;
	_req.callback  = false;
	// execute the timeoutcallback
	_req.timeoutcallback();
}

/* Private function to send a request.
 *
 * If trying to send an asynchrouns request, looks on the all the priority queues (starting at 0)
 * for the next request and sends to server. If busy, will immediately return and wait for the
 * current request to finish and send.
 * If the queues are empty, exits
 *
 * Will send a synchrouns request immediately.
 *
 *   _debug - debug mode (logs to firebug); default none
 *   _req   - synchrounous request; default none;
 *            if supplied, then will send synchrounously, else looks on the queue
 *
 */
RequestBroker.prototype._send = function(_debug, _req)
{
	// find next request
	if(typeof _req === 'undefined') {

		// busy
		if(this.busy) {
			if(typeof _debug === 'boolean' && _debug) {
				printfire('RequestBroker._send: busy');
			}
			return;
		}

		var queue = this.queue,
		priority = 0,
		p;


		// find the lowest priority number
		for(; priority <= 100; priority++) {
			for(p in queue) {
				if(p <= priority && queue[p].length) {
					priority = p;
					break;
				}
			}
			if(priority === p) break;
		}

		// queue empty
		if(typeof queue[priority] === 'undefined' || !queue[priority].length) {
			return;
		}


		// get 1st item (oldest) in the queue
		_req = queue[priority].shift();
	}

	var self = this;


	// send request
	this._indicator(true);
	if(_req.debug) {
		printfire('RequestBroker._send: ',
				  (_req.num = this.req_num++),
				  _req.async,
				  _req.url,
				  _req.method,
				  _req.post);
	}

	// -denote busy if sending an asynchronous request
	//  only sending 1 async at a time
	if(_req.async) this.busy = true;

	HttpRequest(_req.url,
				_req.method,
				function(_r) { self._cb(_req, _r); },
				_req.post,
				_req.async);
};



/* Private function to handle an AJAX response callback
 * Will display an error if the status is not 200, else calls the user-supplied callback.
 * When complete, and handling an asynchrounous request, re-calls _send to process next request.
 *
 * Callback is protected by a try-catch block, exceptions will be logged on firebug or an alert
 *
 *   _req   - request
 *   _resp  - AJAX response
 */
RequestBroker.prototype._cb = function(_req, _resp)
{
	if(_req.debug) {
		printfire('RequestBroker._cb: ', _req.priority, _req.num, _resp.status, _resp.statusText);
	}


	// bad response
	if(_resp.status !== 200) {
		var m = ['Failed to load request '];
		if(_req.debug) {
			m[m.length] = '(',
			m[m.length] = _req.num,
			m[m.length] = ') ';
		}
		m[m.length] = _resp.status;
		m[m.length] = ' ';
		m[m.length] = _resp.statusText;
		m = m.join('');

		if(window.errorfire) errorfire('RequestBroker._cb: ', m);
		if(window.PopupAlert) PopupAlert(document.title, m);
	}

	// request callback
	else {
		try {
			if (_req.timeoutId) clearTimeout(_req.timeoutId);
			
			if (_req.callback)  _req.callback(_resp);
		}
		catch(_e) {
			var m = ['Failed to call callback '];
			if(_req.debug) {
				m[m.length] = '(',
				m[m.length] = _req.num,
				m[m.length] = ') ';
			}
			m[m.length] = _e.message;
			m = m.join('');
			if(window.errorfire) errorfire('RequestBroker._cb: ', m);
			else alert(m);
		}
	}


	// next request
	if(_req.async) {
		this.busy = false;
		this._send();
	}


	this._indicator(false);
};



/* Private function to throw an exception
 *
 *   _msg - message
 */
RequestBroker._error = function(_msg)
{
	throw ['RequestBroker: ', _msg].join('');
};



/* Private function to handle an indicator
 * Will enable indicator on first request, and stop on the last
 *
 *   _enable  - enable the indicator
 */
RequestBroker.prototype._indicator = function(_enable)
{
	if(this.indicator_cb === null) return;

	if(_enable) {
		if(typeof this.indicator_count === 'undefined') this.indicator_count = 1;
		else this.indicator_count++;

		if(this.indicator_count === 1) this.indicator_cb(true);
	}
	else if(--this.indicator_count === 0) {
		this.indicator_cb(false);
	}
};



/* Build a post request
 *
 *   _action  - post action
 *   _nvp     - name value list
 *   returns  - post request (string of name value pairs delimited by =)
 */
RequestBroker.build_post = function(_action, _nvp)
{
	var post = [['action=', _action].join('')],
	name, i, len, a;

	for(name in _nvp) {
		if(typeof _nvp[name] == 'object') {
			for(i = 0, a = _nvp[name], len = a.length; i < len; i++) {
				post[post.length] = ['&', name, '=', a[i]].join('');
			}
		}
		else {
			post[post.length] = ['&', name, '=', _nvp[name]].join('');
		}
	}

	return post.join('');
};

	
/**********************************************************************
 * END PRIVATE OBJECT DEFINITION
 *********************************************************************/

	this._instance = new RequestBroker(	{
		'max_priority': 2
	} );
	return this._instance;
}
/*
 * $Id: coupon.js,v 1.75 2011-07-04 18:18:58 pmiller Exp $
 */
// Arrays containing the coupons for state-keeping
SA_coupon_ids = new Array();
SA_coupons = new Array();

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

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

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

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

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

StatisticsQ = new Array();

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

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

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

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

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


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

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

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

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

}

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

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



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

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

		SA_betslip_old_coupons = null;
		SA_betslip_old_get_state = null;
	}
}

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

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

	return num_to_add;
}

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

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

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

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

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

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


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

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

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

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


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

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

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

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

		SA_linked_id_queue.push(saxgame_id);

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

		}
	}

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

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

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

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

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

		coup_cont = document.getElementById("coupon_container");

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

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

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

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

	var curCoupon = new SA_Coupon(couponData);

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

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

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

	return curCoupon;

}


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

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

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

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

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

	}

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

	this.viewStates = new Array();

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

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

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

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

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

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

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

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

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

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

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

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

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

}

SA_Coupon.prototype.clearAllViews = function () {

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

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

	this.cur_view = default_view;

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

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

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

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

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

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

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

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

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

	if (secs < 0)
		secs = 0;

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

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

	if (days > 3) {

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

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

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

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

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

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



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

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

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

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

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

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

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

}

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

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

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

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

	thisEl.className += " failed ";

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

}


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	var value = thisArray[x][y];

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

	thisArray[x][y] = value;

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

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

	SA_saveCouponState(this.game_id);

	return true;

}

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

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

	var permArray = new Array();

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

		numSelected = 0;

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

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

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

	}

	var numSels = 1;

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

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

	return true;

}

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

	var completed = 1;

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

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

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

	this.permutationFormula();

}

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

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

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

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

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

    // Default value.
    return 1;

}

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

	var permCount = 0;
	var totalSelections = 0;

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

		totalSelections = numSelected;

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

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

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

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

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

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

	this.updateValidationText();

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

	return permValue;
}

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

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

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

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


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



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

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

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

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

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

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

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

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

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

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

	this.updateSubscriptionPrice();

	this.permutationFormula();
	return success;
}

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


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

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

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

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

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

		this.clearAllViews();
	}

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

	this.num_subs = 1;

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

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

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

}

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

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


	var cpn_ele = this.getElement();

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

	if (this.subviewActive()) {

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

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

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

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

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

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

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

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

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

// Utility functions


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

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

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

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

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

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

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

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


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





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

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





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

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

	var origquery = query;

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

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

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

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

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


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


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




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

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

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





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

	frag.appendChild(temp);

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

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


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

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

	var bet_properties = new Array();

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

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

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

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

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

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

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

		}

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

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

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


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

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

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

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

	var target_game_id = this.game_id;

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

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

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

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

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

}


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


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

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

	this.clearAllViews();

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


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

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

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

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

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

}



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

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

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

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



SA_COUPON_STATE_COOKIE_NAME = "SA_CPN_STATE";

SA_COUPON_MAX_COOKIE_SIZE = "2047";



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



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



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

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

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



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



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

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

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

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

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


function SA_refreshCouponBetCounts() {

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

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

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


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




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

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

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

            This method produces a JSON text from a JavaScript value.

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

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

*/

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

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

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

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

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

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

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

  var hop = Object.hasOwnProperty;

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

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

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

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

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

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

    return result;
  };
})();


JSON.parse = jsonParse;
}



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


(function ($) {

	$.fn.textTruncate = function() {

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

		if ( args.length ) {

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

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

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


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

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

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

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

				return true;
			}

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

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

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

			$td.text( text );

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

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

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

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

			this.style.visibility = "visible";

			return true;
		});

		return true;

	};


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

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

	$.fn.textTruncate._native = _native;


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

})(jQuery);

/*
 * $Id: xmlhttp.js,v 1.3 2010-05-31 17:02:58 alongwil Exp $
 *
 * Copyright (c) 2005 Orbis Technology Ltd. All rights reserved.
 *
 * OpenBet Office
 * getXMLHttpRequest
 *
 *
 * 
 * Original source, openbet office shared javascript: xmlhttp.js
 * Id: xmlhttp.js,v 1.11 2009-10-17 15:54:06
 */

__xmlhttp_id__ = '$Id: xmlhttp.js,v 1.3 2010-05-31 17:02:58 alongwil Exp $';

if(document.Package) {
	document.Package.provide('office', 'xmlhttp');
}

/**********************************************************************
 * getXMLHttpRequest
 *********************************************************************/

function getXMLHttpRequest()
{
	var httpReq = false;

	try {
		// non-IE and IE7
		httpReq = new XMLHttpRequest();

		// Some versions of Mozilla are reported as locking up when anything
		// other than XML is returned
		if (httpReq.overrideMimeType) {
			httpReq.overrideMimeType("text/xml");
		}
	}
	catch(e) {
		// IE (not IE7)
		var type = ['MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP'],
			i = 0,
			len = type.length;

		for(; i < len; i++){
			try {
				httpReq = new ActiveXObject(type[i]);
				break;
			} catch(e) {}
		}

	} finally {
		return httpReq;
	}
}

var request_complete;
// AJAX (get|post) HTTP Request
function HttpRequest(_url, _method, _callback, _post, _varAsync)
{

	var req = getXMLHttpRequest();
	if(!req) {
		alert("Your browser does not support AJAX, please upgrade");
		return;
	}

	if(typeof _varAsync == 'undefined') {
		_varAsync = true;
	}

	req.open(_method, _url, _varAsync);
	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");
	req.setRequestHeader("Timeout", SA_REQUEST_TIMEOUT);

	// For special case callback set a timer.
	request_complete = 0;
	if (_callback == 'placedResultsCallback') {
		var thread_id = setTimeout(
			// Abort request and send message back to handler.
			function(req) {
				if (!request_complete) {
					req.abort();
					_callback('ERROR');
					return;
				}
			},SA_REQUEST_TIMEOUT);
	}

	// non-blocking
	if(_varAsync) {
		req.onreadystatechange = function() {
			// Bet placemenet request confirmation.
			if(req.readyState == 4) {
				if (_callback == 'placedResultsCallback') {
					request_complete = 1;
				}

				_callback(req);
			}
		};
	}

	// We now know the outcome.
	clearTimeout(thread_id);

	if(_method == "POST") req.send(_post);
	else req.send(null);

	// blocking
	if(!_varAsync) _callback(req);
}



// toggle AJAX image busy-indicator
// - _id should include the class http_indicator
function HttpIndicator(_id, _enable)
{
	var obj = getObject(_id);
	if(obj && obj.className.indexOf('http_indicator') > -1) {
		obj.style.backgroundImage = _enable ? 'url(' + document.ajaxIndicator.src + ')' : 'none';
	}
}
/*
 * $Id: util.js,v 1.3 2010-07-02 09:29:34 jcheese Exp $
 */
// Window control.
function toggleWindow(div_id) {

	if (document.getElementById(div_id).style.display == 'none') {
		document.getElementById(div_id).style.display = 'block';
	} else {
		document.getElementById(div_id).style.display = 'none';
	}

}
/*
 * $Id: form.js,v 1.2 2010-05-31 17:02:58 alongwil Exp $
 * Copyright (c) 2005 Orbis Technology Ltd. All rights reserved.
 *
 * OpenBet Office
 * Form utilities
 */

if(window.cvsID) {
	cvsID('form', '$Id: form.js,v 1.2 2010-05-31 17:02:58 alongwil Exp $', 'office');
}


var redirect = null;


if(document.Package) {
	document.Package.provide('office', 'form');
	document.Package.require('office', 'base');
	document.Package.require('office', 'alert');
	document.Package.require('office', 'date');
}


/**********************************************************************
 * Form Error Class
 *********************************************************************/

// form-validation error list
function Error()
{
	var errorList = [];


	// any errors
	this.isErr = function()
	{
		return this.errorList.length;
	};


	// reset the error-list
	this.reset = function()
	{
		this.errorList = [];
	};


	// add an error
	this.add = function(_str)
	{
		this.errorList[this.errorList.length] = _str;
	};


	// display the error list within an alert
	this.alert = function()
	{
		if(this.errorList.length) alert(this.errorList.join('\n'));
	};


	// display the error list within a Div Popup Alert
	this.divAlert = function(_title, _callback)
	{
		if(this.errorList.length) PopupAlert(_title, this.errorList.join('\n'), _callback);
	};
}
var err = new Error();



/**********************************************************************
 * Utilities
 *********************************************************************/

// trim a string
function strTrim(_src, _delim)
{
	var r = "",
	b = "",
	i = 0,
	len = _src.length,
	c;

	for(; i < len; i++) {
		c = _src.charAt(i);
		if(_delim.indexOf(c) >= 0) {
			b += c;
		} else {
			if(r.length > 0) r += b;
			b = "";
			r += c;
		}
	}

	return r;
}



// check a mandatory string
function ckMandatory(_str, _minLen, _maxLen, _valChars)
{
	if(
		_str.length <= 0 ||
		(_minLen !== 0 && _str.length < _minLen) ||
		(_maxLen !== 0 && _str.length > _maxLen)
	) {
		return false;
	}

	if(_valChars) {
		var re = "^[A-Za-z0-9\_\-]*$";
		if(!_str.match(new RegExp(re))) return false;
	}

	return true;
}



// check an integer
function ckInteger(_str, _pm, _minLen, _maxLen)
{
	var exp = _pm ? /^[+-]?\d*$/ : /^\d*$/;
	if(!exp.test(_str)) return false;

	if((_minLen !== 0 && _str.length < _minLen) || (_maxLen !== 0 && _str.length > _maxLen)) {
		return false;
	}

	return true;
}



// check a float value
function ckFloat(_str, _pm, _minLen, _maxLen)
{
	if(_str === '') return false;

	var exp = _pm ? /^(([+-]?[1-9]\d*|[+-]?0)(\.\d{0,2})?|\.\d{1,2})$/
	              : /^(([1-9]\d*|0)(\.\d{0,2})?|\.\d{1,2})$/;
	if(!exp.test(_str)) return false;

	if((_minLen !== 0 && _str.length < _minLen) || (_maxLen !== 0 && _str.length > _maxLen)) {
		return false;
	}

	return true;
}



// check a decimal price value (max 3dp)
function ckDecPrice(_str)
{
	if(_str === '') return false;

	return /^(([1-9][\d,]*|0)(\.\d{0,3})?|\.\d{1,3})$/.test(_str);
}



// check a fraction price value
function ckFracPrice(_str)
{
	if(_str === '') return false;

	return /^[1-9][0-9]*\/[1-9][0-9]*$/.test(_str);
}



// check a price value
// -price might be fractional or decimal
function ckPrice(_str)
{
	return ckDecPrice(_str) || ckFracPrice(_str);
}



// cheap + cheerful url checker
function ckURL(_url)
{
	var exp =
		/^[a-zA-Z]{3,}:\/\/[\-a-zA-Z0-9\.]+\/*[\-a-zA-Z0-9\/\\%_.]*\?*[\-a-zA-Z0-9\/\\%_.=&]*$/;

	/*Fixes formatting bug caused by regexp above*/
	return exp.test(_url);
}



// check informix date
function ckInfDate(_str, _full, _date, _time)
{
	var exp;

	if(typeof _date !== 'undefined' && _date) exp = Date.inf_date_exp;
	else if(typeof _time !== 'undefined' && _time) exp = Date.inf_time_exp;
	else exp = Date.inf_exp;

	return exp.test(_str);
}



// submit a form
function submitOBForm(_name, _action, _button)
{
	var f = document.forms[_name];

	if(_button) {
		_button.value = "Busy...";
		_button.disabled = true;
	}

	var actionObj = getObject([_name, 'Action'].join(''));
	if (actionObj == null) {
		insertInputObj(f, 'hidden', [_name, 'Action'].join(''), 'action', _action);
	} else {
		if (!browser.ie) {
			actionObj.name = 'action';
		}
		actionObj.value = _action;
	}
	if(redirect != null) insertInputObj(f, 'hidden', 'location', 'location', redirect);

	f.submit();

	return true;
}



// reset a form
function resetForm(_name)
{
	var f = document.forms[_name],
	i = 0,
	len = f.length;

	for(; i < len; i++) {
		if(f[i].type === "text") f[i].value = "";
		else if(f[i].type === "checkbox") f[i].checked = 0;
		else if(f[i].type === "select-one") f[i].selectedIndex = 0;
	}
}



// write a select input box
function writeSelect()
{
	var sel = arguments[0],
	i = 1,
	len = arguments.length,
	n, v, sel_tag;

	for(; i < len; i += 2) {
		v = arguments[i];
		n = arguments[i + 1];
		sel_tag = (sel == v) ? " selected" : "";
		document.writeln(['<option value=\"', v, '\"', sel_tag, '>', n, '</option>'].join(''));
	}
}



/* update a set of checkboxes from a string
 *
 *    _f              - the html form element
 *    _checkBoxPrefix - the common prefix that each checkbox name will have
 *    _string         - the string which contains the values
 *    _stringType     - COMMA | CHAR
 *                      how string is separated (comma sep, 1 char per value etc)
 */
function setCheckBoxesFromString(_f, _checkBoxPrefix, _string, _stringType)
{
	var length = _checkBoxPrefix.length,
	inputs = _f.getElementsByTagName('input'),
	i = 0,
	len = inputs.length,
	values = _splitString(_string, _stringType),
	input, checkBox;

	for(; i < len; i++) {
		input = inputs[i];
		if(input.type != 'checkbox' || input.name.substr(0, length) != _checkBoxPrefix) {
			continue;
		}
		input.checked = false;
	}


	// for each value 'tick' the corresponding checkbox
	for(i = 0, len = values.length; i < len; i++) {

		// try to get the checkbox
		checkBox = f[_checkBoxPrefix + values[i]];
		if(checkBox) checkBox.checked = true;
	}
}



/* Get a string from a set of checkboxes
 *
 *    f              - the html form element
 *    _checkBoxPrefix - the common prefix that each checkbox name will have
 *    _stringType     - COMMA | CHAR
 *                     how string is separated (comma sep, 1 char per value etc)
 */
function getStringFromCheckBoxes(f, _checkBoxPrefix, _stringType)
{
	var length = _checkBoxPrefix.length,
	result = new Array(),
	counter = 0,
	inputs = f.getElementsByTagName('input'),
	i = 0,
	len = inputs.length,
	input;

	for(; i < len; i++) {
		input = inputs[i];
		if(input.type !== 'checkbox' || input.name.substr(0, length) != _checkBoxPrefix) {
			continue;
		}
		if(input.checked) {
			result[counter] = input.value;
			counter++;
		}
	}

	return _joinArray(result, _stringType);
}



// split up a string into an array, based on type
function _splitString(_string, _stringType)
{
	var result;

	if(_stringType == 'COMMA') {
		result = _string.split(',');
		return result;
	}
	else if(_stringType == 'CHAR') {
		result = new Array();
		var length = _string.length,
			i = 0;

		for(; i < length; i++) result[i] = _string.charAt(i);
		return result;
	}

	return new Array();
}



// join an array into a string, based on type
function _joinArray(_array, _stringType)
{
	if(_stringType != 'CHAR' && _stringType != 'COMMA') return '';

	// at the moment, all the type does is indicate the delimiter :)
	var result = '',
	i = 0,
	len = _array,length;

	for(; i < len; i++) {
		result += _array[i];
		if(_stringType === 'COMMA' && i < (_array.length-1)) result += ',';
	}

	return result;
}



// pad a string with zeros
function zeroPad(_val, _n)
{
	var s = _val.toString();

	if(s.length < _n) {
		var p = '',
		j = parseInt(_n) - s.length,
		i = 0;

		for(; i < j; i++) p += '0';
	}
	else {
		return s;
	}

	return p + s;
}



// add commas to a number (represented as a string)
function addCommas(_n)
{
	if(!ckFloat(_n, true, 1, 0)) return _n;

	var x = _n.split('.'),
	x1 = x[0],
	x2 = x.length > 1 ? '.' + x[1] : '',
	rgx = /(\d+)(\d{3})/;

	while(rgx.test(x1)) {
		x1 = x1.replace(rgx, '$1' + ',' + '$2');
	}

	return x1 + x2;
}



// get the selected option's value from a select box
function getSelectedValue(_sel)
{
	return _sel.type && _sel.type == 'select-one'
		? _sel.options[_sel.selectedIndex].value
		: null;
}
/*
 * $Id: cookie.js,v 1.2 2010-05-31 17:02:58 alongwil Exp $
 * Copyright (c) 2005 Orbis Technology Ltd. All rights reserved.
 *
 * OpenBet Office
 * Cookies
 */

if(window.cvsID) {
	cvsID('cookie', '$Id: cookie.js,v 1.2 2010-05-31 17:02:58 alongwil Exp $', 'office');
}

if(document.Package) {
	document.Package.provide('office', 'cookie');
	document.Package.require('office', 'form');
}


/**********************************************************************
 * Cookie Utilities
 *********************************************************************/

// get a cookie
function getCookie(_name)
{
	var dc = document.cookie.split(';'),
		prefix = _name + "=",
		i = 0,
		len = dc.length;

	for(; i < len; i++) {
		dc[i] = strTrim(dc[i], ' ');
		if(dc[i].indexOf(prefix) != -1) return unescape(dc[i].substr(prefix.length));
	}

	return null;
}



// set a cookie
function setCookie(_name, _value, _expire, _path, _domain, _secure)
{
	var c = new Array();

	c[c.length] = _name;
	c[c.length] = '=';
	c[c.length] = escape(_value);

	if(_expire && typeof(_expire) == 'object') {
		c[c.length] = ';expires=';
		c[c.length] = _expire.toGMTString();
	}

	if(_path != null && _path.length) {
		c[c.length] = ';path=';
		c[c.length] = _path;
	}

	if(_domain != null && _domain.length) {
		c[c.length] = ';domain=';
		c[c.length] = _domain;
	}

	if(_secure != null && _secure.length) {
		c[c.length] = ';secure';
	}

	document.cookie = (c = c.join(''));

	return c;
}
/*
 * $Id: betslip.js,v 1.28 2011-06-27 13:35:02 pmiller Exp $
###################################################################################################
#                                                                                                 #
#   SLIPBETS cookie structure:                                                                    #
#                                                                                                 #
#   Where there is a single bet stored:                                                           #
#   saxgame|cost|plan|sels|incl                                                                   #
#                                                                                                 #
#   incl (included) refers to whether there is a tick next to the bet on the betslip.             #
#                                                                                                 #
#   Where there is more than 1 bet the cookie structure is of the form:                           #
#   saxgame#1|cost#1|plan#1|sels#1|incl#1_saxgame#2|cost#2|plan#2|sels#2|incl#2                   #
#                                                                                                 #
###################################################################################################
*/



// Betslip object. Properties can be added in the same fashion as SA_Betslip where necessary.
SA_Betslip = function() {

	// To hold data about the betslip.

}

SA_Betslip.addToSlipTimeout = null;

// Generic initialisation code for the betslip.
SA_betslip_init = function() {

	SA_betslip_obj = new SA_Betslip();

	// Initialise betslip contents according to cookies the customer may have.
	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsUpdateBetSlip&"+SA_get_no_cache_param(),
		'callback': SA_betslipUpdateCallback,
		'debug'   : true
	} );

}



// Switch bet inclusion.
SA_Betslip.prototype.switchInclusion = function(bet_num) {

	// Switch the values to what is currently stated.
	var included;
	if (document.getElementById('bs_bet_'+bet_num).checked == true) {
		included = '1';
	} else {
		included = '-';
	}

	// Make request.
	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsSwitchInclusion&bet_num="+bet_num+"&included="+included+"&"+SA_get_no_cache_param(),
		'callback': SA_betslipSwitchInclusionCallback,
		'debug'   : true
	} );



}


// Kick off for slip placement.
// This is invoked from betslip submission.
SA_Betslip.prototype.startBetProcess = function() {

	// Strategy based on login status.
	if (SA_IS_GUEST) {
		showPanelNoEvent($("#panelLoginJoin"), close);
	} else {
		SA_betslipBetPlaceGateway();
	}

}



// Place bets on slip.
SA_Betslip.prototype.placeBets = function() {

	var num_bets = document.getElementById('bs_bet_count').value;
	// Shouldn't happen - sanity check.
	if (num_bets == 0) {
		alert("No bets to place.");
		return;
	}

	// Place the bets.
	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsPlaceSelectSlipBets&"+SA_get_no_cache_param(),
		'callback': SA_betslipPlacedResultsCallback,
		'debug'   : true,
		'timeoutmsecs'    : SA_REQUEST_TIMEOUT,
		'timeoutcallback' : function(){
						// This code will be executed if a timeout occurs. The contents of the modal panel will be replaced.
						showPanelNoEvent($("#panelPlaceBetsTimeout"), true);
						document.getElementById('betslip_report').innerHTML = "<h3 class='header'>" +
												      SA_XL('SA_BETSLIP_TIMOUT_ERR_HEADER') + 
												      "</h3><p class='BetSlipTimeout'>" + 
												      SA_XL('SA_BETSLIP_TIMOUT_ERR_BODY') + 
												      "</p>";
						var modal = $.modal.impl;
					
						modal.d.container.prepend($(modal.o.closeHTML).addClass(modal.o.closeClass));
					
						modal.unbindEvents();
						modal.bindEvents();
				    }
	} );

}

// Stores the previous request arguments so we can go back to where we were.
SA_betslip_old_get_state = null;
SA_betslip_old_coupons = null;

// Generate editable coupon.
// This is invoked from the comp link on the betslip.
SA_Betslip.prototype.editBet = function(saxgame_id,bet_num) {

	if (!SA_betslip_old_coupons) {
		SA_betslip_old_coupons = SA_coupon_ids.join(" ");
	}

	if (!SA_betslip_old_get_state) {
		SA_betslip_old_get_state = new Object();
		SA_betslip_old_get_state.criteria = SA_moreCouponsFetchParams.criteria;
		SA_betslip_old_get_state.value    = SA_moreCouponsFetchParams.value;
	}

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

	// Show the relevant coupon.
	SA_get_coupons_by_bet_num(bet_num);

	// Close
	SA_switchBetslip();

}



// Update bet number on coupon button.
SA_Betslip.prototype.updateBetslipCount = function() {

	// Get the count.
	var bet_count = this.checkBetSlipCount();

	// Write out value.
	document.getElementById('bs_bet_count').value = bet_count;

}




// Check current betslip count value.
SA_Betslip.prototype.checkBetSlipCount = function(count_disabled) {

	var cookie_contents = getCookie(SA_BETSLIP_COOKIE_NAME);
	if (cookie_contents == "" || cookie_contents == null) {
		return 0;
	}

	// Count separators.
	var bet_count = 0;
	var bets = cookie_contents.split(SA_BETSLIP_SEPARATOR);
	for (var i = 0; i < bets.length; i++) {
		// Check to see if the inclusion is 1
		// if it is, increment the counter
		var bet_properties_array = bets[i].split(SA_BET_PROPERTY_SEPARATOR);
		if (bet_properties_array[5] == "1" || count_disabled) {
			bet_count++;
		}
	}

	return bet_count;

}


// Wipe the contents of the betslip cookie.
SA_Betslip.prototype.clearBetSlip = function() {

	// Update the betslip.
	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsDeleteSlipCookie",
		'callback': SA_betslipUpdateCallback,
		'debug'   : true
	} );

	// Close the empty betslip.
	document.getElementById('parent_betslip_container').className = betslip_close_state;

	// Update total stake displayed.
	SA_betslip_obj.calculateTotalStake();

}



// Delete a specific selection from the betslip. Works on the basis of a bet iterator id since
// there could be multiple bets with the same selections on the same competition. This provides
// uniqueness.
SA_Betslip.prototype.deleteBetSlipSelection = function(bet_num) {

	// Update the betslip.
	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsDeleteSlipBatch&bet_nums="+bet_num+"&"+SA_get_no_cache_param(),
		'callback': SA_betslipAddDelCallback,
		'debug'   : true
	} );

	var total_num = SA_betslip_obj.checkBetSlipCount();

	// If the deleted selection was not the last bet the bets need renumbering.
	if (bet_num + 1 != total_num) {
		for (var i = bet_num; i < total_num; i++) {
			var next_i = i + 1;
			document.getElementById('bs_bet_'+i).id = 'bs_bet_'+next_i;
		}
	}

}



// Delete specified bets from slip.
SA_Betslip.prototype.deletePlacedFromBetSlip = function () {

	var num_bets = document.getElementById('bet_attempt_count').value;
	var bet_nums = Array();
	var prefix = "bet_place_status_";

	// Determine which bets are marked to delete.
	// Also, delete those which failed because comp was closed.
	var j = 0;
	for (var i = 0; i < num_bets; i++) {
		var element = document.getElementById(prefix + i);
		var reason  = document.getElementById(prefix + 'code_' + i);
		if ((element && element.value == 1) || (reason && reason.value == 'CLOSED')) {
			bet_nums[j] = i;
			j++;
		}
	}

	// Comma separated string of bets to delete.
	var bet_nums = bet_nums.join(",");

	var r = SA_singleton_request_broker.instance();
	r.send( {
		'priority': r.COUPONS_PRI,
		'method'  : 'GET',
		'url'     : "?action=GoPoolsDeleteSlipBatch&bet_nums="+bet_nums+"&"+SA_get_no_cache_param(),
		'callback': SA_betslipUpdateCallback,
		'debug'   : true
	} );

	SA_betslip_obj.calculateTotalStake();

}



// Calculate the stake based on items on betslip that are included.
SA_Betslip.prototype.calculateTotalStake = function () {

	// Extract the stakes currently in the cookie.
	var total_stake = parseFloat(0.00);
	var do_calc     = 1;

	// Some browsers throw an error if we do the split below on an empty var
	var cookie_contents = getCookie(SA_BETSLIP_COOKIE_NAME);

	if (cookie_contents == null) {
		do_calc = 0;
	}

	// Extract all the stakes for the bets in the cookie.
	if (do_calc) {
		var bet_array            = cookie_contents.split(SA_BETSLIP_SEPARATOR);
		var bet_properties_array = Array();
		var stake_array          = Array();
	

		// bet_properties_array:
		//   saxgame_id|stake|num_subs|plan|selections
		for (var i = 0; i < bet_array.length; i++) {
			bet_properties_array = bet_array[i].split(SA_BET_PROPERTY_SEPARATOR);
			stake    = bet_properties_array[1];
			num_subs = bet_properties_array[2];
			stake_array[i] = stake * num_subs;
		}
	}

	// Exit now if nothing on betslip.
	var num_bets = document.getElementById('bs_bet_count').value;
	if (num_bets == 0) {
		do_calc = 0;
	}

	// Sum the stakes for the bets that are included.
	if (do_calc) {
		var num_bets = cookie_contents.split(SA_BETSLIP_SEPARATOR).length;
		// Add to total stake only where bet is included.
		for (var i = 0; i < num_bets; i++) {
			if (document.getElementById('bs_bet_'+i).checked) {
				total_stake = total_stake + parseFloat(stake_array[i]);
			}
		}
	}

	document.getElementById('total_stake').innerHTML = total_stake.toFixed(2);
	document.getElementById('bs_bet_value').innerHTML = '(&pound;' + total_stake.toFixed(2) + ')';

}



// Gateway proc to place bets.
SA_betslipBetPlaceGateway = function() {

	// Close the betslip.
	document.getElementById('parent_betslip_container').className = betslip_close_state;

	// Show the progress panel.
	showPanelNoEvent($("#panelPlaceBets"), false);

	// Delay for a couple of seconds for smoother transition to report.
	var thread_id = setTimeout("SA_betslip_obj.placeBets();",2000);

}



// Callback function for bet placement.
SA_betslipPlacedResultsCallback = function (xmlhttp) {

	// Get response.
	var response = xmlhttp.responseText;

	// Deal with, predominantly, timeout errors.
	if (response == 'ERROR') {
		// technical error
		//showPanelNoEvent(GoPoolsGetErrorPanel);
	}

	document.getElementById('betslip_report').innerHTML = response;

	// Get rid of those included items.
	SA_betslip_obj.deletePlacedFromBetSlip();

	var modal = $.modal.impl;

	modal.d.container.prepend($(modal.o.closeHTML).addClass(modal.o.closeClass));

	modal.unbindEvents();
	modal.bindEvents();

	if (updateAccountBalances) {
		updateAccountBalances();
	}
	SA_refreshCouponBetCounts();

}


// Callback function for betslip update.
SA_betslipUpdateCallback = function(xmlhttp) {

	var response = xmlhttp.responseText;

	document.getElementById('betslip_container').innerHTML = response;

	// Adjust to reflect status.
	SA_betslip_obj.updateBetslipCount();
	SA_betslip_obj.calculateTotalStake();

}

// Wrapper for the addDelCallback function
SA_betsAddToSlipCallback = function(xmlhttp) {
	SA_betslipAddDelCallback(xmlhttp);

	if(SA_Betslip.addToSlipTimeout) clearTimeout(SA_Betslip.addToSlipTimeout);
	SA_Betslip.addToSlipTimeout = setTimeout(SA_closeBetslip,SA_ADDTOSLIP_TIMEOUT);
}

// Callback for add to slip.
SA_betslipAddDelCallback = function(xmlhttp) {

	var response = xmlhttp.responseText;

	if(SA_betslip_checkAJAXError(response)) return;

	document.getElementById('betslip_container').innerHTML = response;

	// Adjust to reflect status.
	SA_betslip_obj.updateBetslipCount();

	SA_betslip_obj.calculateTotalStake();

	var total_num = SA_betslip_obj.checkBetSlipCount();
	// Collapse betslip with no selections.
	if (total_num == 0) {
		SA_switchBetslip();
	} else {
		SA_openBetslip();
	}
}

// Checks to see if an AJAX response is an error message
SA_betslip_checkAJAXError = function(response) {

	if (response.split('|')[0] == "ERROR") {
		/* Note that the AJAX error reply will have the format:
			"ERROR"|GEN_BETSLIP_ERR|err_msg
			where 
				GEN_BETSLIP_ERR is a translation code and
				err_msg is a more refined description of the mesage (currently not displayed)
			
		*/
		showPanelNoEvent($("#panelSlipOpsErr"), true);
		return true;
	}

	return false;
}

// Callback for inclusion switch.
SA_betslipSwitchInclusionCallback = function(xmlhttp) {

	// Determine new stake level.
	SA_betslip_obj.updateBetslipCount();
	SA_betslip_obj.calculateTotalStake();

}



// Open the betslip on command.
function SA_openBetslip () {

	if (document.getElementById('parent_betslip_container').className == betslip_close_state) {
		document.getElementById('parent_betslip_container').className = betslip_open_state;
	}

	// User invoked rather than onload. This way we can be certain the betslip is fully loaded befor
	// accessing items within it.
	SA_betslip_obj.calculateTotalStake();

}


// Close the betslip on command.
function SA_closeBetslip () {

	if (document.getElementById('parent_betslip_container').className == betslip_open_state) {
		document.getElementById('parent_betslip_container').className = betslip_close_state;
	}

}

// Switch open/close of betslip.
function SA_switchBetslip () {

	if (document.getElementById('parent_betslip_container').className == betslip_close_state) {
		document.getElementById('parent_betslip_container').className = betslip_open_state;
	} else {
		document.getElementById('parent_betslip_container').className = betslip_close_state;
	}

	// User invoked rather than onload. This way we can be certain the betslip is fully loaded befor
	// accessing items within it.
	SA_betslip_obj.calculateTotalStake();

}

// Populate selections on coupon when editing.
SA_Betslip.prototype.populateSelections = function(saxgame_id,bet_num,num_cols,num_balls) {

	// Get the cookie contents.
	var cookie_contents = getCookie(SA_BETSLIP_COOKIE_NAME);
	if (cookie_contents == "" || cookie_contents == null) {
		alert('Unable to find cookie');
		return;
	}

	var bets_array = cookie_contents.split(SA_BETSLIP_SEPARATOR);
	var bet;
	// There is only 1 bet in the cookie.
	if (bets_array.length == 0) {
		bet = cookie_contents;
	} else {
		bet = bets_array[bet_num];
	}

	if (bet == "" || bet == null) {
		alert('Unable to find bet');
		return;
	}

	// Extract required bet information.
	var bet_properties = bet.split(SA_BET_PROPERTY_SEPARATOR);
	var saxgame_id     = bet_properties[0];
	var selections     = bet_properties[4];

	var cpn_obj = SA_coupons['cpn_'+saxgame_id];

	if (saxgame_id == "" || saxgame_id == null || selections == "" || saxgame_id == null) {
		alert('Unable to find selections for bet');
		return;
	}
	
	// Tick selections stored in cookie.
	var selection_arr = Array();
	var selection_arr = selections.split(SA_SEPARATOR_BET_SELECTIONS);

	_validateTCHCoupon(bet_properties);

	var row = 0;
	var col = 0;

	// Foreach ball possibly selected
	for (var i = 0; i < num_balls; i++) {
		// If it is stored in the seleciton array
		if (SA_arrayContains(selection_arr,i)) {
			// Tick it
			cpn_obj.buttonClick(row,col);
			//document.getElementById('DATA_'+saxgame_id+'_'+row+'_'+col).checked = true;
		}

		// Advance row and cols for data format.
		if ((i + 1) % num_cols == 0) {
			row++;
			col = 0;
		} else {
			col++;
		}
	}

}

// Helper function for TCH plan validation
_validateTCHCoupon = function(bet_properties) {

	if (!$('#cpn_' + bet_properties[0]).hasClass('TCH')) return;

	var saxgame_id = bet_properties[0];
	var plan_id    = bet_properties[3];

	var cpn_obj = SA_coupons['cpn_' + bet_properties[0]];

	// switch to the correct plan
	cpn_obj.switchPlan(plan_id,true);

	// Uncheck all plan radios
	$('#cpn_' + saxgame_id + ' .planRadios input:radio').attr('checked', false);

	// If present, check only the one that is in the betstring
	if ( $('#plan-' + plan_id + '-' + saxgame_id + '-fixtures').length > 0 )
		$('#plan-' + plan_id + '-' + saxgame_id + '-fixtures').attr('checked', true);
}

// Helper method for populateSelections.
function SA_arrayContains (array,element) {

	for (var i = 0; i < array.length; i++) {
		adjusted_selection = array[i] - 1;
		if (adjusted_selection == element) {
			return true;
		}
	}

	return false;

}



// Removes ( and ) from the given string
function SA_strip_parentheses (string) {

	var parentheses_exp = /[()]/g;
	return string.replace(parentheses_exp, "");

}


// Generates a string of the form:
// nocache=0.4837258245245
// For use in a get request, to avoid caching in IE
SA_get_no_cache_param = function () {

	return "nocache="+Math.random();

}

