/**
	AbstractNotifier

	This is an add-on to turn any object into one to which you can register for events

	Methods:
		register{Name}Listener
			register third-party object as a listener for events. The object should
			have a method: handle{Name}Event
		deregister{Name}Listener
			deregisters the third party object as no longer a listener. The object
			should call this method when cleaning itself up, so that the notifier
			releases its handle on the object (hence allowing garbage collection)
		notify{Name}
			sends an event to all registered listeners
		clear{Name}Listeners
			deregisters all listeners
*/
function AbstractNotifier( obj, notification_name )
{
	var array_name = notification_name + "_listeners";
	obj[array_name] = new Array();

	obj["register" + notification_name + "Listener"] = function( listener )
	{
		var array_name = notification_name + "_listeners";
		for( var i=0; i < obj[ array_name ].length; i++ )
		{
			var l = obj[ array_name ][i];
			if( l == listener ) return;
		}
		obj[ array_name ].push( listener );
	}

	obj["deregister" + notification_name + "Listener"] = function( listener )
	{
		var array_name = notification_name + "_listeners";
		var i = 0;
		while( i < obj[array_name].length )
		{
			if( obj[array_name][i] == listener )
			{
				obj[array_name].splice( i, 1 );
			}
			else i++;
		}
	}

	obj["notify" + notification_name + "Listener"] = function()
	{
		var array_name = notification_name + "_listeners";
		var function_name = "handle" + notification_name + "Event";
		for( var id=0; id < obj[ array_name ].length; id++ )
		{
			var l = obj[ array_name ][id];
			if( l[function_name] )
			{
				var args = new Array();
            	args.push( obj );
            	for( var i = 0 ; i < arguments.length ; i++ )
            	{   
            	    args.push( arguments[i] );
            	}  
				l[function_name].apply( l, args );
			}
			else
			{
				do_debug( "notify" + notification_name + "Listener: " + l + "." + function_name + " does not exist" );
			}
		}
	}

	obj["clear" + notification_name + "Listeners"] = function()
	{
		var array_name = notification_name + "_listeners";
		obj[array_name] = new Array();
	}
}
new AbstractPool();

function AbstractPool( pool_code, num_legs, is_multi_race, max_runners )
{
	if( ! pool_code ) return;
	do_debug( "AbstractPool::constructor( " + pool_code + ", " + num_legs + ", " + is_multi_race + ", " + max_runners + " )" );
	this.pool_code = pool_code;
	this.pool_oid = -1;
	this.type = "";
	this.status = AbstractPool.CLOSED;
	this.num_legs = num_legs;
	this.is_multi_race = is_multi_race || false;
	this.max_runners = max_runners || 128;
	this.min_stake = 0;
	this.max_stake = 0;
	this.currency = "USD";
	this.allows_penny_betting = true;
	this.requires_mult_min_stake = false;
	this.penny_multiple = 100;
	this.is_boxable = false;
	this.is_unboxable = false;
	this.box_pool_oid = -1;
	this.unbox_pool_oid = -1;
	this.use_site_currency = false;
	this.has_favourite = false;

	if( ! pool_code ) Common.createAlert( "Pool: invalid pool_code" );
	if( ! num_legs || num_legs <= 0 ) Common.createAlert( "Pool: invalid number of legs" );

	this.selection_validators = new Array();
	this.min_stakers = new Array();
	this.max_stakers = new Array();
	this.legs = new Object(); 
	this.dividends = null;
	this.payouts = new Array();
	this.willpays = new Array();

	AbstractNotifier( this, "Pool" );
}

AbstractPool.CHANGE = "C";

AbstractPool.CLOSED = "C";
AbstractPool.OPEN = "O";

AbstractPool.prototype.setLeg = function( sequence_number, race )
{
	this.legs[sequence_number] = new Leg( this, race, sequence_number );
}

AbstractPool.prototype.setPoolOid = function( pool_oid )
{
	this.pool_oid = pool_oid;
}

AbstractPool.prototype.setType = function( type )
{
	this.type = type;
}

AbstractPool.prototype.setStatus = function( status )
{
	this.status = status;
}

AbstractPool.prototype.setMinimumStake = function( min_stake )
{
	this.min_stake = min_stake || 0;
}

AbstractPool.prototype.setMaximumStake = function( max_stake )
{
	this.max_stake = max_stake || 1000000;
}

AbstractPool.prototype.setCurrency = function( currency, penny_multiple )
{
	this.currency = currency || "USD";
	this.penny_multiple = penny_multiple || 100;
}

AbstractPool.prototype.setAllowsPennyBetting = function( allows_penny_betting )
{
	this.allows_penny_betting = allows_penny_betting;
}

AbstractPool.prototype.setRequiresMultipleMinStake = function( requires_mult_min_stake )
{
	this.requires_mult_min_stake = requires_mult_min_stake;
}

AbstractPool.prototype.setDividends = function( dividends )
{
	this.dividends = dividends;
}

AbstractPool.prototype.setPayouts = function( payouts )
{
	this.payouts = payouts;
}

AbstractPool.prototype.setWillpays = function( willpays )
{
	this.willpays = willpays;
}

AbstractPool.prototype.setIsBoxBet = function()
{
	this.is_unboxable = true;
}

AbstractPool.prototype.setBoxable = function( is_boxable )
{
	this.is_boxable = is_boxable;
}

AbstractPool.prototype.setUnboxable = function( is_unboxable )
{
	this.is_unboxable = is_unboxable;
}

AbstractPool.prototype.setBoxPoolOid = function( pool_oid )
{
	this.box_pool_oid = pool_oid;
}

AbstractPool.prototype.setUnboxPoolOid = function( pool_oid )
{
	this.unbox_pool_oid = pool_oid;
}

AbstractPool.prototype.setUseSiteCurrency = function( pool_oid )
{
	this.use_site_currency = pool_oid;
}

AbstractPool.prototype.setHasFavourite = function( has_favourite )
{
	this.has_favourite = has_favourite;
}

AbstractPool.prototype.getPoolCode = function()
{
	return this.pool_code;
}

AbstractPool.prototype.getPoolOid = function()
{
	return this.pool_oid;
}

AbstractPool.prototype.getType = function()
{
	return this.type;
}

AbstractPool.prototype.getStatus = function()
{
	return this.status;
}

AbstractPool.prototype.getNumLegs = function()
{
	return this.num_legs;
}

AbstractPool.prototype.getLegs = function()
{
	return this.legs;
}

AbstractPool.prototype.getLeg = function( sequence_number )
{
	return this.legs[sequence_number];
}

AbstractPool.prototype.isSingleLeg = function()
{
	if( this.is_multi_race ) return false;
	return this.num_legs == 1;
}

AbstractPool.prototype.isMultiRace = function()
{
	return this.is_multi_race;
}

AbstractPool.prototype.isMultiLegSingleRace = function()
{
	if( this.is_multi_race ) return false;
	return this.num_legs > 1;
}

AbstractPool.prototype.getMaxRunners = function()
{
	return this.max_runners;
}

AbstractPool.prototype.requiresMultipleMinStake = function()
{
	return this.requires_mult_min_stake;
}

AbstractPool.prototype.getMinimumStake = function()
{
	return this.min_stake;
}

AbstractPool.prototype.getAbsoluteMinimumStake = function()
{
	var min = this.min_stake;
	for( var fn in this.min_stakers )
	{
		min = fn( min );
	}
	return min;
}

AbstractPool.prototype.getMaximumStake = function()
{
	return this.max_stake;
}

AbstractPool.prototype.getAbsoluteMaximumStake = function()
{
	var max = this.max_stake;
	for( var fn in this.max_stakers )
	{
		max = fn( max );
	}
	return max;
}

AbstractPool.prototype.allowsPennyBetting = function()
{
	return this.allows_penny_betting;
}

AbstractPool.prototype.getPennyMultiple = function()
{
	return this.penny_multiple;
}

AbstractPool.prototype.addSelectionValidator = function( fn )
{
	this.selection_validators( this.selection_validators.length ) = fn;
}

AbstractPool.prototype.addMinimumStakeCalculator = function( fn )
{
	this.min_stakers( this.min_stakers.length ) = fn;
}

AbstractPool.prototype.addMaximumStakeCalculator = function( fn )
{
	this.max_stakers( this.max_stakers.length ) = fn;
}

AbstractPool.prototype.getNumCombos = function( bet )
{
	if( ! this._getNumCombos )
	{
		Common.createAlert( "Pool: missing _getNumCombos" );
	}
	if( ! bet.allLegsSelected() )
	{
		do_debug( "AbstractPool::getNumCombos: not all legs selected" );
		return 0;
	}
	return this._getNumCombos( bet );
}

AbstractPool.prototype.validateStake = function( bet )
{
	var stake = bet.getStake();
	if( stake == 0 ) return Val.NO_STAKE;
	if( stake < this.min_stake ) return Val.STAKE_TOO_SMALL;
	if( this.require_mult_min_stake && ! Common.isMultipleOf( stake, this.min_stake ) )
	{
		return Val.NOT_A_MULTIPLE_OF_MIN_STAKE;
	}
	if( this.max_stake > 0 && stake > this.max_stake ) 
	{
		return Val.STAKE_TOO_LARGE;
	}
	var st = stake/this.penny_multiple;
	if( ! this.allows_penny_betting && ( st != floor(st) ) )
	{
		return Val.PENNY_BETTING_NOT_ALLOWED;
	}

	return Val.VALID;
}

AbstractPool.prototype.validateSelection = function( bet )
{
	do_debug( "AbstractPool::validateSelection()" );
	// All legs must be filled out
	if( ! bet.allLegsSelected() )
	{
		do_debug( "AbstractPool::not all legs selected" );
		return Val.INCOMPLETE_LEGS;
	}

	for( var fn in this.selection_validators )
	{
		var result = fn(bet);
		if( result != Val.VALID ) return result;
	}

	return Val.VALID;
}

