/*
 * file:    play_picker.js
 * authors: Ivan Wills
 *
 * API:
 *
 * events:
 *    clear_picks    -
 *    click_board    -
 *    enter_entry    -
 *    exit_entry     -
 *    out_number     -
 *    over_number    -
 *    quick_pick     - When the quick_pick button is pressed (generates the numbers for that game)
 *    quick_pick_all - When the quick_pick_all button is pressed (all games have numbers picked)
 *    select_game    -
 *    type_entry     -
 *
 * other functions:
 *    clear_board_number  - clears the number off the board
 *    clear_board         - clears the board of all set numbers
 *    get_position        - gets the position of a number
 *    has_position        - checks if a number already has a position
 *    is_board_marked     - checks if the board number is set
 *    is_valid_game       - throw an error if the game number passed is not valid
 *    is_valid_number     - throws an error if the number passed is invalid
 *    is_valid_set        - throws an error if the set number is unknown
 *    set_board_number    - sets a number on the board
 *    set_current_entry   - sets the number into the current entry position
 *    set_next_position   - moves the current position to the next available position
 *    setup_board         - sets up the board baised on the current game
 *    toggle_board_number - toggles a number on the board
 *    update_position     - focuses and selects the current position
 *    validate_game       - returns true if the game is valid
 *    validate_set        - returns true if all nunbers in a set are valid
 *    validate_games      - returns ture if all games are completed correctly
 *
 * general functions
 *    total_offset - determins the total x or y offset for an element
 *    debug        - displays messages if DEBUG is true
 *    error        - displays a debug message (if debugging is turned on) then throws an error with the same string
 *    uniq         - returns a list of unique values from a supplied list
 *
 * globals:
 *    CONSTANTS:
 *      MAX.GAMES     * the number of games that can be plaied
 *      MAX.SETS      * the number of sets available (usually 1 or 2)
 *      SETS          * stores options per set
 *      SETS[i]       *  settings for the set i
 *      SETS[i].count *   the number of choices for the set i
 *      SETS[i].first *   the first number available for set i
 *      SETS[i].last  *   the last number available to choose for set i
 *
 *    Variables:
 *      current_game             - the current game being palied
 *      entry_typed              - flags that the user has typed into the current entry
 *      board                    - stores all the board <td>'s for quick access
 *      board[set]               -  the board set
 *      board[set][number]       -  the board number (<td>)
 *      games                    - stores all the game entry <input>s
 *      games[games]             -  each individual game
 *      games[games][set]        -   each game set
 *      games[games][set][entry] -   the individual <input>s
 *      entry                    - stores information about weather an entry has been altered and the original value
 *      hover                    - stores information about which element currently has the cursor over it
 *      current                  - Stores the current values of various elements (position,game)
 *
 *  * These are should be defined before this file is called
 *
 */

// generally static constants
var MIN = {
	'TYPE':   0,
	'GAME':   0,
	'SET':    0,
	'NUMBER': 0,
	'ENTRY':  0
};

// set up the global variables
var board = new Array();
var games = new Array();
var entry = { 'typed': '', 'original': '' };
var hover = { 'type':  '', 'number':   '' };
var point;
var picker = document.getElementById('picker');

current.type     = MIN.TYPE;
current.game     = false;
current.set      = new Array();
current.number   = MIN.NUMBER;
current.entry    = { 'typed': '', 'original': '' };

/**
 *	Sets the global parameters board_numbers, board_supps and game_numbers
 */
function set_params() {
	if ( !current.game ) {
		current.game     = MIN.GAME;
	}

	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		current.set[set] = MIN.SET;
		board[set]       = new Array();
		for ( var entry = SETS[set].first; entry <= SETS[set].last; entry++ ) {
			board[set][entry] = document.getElementById('set'+set+'_number'+entry);
		}
	}
	for ( var game = MIN.GAME; game < MAX.GAMES; game++ ) {
		games[game] = new Array();
		for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
			games[game][set] = new Array();
			for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
				games[game][set][entry] = document.getElementById('game'+game+'_set'+set+'_'+entry);
				if ( !games[game][set][entry] ) throw 'No game input found for game'+game+'_set'+set+'_'+entry;
			}
		}
	}

	update_position();
}

/******    USER EVENTS   *****/

/**
 *	@param	number:	The number the mouse is over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *
 */
function over_number( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('over_number input error: ' + e);
	}
	var class_lab = 'set'+set;
	var square    = board[set][number];
	var hover     = is_board_marked(set, number) ? ' selected hover ' : ' hover '

	square.className = class_lab + hover;
	hover.number     = number;
	hover.set        = set;
	return true;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *
 */
function out_number( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('out_number input error: ' + e);
	}
	var class_lab = 'set'+set;
	var square    = board[set][number];
	var unhover   = is_board_marked(set, number) ? ' selected' : ''

	square.className = class_lab + unhover;
	hover.number     = '';
	hover.set        = '';
	return true;
}

/**
 *	@param	number:	The number that has been clicked
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug	Entry not always properly cleared
 *
 *	This is the action taken when the game board is clicked on
 *	It should either set or clear a number of the board.
 */
function click_board( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('click_board input error: ' + e);
	}

	get_position( set, number );
	var square  = board[set][number];
	var numbers = games[current.game][set][current.set[set]];
	if (!numbers) throw 'Lost the game numbers for game '+current.game+', set '+set+', match? '+current.set[set];

	// check if the current number is set else where on the board and clear it if is it set and not the current number
	if ( numbers.value && numbers.value != number && is_board_marked( set, numbers.value ) ) {
		clear_board_number( set, numbers.value );
	}
	var new_number = toggle_board_number(set, number);
	set_current_entry( set, new_number );
	if ( !validate_games() ) {
		if ( validate_game() ) {
			current.type   = MIN.TYPE;
			set_next_position(current.type);
			setTimeout('select_game( '+current.game+' + 1 );', 10 ); // IE scheduling issue
		}
		else if ( validate_set() ) {
			set_next_set();
			set_next_position(current.type);
		}
	}
}

/**
 *	@param	game:	The game the entry is part of
 *	@param	position:	The game entry position
 *	@param	set:	The poistion set
 *	@return
 *	@todo REMOVE? Does not appear to be called any where
 *	@bug
 *
 *
 */
function enter_entry( game, set, position ) {
	current.entry.typed = false;
	game               *= 1;
	current.set[set]    = position;
	current.type        = set;

	if ( current.game != game )
		select_game(game);

	if ( games[game][set][position].value )
		current.entry.original = games[game][set][position].value * 1;
	current.entry.typed  = false;
	current.type = set;
}

/**
 *	@param	game:	The game the entry is part of
 *	@param	position:	The game entry position
 *	@param	set:	The poistion set
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks if the game entry position we are exiting has been typed into
 *	by the user. If it has we clear the previous value from the board (if it
 *	existed) and enter
 */
function exit_entry( game, set, position ) {
	if ( current.entry.typed ) {
		var value  = games[game][set][position].value;
		var number = value * 1;
		if ( number != current.entry.original ) {
			// Check that the number is valid
			if ( number < SETS[set].first || SETS[set].last < number ) {
				games[game][set][position].value = value = '';
				number = MIN.ENTRY;
			}
			if ( current.entry.original )
				clear_board_number( set, current.entry.original );
			if ( number ) {
				set_board_number( set, number );
				// check if the number is entered else where
				var founds = has_position_all( set, number );
				if ( founds.length > 1 ) {
					for ( var found = 0; found < founds.length; found++ ) {
						if ( founds[found] != position ) {
							games[game][set][founds[found]].value = '';
						}
					}
				}
			}
		}
		current.entry.typed = false;
	}
	current.entry.original = '';
}

/**
 *	@param	game:	The game the entry is part of
 *	@param	position:	The game entry position
 *	@param	set:	The poistion set
 *	@return
 *	@todo
 *	@bug
 *
 *	Flags that the current position is being typed into
 */
function type_entry( game, set, position ) {
	current.entry.typed = 1;
}

/**
 *	@param	game:	the game to select
 *	@return
 *	@todo
 *	@bug
 *
 *	Changes the current game to game
 */