AbstractPool.prototype.costBet = function( bet )
{
	return this.getNumCombos( bet ) * bet.stake;
}

AbstractPool.prototype.isBoxBet = function()
{
	return this.is_unboxable;
}

AbstractPool.prototype.isBoxable = function()
{
	return this.is_boxable;
}

AbstractPool.prototype.isUnboxable = function()
{
	return this.is_unboxable;
}

AbstractPool.prototype.getBoxPoolOid = function()
{
	return this.box_pool_oid;
}

AbstractPool.prototype.getUnboxPoolOid = function()
{
	return this.unbox_pool_oid;
}

AbstractPool.prototype.getUseSiteCurrency = function()
{
	return this.use_site_currency;
}

AbstractPool.prototype.getHasFavourite = function()
{
	return this.has_favourite;
}

AbstractPool.prototype.getBoxPool = function()
{
	if( this.box_pool_oid < 0 ) return null;
	var card = this.legs[1].race.card;
	if( ! card ) return null;
	return card.getPool( this.box_pool_oid );
}

AbstractPool.prototype.getUnboxPool = function()
{
	if( this.unbox_pool_oid < 0 ) return null;
	var card = this.legs[1].race.card;
	if( ! card ) return null;
	return card.getPool( this.unbox_pool_oid );
}
function Bet( pool )
{
	this.pool = pool;
	this.stake = 0;
	this.bet_result = -1;
	this.tsn = '';
	this.cost = 0;
	this.is_valid = Val.INIT;

	this.selections = new Array();

	if( this.pool )
	{
		var legs = pool.getLegs();
		for( var leg_id in legs )
		{
			var leg = legs[leg_id];
			this.selections[leg_id] = new Selection(this, leg);
		}
	}

	AbstractNotifier( this, "Bet" );
}

Bet.SELECTION_CHANGE = "S";
Bet.STAKE_CHANGE = "T";
Bet.BOX_CHANGE = "B";

Bet.prototype.destroy = function()
{
	for( var s in this.selections )
	{
		this.selections[s].destroy();
	}
}

Bet.prototype.clone = function()
{
	var cb = new Bet( this.pool );
	cb.stake = this.stake;
	cb.cost = this.cost;
	cb.is_valid = this.is_valid;
	
	for( s in this.selections )
	{
		var sl = this.selections[s];
		var cbs = cb.selections[s];
		cbs.selected_runners = sl.selected_runners;
	}	
	return cb;
}

Bet.prototype.setStake = function( stake )
{
	do_debug( "Bet::setStake( " + stake + " )" );	
	this.stake = stake;
	if( this.stake < 1 ) this.stake = this.stake.toFixed(2);
	this.validate();
	this.notifyBetListener( Bet.STAKE_CHANGE );
}

Bet.prototype.setPool = function( pool )
{
	this.pool = pool;
	this.is_valid = Val.INIT;
	this.selections = new Array();
	
	var legs = this.pool.getLegs();
	for( var leg_id in legs )
	{
		var leg = legs[leg_id];
		this.selections[leg_id] = new Selection(this, leg);
	}
}

Bet.prototype.setBetResult = function( result_code, result_text )
{
	this.result_code = result_code;
	this.result_text = result_text;
}

Bet.prototype.setTSN = function( tsn )
{
	this.tsn = tsn;
}

Bet.prototype.getBetResultCode = function()
{
	return this.result_code;
}

Bet.prototype.getBetResultText = function()
{
	return this.result_text;
}

Bet.prototype.getTSN = function()
{
	return this.tsn;
}

Bet.prototype.isComplete = function()
{
	return this.is_valid == Val.VALID;
}

Bet.prototype.validate = function()
{
	do_debug( "Bet::validate()" );
	this.result_text = "";
	this.result_code = "";
	this.tsn = "";
	if( !this.pool ) return;
	this.costBet();
	var result = this.pool.validateStake( this );
	if( result == Val.VALID )
	{
		result = this.pool.validateSelection( this );
	}
	if( result == Val.VALID )
	{
		if( this.cost == 0 )
		{
			result = Val.ZERO_COST;
		}
	}
	do_debug( "Bet::validate: result=" + result + ", cost=" + this.cost );
	this.is_valid = result;
}

Bet.prototype.costBet = function()
{
	this.cost = this.pool.costBet( this );
	if( this.stake < 1 ) this.cost = this.cost.toFixed(2);
}

Bet.prototype.getCost = function()
{
	return this.cost;
}

Bet.prototype.allLegsSelected = function()
{
	var sel_legs = 0;
	for( var sel in this.selections )
	{
		if( this.numSelectedRunners( sel ) == 0 ) return false;
		sel_legs++;
	}
	return sel_legs == this.pool.getNumLegs();
}

Bet.prototype.numSelectedRunners = function( sequence_number )
{
	var sel = this.selections[sequence_number];
	if( ! sel ) return 0;
	var i = 0;
	for( var q in sel.selected_runners ) i++;
	return i;
}

Bet.prototype.getSelection = function( sequence_number )
{
	return this.selections[sequence_number];
}

Bet.prototype.getSelectionAsBitmap = function( sequence_number )
{
	var s = this.selections[sequence_number];
	if( s == null ) return 0;
	return s.getSelectionAsBitmap();
}

Bet.prototype.getSelectionAsString = function()
{
	var race = this.pool.getLeg(1).race;
	var text = "";
	var first_leg = true;
	var is_empty = true;
	var num_legs = this.pool.getNumLegs();
	for( var sel_id = 1 ; sel_id <= num_legs ; sel_id ++ )
	//for( var sel_id in this.selections )
	{
		if( ! first_leg ) text += "/";
		var sel = this.selections[sel_id];
		if( sel == null || sel.hasSelectedRunners() )
		{
			is_empty = false;
		}
		var first_runner = true;
		if( sel ) for( var runner in sel.selected_runners )
		{
			if( ! first_runner ) text += ",";
			text += ""+runner;
			first_runner = false;
		}
		first_leg = false;
	}
	if( is_empty ) return "";
	return text;
}

Bet.prototype.isBoxable = function()
{
	return this.pool.isBoxable();
}

Bet.prototype.isUnboxable = function()
{
	return this.pool.isUnboxable();
}

Bet.prototype.box = function()
{
	if( ! this.isBoxable() ) return;
	var pool = this.pool.getBoxPool();
	if( ! pool ) return;
	this.pool = pool;
	var new_selections = new Array();
	var legs = pool.getLegs();
	var leg = legs[1];
	new_selections[1] = new Selection( this, leg );
	for( var sel_id in this.selections )
	{
		for( var bi in this.selections[sel_id].selected_runners )
		{
			new_selections[1].selectRunner( bi );
		}
	}
	for( var leg_id in legs )
	{
		var leg = legs[leg_id];
		this.selections[leg_id].destroy();
	}

	this.selections = new_selections;
	this.validate();
	this.notifyBetListener( Bet.BOX_CHANGE );
}

Bet.prototype.unbox = function()
{
	if( ! this.isUnboxable() ) return;
	var pool = this.pool.getUnboxPool();
	if( ! pool ) return;
	this.pool = pool;
	var new_selections = new Array();
	var legs = pool.getLegs();
	for( var leg_id in legs )
	{
		var leg = legs[leg_id];
		new_selections[leg_id] = new Selection(this, leg);
	}
	for( var sel_id in this.selections )
	{
		for( var bi in this.selections[sel_id].selected_runners )
		{
			new_selections[1].selectRunner( bi );
		}
		this.selections[sel_id].destroy();
	}

	this.selections = new_selections;
	this.validate();
	this.notifyBetListener( Bet.BOX_CHANGE );
}

function Selection( bet, leg )
{
	this.bet = bet;
	this.leg = leg;
	this.selected_runners = new Array();
	this.leg.race.registerRaceChangeListener( this );

	AbstractNotifier( this, "Selection" );
}

Selection.prototype.destroy = function()
{
	this.leg.race.deregisterRaceChangeListener( this );
}

Selection.SELECTION_CHANGE = "SC"; 
Selection.prototype.selectRunner = function( betting_interest_number )
{
	do_debug( "Selection::selectRunner( " + betting_interest_number + " )" );

	// make sure the runner exists and is not scratched
	var rlist = this.leg.getRunners( betting_interest_number );
	if( rlist.length == 0 )
	{
		do_debug( "No such runner: " + betting_interest_number );
		return;
	}
	var found_unscratched_runner = false;
	for( var r in rlist )
	{
		var runner = rlist[r];
		if( ! runner.isScratched() )
		{
			found_unscratched_runner = true;
			this.selected_runners[betting_interest_number] = true;
			break;
		}
	}
	if( ! found_unscratched_runner )
	{
		do_debug( "Selection::selectRunner: will not select a scratched runner" );
		delete this.selected_runners[betting_interest_number];
	}

	this.notifySelectionListener( Selection.SELECTION_CHANGE );
	this.bet.validate();
	this.bet.notifyBetListener( Bet.SELECTION_CHANGE );
}

Selection.prototype.deselectRunner = function( betting_interest_number )
{
	do_debug( "Selection::deselectRunner( " + betting_interest_number + " )" );
	delete this.selected_runners[betting_interest_number];
	this.notifySelectionListener( Selection.SELECTION_CHANGE );
	this.bet.validate();
	this.bet.notifyBetListener( Bet.SELECTION_CHANGE );
}

Selection.prototype.toggleSelection = function( betting_interest_number )
{
	do_debug( "Selection::toggleSelection( " + betting_interest_number + " )" );
	if( ! this.selected_runners[betting_interest_number] )
	{
		this.selectRunner( betting_interest_number );
	}
	else
	{
		this.deselectRunner( betting_interest_number );
	}
}

Selection.prototype.isSelected = function( betting_interest_number )
{
	return this.selected_runners[betting_interest_number];
}