function select_game( game, leave_position, force ) {
	if (current.game == game && !force) return;

	if ( game < MIN.GAME || game >= MAX.GAMES ) {
		game = MIN.GAME;
	}
	if ( games[current.game] ) {
		document.getElementById('game'+current.game+'_row').className = current.game % 2 ? 'odd' : 'even';
	}
	current.game = game;
	if ( !picker ) {
		picker = document.getElementById('picker');
	}

	var game_row = document.getElementById('radio_game'+current.game);
	game_row.checked = true;

	var picker_arrow = document.getElementById('picker_arrow');
	var offset_top   = Math.floor(game_row.offsetHeight/2) - -game_row.offsetParent.offsetTop - -Math.floor(game_row.offsetParent.offsetHeight/2) - -game_row.offsetParent.offsetParent.offsetTop - -game_row.offsetParent.offsetParent.offsetParent.offsetTop;
	var offset       = offset_top - Math.floor(picker_arrow.offsetParent.offsetHeight/2);

	if ( !select_game.max_offset ) {
		var add_to_basket = document.getElementById('add_to_basket_td');
		select_game.max_offset = total_offset(add_to_basket,'Top') + add_to_basket.offsetHeight - total_offset(picker,'Top') - picker.offsetHeight;
		var number_entries = document.getElementById('number_entries');
		select_game.min_offset = total_offset(number_entries,'Top') - total_offset(picker,'Top');
		var play_picker_td = document.getElementById('play_picker_td');

		// check that the table (once the game is shifted) is large enough
		// TODO currently missing about ~20px need to find where they go
		var holder = document.getElementById('play_hold');
		if ( holder.offsetHeight < select_game.min_offset + picker.offsetHeight ) {
			play_picker_td.offsetParent.style.height = (select_game.min_offset + picker.offsetHeight) + 'px';
		}
	}

	if ( offset < select_game.min_offset ) {
		picker.style.top = select_game.min_offset + 'px';
		picker_arrow.style.top = '-' + (select_game.min_offset - offset) + 'px'
	}
	else if ( offset > select_game.max_offset ) {
		picker.style.top = select_game.max_offset + 'px';
		picker_arrow.style.top = (offset - select_game.max_offset) + 'px';
	}
	else {
		picker.style.top = offset + 'px';
		picker_arrow.style.top = '0px';
	}

	document.getElementById('game'+current.game+'_row').className = ( current.game % 2 ? 'odd' : 'even' ) + ' current';
	if ( !leave_position ) {
		current.type = 0;
		current.set[current.type] = 0;
	}
	update_position();
	clear_board();
	setup_board();
	if ( document.all && !leave_position )
		setTimeout('current.game=-1; select_game('+game+', true);', 10);
}

function debug_offsets(element) {
	name = element.nodeName + ( element.id ? '#'+element.id : '' ) + ': ';
	for ( var i = name.length; i < 30; i++ ) name += ' ';
	return name
		+ '\tT='+element.offsetTop
		+ '\tL='+element.offsetLeft
		+ '\tH='+element.offsetHeight
		+ '\tW='+element.offsetWidth
		+ '\n'
		+ ( element.offsetParent ? debug_offsets(element.offsetParent) : '' );
}

/**
 *	@param element: The element that the total offset is required for
 *	@param type: The type of offset required (Top, Left, Height or Width)
 *	@todo
 *	@bug
 *
 *	Calculates the total offset on a page for the supplied element
 */
function total_offset(element, type) {
	if ( !element ) return 0;
	var offset = 0;
	if      ( type == 'Top'    ) offset = element.offsetTop;
	else if ( type == 'Left'   ) offset = element.offsetLeft;
	else if ( type == 'Height' ) offset = element.offsetHeight;
	else if ( type == 'Width'  ) offset = element.offsetWidth;

	if ( element.nodeName != 'BODY' )
		offset += total_offset(element.offsetParent, type);
	return offset;
}

/**
 *	@todo
 *	@bug
 *
 *	Makes random entry selections for the current game then moves onto the next game
 */