Selection.prototype.handleRaceChangeEvent = function( race, event_type )
{
	if( ! race == this.leg.race ) return;
	if( event_type == Race.RUNNER_CHANGE )
	{
		// validate all runners - remove any scratched selections
		for( var betting_interest_number in this.selected_runners )
		{
			if( this.selected_runners[betting_interest_number] )
			{
				this.selectRunner( betting_interest_number );
			}
		}
	}
}

Selection.prototype.getSelectionAsBitmap = function()
{
	if( this.selected_runners == null ) return 0;
	var bm = new Bitmap();
	for( var bin in this.selected_runners )
	{
		bm.set( bin );
	}
	return bm;
}

Selection.prototype.hasSelectedRunners = function()
{
	for( var bin in this.selected_runners ) return true;
	return false;
}

Selection.prototype.selectAll = function()
{
	do_debug( "Selection::selectAll()" );
	for( var ri in this.leg.race.runners )
	{
		var runner = this.leg.race.runners[ri];
		this.selectRunner( runner.betting_interest_number );
	}
}

Selection.prototype.deselectAll = function()
{
	for( var ri in this.leg.race.runners )
	{
		var runner = this.leg.race.runners[ri];
		this.deselectRunner( runner.betting_interest_number );
	}
}

Selection.prototype.getSelectionAsString = function()
{
	var text = "";
	var first_runner = true;
	for( var runner in this.selected_runners )
	{
		if( ! first_runner ) text += ",";
		text += ""+runner;
		first_runner = false;
	}

	return text;
}

function Bitmap( max_bits )
{
	this.max_bits = max_bits || Bitmap.MAX_BITS;
	this.bms = new Array();
	this.num_words = Math.ceil(this.max_bits/Bitmap.BITS_PER_WORD);
	for( var i = 0 ; i < this.num_words ; i++ )
	{
		this.bms[i] = 0;
	}
}

Bitmap.MAX_BITS = 128;
Bitmap.BITS_PER_WORD = 32;

Bitmap.prototype.numWords = function()
{
	return this.num_words;
}

Bitmap.prototype.calcWord = function( position )
{
	return Math.floor((position-1)/Bitmap.BITS_PER_WORD);
}

Bitmap.prototype.calcPositionInWord = function( position )
{
	return (position-1) % Bitmap.BITS_PER_WORD;
}

Bitmap.prototype.set = function( position )
{
	var word = this.calcWord( position );
	var pos = this.calcPositionInWord( position );
	this.bms[word] |= (1<<pos);
	return this;
}

Bitmap.prototype.unset = function( position )
{
	var word = this.calcWord( position );
	var pos = this.calcPositionInWord( position, word );
	this.bms[word] &= ~(1<<pos);
	return this;
}

Bitmap.prototype.isSet = function( position )
{
	var word = this.calcWord( position );
	var pos = this.calcPositionInWord( position, word );
	var set = this.bms[word] & (1<<pos);
	return set != 0;
}

Bitmap.prototype.count = function()
{
	var count = 0;
	for( var i=0, len = this.bms.length; i<len; i++ )
	{
		for( var pos = 0 ; pos < Bitmap.BITS_PER_WORD ; pos++ )
		{
			count += ( ( this.bms[i] & (1<<pos) ) != 0 );
		}
	}
	return count;
}

Bitmap.prototype.and = function( bitmap )
{
	var newmap = new Bitmap();
	for( var i = 0 ; i < this.num_words ; i++ )
	{
		newmap.bms[i] = ( this.bms[i] & bitmap.bms[i] );
	}
	return newmap;
}

Bitmap.prototype.or = function( bitmap )
{
	var newmap = new Bitmap();
	for( var i = 0 ; i < this.num_words ; i++ )
	{
		newmap.bms[i] = ( this.bms[i] | bitmap.bms[i] );
	}
	return newmap;
}
function Card( card_oid )
{
	this.card_oid = card_oid;
	this.card_name = "";
	this.card_date = "";
	this.mtp = "";
	this.post_time = "";
	this.status = Card.BETTING_UNAVAILABLE;
	this.current_race = "";
	this.current_race_status = "";
	this.notes = "";
	this.weather = null;

	this.races = new Object();
	this.pools = new Object();

	this.timestamp = new Date(0);

	this.card_change_listeners = new Array();

	AbstractNotifier( this, "CardChange" );
}

Card.BETTING_UNAVAILABLE = "U";
Card.OPEN = "O";
Card.ADMIN_BETTING_UNAVAILABLE = "AB";
Card.CLOSED = "C";
Card.ADMIN_CLOSED = "AC";

Card.CHANGE = "CC";
Card.STATUS_CHANGE = "SC";
Card.MTP_CHANGE = "MC";
Card.CURRENT_RACE_CHANGE = "RC";
Card.RACE_LIST_CHANGE = "RLC";
Card.RACE_DELETE = "RD";
Card.RACE_ADD = "RA";

Card.prototype.isComplete = function()
{
	return this.races.length > 0;
}

Card.prototype.setStatus = function( status )
{
	this.status = status;
	this.notifyCardChangeListener( Card.STATUS_CHANGE );
}

Card.prototype.setMTP = function( mtp )
{
	this.mtp = mtp;
	this.notifyCardChangeListener( Card.MTP_CHANGE );
}

Card.prototype.setCurrentRace = function( current_race )
{
	this.current_race = current_race;
	this.notifyCardChangeListener( Card.CURRENT_RACE_CHANGE );
}

Card.prototype.getRace = function( tote_race_number )
{
	return this.races[tote_race_number];
}

Card.prototype.getPool = function( pool_oid )
{
	return this.pools[pool_oid];
}
function Common() {};

function _createAlert( message )
{
	alert( message );
}

function _createConfirm( message )
{
	return confirm( message );
}

Common.createAlert = function( message )
{
	_createAlert( message );
	if( this )
	{
		this.invalid = true;
	}
}

Common.createConfirm = function( message )
{
	_createConfirm( message );
	if( this )
	{
		this.invalid = true;
	}
}

Common.isMultipleOf = function( value, multiplee )
{
	return (value % multiplee) == 0;
}

Common.zeroPadNumber = function( number, length )
{
	var s = "".concat(number);
	while( s.length < length ) s = "0".concat(s);
	return s;
}

Common.createDate = function( date_str )
{
	var arr = date_str.match( /(\d\d\d\d)(\d\d)(\d\d)\s+(\d\d):(\d\d):(\d\d).(\d+)/ );
	if( ! arr || arr.length != 8 ) return new Date(0);
	return new Date( Date.UTC(arr[1],arr[2],arr[3],arr[4],arr[5],arr[6],arr[7]) );
}

Common.formatDate = function( date )
{
	if( ! date ) date = new Date(1970,0,1,0,0,0,0);
	var ds = "" + date.getUTCFullYear();
	ds = ds.concat( Common.zeroPadNumber( date.getUTCMonth(), 2 ) );
	ds = ds.concat( Common.zeroPadNumber( date.getUTCDate(), 2 ) );
	ds = ds.concat( " " );
	ds = ds.concat( Common.zeroPadNumber( date.getUTCHours(), 2 ), ":" );
	ds = ds.concat( Common.zeroPadNumber( date.getUTCMinutes(), 2 ), ":" );
	ds = ds.concat( Common.zeroPadNumber( date.getUTCSeconds(), 2 ) );
	ds = ds.concat( "." );
	ds = ds.concat( date.getUTCMilliseconds() );

	return ds;
}

Common.clearHTML = function( html_el )
{
	if( ! html_el || ! html_el.firstChild ) return;
	while( html_el.firstChild != null )
	{
		html_el.removeChild( html_el.firstChild );
	}
}

Common.setHTML = function( html_el, text )
{
	if( ! html_el ) return;
	Common.clearHTML( html_el );
	html_el.appendChild( document.createTextNode( text ) );
}

function $( html_id, parent_id )
{
	var el = parent_id ? document.getElementById( parent_id ) : document;
	if( ! el ) return null;
	return el.getElementById( html_id );
}

function setId( obj, html_id )
{
	if( html_id == null ) return false;

	if( typeof html_id == "object" )
	{
		obj["html_el"] = html_id;
		obj["html_id"] = html_id.id;
	}
	else
	{
		obj["html_id"] = html_id;
		obj["html_el"] = document.getElementById( html_id );
	}
}

function sortNumber(a, b)
{
	return a - b
}


function formatSingleLeg( selection_string )
{
	var selections = new String(selection_string);
	var tok = selections.split("," );
	var last = -12;
	var last_digits = 0;
	var dash_on = false;
	var s = '';
	tok.sort(sortNumber);
	for( var i=0; i<tok.length; i++ )
	{
		var s_runner = tok[i];
		var runner = parseInt( s_runner );

		if( last <= 0 )
		{
			// ignore
		}
		else if( runner - last == 1 )
		{
			if( dash_on )
			{
				var size = s.length - 1 - last_digits;
				s = s.slice( 0 , size );
			}
			s += "-";
			dash_on = true;
		}
		else
		{
			s += "," ;
			dash_on = false;
		}
		s += s_runner;
		last = runner;
		last_digits = s_runner.length;
	}
	return s;
}


function formatSelection( selection_string )
{
	var selection = new String(selection_string);
	if (selection.indexOf("/")==-1)
	{
		return formatSingleLeg( selection );
	}
	else
	{	
		var s = "";
		var tok = selection.split("/" );
		for( var i=0; i<tok.length; i++ )
		{
			var s_runners = tok[i];
			s += formatSingleLeg( s_runners ) + "/";
		}

		if ( s.lastIndexOf("/")==s.length-1 )
		{
			s = s.substring( 0 , s.lastIndexOf("/") );
		}

		return s;
	}
}