function quick_pick_all(start_game, dot_text) {
	clear_board();
	var start = new Date();
	var button;
	var text;
	var dots;

	if ( MAX.GAMES > 20 ) {
		if (!start_game) {
			button = document.getElementById('quickpick_all_button');
			text   = document.getElementById('quickpick_all_text');
			button.style.backgroundColor = 'silver';
			quick_pick_all.button_text = text.firstChild.data;
			text.replaceChild(document.createTextNode('Generating'), text.firstChild);
			quick_pick_all.button_href = button.href;
			button.setAttribute('href', 'javascript: void(0);');
			start_game = MIN.GAME;
			dot_text   = '.';
		}
		if ( start_game % 3 == 0 ) {
			dots   = document.getElementById('quickpick_all_dots');
			if (dot_text.length == 4) {
				dot_text = '';
			}
			if (dots.firstChild)
				dots.replaceChild(document.createTextNode(dot_text), dots.firstChild);
			else
				dots.appendChild(document.createTextNode(dot_text));
			dot_text += '.';
		}
	}

	if (!start_game || start_game < MIN.GAME) start_game = MIN.GAME;

	for ( var game = start_game; game < MAX.GAMES; game++ ) {
		// check that we are not taking too long
		var now = new Date();
		if (now.getSeconds()*1000 + now.getMilliseconds() > start.getSeconds()*1000 + start.getMilliseconds() + 100) {
			setTimeout('quick_pick_all('+game+',"'+dot_text+'")', 100);
			return;
		}

		// use new ozl-quickpick.js
		var quickpick_results = (new OzLotteries.QuickPickSets(SETS)).quickpick_sets();

		// each set
		for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
			var list = quickpick_results[set];
			// each number choice
			for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
				games[game][set][entry].value = list[entry];
			}
		}

		if ( game > MIN.GAME && !unique_game(game, game) ) {
			game--;
		}
	}

	if ( MAX.GAMES > 20 ) {
		button = button || document.getElementById('quickpick_all_button');
		text   = text   || document.getElementById('quickpick_all_text');
		dots   = dots   || document.getElementById('quickpick_all_dots');
		text.replaceChild(document.createTextNode(quick_pick_all.button_text), text.firstChild);
		if (dots.firstChild || dots.childNodes[0]) dots.replaceChild(document.createTextNode(''), dots.firstChild || dots.childNodes[0]);
		button.style.backgroundColor = '';
		button.href = quick_pick_all.button_href;
	}

	setup_board();
	return;
}

/**
 *	@todo
 *	@bug
 *
 *	Makes random entry selections for the current game then moves onto the next game
 */
function quick_pick() {
	clear_picks();

	var quickpick_results = (new OzLotteries.QuickPickSets(SETS)).quickpick_sets();

	// each set
	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		var list = quickpick_results[set];
		// each number choice
		for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
			get_position( set, list[entry] );
			set_current_entry( set, list[entry] );
		}
	}

	// only need to check games before current game
	if (!unique_game(current.game, MAX.GAMES)) {
		quick_pick();
	}

	setTimeout('select_game( '+current.game+' + 1 );', 10 ); // IE scheduling issue
	return;
}

/**
 *	@return bool - True if the game is not repeted, False otherwise.
 *	@bug
 *
 *	Tests if the game supplied is repeted by any other game
 */
function unique_game(test_game, last_game) {
	var all_games = [];
	if (unique_game.arguments.length != 2) last_game = MAX.GAMES;
	is_valid_game(last_game);

	var test = [];
	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		test[set] = [];
		for ( var entry = MIN.ENTRY; entry < games[test_game][set].length; entry++ ) {
			test[set][entry] = games[test_game][set][entry].value;
		}
	}

	for ( var game = MIN.GAME; game < last_game; game++ ) {
		if ( test_game != game ) {
			var match = true;
			for ( var set = MIN.SET; set < MAX.SETS && match; set++ ) {
				var set_match_count = 0;
				var set_numbers     = [];
				for ( var entry = MIN.ENTRY; entry < games[game][set].length; entry++ ) {
					set_numbers[entry] = games[game][set][entry].value;
				}

				var test_set = uniq(test[set].concat(set_numbers));
				match = match && test_set.length == games[game][set].length;
			}

			if (match) {
				return false;
			}
		}
	}

	return true;
}

/**
 *	@todo
 *	@bug
 *
 *	Clears the board and the entry positions for the current game
 */
function clear_all() {
	clear_board();
	for ( var game = MIN.GAME; game < MAX.GAMES; game++ ) {
		for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
			for ( var entry = MIN.ENTRY; entry < games[game][set].length; entry++ )
				games[game][set][entry].value = '';
		}
	}
}


/**
 *	@todo
 *	@bug
 *
 *	Clears the board and the entry positions for the current game
 */
function clear_picks() {
	clear_board();
	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		for ( var entry = MIN.ENTRY; entry < games[current.game][set].length; entry++ )
			games[current.game][set][entry].value = '';
	}
}

/* *** Helper Functions *** */

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Enters number into the current.game set position
 */
function set_current_entry( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error( 'set_current_entry: ' + e );
	}

	var numbers =  games[current.game][set][current.set[set]];
	if (numbers.value != number)
		numbers.value = number;
	else
		numbers.value = '';
	set_next_position(set);
	validate_game(current.game);
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Toggles on/off a number on the board
 */
function toggle_board_number( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error( 'toggle_board_number: ' + e );
	}

	if ( is_board_marked(set, number) ) {
		return clear_board_number( set, number );
	}
	else {
		return set_board_number( set, number );
	}
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Turns on a number of the set board.
 */
function set_board_number( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error( 'set_board_number: ' + e );
	}

	var square       = board[set][number];
	var base         = 'set'+set;
	var hover_class  = ( number == hover.number && set == hover.set ) ? 'hover' : '';
	square.className = base + ' selected ' + hover_class;
	return number;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Clears number of the board set
 */
function clear_board_number( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error( 'clear_board_number: ' + e );
	}
	var square       = board[set][number];
	var base         = 'set'+set;
	var hover_class  = ( number == hover.number && set == hover.set ) ? ' hover' : '';
	square.className = base + hover_class;
	return number;
}

/**
 *	@todo
 *	@bug
 *
 *	Clears all numbers of both boards
 */
function clear_board() {
	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		for ( var entry = SETS[set].first; entry <= SETS[set].last; entry++ ) {
			clear_board_number( set, entry );
		}
	}
}

/**
 *	@todo
 *	@bug
 *
 *	Sets all the numbers from the current game onto the board
 */
function setup_board() {
	for ( var set = MIN.SET; set < MAX.SETS; set++ ) {
		for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
			if ( games[current.game] && games[current.game][set] && games[current.game][set][entry] && games[current.game][set][entry].value )
				set_board_number( set, games[current.game][set][entry].value );
		}
	}
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Gets the entry position to enter a number to (if number is found that position is returned) other wise the current position is returned
 */
function get_position( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('get_position input error: ' + e);
	}
	var found = has_position( set, number );
	if ( found >= MIN.ENTRY ) {
		current.set[set] = found;
		return found;
	}
	return current.set[set];
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Returns the position of number if it has been entered some where (returns -1 if number is not found)
 */
function has_position( set, number ) {
	var found = has_position_all(set, number);
	return found.length ? found[0] : MIN.ENTRY - 1;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Returns all positions that number has been entered in for the supplied set
 */
function has_position_all( set, number ) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('has_position input error: ' + e);
	}

	var found   = [];
	var numbers = games[current.game][set];
	for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
		if ( numbers && numbers[entry] && numbers[entry].value == number ) {
			found.push(entry);
		}
	}

	return found;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Updates the borad with the current positions (possibly) new value
 */