function formatCurrency( currency_code )
{
	if( ! currency_code ) return;

	switch( currency_code )
	{
		case "EUR" :
			return "&#8364;";
		case "USD" :
			return "$";
	}
}
function Debug( html_el )
{
	this.html_el = html_el;
	this.stopped = false;

	if( this.html_el )
	{
		var h = '<table width="100%" cellspacing="0" cellpadding="2">';
		h += '<tr><td>Debug Output</td>';
		h += '<td>';
		h += '<input type="button" id="debug_button" value="Clear"/>';
		h += '<input type="button" id="stop_button" value="Stop"/>';
		h += '&nbsp;&nbsp;&nbsp;Regex: <input type="text" id="filter" value=".*" size="30"/>';
		h += '</td></tr></table>';
		h += '<div style="overflow: auto; width: 650px; height: 350px; border: 1px gray solid; padding:2px; margin: 2px">';
		h += '<table id="debug_text" width="100%" cellspacing="0" cellpadding="2"></table></div>';
		this.html_el.innerHTML = h;

		this.debug_button_el = this.html_el.getElementsByTagName( "input" )[0];
		this.stop_button_el = this.html_el.getElementsByTagName( "input" )[1];
		this.debug_text_el = this.html_el.getElementsByTagName( "table" )[1];
		this.filter_el = this.html_el.getElementsByTagName( "input" )[2];

		var me = this;
		this.debug_button_el.onclick = function() { me.doClick(); };
		this.stop_button_el.onclick = function() { me.toggleStop(); };
	}
	else alert( "No html_el for : " + html_id );
}

Debug.prototype.doClick = function()
{
	if( ! this.debug_text_el ) return;
	while( this.debug_text_el.firstChild ) 
	{
		this.debug_text_el.removeChild( this.debug_text_el.firstChild );
	}
};

Debug.prototype.toggleStop = function()
{
	if( this.stopped )
	{
		this.stopped = false;
		this.stop_button_el.value = "Stop";
	}
	else
	{
		this.stopped = true;
		this.stop_button_el.value = "Start";
	}
}

Debug.prototype.print = function( message )
{
	if( ! this.debug_text_el || debug_popup.closed || this.stopped ) return;
	if( ! message.match(new RegExp(this.filter_el.value) ) ) return;
	var td = this.debug_text_el.insertRow(0).insertCell(0);
	var dt = new Date();
	var df = dt.getFullYear() + "-" + dt.getMonth() + "-" + dt.getDay();
	df += " " + dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds() + "." + dt.getMilliseconds();
	td.innerHTML = df + " " + message;
};

window.debug_popup = null;
window.debug_widget = null;

function create_debug()
{
	window.debug_popup = window.open( "", "DebugOutput", 'height=420,width=670');
	var body = window.debug_popup.document.body;
	if( body )
	{
		body.innerHTML = '<div id="debug"/>';
		window.debug_widget = new Debug( body.getElementsByTagName( "div" )[0] );
	}
}

function do_debug( message )
{
	if( ! window.debug_widget ) return;
	window.debug_widget.print( message );
}

function debug_object( obj, location )
{
	var q = (location ? location : "Debug Object") + ": ";
	var first = true;
	for( var x in obj )
	{
		if( ! first ) q += ", ";
		q += x;
		first = false;
	}
	do_debug( q );
}

function Dividend( pool, type )
{
	if( ! pool ) return;
	this.pool = pool;
	this.type = type;
	this.pool_total = 0;
}

function SingleLegDividend( pool )
{
	this.base = Dividend;
	this.base(pool, "single-leg");
	this.entries = new Array();
}
SingleLegDividend.prototype = new Dividend();

SingleLegDividend.prototype.addRunner = function( runner )
{
	this.entries[runner.betting_interest_number] = runner;
}

SingleLegDividend.prototype.getRunner = function( betting_interest_number )
{
	return this.entries[betting_interest_number];
}

function SingleLegDividendRunner( parent )
{
	this.parent = parent;
	this.betting_interest_number = 0;
	this.pp = 0;
	this.hi_pp = 0;
	this.lo_pp = 0;
	this.odds = "-";
	this.total = 0;
}

function TwoLegDividend( pool )
{
	this.base = Dividend;
	this.base(pool, "two-leg");
	this.entries = new Array();
}
TwoLegDividend.prototype = new Dividend();

TwoLegDividend.prototype.addRunner = function( runner )
{
	this.entries[runner.betting_interest_number] = runner;
}

TwoLegDividend.prototype.getRunner = function( betting_interest_number )
{
	return this.entries[betting_interest_number];
}

function TwoLegDividendRunner( parent )
{
	this.parent = parent;
	this.betting_interest_number = 0;
	this.entries = new Array();
}

TwoLegDividendRunner.prototype.addRunner = function( runner )
{
	this.entries[runner.betting_interest_number] = runner;
}

TwoLegDividendRunner.prototype.getRunner = function( betting_interest_number )
{
	return this.entries[betting_interest_number];
}
function LRU( max_length )
{
	this.head = null;
	this.tail = null;
	this.size = 0;
	this.values = new Array();
	this.max_length = max_length || 1;
}

LRU.prototype.getSize = function()
{
	return this.size;
}

LRU.prototype.setMaxLength = function( max_length )
{
	this.max_length = max_length;
	this.resizeQueue();
}

LRU.prototype.getValues = function()
{
	var values = new Array();
	var e = this.head;
	while( e != null )
	{
		values.push( e.value );
		e = e.next;
	}
	return values;
}

LRU.prototype.add = function( value )
{
	var e = this.getEntry( value );
	if( e != null )
	{
		// entry exists, remove it
		this.removeEntry( e );
	}

	// add the entry to the front
	e = new LRUItem( value, null, this.head );
	if( this.head != null )
	{
		this.head.prev = e;
	}
	this.head = e;
	if( this.tail == null )
	{
		this.tail = e;
	}
	this.values[value] = e;
	this.size++;

	// if the queue is too large, remove the oldest
	this.resizeQueue();
}

LRU.prototype.remove = function( value )
{
	var e = this.getEntry( value );
	if( e != null )
	{
		this.removeEntry( e );
	}
}

LRU.prototype.getEntry = function( value )
{
	var e = this.values[value];
	return e;
}

LRU.prototype.removeEntry = function( entry )
{
	var e = entry;
	if( e.prev != null )
	{
		e.prev.next = e.next;
	}
	if( e.next != null )
	{
		e.next.prev = e.prev;
	}
	if( this.head == e )
	{
		this.head = e.next;
	}
	if( this.tail == e )
	{
		this.tail = e.prev;
	}
	delete this.values[e.value];
	this.size--;
}

LRU.prototype.resizeQueue = function()
{
	while( this.size > this.max_length )
	{
		this.removeEntry( this.tail );
	}
}

function LRUItem( value, prev, next )
{
	this.value = value;
	this.prev = prev;
	this.next = next;
}
function Leg( pool, race, sequence_number )
{
	this.pool = pool;
	this.race = race;
	this.sequence_number = sequence_number;
}

Leg.prototype.getRunners = function( betting_interest_number )
{
	return this.race.getRunners( betting_interest_number );
}
function Payout( pool )
{
	this.pool = pool;
	this.stake = 0;
	this.amount = 0;
	this.num_to_win = 0;
	this.betting_interest_list = "";
}
function Polly( poll_interval, callback_obj, callback_function, request_url, params )
{
	this.polling_enabled = false;
	this.poll_interval = poll_interval || 100000;
	this.callback_obj = callback_obj;
	this.callback_function = callback_function;
	this.request_url = request_url;
	this.params = params;
}

Polly.prototype.setPollInterval = function( interval )
{
	this.poll_interval = interval;
}

Polly.prototype.setCallback = function( callback_obj, callback_function )
{
	this.callback_obj = callback_obj;
	this.callback_function = callback_function;
}

Polly.prototype.setRequestURL = function( request_url )
{
	this.request_url = request_url;
}

Polly.prototype.constructParameterList = function( params )
{
	var s = "";
	if( params == null ) return s;
	for( var param in params )
	{
		if( s.length > 0 )
		{
			s += "&";
		}
		s += param + "=" + params[param];
	}
	return s;
}

Polly.prototype.setRequestParameters = function( params )
{
	this.params = params;
}

Polly.prototype.startPolling = function()
{
	do_debug( "Polly::startPolling()" );
	this.polling_enabled = true;
	this.performRequest();
}

Polly.prototype.stopPolling = function()
{
	this.polling_enabled = false;
}

Polly.prototype.adhocRequest = function( block, params )
{
	var temp = this.polling_enabled;
	if( temp ) this.stopPolling();	
	this.performRequest( block, params );
	
	if( temp ) 
	{
		var me = this;
	    parent.setTimeout( function() { me.startPolling(); }, this.poll_interval + 2000 );
	}
}

Polly.prototype.performRequest = function ( block, params )
{
	var post_var = this.constructParameterList( params == null ? this.params : params );	

	do_debug( "Polly::performRequest()" );

	var asynch = block ? false : true;

	do_debug( "Polly:performRequest:asynch=" + asynch + ",polling_enabled=" + this.polling_enabled);
	var http_request;

	if( window.XMLHttpRequest ) // Mozillas
	{
		http_request = new XMLHttpRequest();
	}
	else // IE
	{
		http_request = new ActiveXObject( "Microsoft.XMLHTTP" );
	}

	if( asynch )
	{
		var o = this.callback_obj;
		var callback_fn = this.callback_function;
		http_request.onreadystatechange = function()
		{
			if( http_request && http_request.readyState == 4 )
			{
				if( http_request.status == 200 )
				{
					o[callback_fn]( http_request.responseXML );
				}
				else
				{
					do_debug( "Polly::servletRequest: unexpected response from server: " + http_request.status );
				}
			}
			else
			{
				//do_debug( "Polly::servletRequest: Request Status: " + http_request.readyState );
			}
		}
	}

	http_request.open( 'POST', this.request_url, asynch );
	http_request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
	http_request.send( post_var );
	if( ! asynch )
	{
		this.callback_obj[this.callback_function]( http_request.responseXML );
	}

	if( this.polling_enabled )
	{
		var me = this;
		parent.setTimeout( function() { me.performRequest(); }, this.poll_interval );
	}
}
function PoolFactory()
{
	this.sgr_pool_factory = new SGRPoolFactory();
}