function leave_position(game, set, position) {
	set_board_number(set, games[game][set][position].value);
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Moves the current entry position to the next logical position
 */
function set_position(set, position) {
	try {
		is_valid_set(set);
	}
	catch (e) {
		error('set_next_position input error: ' + e);
	}

	current.type     = set;
	current.set[set] = position;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Moves the current entry position to the next logical position
 */
function set_next_position(set) {
	try {
		is_valid_set(set);
	}
	catch (e) {
		error('set_next_position input error: ' + e);
	}

	var numbers     = games[current.game][set];
	var current_pos = current.set[set] * 1;

	// search for the first unused position
	for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
		// cycle the position arround
		var pos = current_pos + entry < SETS[set].count ? current_pos + entry : current_pos + entry - SETS[set].count;
		if ( !numbers[pos].value ) {
			current.set[set] = pos;
			if ( numbers[pos] ) {
				try {
					numbers[pos].focus();
					numbers[pos].select();
				}
				catch (e) {}
			}
			else debug( pos + ' has no data!');
			return pos;
		}
	}

	// failed to find an unused so go to the next used position
	var pos = current_pos + 1 < SETS[set].count ? current_pos + 1 : current_pos + 1 - SETS[set].count;
	current.set[set] = pos;
	if ( numbers[pos] ) {
		try {
			numbers[pos].focus();
			numbers[pos].select();
		}
		catch (e) {}
	}
	else debug( pos + ' has no data!');
	return pos;
}

/**
 *	@param	number:	The number the mouse was over
 *	@param	set:	The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Determines if the number is set on the board
 */
function is_board_marked(set, number) {
	try {
		is_valid_number(set, number);
		is_valid_set(set);
	}
	catch (e) {
		error('is_board_marked input error: ' + e);
	}
	var square = board[set][number];
	if ( square.className.match( 'selected' ) )
		return true;
	return false;
}

/**
 *	@todo
 *	@bug
 *
 *	Sets the current entry position
 */
function update_position() {
	try {
		games[current.game][current.type][current.set[current.type]].focus();
		games[current.game][current.type][current.set[current.type]].select();
	}
	catch (e) {}
}

/**
 *	@param	game:	The game to validate
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that all numbers are entered and not repeated
 */
function validate_game(game) {
	if ( !game )
		game = current.game;

	var valid = true;
	for ( var set = MIN.SET; set < MAX.SETS; set++ )
		valid = valid && validate_set( set, game );
	return valid;
}

/**
 *	@param	game:	The game to validate
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that all ordinary numbers have been entered and not repeated
 */
function validate_set( set, game ) {
	if ( !game )
		game = current.game;
	if ( !set )
		set = current.type;

	var numbers = new Array();
	for ( var entry = MIN.ENTRY; entry < SETS[set].count; entry++ ) {
		var number = games[game][set][entry].value;
		if ( !number || numbers[number] || number < SETS[set].first || SETS[set].last < number ) {
			return false;
		}
		numbers[number] = true;
	}

	return true;
}

/**
 *	@return
 *	@todo
 *	@bug
 *
 *	Sets the next game set to use looping if the end is reached
 */
function set_next_set() {
	var set = current.type;
	if ( set + 1 < MAX.SETS )
		return current.type = set + 1;
	else
		return current.type = MIN.TYPE;
}

/**
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that all games have been entered and are valid
 */
function validate_games() {
	for ( var game = MIN.GAME; game < MAX.GAMES; game++ ) {
		if ( !validate_game(game) )
			return false;
		else if (!unique_game(game,game))
			return false;
	}
	return true;
}

/**
 *	@param	number:	The number the mouse was over
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that the passed number is a valid number (throws and error if it is not)
 */
function is_valid_number( set, number ) {
	try {
		is_valid_set(set);
	}
	catch (e) {
		error('is_valid_number input error: ' + e);
	}
	if ( number == '' )
		throw 'No number passed!';
	if ( number < SETS[set].first || SETS[set].last < number )
		throw 'The number '+number+' is out of bounds!';
	return true;
}

/**
 *	@param set: The board set
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that the passed set is a valid number (throws and error if it is not)
 */
function is_valid_set( set ) {
	if ( set < MIN.SET || MAX.SETS < set )
		error( 'Unknown set "'+set+'"!' );
	return true;
}

/**
 *	@param	game:	The game to validate
 *	@return
 *	@todo
 *	@bug
 *
 *	Checks that the passed game is a valid number (throws and error if it is not)
 */
function is_valid_game( game ) {
	if ( game === '' )
		error( 'No game number passed!' );
	if ( game < MIN.GAME || MAX.GAMES < game )
		error( 'The game number "'+game+'" is out of bounds!' );
}

/**
 *	@param string: Debug message to display
 *
 *	Prints descriptive messages onto the game to testing purposes. This is turned on/off with the DEBUG variable.
 */
function debug( string ) {
	if (!DEBUG)
		return;
	if (!point) {
		point = document.getElementById('play_hold');
		point = point.appendChild( document.createElement('tr') );
		point = point.appendChild( document.createElement('td') );
		point.style.fontSize = '0.9em';
		point.setAttribute('colspan', 2);
	}
	var p = document.createElement('pre');
	p.appendChild( document.createTextNode(string+'\n') );
	p.style.width   = '100%';
	p.style.padding = '1px';
	p.style.margin  = '0px';
	point.appendChild(p);
}

var errors = new Object();
function error( string ) {
	if ( DEBUG && !errors[string] && string.length < 30 )
		debug(string);

	errors[string] = true;
	throw(string);
}

function uniq(orig) {
	// zero or one element arrays are always unique
	if (orig.length < 2) {
		return orig;
	}

	var list = [orig[0]];
	for ( var i = 0; i < orig.length; i++ ) {
		var check = true;
		for ( var j = 0; j < list.length; j++ ) {
			if ( list[j] == orig[i] ) {
				check = false;
			}
		}
		if (check) {
			list.push( orig[i] );
		}
	}
	return list;
}