PoolFactory.prototype.constructPool = function( xml_pool, cards )
{
	var pool;
	var type = xml_pool.getAttribute( "type" );
	do_debug( "PoolFactory::constructPool( " + type + " )" );
	if( type == "sgr" || type == "hri" || type == "uk" )
	{
		pool = this.sgr_pool_factory.constructPoolObject( xml_pool );
	}
	else
	{
		do_debug( "PoolFactory::unhandledPoolType( " + type + " )" );
		return;
	}

	pool.setPoolOid( xml_pool.getAttribute( "pool-oid" ) );

	var xml_legs = xml_pool.getElementsByTagName( "leg" );
	for( var i = 0 ; i < xml_legs.length ; i++ )
	{
		var xml_leg = xml_legs[i];
		var sequence_number = xml_leg.getAttribute( "sequence" );
		var card_oid = xml_leg.getAttribute( "card-oid" );
		var tote_race_number = xml_leg.getAttribute( "tote-race-number" );

		var card = cards[card_oid];
		var race = card.getRace( tote_race_number );

		pool.setLeg( sequence_number, race );
	}

	this.updatePool( pool, xml_pool );

	return pool;
}

PoolFactory.prototype.updatePool = function( pool, xml_pool )
{
	pool.setType( xml_pool.getAttribute( "type" ) );
	pool.setStatus( xml_pool.getAttribute( "status" ) );
	pool.setCurrency( xml_pool.getAttribute( "currency" ), xml_pool.getAttribute( "penny-multiple" ) );
	pool.setMinimumStake( xml_pool.getAttribute( "min-stake" ) );
	pool.setMaximumStake( xml_pool.getAttribute( "max-stake" ) );
	pool.setAllowsPennyBetting( xml_pool.getAttribute( "allows-penny-betting" ) );
	pool.setRequiresMultipleMinStake( xml_pool.getAttribute( "requires-multiple-min-stake" ) );
	pool.setBoxable( xml_pool.getAttribute( "boxable" ) == "true" );
	pool.setUnboxable( xml_pool.getAttribute( "unboxable" ) == "true" );
	pool.setBoxPoolOid( xml_pool.getAttribute( "box-pool-oid" ) || -1 );
	pool.setUnboxPoolOid( xml_pool.getAttribute( "unbox-pool-oid" ) || -1 );
	pool.setUseSiteCurrency( xml_pool.getAttribute( "use-site-currency" ) || "false" );
	
	// we display a favourite button if one of the following is true:
	// 1) permutations contains "FAV"
	// 2) redirect-favourite flag is true (redirection will be done in the core)
	var perms = xml_pool.getAttribute( "permutations" ).split( "," );
	var fav = false;
	for(i = 0; i < perms.length; i++)
	{
		if( perms[i] == "FAV" )
		{
			fav = true;
		}
	}
	if( !fav ) fav = ( xml_pool.getAttribute( "redirect-favourite" ) == "true" );
	
	pool.setHasFavourite( fav || false );
	
	if( pool.getType() == "sgr" || pool.getType() == "hri" || pool.getType() == "uk")
	{
		this.sgr_pool_factory.updatePool( pool, xml_pool );
	}

	if( pool.getNumLegs() == 1 )
	{
		var xml_dividends = xml_pool.getElementsByTagName( "single-leg-dividends" );
		if( xml_dividends.length > 0 )
		{
			this.constructSingleLegDividend( pool, xml_dividends[0] );
		}
	}
	else
	{
		var xml_dividends = xml_pool.getElementsByTagName( "two-leg-dividends" );
		if( xml_dividends.length > 0 )
		{
			this.constructTwoLegDividend( pool, xml_dividends[0] );
		}
	}

	var xml_payouts = xml_pool.getElementsByTagName( "payout" );
	var payouts = new Array();
	for( var i = 0 ; i < xml_payouts.length ; i++ )
	{
		this.constructPayout( payouts, pool, xml_payouts[i] );
	}
	pool.setPayouts( payouts );

	var xml_willpays = xml_pool.getElementsByTagName( "willpay" );
	var willpays = new Array();
	for( var i = 0 ; i < xml_willpays.length ; i++ )
	{
		this.constructWillpay( willpays, pool, xml_willpays[i] );
	}
	pool.setWillpays( willpays );
}

PoolFactory.prototype.constructSingleLegDividend = function( pool, xml_dividend )
{
	var div = new SingleLegDividend( pool );
	div.pool_total = xml_dividend.getAttribute( "pool-total" );
	var xml_runners = xml_dividend.getElementsByTagName( "runner" );
	for( var i = 0 ; i < xml_runners.length ; i++ )
	{
		var xml_runner = xml_runners[i];
		var divr = new SingleLegDividendRunner( div );
		divr.betting_interest_number = xml_runner.getAttribute( "betting-interest-number" );
		divr.pp = xml_runner.getAttribute( "pp" );
		divr.hi_pp = xml_runner.getAttribute( "hi-pp" );
		divr.lo_pp = xml_runner.getAttribute( "lo-pp" );
		divr.odds = xml_runner.getAttribute( "odds" );
		divr.total = xml_runner.getAttribute( "total" );
		div.addRunner( divr );
	}
	pool.setDividends( div );
}

PoolFactory.prototype.constructTwoLegDividend = function( pool, xml_dividend )
{
	var div = new TwoLegDividend( pool );
	div.pool_total = xml_dividend.getAttribute( "pool-total" );
	var xml_runners_1 = xml_dividend.getElementsByTagName( "runner" );
	for( var i = 0 ; i < xml_runners_1.length ; i++ )
	{
		var xml_runner_1 = xml_runners_1[i];
		var divr1 = new TwoLegDividendRunner( div );
		divr1.betting_interest_number = xml_runner_1.getAttribute( "betting-interest-number" );
		var xml_runners_2 = xml_runner_1.getElementsByTagName( "with" );
		for( var j = 0 ; j < xml_runners_2.length ; j++ )
		{
			var xml_runner_2 = xml_runners_2[j];
			var divr2 = new SingleLegDividendRunner( divr1 );
			divr2.betting_interest_number = xml_runner_2.getAttribute( "betting-interest-number" );
			divr2.pp = xml_runner_2.getAttribute( "pp" );
			divr2.hi_pp = xml_runner_2.getAttribute( "hi-pp" );
			divr2.lo_pp = xml_runner_2.getAttribute( "lo-pp" );
			divr2.odds = xml_runner_2.getAttribute( "odds" );
			divr2.total = xml_runner_2.getAttribute( "total" );
			divr1.addRunner( divr2 );
		}
		div.addRunner( divr1 );
	}
	pool.setDividends( div );
}

PoolFactory.prototype.constructPayout = function( payouts, pool, xml_payout )
{
	var payout = new Payout( pool );
	payout.stake = xml_payout.getAttribute( "stake" );
	payout.amount = xml_payout.getAttribute( "amount" );
	payout.num_to_win = xml_payout.getAttribute( "number-to-win" );
	payout.betting_interest_list = xml_payout.getAttribute( "betting-interest-list" );
	payouts.push( payout );
}

PoolFactory.prototype.constructWillpay = function( willpays, pool, xml_willpay )
{
	var willpay = new Willpay( pool );
	willpay.leg1_betting_interest_list = xml_willpay.getAttribute( "betting-interest-list" );
	var xml_runners = xml_willpay.getElementsByTagName( "with" );
	for( var i = 0 ; i < xml_runners.length ; i++ )
	{
		var xml_runner = xml_runners[i];
		var wpr = new WillpayRunner( willpay );
		wpr.betting_interest_number = xml_runner.getAttribute( "betting-interest-number" );
		wpr.take = xml_runner.getAttribute( "stake" );
		wpr.willpay = xml_runner.getAttribute( "amount" );
		wpr.num_needed = xml_runner.getAttribute( "number-needed" );
		willpay.leg2_runners.push( wpr );
	}
	willpays.push( willpay );
}
function Race( card, tote_race_number, display_race_number )
{
	do_debug( "Race::constructor( " + card.card_oid + ", " + tote_race_number + ", " + display_race_number + " )" );
	this.card = card;
	this.tote_race_number = tote_race_number;
	this.display_race_number = display_race_number;
	this.race_name = "";
	this.post_time = "";
	this.status = Race.BETTING_UNAVAILABLE;
	this.race_description = "";
	this.has_finishers = false;
	this.race_comment = "";
	this.breed_type = "";
	this.race_type = "";
	this.race_type_description = "";
	this.restrictions = "";
	this.age_restriction = "";
	this.sex_restriction = "";
	this.currency = "";
	this.purse = "";
	this.min_claiming_price = "";
	this.max_claiming_price = "";
	this.distance = "";
	this.distance_text = "";
	this.surface = "";
	this.course_type = "";
	this.race_grade = "";
	this.chute_starts = "";
	this.conditions = "";
	this.has_coupled_runners = "";
	this.published_bet_types ="";
	this.num_runners = "";

	this.runners = new Object();
	this.pools = new Object();
	this.poolsMap = new Object();

	this.race_change_listeners = new Array();

	AbstractNotifier( this, "RaceChange" );
}

Race.BETTING_UNAVAILABLE = "U";
Race.ADMIN_BETTING_UNAVAILABLE = "AB";
Race.OPEN = "O";
Race.CLOSED = "C";
Race.OFFICIAL = "F";
Race.SCRATCHED = "S";
//Race.OFF = "F";
Race.ADMIN_CLOSED = "AC";

Race.CHANGE = "RC";
Race.STATUS_CHANGE = "SC";
Race.RUNNER_CHANGE = "UC";

Race.prototype.setStatus = function( status )
{
	this.status = status;
	this.notifyRaceChangeListener( Race.STATUS_CHANGE );
}

Race.prototype.getRunners = function( betting_interest_number )
{
	var rlist = new Array();
	for( var r in this.runners )
	{
		var runner = this.runners[r];
		if( runner.betting_interest_number == betting_interest_number )
		{
			rlist.push( runner );
		}
	}
	return rlist;
}

Race.prototype.getFinishers = function( position )
{
	var flist = new Array();
	for( var r in this.runners )
	{
		var runner = this.runners[r];
		if( runner.finish_position == position )
		{
			flist.push( runner );
		}
	}
	return flist;
}

Race.prototype.getPool = function( pool_code )
{
    /*
	for( var pid in this.pools )
	{
		var pool = this.pools[pid];
		if( pool.getPoolCode() == pool_code ) return pool;
	}
	*/
	return this.poolsMap[pool_code];
}

Race.prototype.addPool = function( pool )
{
	this.pools[pool.getPoolOid()] = pool;
	pool.registerPoolListener( this );

	/* Add pool only if not exists another with the same pool code */
	if(this.poolsMap[pool.getPoolCode()] == null) {
	  this.poolsMap[pool.getPoolCode()] = pool;
	}
	
}

Race.prototype.destroy = function()
{
	for( var pid in this.pools )
	{
		this.pools[pid].deregisterPoolListener( this );
	}
}

Race.prototype.handlePoolEvent = function( pool, event_type )
{
	for( var rid in this.runners )
	{
		this.runners[rid].updatePoolInfo();
	}
}
function SiteContext()
{
	this.card_servlet_url = "";
	this.bet_servlet_url = "";
	this.customer_information_servlet_url = "";
	this.statement_servlet_url = "";
	this.deposit_servlet_url = "";
	this.currency = "";
	this.enable_account = true;
	this.enable_account_details = true;
	this.enable_account_history = true;
	this.enable_account_deposit = true;
	this.enable_account_withdrawal = true;
}
// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
// ===================================================================

// ------------------------------------------------------------------
// These functions use the same 'format' strings as the 
// java.text.SimpleDateFormat class, with minor exceptions.
// The format string consists of the following abbreviations:
// 
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
//              | NNN (abbr.)        |
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Day of Week  | EE (name)          | E (abbr)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
// Examples:
//  "MMM d, y" matches: January 01, 2000
//                      Dec 1, 1900
//                      Nov 20, 00
//  "M/d/yy"   matches: 01/20/00
//                      9/2/00
//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
// ------------------------------------------------------------------

var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x}

// ------------------------------------------------------------------
// isDate ( date_string, format_string )
// Returns true if date string matches format of format string and
// is a valid date. Else returns false.
// It is recommended that you trim whitespace around the value before
// passing it to this function, as whitespace is NOT ignored!
// ------------------------------------------------------------------
function isDate(val,format) {
	var date=getDateFromFormat(val,format);
	if (date==0) { return false; }
	return true;
	}

// -------------------------------------------------------------------
// compareDates(date1,date1format,date2,date2format)
//   Compare two date strings to see which is greater.
//   Returns:
//   1 if date1 is greater than date2
//   0 if date2 is greater than date1 of if they are the same
//  -1 if either of the dates is in an invalid format
// -------------------------------------------------------------------
function compareDates(date1,dateformat1,date2,dateformat2) {
	var d1=getDateFromFormat(date1,dateformat1);
	var d2=getDateFromFormat(date2,dateformat2);
	if (d1==0 || d2==0) {
		return -1;
		}
	else if (d1 > d2) {
		return 1;
		}
	return 0;
	}

// ------------------------------------------------------------------
// formatDate (date_object, format)
// Returns a date in the output format specified.
// The format string uses the same abbreviations as in getDateFromFormat()
// ------------------------------------------------------------------
function formatDate(date,format) {
	format=format+"";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=date.getYear()+"";
	var M=date.getMonth()+1;
	var d=date.getDate();
	var E=date.getDay();
	var H=date.getHours();
	var m=date.getMinutes();
	var s=date.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	// Convert real date parts into formatted versions
	var value=new Object();
	if (y.length < 4) {y=""+(y-0+1900);}
	value["y"]=""+y;
	value["yyyy"]=y;
	value["yy"]=y.substring(2,4);
	value["M"]=M;
	value["MM"]=LZ(M);
	value["MMM"]=MONTH_NAMES[M-1];
	value["NNN"]=MONTH_NAMES[M+11];
	value["d"]=d;
	value["dd"]=LZ(d);
	value["E"]=DAY_NAMES[E+7];
	value["EE"]=DAY_NAMES[E];
	value["H"]=H;
	value["HH"]=LZ(H);
	if (H==0){value["h"]=12;}
	else if (H>12){value["h"]=H-12;}
	else {value["h"]=H;}
	value["hh"]=LZ(value["h"]);
	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
	value["k"]=H+1;
	value["KK"]=LZ(value["K"]);
	value["kk"]=LZ(value["k"]);
	if (H > 11) { value["a"]="PM"; }
	else { value["a"]="AM"; }
	value["m"]=m;
	value["mm"]=LZ(m);
	value["s"]=s;
	value["ss"]=LZ(s);
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
		}
	return result;
	}
	
// ------------------------------------------------------------------
// Utility functions for parsing in getDateFromFormat()
// ------------------------------------------------------------------
function _isInteger(val) {
	var digits="1234567890";
	for (var i=0; i < val.length; i++) {
		if (digits.indexOf(val.charAt(i))==-1) { return false; }
		}
	return true;
	}
function _getInt(str,i,minlength,maxlength) {
	for (var x=maxlength; x>=minlength; x--) {
		var token=str.substring(i,i+x);
		if (token.length < minlength) { return null; }
		if (_isInteger(token)) { return token; }
		}
	return null;
	}
	
// ------------------------------------------------------------------
// getDateFromFormat( date_string , format_string )
//
// This function takes a date string and a format string. It matches
// If the date string matches the format string, it returns the 
// getTime() of the date. If it does not match, it returns 0.
// ------------------------------------------------------------------
function getDateFromFormat(val,format) {
	val=val+"";
	format=format+"";
	var i_val=0;
	var i_format=0;
	var c="";
	var token="";
	var token2="";
	var x,y;
	var now=new Date();
	var year=now.getYear();
	var month=now.getMonth()+1;
	var date=1;
	var hh=now.getHours();
	var mm=now.getMinutes();
	var ss=now.getSeconds();
	var ampm="";
	
	while (i_format < format.length) {
		// Get next token from format string
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		// Extract contents of value based on format token
		if (token=="yyyy" || token=="yy" || token=="y") {
			if (token=="yyyy") { x=4;y=4; }
			if (token=="yy")   { x=2;y=2; }
			if (token=="y")    { x=2;y=4; }
			year=_getInt(val,i_val,x,y);
			if (year==null) { return 0; }
			i_val += year.length;
			if (year.length==2) {
				if (year > 70) { year=1900+(year-0); }
				else { year=2000+(year-0); }
				}
			}
		else if (token=="MMM"||token=="NNN"){
			month=0;
			for (var i=0; i<MONTH_NAMES.length; i++) {
				var month_name=MONTH_NAMES[i];
				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
					if (token=="MMM"||(token=="NNN"&&i>11)) {
						month=i+1;
						if (month>12) { month -= 12; }
						i_val += month_name.length;
						break;
						}
					}
				}
			if ((month < 1)||(month>12)){return 0;}
			}
		else if (token=="EE"||token=="E"){
			for (var i=0; i<DAY_NAMES.length; i++) {
				var day_name=DAY_NAMES[i];
				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
					i_val += day_name.length;
					break;
					}
				}
			}
		else if (token=="MM"||token=="M") {
			month=_getInt(val,i_val,token.length,2);
			if(month==null||(month<1)||(month>12)){return 0;}
			i_val+=month.length;}
		else if (token=="dd"||token=="d") {
			date=_getInt(val,i_val,token.length,2);
			if(date==null||(date<1)||(date>31)){return 0;}
			i_val+=date.length;}
		else if (token=="hh"||token=="h") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>12)){return 0;}
			i_val+=hh.length;}
		else if (token=="HH"||token=="H") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>23)){return 0;}
			i_val+=hh.length;}
		else if (token=="KK"||token=="K") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>11)){return 0;}
			i_val+=hh.length;}
		else if (token=="kk"||token=="k") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>24)){return 0;}
			i_val+=hh.length;hh--;}
		else if (token=="mm"||token=="m") {
			mm=_getInt(val,i_val,token.length,2);
			if(mm==null||(mm<0)||(mm>59)){return 0;}
			i_val+=mm.length;}
		else if (token=="ss"||token=="s") {
			ss=_getInt(val,i_val,token.length,2);
			if(ss==null||(ss<0)||(ss>59)){return 0;}
			i_val+=ss.length;}
		else if (token=="a") {
			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
			else {return 0;}
			i_val+=2;}
		else {
			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
			else {i_val+=token.length;}
			}
		}
	// If there are any trailing characters left in the value, it doesn't match
	if (i_val != val.length) { return 0; }
	// Is date valid for month?
	if (month==2) {
		// Check for leap year
		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
			if (date > 29){ return 0; }
			}
		else { if (date > 28) { return 0; } }
		}
	if ((month==4)||(month==6)||(month==9)||(month==11)) {
		if (date > 30) { return 0; }
		}
	// Correct hours value
	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
	else if (hh>11 && ampm=="AM") { hh-=12; }
	var newdate=new Date(year,month-1,date,hh,mm,ss);
	return newdate.getTime();
	}

// ------------------------------------------------------------------
// parseDate( date_string [, prefer_euro_format] )
//
// This function takes a date string and tries to match it to a
// number of possible date formats to get the value. It will try to
// match against the following international formats, in this order:
// y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
// M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
// d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
// A second argument may be passed to instruct the method to search
// for formats like d/M/y (european format) before M/d/y (American).
// Returns a Date object or null if no patterns match.
// ------------------------------------------------------------------
function parseDate(val) {
	var preferEuro=(arguments.length==2)?arguments[1]:false;
	generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
	var d=null;
	for (var i=0; i<checkList.length; i++) {
		var l=window[checkList[i]];
		for (var j=0; j<l.length; j++) {
			d=getDateFromFormat(val,l[j]);
			if (d!=0) { return new Date(d); }
			}
		}
	return null;
	}
//---------------------------------------------------------------------------
// Exacta Pool
//---------------------------------------------------------------------------
function ExactaPool()
{
	this.base = AbstractPool;
	this.base( "EXA", 2, false );
}
ExactaPool.prototype = new AbstractPool();

ExactaPool.prototype._getNumCombos = function( bet )
{
	var count = bet.numSelectedRunners(1) * bet.numSelectedRunners(2);
	count -= bet.getSelectionAsBitmap(1).and( bet.getSelectionAsBitmap(2) ).count();
	return count;
}

//---------------------------------------------------------------------------
// Exacta Box Pool
//---------------------------------------------------------------------------
function ExactaBoxPool()
{
	this.base = AbstractPool;
	this.base( "EBX", 1, false );
}
ExactaBoxPool.prototype = new AbstractPool();

ExactaBoxPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 2 ) return 0;
	var count = num * (num-1);
	return count;
}
//---------------------------------------------------------------------------
// PickN Pool
//---------------------------------------------------------------------------
function PickNPool( pool_code, num_legs )
{
	this.base = AbstractPool;
	this.base( pool_code, num_legs, true );
}
PickNPool.prototype = new AbstractPool();

PickNPool.prototype._getNumCombos = function( bet )
{
	var count = 1;
	for( var leg = 1 ; leg <= this.num_legs ; leg++ )
	{
		count *= bet.numSelectedRunners(leg);
	}
	return count;
}

//---------------------------------------------------------------------------
// DailyDouble Pool
//---------------------------------------------------------------------------
function DailyDoublePool()
{
	this.base = PickNPool;
	this.base( "DBL", 2 );
}
DailyDoublePool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick3 Pool
//---------------------------------------------------------------------------
function Pick3Pool()
{
	this.base = PickNPool;
	this.base( "PK3", 3 );
}
Pick3Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick4 Pool
//---------------------------------------------------------------------------
function Pick4Pool()
{
	this.base = PickNPool;
	this.base( "PK4", 4 );
}
Pick4Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick5 Pool
//---------------------------------------------------------------------------
function Pick5Pool()
{
	this.base = PickNPool;
	this.base( "PK5", 5 );
}
Pick5Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick6 Pool
//---------------------------------------------------------------------------
function Pick6Pool()
{
	this.base = PickNPool;
	this.base( "PK6", 6 );
}
Pick6Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick7 Pool
//---------------------------------------------------------------------------
function Pick7Pool()
{
	this.base = PickNPool;
	this.base( "PK7", 7 );
}
Pick7Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick8 Pool
//---------------------------------------------------------------------------
function Pick8Pool()
{
	this.base = PickNPool;
	this.base( "PK8", 8 );
}
Pick8Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick9 Pool
//---------------------------------------------------------------------------
function Pick9Pool()
{
	this.base = PickNPool;
	this.base( "PK9", 9 );
}
Pick9Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Pick10 Pool
//---------------------------------------------------------------------------
function Pick10Pool()
{
	this.base = PickNPool;
	this.base( "P10", 10 );
}
Pick10Pool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// Irish Jackpot Pool
// Winners at 3rd, 4th, 5th, and 6th races of a single meeting
//---------------------------------------------------------------------------
function IrishJackpotPool()
{
	this.base = PickNPool;
	this.base( "JPT", 4 );
}
IrishJackpotPool.prototype = new PickNPool();


//---------------------------------------------------------------------------
// Irish Placepot Pool
// Placed runners at first six races of a single meeting
//---------------------------------------------------------------------------
function IrishPlacepotPool()
{
	this.base = PickNPool;
	this.base( "PLP", 6 );
}
IrishPlacepotPool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// UK Jackpot Pool
// Placed runners at six races of a single meeting
//---------------------------------------------------------------------------
function UKJackpotPool()
{
	this.base = PickNPool;
	this.base( "JPT", 6 );
}
UKJackpotPool.prototype = new PickNPool();

//---------------------------------------------------------------------------
// UK Quadpot Pool
// Placed runners at four races of a single meeting
//---------------------------------------------------------------------------
function UKQuadpotPool()
{
	this.base = PickNPool;
	this.base( "QPT", 4 );
}
UKQuadpotPool.prototype = new PickNPool();

function Scoop7()
{
	this.base = PickNPool;
	this.base( "SP7", 7 );
}
Scoop7.prototype = new PickNPool();

function Scoop6()
{
	this.base = PickNPool;
	this.base( "SP6", 6 );
}
Scoop6.prototype = new PickNPool();
//---------------------------------------------------------------------------
// Place Pool
//---------------------------------------------------------------------------
function PlacePool()
{
	this.base = AbstractPool;
	this.base( "PLC", 1, false );
}
PlacePool.prototype = new AbstractPool();

PlacePool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1);
}
//---------------------------------------------------------------------------
// WinShow Pool
//---------------------------------------------------------------------------
function PlaceShowPool()
{
	this.base = AbstractPool;
	this.base( "PS", 1, false );
}
PlaceShowPool.prototype = new AbstractPool();

PlaceShowPool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1) * 2;
}
//---------------------------------------------------------------------------
// Quinella Pool
//---------------------------------------------------------------------------
function QuinellaPool()
{
	this.base = AbstractPool;
	this.base( "QNL", 2, false );
}
QuinellaPool.prototype = new AbstractPool();

QuinellaPool.prototype._getNumCombos = function( bet )
{
	var bm1 = bet.getSelectionAsBitmap(1);
	var bm2 = bet.getSelectionAsBitmap(2);

	var count = 
		bet.numSelectedRunners(1) * bet.numSelectedRunners(2)
		- (   bm1.and(bm2).count()
		    * ( bm1.and(bm2).count() +1 )
		  )/2;
	return count;
}

//---------------------------------------------------------------------------
// Quinella Box Pool
//---------------------------------------------------------------------------
function QuinellaBoxPool()
{
	this.base = AbstractPool;
	this.base( "QBX", 1, false );
}
QuinellaBoxPool.prototype = new AbstractPool();

QuinellaBoxPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 2 ) return 0;
	var count = num * (num-1) / 2;
	return count;
}
//---------------------------------------------------------------------------
// Show Pool
//---------------------------------------------------------------------------
function ShowPool()
{
	this.base = AbstractPool;
	this.base( "SHW", 1, false );
}
ShowPool.prototype = new AbstractPool();

ShowPool.prototype._getNumCombos = function( bet )
{
	return bet.numSelectedRunners(1);
}
//---------------------------------------------------------------------------
// Superfecta Pool
//---------------------------------------------------------------------------
function SuperfectaPool()
{
	this.base = AbstractPool;
	this.base( "SPR", 4, false );
}
SuperfectaPool.prototype = new AbstractPool();

SuperfectaPool.prototype._getNumCombos = function( bet )
{
	var bm1 = bet.getSelectionAsBitmap(1);
	var bm2 = bet.getSelectionAsBitmap(2);
	var bm3 = bet.getSelectionAsBitmap(3);
	var bm4 = bet.getSelectionAsBitmap(4);
	var count = 0;
	for( var i=1 ; i <= this.max_runners ; i++ )
	{
		if( bm1.isSet(i) )
		{
			var hide = bm1.or(bm2).or(bm3).or(bm4).unset(i);
			var use2 = bm2.and(hide);
			var use3 = bm3.and(hide);
			var use4 = bm4.and(hide);
			count += 
				( use2.count() * use3.count() * use4.count() )
				- ( use2.and(use3).count() * use4.count() )
				- ( use2.and(use4).count() * use3.count() )
				- ( use3.and(use4).count() * use2.count() )
				+ ( 2 * use2.and(use3).and(use4).count() );
		}
	}
	return count;
}

//---------------------------------------------------------------------------
// Superfecta Box Pool
//---------------------------------------------------------------------------
function SuperfectaBoxPool()
{
	this.base = AbstractPool;
	this.base( "SFX", 1, false );
}
SuperfectaBoxPool.prototype = new AbstractPool();

SuperfectaBoxPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 4 ) return 0;
	var count = num * (num-1) * (num-2) * (num-3);
	return count;
}
//---------------------------------------------------------------------------
// Trifecta Pool
//---------------------------------------------------------------------------
function TrifectaPool()
{
	this.base = AbstractPool;
	this.base( "TRI", 3, false );
}
TrifectaPool.prototype = new AbstractPool();

TrifectaPool.prototype._getNumCombos = function( bet )
{
	var bm1 = bet.getSelectionAsBitmap(1);
	var bm2 = bet.getSelectionAsBitmap(2);
	var bm3 = bet.getSelectionAsBitmap(3);
	var count = 0;
	count = 
		( bm1.count() * bm2.count() * bm3.count() )
		- ( bm1.and(bm2).count() * bm3.count() )
		- ( bm1.and(bm3).count() * bm2.count() )
		- ( bm2.and(bm3).count() * bm1.count() )
		+ ( 2 * bm1.and(bm2).and(bm3).count() );
	return count;
}

//---------------------------------------------------------------------------
// Trifecta Box Pool
//---------------------------------------------------------------------------
function TrifectaBoxPool()
{
	this.base = AbstractPool;
	this.base( "TBX", 1, false );
}
TrifectaBoxPool.prototype = new AbstractPool();

TrifectaBoxPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 3 ) return 0;
	var count = num * (num-1) * (num-2);
	return count;
}
//---------------------------------------------------------------------------
// Trio Pool - similar to Trifecta but only 3 selections of 1st,2nd,3rd in
// and order (equivalent to a Trifecta single combination (boxed) bet).
// Trio Combination is more than 3 selections and 3 of which must be 1st 2nd 3rd.
// Trio Banker 3 or more plus bankers(1 single, 2 double), bankers must finish 1st, 2nd, or 3rd
// NOTE: Bankers not yet supported.
//---------------------------------------------------------------------------
function TrioPool()
{
	this.base = AbstractPool;
	this.base( "TRO", 1, false );
}
TrioPool.prototype = new AbstractPool();

TrioPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 3 ) return 0;
	var count = num * (num-1) * (num-2);
	return count;
}

//---------------------------------------------------------------------------
// Trio Box Pool
//---------------------------------------------------------------------------
function TrioBoxPool()
{
	this.base = AbstractPool;
	this.base( "TRX", 1, false );
}
TrioBoxPool.prototype = new AbstractPool();

TrioBoxPool.prototype._getNumCombos = function( bet )
{
	var num = bet.numSelectedRunners(1);
	if( num < 3 ) return 0;
	var count = num * (num-1) * (num-2);
	return count;
}
//---------------------------------------------------------------------------
// Placeholder pool for unsupported types
//---------------------------------------------------------------------------
function UnsupportedPool()
{
	this.base = AbstractPool;
	this.base( "XXX", 1, false );
}
UnsupportedPool.prototype = new AbstractPool();

UnsupportedPool.prototype._getNumCombos = function( bet )
{
	return 0;
}
//---------------------------------------------------------------------------
// WinPlace Pool
//---------------------------------------------------------------------------
function WinPlacePool()
{
	this.base = AbstractPool;
	this.base( "WP", 1, false );
}
WinPlacePool.prototype = new AbstractPool();

WinPlacePool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1) * 2;
}

//---------------------------------------------------------------------------
// Each Way Pool - same as Win Place
//---------------------------------------------------------------------------
function EachWayPool()
{
	this.base = AbstractPool;
	this.base( "E/W", 1, false );
}
EachWayPool.prototype = new AbstractPool();

EachWayPool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1) * 2;
}
//---------------------------------------------------------------------------
// WinPlaceShow Pool
//---------------------------------------------------------------------------
function WinPlaceShowPool()
{
	this.base = AbstractPool;
	this.base( "WPS", 1, false );
}
WinPlaceShowPool.prototype = new AbstractPool();

WinPlaceShowPool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1) * 3;
}
//---------------------------------------------------------------------------
// Win Pool
//---------------------------------------------------------------------------
function WinPool()
{
	this.base = AbstractPool;
	this.base( "WIN", 1, false );
}
WinPool.prototype = new AbstractPool();

WinPool.prototype._getNumCombos = function( bet )
{
	return bet.numSelectedRunners(1);
}
//---------------------------------------------------------------------------
// WinShow Pool
//---------------------------------------------------------------------------
function WinShowPool()
{
	this.base = AbstractPool;
	this.base( "WS", 1, false );
}
WinShowPool.prototype = new AbstractPool();

WinShowPool.prototype._getNumCombos = function(bet)
{
	return bet.numSelectedRunners(1) * 2;
}
function AbstractSGRPool( pool )
{
	pool.spec_min_stake = 0;
	pool.num_combos_min = 0;

	pool.spec_max_stake = 0;
	pool.num_combos_max = 0;

	pool.validateStake = function( bet )
	{
		var stake = bet.stake;
		var num_combos = this.getNumCombos( bet );

		if( stake == 0 ) return Val.NO_STAKE;

		if( this.getSpecialMinimumCombos() > 0 && num_combos >= this.getSpecialMinimumCombos() )
		{
			if( stake < this.getSpecialMinimumStake() ) return Val.STAKE_TOO_SMALL;
			if( this.requiresMultipleMinStake() && ! Common.isMultipleOf( stake, this.getSpecialMinimum() ) )
			{
				return Val.NOT_A_MULTIPLE_OF_MIN_STAKE;
			}
		}
		else
		{
			if( stake < this.getMinimumStake() ) return Val.STAKE_TOO_SMALL;
			if( this.requiresMultipleMinStake()  && ! Common.isMultipleOf( stake, this.getMinimumStake() ) )
			{
				return Val.NOT_A_MULTIPLE_OF_MIN_STAKE;
			}
		}

		if( this.getSpecialMaximumCombos() > 0 && num_combos >= this.getSpecialMaximumCombos() )
		{
			if( stake > this.getSpecialMaximum() ) return Val.STAKE_TOO_LARGE;
		}
		else
		{
			if( stake > this.getMaximumStake() ) return Val.STAKE_TOO_LARGE;
		}

		var st = stake/this.getPennyMultiple();

		if( ! this.allowsPennyBetting() && ( st != Math.floor(st) ) )
		{
			return Val.PENNY_BETTING_NOT_ALLOWED;
		}

		return Val.VALID;
	};

	pool.setSpecialMinimumStake = function( spec_min_stake )
	{
		this.spec_min_stake = spec_min_stake || 0;
	}

	pool.setSpecialMinimumCombos = function( num_combos )
	{
		this.num_combos_min = num_combos || 0;
	}

	pool.setSpecialMaximumStake = function( spec_max_stake )
	{
		this.spec_max_stake = spec_max_stake || 0;
	}

	pool.setSpecialMaximumCombos = function( num_combos )
	{
		this.num_combos_max = num_combos || 0;
	}

	pool.getSpecialMinimumStake = function()
	{
		return this.spec_min_stake;
	}

	pool.getSpecialMaximumStake = function()
	{
		return this.spec_max_stake;
	}

	pool.getSpecialMinimumCombos = function()
	{
		return this.num_combos_min;
	}

	pool.getSpecialMaximumCombos = function()
	{
		return this.num_combos_max;
	}
}
function SGRPoolFactory()
{
}

SGRPoolFactory.prototype.constructPoolObject = function( xml_pool )
{
	var pool_code = xml_pool.getAttribute( "pool-code" );
	var type = xml_pool.getAttribute( "type" );
	var pool;

	switch( pool_code )
	{
		case "WIN" :
			pool = new WinPool();
			break;
		case "PLC" :
			pool = new PlacePool();
			break;
		case "SHW" :
			pool = new ShowPool();
			break;
		case "WP" :
			pool = new WinPlacePool();
			break;
		case "WS" :
			pool = new WinShowPool();
			break;
		case "PS" :
			pool = new PlaceShowPool();
			break;
		case "WPS" :
			pool = new WinPlaceShowPool();
			break;
		case "QU" :
		case "QNL":
			pool = new QuinellaPool();
			break;
		case "QBX":
			pool = new QuinellaBoxPool();
			break;
		case "EXA" :
			pool = new ExactaPool();
			break;
		case "EBX" :
			pool = new ExactaBoxPool();
			break;
		case "TRI" :
			pool = new TrifectaPool();
			break;
		case "TBX" :
			pool = new TrifectaBoxPool();
			break;
		case "SFC" :
		case "SPR" :
			pool = new SuperfectaPool();
			break;
		case "SFX" :
			pool = new SuperfectaBoxPool();
			break;
		case "DD" :
		case "DBL" :
			pool = new DailyDoublePool();
			break;
		case "PK3" :
			pool = new Pick3Pool();
			break;
		case "PK4" :
			pool = new Pick4Pool();
			break;
		case "PK5" :
			pool = new Pick5Pool();
			break;
		case "PK6" :
			pool = new Pick6Pool();
			break;
		case "PK7" :
			pool = new Pick7Pool();
			break;
		case "PK8" :
			pool = new Pick8Pool();
			break;
		case "PK9" :
			pool = new Pick9Pool();
			break;
		case "P10" :
			pool = new Pick10Pool();
			break;
		// added for tote ireland - should break out into own factory
		case "E/W" :
			pool = new EachWayPool();
			break;
		case "TRO" :
			pool = new TrioPool();
			break;
		case "TRX" :
			pool = new TrioBoxPool();
			break;
		case "JPT" :
			if( type == "uk" )
				pool = new UKJackpotPool();
			else	
				pool = new IrishJackpotPool();
			break;
		case "PLP" :
			pool = new IrishPlacepotPool();
			break;
		case "QPT" :
			pool = new UKQuadpotPool();
			break;
		case "SP6" :
			pool = new Scoop6();
			break;
		case "SP7" :
			pool = new Scoop7();
			break;
		default :
			pool = new UnsupportedPool();
			do_debug( "AbstractSGRPool::constructPoolObject: unrecognised pool code: " + pool_code );
			break;
	}

	AbstractSGRPool( pool );

	pool.setSpecialMinimumStake( xml_pool.getAttribute( "spec-min-stake" ) );
	pool.setSpecialMinimumCombos( xml_pool.getAttribute( "spec-min-combos" ) );
	pool.setSpecialMaximumStake( xml_pool.getAttribute( "spec-max-stake" ) );
	pool.setSpecialMaximumCombos( xml_pool.getAttribute( "spec-max-combos" ) );

	return pool;
}

SGRPoolFactory.prototype.updatePool = function( pool, xml_pool )
{
	pool.setSpecialMinimumStake( xml_pool.getAttribute( "spec-min-stake" ) );
	pool.setSpecialMinimumCombos( xml_pool.getAttribute( "spec-min-combos" ) );
	pool.setSpecialMaximumStake( xml_pool.getAttribute( "spec-max-stake" ) );
	pool.setSpecialMaximumCombos( xml_pool.getAttribute( "spec-max-combos" ) );
}
