/**
 * AJAX/DHTML Concentration Game
 * This code is Copyright Gaby Vanhegan <gaby@vanhegan.net> 20060930
 * Version 1.0
 * http://www.playr.co.uk/memory/
 *
 * Click on the cards to turn them over and find the pairs.  You can only
 * see two cards at once, so they will turn themselves back over.  Different
 * sets of cards, different sizes of deck, time and click counters.  Play
 * any of the available decks or play in challenge mode, where there are 15
 * levels of cards to beat, some with time limits, some with click limits,
 * some with both.
 */

var selected_cards	= [];		// Actually selected cards
var matched_cards	= 0;		// How many cards have been matched?
var reset_delay		= 2000;		// Delay before turning cards back over
var reset_timer		= false;	// Holder for the reset's setTimeout()
var play_level		= 0;		// Current level we're playing
var click_total		= 0;		// Total clicks so far
var click_limit		= false;	// Do we have a click limit?
var cur_set_name	= false;	// What icon set is in use?
var num_cards		= 0;		// How many cards do we have?
var timer_from		= 0;		// What time are we timing from?
var timer_total		= 0;		// How many seconds have elapsed?
var timer_limit		= false;	// Is there a time limit?
var timer			= false;	// Holder for the timer's setTimeout()
var limit_over		= false;	// Have we exceeded a time/click limit?
var cur_game_type	= false;	// Do we have a game type?

// Levels of play, including name, time limit, click limit, sets and size
var game_levels	= new Array(
	{"name":"fun",    "time":false, "clicks":false, "set":["creatures"], "size":8 },
	{"name":"too",    "time":24,    "clicks":false, "set":["cars"], "size":8 },
	
	{"name":"tree",   "time":false, "clicks":false, "set":["random"], "size":16 },
	{"name":"core",   "time":false, "clicks":46,    "set":["trek"],  "size":16 },
	
	{"name":"live",   "time":270,   "clicks":false, "set":["egypt"], "size":24 },
	{"name":"nix",    "time":240,   "clicks":false, "set":false, "size":24 },

	{"name":"heaven", "time":false, "clicks":false, "set":["egypt"], "size":32 },	
	{"name":"skim",   "time":270,   "clicks":false, "set":false, "size":32 },
	{"name":"plate",  "time":240,   "clicks":98,    "set":false, "size":32 },
	
	{"name":"pine",   "time":false, "clicks":false, "set":false, "size":40 },
	{"name":"den",    "time":false, "clicks":104,   "set":false, "size":40 },
	
	{"name":"elebber","time":false, "clicks":false, "set":false, "size":48 },
	{"name":"delve",  "time":false, "clicks":110,   "set":false, "size":48 },
	
	{"name":"throaty","time":false, "clicks":false, "set":["newzealand"], "size":56 },
	{"name":"thrifty","time":315,   "clicks":false, "set":["creatures"], "size":56 },
	{"name":"porcine","time":300,   "clicks":158,   "set":["drives"], "size":56 }
);

// Total number of levels we have to play
var total_levels	= game_levels.length;
var cur_level		= 0;

// Sets of icons that we have to play with
var icon_sets		= {
	"egypt": [	
		"imagesets/egypt/ankh.png",
		"imagesets/egypt/bee.png",
		"imagesets/egypt/horus.png",
		"imagesets/egypt/leg.png",
		"imagesets/egypt/lotus.png",
		"imagesets/egypt/lotusbud.png",
		"imagesets/egypt/lute.png",
		"imagesets/egypt/owl.png",
		"imagesets/egypt/papyrus.png",
		"imagesets/egypt/quail.png",
		"imagesets/egypt/reed.png",
		"imagesets/egypt/scarab.png",
		"imagesets/egypt/vulture.png",
		"imagesets/egypt/wave.png",
		"imagesets/egypt/wick.png",
		"imagesets/egypt/sedge.png"
	],
	"cars": [
		"imagesets/cars/alfaromeo.png",
		"imagesets/cars/astonmartin.png",
		"imagesets/cars/bugatti.png",
		"imagesets/cars/ferrari.png",
		"imagesets/cars/lamborghini.png",
		"imagesets/cars/lotus.png",
		"imagesets/cars/maserati.png",
		"imagesets/cars/porsche.png"
	],
	"trek": [
		"imagesets/trek/All Good Things.png",
		"imagesets/trek/Borg Collective.png",
		"imagesets/trek/DS9Voyager.png",
		"imagesets/trek/Future Imperfect.png",
		"imagesets/trek/Klingon Empire.png",
		"imagesets/trek/TMP Admiral.png",
		"imagesets/trek/TMP Command.png",
		"imagesets/trek/TMP Engineering.png",
		"imagesets/trek/TMP Medical.png",
		"imagesets/trek/TMP Operations.png",
		"imagesets/trek/TMP Sciences.png",
		"imagesets/trek/TMP Security.png",
		"imagesets/trek/TMP Visitor.png",
		"imagesets/trek/TNG.png",
		"imagesets/trek/United Federation of Planets.png",
		"imagesets/trek/Vulcan IDIC.png"
	],
	"drives": [
		"imagesets/drives/10.1.png",
		"imagesets/drives/10.2.png",
		"imagesets/drives/10.3.png",
		"imagesets/drives/Bumblebeed.png",
		"imagesets/drives/Cattle Drive.png",
		"imagesets/drives/Deep Freeze.png",
		"imagesets/drives/Doe, a Deer.png",
		"imagesets/drives/G5 Apple Drive.png",
		"imagesets/drives/G5 Hard Drive.png",
		"imagesets/drives/G5 Labeled Drive.png",
		"imagesets/drives/G5 Network Volume 2.png",
		"imagesets/drives/G5 Matrix Drive.png",
		"imagesets/drives/G5 iDisk.png",
		"imagesets/drives/How Now.png",
		"imagesets/drives/Internal.png",
		"imagesets/drives/Jaguar.png",
		"imagesets/drives/Network.png",
		"imagesets/drives/On Beyond Zebra.png",
		"imagesets/drives/Rev2 Drive Black Network.png",
		"imagesets/drives/Rev2 Drive Black iDisk.png",
		"imagesets/drives/Rev2 Drive Black.png",
		"imagesets/drives/Rev2 Drive Ice Tiger Pelt.png",
		"imagesets/drives/Rev2 Drive Ice Tiger.png",
		"imagesets/drives/Rev2 Drive Tiger Pelt.png",
		"imagesets/drives/Rev2 Drive Tiger.png",
		"imagesets/drives/Skunk Works.png",
		"imagesets/drives/Stitched Leather.png",
		"imagesets/drives/iDisk.png"
	],
	"spaceships": [
		"imagesets/spaceships/Battlestar Galactica.png",
		"imagesets/spaceships/Defiant.png",
		"imagesets/spaceships/Discovery.png",
		"imagesets/spaceships/Eagle.png",
		"imagesets/spaceships/NCC-1701-A.png",
		"imagesets/spaceships/NCC-1701-B.png",
		"imagesets/spaceships/NCC-1701-C.png",
		"imagesets/spaceships/NCC-1701-D.png",
		"imagesets/spaceships/NCC-1701-E.png",
		"imagesets/spaceships/NCC-1701.png",
		"imagesets/spaceships/NX-01.png",
		"imagesets/spaceships/Raptor.png",
		"imagesets/spaceships/Republic Attack Cruiser.png",
		"imagesets/spaceships/Republic Gunship.png",
		"imagesets/spaceships/Viper Mark II.png",
		"imagesets/spaceships/Viper mark VII.png"
	],
	"folders": [
		"imagesets/folders/Applications Folder.png",
		"imagesets/folders/Apps Folder.png",
		"imagesets/folders/Aqua.png",
		"imagesets/folders/Cherry.png",
		"imagesets/folders/Classic Folder.png",
		"imagesets/folders/Connected Folder.png",
		"imagesets/folders/Drop Folder.png",
		"imagesets/folders/Favorites Folder.png",
		"imagesets/folders/Graphite.png",
		"imagesets/folders/Library Folder.png",
		"imagesets/folders/Library.png",
		"imagesets/folders/Lime.png",
		"imagesets/folders/Movies 2 Folder.png",
		"imagesets/folders/Movies.png",
		"imagesets/folders/OS.png",
		"imagesets/folders/Pictures.png",
		"imagesets/folders/Sounds.png",
		"imagesets/folders/Systm Folder.png",
		"imagesets/folders/Tangerine.png",
		"imagesets/folders/WoA 3.png"
	],
	"random": [
		"imagesets/random/(Bonus) WOA iPod Preview.png",
		"imagesets/random/Alien Egg.png",
		"imagesets/random/Bluetooth.png",
		"imagesets/random/ChalkEdit.png",
		"imagesets/random/Cherry.png",
		"imagesets/random/Crop Circle.png",
		"imagesets/random/CuneiEdit.png",
		"imagesets/random/Debugging.png",
		"imagesets/random/Eagle Transporter.png",
		"imagesets/random/Fuchikoma.png",
		"imagesets/random/H.A.L 2.png",
		"imagesets/random/H.A.L.png",
		"imagesets/random/Hedge Labyrinth.png",
		"imagesets/random/Jalapeno Pepper.png",
		"imagesets/random/Kaffe!.png",
		"imagesets/random/Martians!.png",
		"imagesets/random/Retro Toaster.png",
		"imagesets/random/Season 2 Alpha Logo.png",
		"imagesets/random/TOS - Pad.png",
		"imagesets/random/The Flying Sub.png",
		"imagesets/random/The One Ring.png",
		"imagesets/random/USB Disk.png",
		"imagesets/random/USCMC APC.png",
		"imagesets/random/iMac 2002.png"
	],
	"transformers": [
		"imagesets/transformers/Brawl.png",
		"imagesets/transformers/Bumblebee.png",
		"imagesets/transformers/Cliffjumper.png",
		"imagesets/transformers/Evil Decepticons.png",
		"imagesets/transformers/Grimlock.png",
		"imagesets/transformers/Heroic Autobots.png",
		"imagesets/transformers/Hound.png",
		"imagesets/transformers/Ironhide.png",
		"imagesets/transformers/Jazz.png",
		"imagesets/transformers/Laserbeak.png",
		"imagesets/transformers/Megatron.png",
		"imagesets/transformers/Onslaught.png",
		"imagesets/transformers/Optimus Prime.png",
		"imagesets/transformers/Rumble.png",
		"imagesets/transformers/Scrapper.png",
		"imagesets/transformers/Shrapnel.png",
		"imagesets/transformers/Skyfire.png",
		"imagesets/transformers/Soundwave.png",
		"imagesets/transformers/Starscream.png",
		"imagesets/transformers/Ultra Magnus.png"
	],
	"creatures": [
		"imagesets/creatures/A Regular Bean.png",
		"imagesets/creatures/Aquisition Al.png",
		"imagesets/creatures/Butterflying.png",
		"imagesets/creatures/Crabby.png",
		"imagesets/creatures/DJ Beanie.png",
		"imagesets/creatures/Dragthing.png",
		"imagesets/creatures/Duck.png",
		"imagesets/creatures/Firefox.png",
		"imagesets/creatures/Font Manager.png",
		"imagesets/creatures/Hank.png",
		"imagesets/creatures/Keynote.png",
		"imagesets/creatures/Mailman Marvin.png",
		"imagesets/creatures/Mozilla.png",
		"imagesets/creatures/Red.png",
		"imagesets/creatures/Rufus.png",
		"imagesets/creatures/Somatic Pages.png",
		"imagesets/creatures/Transmit.png",
		"imagesets/creatures/Wade.png",
		"imagesets/creatures/candybar.png",
		"imagesets/creatures/empty.png",
		"imagesets/creatures/favorites.png",
		"imagesets/creatures/iMovie.png",
		"imagesets/creatures/iSync Steve.png",
		"imagesets/creatures/ichat.png",
		"imagesets/creatures/itunes.png",
		"imagesets/creatures/mail.png",
		"imagesets/creatures/safari.png",
		"imagesets/creatures/sleep.png"
	],
	"newzealand":[
		"imagesets/newzealand/All Blacks.png",
		"imagesets/newzealand/Alt NZ Flag.png",
		"imagesets/newzealand/Bach.png",
		"imagesets/newzealand/Beehive.png",
		"imagesets/newzealand/Buzzy Bee.png",
		"imagesets/newzealand/Cricket.png",
		"imagesets/newzealand/Greenstone.png",
		"imagesets/newzealand/Jandals.png",
		"imagesets/newzealand/Kiwi Fruit.png",
		"imagesets/newzealand/Kiwi.png",
		"imagesets/newzealand/L&P.png",
		"imagesets/newzealand/Maori Flag.png",
		"imagesets/newzealand/Marae.png",
		"imagesets/newzealand/Moko 1.png",
		"imagesets/newzealand/Moko 2.png",
		"imagesets/newzealand/Mount Taranaki.png",
		"imagesets/newzealand/NZ Flag.png",
		"imagesets/newzealand/Nuclear Free.png",
		"imagesets/newzealand/Peter Jackson.png",
		"imagesets/newzealand/Rugby Ball.png",
		"imagesets/newzealand/Sheep.png",
		"imagesets/newzealand/Silver Fern.png",
		"imagesets/newzealand/Skater Cap.png",
		"imagesets/newzealand/Tomato Sauce.png",
		"imagesets/newzealand/Tramping Boots.png",
		"imagesets/newzealand/Truck with Dog.png",
		"imagesets/newzealand/Whale Watching.png",
		"imagesets/newzealand/newzealand.png"
	],
	"animals":[
		"imagesets/animals/bull.png",
		"imagesets/animals/cat.png",
		"imagesets/animals/chick.png",
		"imagesets/animals/chiken.png",
		"imagesets/animals/cow.png",
		"imagesets/animals/dog.png",
		"imagesets/animals/duck.png",
		"imagesets/animals/duckling.png",
		"imagesets/animals/goat.png",
		"imagesets/animals/goose.png",
		"imagesets/animals/horse.png",
		"imagesets/animals/male sheep.png",
		"imagesets/animals/mouse.png",
		"imagesets/animals/pig.png",
		"imagesets/animals/rabbit.png",
		"imagesets/animals/sheep.png"
	],
	"fruitandveg":[
		"imagesets/fruitandveg/apple.png",
		"imagesets/fruitandveg/apricot.png",
		"imagesets/fruitandveg/artichoke.png",
		"imagesets/fruitandveg/bananas.png",
		"imagesets/fruitandveg/carrot.png",
		"imagesets/fruitandveg/cherries.png",
		"imagesets/fruitandveg/chilli.png",
		"imagesets/fruitandveg/cucumber.png",
		"imagesets/fruitandveg/grape.png",
		"imagesets/fruitandveg/leek.png",
		"imagesets/fruitandveg/lemon.png",
		"imagesets/fruitandveg/melon.png",
		"imagesets/fruitandveg/mushroom.png",
		"imagesets/fruitandveg/orange.png",
		"imagesets/fruitandveg/peach.png",
		"imagesets/fruitandveg/pear.png",
		"imagesets/fruitandveg/peas.png",
		"imagesets/fruitandveg/potato.png",
		"imagesets/fruitandveg/pumpkin.png",
		"imagesets/fruitandveg/radish.png",
		"imagesets/fruitandveg/strawberry.png",
		"imagesets/fruitandveg/tomato.png",
		"imagesets/fruitandveg/turnip.png",
		"imagesets/fruitandveg/watermelon.png"
	],
	"balls":[
		"imagesets/balls/Baseball I.png",
		"imagesets/balls/Basketball I.png",
		"imagesets/balls/Cricket I.png",
		"imagesets/balls/Cricket II.png",
		"imagesets/balls/Football I.png",
		"imagesets/balls/Football II.png",
		"imagesets/balls/Lacrosse II.png",
		"imagesets/balls/Rugby II.png",
		"imagesets/balls/Soccer I.png",
		"imagesets/balls/Soccer II.png",
		"imagesets/balls/Softball II.png",
		"imagesets/balls/Volleyball II.png"
	]
}

// Calculate the size of the icon sets
var icon_set_sizes	= {};

// Set sizes are in steps of 8 cards
for ( var i = 8 ; i <= 56 ; i += 8 ) {

	size_name					= "" + i + ""
	icon_set_sizes[ size_name ]	= [];

	// Check each set
	for ( name in icon_sets ) {
		
		// How many cards in total can this set make?
		the_size	= ( icon_sets[ name ].length * 2 );
		
		// If it's big enough for this set, we'll take it
		if ( the_size >= i ) {
			icon_set_sizes[ size_name ].push( name );
		}
	}
}

/**
 * Load a given set into the game
 * @param	string	set_name	Name of the icon_set
 * @param	string	set_size	Number of cards to add
 */
function loadSet ( set_name, set_size ) {
		
	// Remember what set name we're using
	cur_set_name		= set_name;
	
	// Select a set of cards and mix them up a bit
	var card_vals_orig	= icon_sets[ set_name ];
	fisherYates( card_vals_orig );
	fisherYates( card_vals_orig );

	// How many cards do we have in this set?
	var how_many		= card_vals_orig.length;

	// Which is smaller, the requested set size or the number of cards?
	num_cards			= Math.min( set_size, ( how_many * 2 ) );

	// The actual values on the cards
	var card_vals		= new Array();

	// Add the cards onto the list
	for ( var i = 0 ; i < ( num_cards / 2 ) ; i++ ) {
		card_vals.push( card_vals_orig[ i ] );
		card_vals.push( card_vals_orig[ i ] );
	}

	// Mix the cards up a little more
	fisherYates( card_vals );
	fisherYates( card_vals );
	
	// Add our cards to the page
	for ( var i = 0 ; i < num_cards ; i++ ) {

		the_val		= "http://playr.co.uk/memory/"
					+ card_vals[ i ];
		card_text	= Builder.node( "img", {src:"card.back.png",width:60,height:80} );
		card_img	= Builder.node( "img", {src:the_val} );
		card_val	= Builder.node( "div", {className:'value'}, [card_img] );	

		card_val.style["display"]	= 'none';

		var opts	= {
			className:'card',
			onClick:'showCard( this );'
		}

		// Create a new card
		new_card				= Builder.node( "div", opts, [card_text, card_val] );
		new_card.card_value		= the_val;
		new_card.side_showing	= 'back';
		new_card.card_id		= i;
		new_card.card_matched	= false;
		new_card.card_clicked	= false;
		
		// Hide the card
		new_card.style["display"]	= "none";

		// Add the card to the list
		$( "game" ).appendChild( new_card );
		
		var opts 		= {
			duration:0.08,
			queue:{
				scope:"create",
				position:"end"
				}
		};

		new Effect.Appear( new_card, opts );
	}
}

/**
 * Load a new set by clearing the old set first then loading a new one
 * @param	string	set_name	Name of the icon_set
 * @param	string	set_size	Number of cards to add
 */
function loadNewSet ( set_name, set_size ) {
	clearNode( $( "game" ) );
	loadSet( set_name, set_size );
}

/**
 * Add a click to the counter
 */
function addClick ( ) {
	click_total++;
	scoreSet( click_total );
}

/**
 * Set the score
 * @param	integer	the_score	Score to set
 */
function scoreSet ( the_score ) {
	var the_text	= the_score;
	var the_elem	= $( "clicks" );
	the_elem.replaceChild( Builder.node( "span", {}, the_text ), the_elem.firstChild );
}

/**
 * Reset the score
 */
function scoreReset ( ) {
	scoreSet( "0" );
}

/**
 * Show a given card
 * @param	integer	card_num	The card number to show
 */
function showCard ( card ) {

	// Already matched cards and already clicked cards aren't allowed
	if ( card.card_matched == true ) { return true; }
	if ( card.card_clicked == true ) { return true; }
	
	// Start the timer on the first click of the cards
	if ( timer == false ) {
		timerReset();		
		timerStart();		
	}
	
	// If there's already two cards in the stack...
	if ( selected_cards.length >= 2 ) {
		
		// Clear the auto reset
		reset_timer		= false;
		
		// Hode these two cards now
		_hideCardNow( selected_cards.shift() );
		_hideCardNow( selected_cards.shift() );
	}

	// Add a click on
	addClick();
	card.card_clicked	= true;
	
	// Add the card to the list
	selected_cards.push( card );
	
	// Show it now please
	_showCardNow( card );
	
	// If this was the second card we clicked
	if ( selected_cards.length == 2 ) {

		// Get the two cards
		var card_1_elem		= selected_cards[ 0 ];
		var card_2_elem		= selected_cards[ 1 ];
				
		// Their values
		var card_1_val	= card_1_elem.card_value;
		var card_2_val	= card_2_elem.card_value;
		
		// If the two values match, we have a pair
		if ( card_1_val == card_2_val ) {
			
			// They're matched
			card_1_elem.card_matched	= true;
			card_2_elem.card_matched	= true;
			
			// We have two more matched cards
			matched_cards++;
			matched_cards++;

			// Remove them from the list
			trash	= selected_cards.shift();
			trash	= selected_cards.shift();

			// Add on our two new cards
			p_opts	= { 
				duration:0.3,
				queue:{
					scope:"selected",
					position:"end"
					}
				};
			
			// If we managed to match them all, game complete!
			if ( matched_cards >= num_cards ) {
				gameComplete();
			}
			
			// Just play the normal effect
			else {
				Effect.Pulsate( card_1_elem, p_opts );
				Effect.Pulsate( card_2_elem, p_opts );
			}
		}
	}
}

/**
 * Show the given card now
 * @param	integer	card_num	The index of the card
 */
function _showCardNow ( card ) {
	
	var card_text	= card.childNodes[ 0 ];
	var card_value	= card.childNodes[ 1 ];
	
	var opts 		= {
		duration:0.1,
		queue:{
			scope:"selected",
			position:"end"
			}
	};

	if ( card.side_showing == "back" ) {
		new Effect.Fade( card_text, opts );
		new Effect.Appear( card_value, opts );
		card.side_showing	= "front";
	}
}

/**
 * Hide the given card now
 * @param	integer	card_num	The index of the card
 */
function _hideCardNow ( card ) {
	
	var card_text	= card.childNodes[ 0 ];
	var card_value	= card.childNodes[ 1 ];
	
	var opts 		= {
		duration:0.1,
		queue:{
			scope:"selected",
			position:"end"
			}
	};

	// This card is no longer clicked
	card.card_clicked	= false;
	
	if ( card.side_showing == "front" ) {
		new Effect.Fade( card_value, opts );
		new Effect.Appear( card_text, opts );
		card.side_showing	= "back";
	}
}

/**
 * Schedule two cards to be hidden after the reset_delay 
 * @param	integer	card_1		The index of the first card
 * @param	integer	card_2		The index of the second card
 */
function resetCards ( card_1, card_2 ) {
	var reset_func	= "resetCardsNow( " 
					+ card_1 + ", " 
					+ card_2 + " "
					+ ")";
	reset_timer		= setTimeout( reset_func, reset_delay );
}

/**
* Hide two cards right now, resetting the timer
* @param	integer	card_1		The index of the first card
* @param	integer	card_2		The index of the second card
 */
function resetCardsNow ( card_1, card_2 ) {
	
	// Clear the timer
	reset_timer		= false;
	_hideCardNow( card_1 );
	_hideCardNow( card_2 );
}

/**
 * Reveal every card
 */
function revealAll ( ) {
	
	var end	 = $( "game" ).childNodes.length;
	for ( var i = 0 ; i < end ; i++ ) {

		var card		= $( "game" ).childNodes[ i ];
		
		if ( !( card.card_matched ) ) {
		
			var card_text	= card.childNodes[ 0 ];
			var card_value	= card.childNodes[ 1 ];

			var opts 		= {
				duration:0.08,
				queue: {
					scope:"revealAll",
					position:"end"
				}
			};

			new Effect.Fade( card_text, opts );
			new Effect.Appear( card_value, opts );
			card.card_view		= 'front';
			card.card_matched	= true;
		}
	}
}

/**
 * Reset the entire deck of cards
 */
function resetAll ( ) {
	
	var end	 = $( "game" ).childNodes.length;
	for ( var i = 0 ; i < end ; i++ ) {
		card_num		= i;
		var card		= $( 'game' ).childNodes[ card_num ];
		var card_text	= card.childNodes[ 0 ];
		var card_value	= card.childNodes[ 1 ];

		var opts 		= {
			duration:0.2,
			queue: {
				scope:'card_' + i,
				position:'end'
			}
		};

		new Effect.Fade( card_value, opts );
		new Effect.Appear( card_text, opts );
		card.side_showing	= 'back';
		card.card_matched	= false;
		card.card_clicked	= false;
	}

	matched_cards		= 0;
	click_total			= 0;
	selected_cards		= [];

	// Reset and start the timer
	timerReset();
	timerStart();
	scoreReset();
}

/**
 * Holder for the list of cards that will be flashed when the game
 * is complete.  This is used by _flashCard, called repeatedly in
 * a setTimeout method.
 */
var rand_cell_list;

/**
 * Flag the game as complete, play the winning notification
 */
function gameComplete ( ) {
	
	// Write the data to the server
	storeData( timer_total, click_total, cur_set_name, num_cards );
	
	// Time's up!
	timerStop();

	// If we're over the limit, we're not done.
	if ( limit_over == true ) {
		return false;
	}
	
	// If we're playing with levels, we've cleared one!
	if ( $( "level" ) ) {
		levelClear( play_level );
	}

	var end	 		= $( "game" ).childNodes.length;
	rand_cell_list	= [];

	// Add each number to the list 3 times
	for ( var i = 0 ; i < end ; i++ ) {
		rand_cell_list.push( i );
		rand_cell_list.push( i );
		
		// If this is the last level, we'll flash a few more
		if ( cur_level >= total_levels ) {
			rand_cell_list.push( i );
			rand_cell_list.push( i );
			rand_cell_list.push( i );
		}
	}

	// Randomise this list
	fisherYates( rand_cell_list );
	fisherYates( rand_cell_list );
	fisherYates( rand_cell_list );

	_flashCard();
}

/**
 * Run the winning notification by flashing cards in the list until there
 * are no more cards left to flass
 */
function _flashCard ( ) {
	
	if ( rand_cell_list.length > 0 ) {
		var card_num	= rand_cell_list.shift();
		var card		= $( "game" ).childNodes[ card_num ];
		
		var the_colours	= ['#88ff88'];
		
		// If it's the end we'll take some nicer colours
		if ( cur_level >= total_levels ) {
			the_colours	= [ '#ff8888',
							'#88ff88',
							'#8888ff',
							'#ffff88',
							'#ff88ff',
							'#88ffff' ];
		}
		
		var opts		= {
			startcolor:randomFromList( the_colours ),
			queue:{
				duration:0.6,
				scope:"theCard" + card_num,
				position:"end"
			}
		};
		new Effect.Highlight( card, opts );
		
		setTimeout( "_flashCard()", 35 );
	}
	else {
		return true;
	}
}

/**
 * Store the data for this game
 * @param	integer		the_time	Seconds taken to play the game
 * @param	integer		the_clicks	Clicks made
 * @param	string		the_name	Set name of cards used
 * @param	integer		the_size	Number of cards in the game
 */
function storeData ( the_time, the_clicks, the_name, the_size ) {

	// Create a cache-busting URL
	var theDate	= new Date();
	var url		= "data.php"
				+ "?t=" + the_time
				+ "&c=" + the_clicks
				+ "&n=" + the_name
				+ "&s=" + the_size
				+ "&d=" + theDate.getTime()
				+ "";

	// Options for the AJAX call.  Do everything quietly.
	var opts = {
		asynchronous: true,
	    method: 'GET',
	    onSuccess: function ( t ) { return false; },
	    on404: function( t ) { return false; },
	    onFailure: function( t ) { return false; }
	}
	new Ajax.Request( url, opts );
}

/**
 * Add a link to the next level to th level list
 * @param	integer		this_level	The level number
 */
function levelAdd ( this_level ) {

	if ( this_level < game_levels.length ) {
		var lv_num	= this_level;
		lv_num++;
		
		var level	= game_levels[ this_level ];
		level_name	= level["name"];
		level_text	= "Level " + lv_num + ": " + level_name;
		onClickText	= "levelLoad( " + this_level + ");";
		the_opts	= {
			className:"level",
			onClick:onClickText
		};
		the_text	= Builder.node( "li", the_opts, level_text );
		$( "level" ).appendChild( the_text );
		new Effect.Highlight( the_text, {startcolor:'#ff6666'} );
	}
}

/**
 * Denote that a level has been completed
 * @param	integer		this_level	The level number
 */
function levelClear ( this_level ) {
	
	if ( this_level >= total_levels ) { return false; }
	
	// Add one to the current level
	var the_level	= $( "level" ).childNodes[ this_level ];
	var opts	= {
		startcolor:'#66dd66',
		endcolor:'#bbffbb',
		afterFinish:function () { the_level.style["background"] = "#bbffbb"; }
	};
	new Effect.Highlight( the_level, opts );
	
	// Stop the timer
	timerStop();

	if ( this_level == cur_level ) {
		if ( cur_level < total_levels ) {
			cur_level++;
			levelAdd( cur_level );
		}
	}
}

/**
 * Load the content for a level
 * @param	integer		this_level	The level number
 */
function levelLoad ( this_level ) {

	// Reset and start the timer
	timerReset();
	scoreReset();
	limitReset();
	
	matched_cards	= 0;
	click_total		= 0;
	selected_cards	= [];
	
	play_level		= this_level;
	var level		= game_levels[ play_level ];
	
	// Get the level name and possible set of cards
	level_name		= level["name"];
	level_set		= level["set"];

	// Get the size of the level
	level_size		= level["size"];
	
	// Limits for the timer and clicks
	timer_limit		= level["time"];
	click_limit		= level["clicks"];
		
	// No specific set?
	if ( level_set == false ) {
		
		// Build a list of set names to pick from
		set_names	= [];
		for ( var name in icon_sets ) { set_names.push( name ); }
		level_set	= randomFromList(set_names);
	}
	
	// Take one from our list
	else {
		level_set	= randomFromList(level_set);
	}
	
	// Is this set large enough for this size?
	this_size	= ( icon_sets[ level_set ].length * 2 );
	if ( this_size < level_size ) {
		
		// Pick a random set from the sets of this size
		level_set	= randomFromList( icon_set_sizes[ level_size ] );
	}
	
	// Set the deck name that is shown
	elem	= $( "setname" );
	elem.replaceChild( Builder.node( "span", {}, [ level_set ] ), elem.firstChild );

	// Set the level name that is shown
	elem	= $( "levelname" );
	elem.replaceChild( Builder.node( "span", {}, [ level_name ] ), elem.firstChild );

	if ( click_limit != false ) {
		elem	= $( "clicklimit" );
		elem.replaceChild( Builder.node( "span", {}, [ click_limit ] ), elem.firstChild );
	}

	if ( timer_limit != false ) {
		elem		= $( "timelimit" );
		limit_str	= formatTime( timer_limit );
		elem.replaceChild( Builder.node( "span", {}, [ limit_str ] ), elem.firstChild );
	}
	
	// Load this set please
	loadNewSet( level_set, level_size );	
}

/**
 * Jump to a specific level
 * @param	integer		this_level	The level number
 */
function levelJumpTo ( this_level ) {
	
	// First clear the existing list of levels
	clearNode( $( "level" ) );
	
	// Set the current level
	cur_level	= this_level;

	// Add all the leves up to the one we have
	for ( var i = 0 ; i < this_level ; i++ ) { 
		levelAdd( i ); 
		levelClear( i );
	}

	// Now add the actual level
	levelAdd( this_level );
	levelLoad( this_level );
}

/**
 * Reset the timer point to the current number of seconds
 */
function timerReset ( ) {
	timerStop();
	var the_date	= new Date();
	timer_from		= Math.floor( the_date.getTime() / 1000 );
	timerSetTime( 0 );
	timer			= false;
}

/**
 * Start the timer
 */
function timerStart ( ) {
	
	var the_date	= new Date();
	timer_to		= Math.floor( the_date.getTime() / 1000 );

	// How many seconds since we were reset?
	timer_total		= timer_to - timer_from;
	timerSetTime( timer_total );

	// Check that our limits haven't been exceeded
	limitCheck();

	// Add the timer again in one second
	timer			= setTimeout( "timerStart()", 1000 );
}

/** 
 * Set the timer to the given number of seconds
 * @param	integer	the_time	Seconds to set the clock to
 */
function timerSetTime ( the_time ) {
	timer_string	= formatTime( the_time );
	new_time		= Builder.node( "span", {}, [ timer_string ] );
	$( "time" ).replaceChild( new_time, $("time").firstChild );
}

/**
 * Stop the timer
 */
function timerStop ( ) {
	clearTimeout( timer );
}

/**
 * Check that our possible limits have not been exceeded
 */
function limitCheck ( ) {

	var opts	= {
		startcolor:'#dd6666',
		endcolor:'#eeaaaa',
		duration:0.6
	};

	var elem;

	// If we have a time limit, and it's been exceeded
	if ( timer_limit != false ) {
		if ( timer_total > timer_limit ) {
			elem		= $( "timelimit" );
			new Effect.Highlight( elem, opts );
			elem.style["background"]	= "#eeaaaa";
			limit_over	= true;
		}
	}

	// If we have a time limit, and it's been exceeded
	if ( click_limit != false ) {
		if ( click_total > click_limit ) {
			elem		= $( "clicklimit" );
			new Effect.Highlight( elem, opts );
			elem.style["background"]	= "#eeaaaa";
			limit_over	= true;
		}
	}
}

/**
 * Reset our limit boxes and variables
 */
function limitReset ( ) {

	elem						= $( "timelimit" );
	elem.style["background"]	= '#ffffff';
	elem.replaceChild( Builder.node( "span", {}, "none" ), elem.firstChild );

	elem						= $( "clicklimit" );
	elem.style["background"]	= '#ffffff';
	elem.replaceChild( Builder.node( "span", {}, "none" ), elem.firstChild );

	limit_over	= false;	
}

/**
 * Enter a password to skip to a level
 */
function passwordTry ( pass ) {
	
	for ( var level in game_levels ) {
		
		var game_data	= game_levels[ level ];
		var known		= game_data["name"];
		
		// If the provided password matches this level
		if ( pass == known ) {
			levelJumpTo( level );
			
			// Highlight the password box for success
			new Effect.Highlight( "pass", {startcolor:'#bbffbb'} );
			return false;
		}
	}
	
	// Highlight the password box for error
	new Effect.Highlight( "pass", {startcolor:'#ffbbbb'} );
	return false;
}

/*
 * Endurance game functions
 */

// The initial game parameters
var endure_time			= 40;	// How many seconds between lines?
var endure_click		= 20;	// How many clicks between lines?

// The current game state
var endure_time_next	= 0;	// At what time do we expect the next row?
var endure_click_next	= 0;	// At what click to we get the next row?

// How the game gets harder
var endure_mod_time		= -1;	// How the time between rows changes with each new row
var endure_mod_click	= -1;	// How the clicks between rows changes with each new row

var endure_rows			= 0;	// How many rows are there in the game?


/**
 * Start a game of endurance
 */
function endureStart ( ) {
	
	// Clear the timer
	timerReset();
	
	// When do the next rows appear, for time and clicks?
	endure_time_next	= ( timer_total + endure_time );
	endure_click_next	= ( click_total + endure_click );
	
	// Add a row
	endureAddRow();
	
	// Start the clock!
	endureTimerStart();
}

/**
 * The countdown timer
 */
function endureTimerStart ( ) {
	
	var the_date		= new Date();
	timer_to			= Math.floor( the_date.getTime() / 1000 );

	// How many seconds since we were reset?
	timer_total			= timer_to - timer_from;
	timerSetTime( timer_total );
	
	// Check that we haven't reached our row limit
	endureLimitCheck();

	// Show how long until the next rows
	endureLimitShow();
	
	// Run this again in one second
	timer			= setTimeout( "endureTimerStart()", 1000 );
}

/**
 * Show the remaining time/click limit until the next row
 */
function endureLimitShow ( ) {
	
	var the_time	= Math.abs( timer_total - endure_time_next );
	var the_click	= Math.abs( click_total - endure_click_next );	
	
	// Set the time
	timer_string	= formatTime( the_time );
	new_time		= Builder.node( "span", {}, [ timer_string ] );
	$( "nexttime" ).replaceChild( new_time, $( "nexttime" ).firstChild );

	// Set the clicks
	new_click		= Builder.node( "span", {}, [ the_click ] );
	$( "nextclick" ).replaceChild( new_click, $( "nextclick" ).firstChild );
}

/**
 * Check that we've not overstepped the limits
 */
function endureLimitCheck ( ) {

	var the_time	= Math.floor( timer_total - endure_time_next );
	var the_click	= Math.floor( click_total - endure_click_next );	

	// If the time is over
	if ( the_time >= 0 ) { 

		// Modify the next break time
		endure_time			+= endure_mod_time;
		
		// Set the next timeout
		endure_time_next	= Math.floor( timer_total + endure_time );

		// Add a row
		endureAddRow(); 
	}

	// If the clicks are over
	if ( the_click >= 0 ) { 
		
		// Modify the next click time
		endure_click		+= endure_mod_click;
				
		// Set the next timeout
		endure_click_next	= Math.floor( click_total + endure_click );
				
		debug( "Click-total: " + click_total );
		debug( "Endure-click: " + endure_click );
		debug( "Endure-mod-click: " + endure_mod_click );
		debug( "Endure-click-next: " + endure_click_next );
		debug( "---" );

		// Add a row
		endureAddRow(); 
	}
}

/**
 * Add a new row to the endurance game
 */
function endureAddRow ( ) {
	
	// Build a list of set names to pick from
	set_names	= [];
	for ( var name in icon_sets ) { set_names.push( name ); }
	level_set	= randomFromList( set_names );
	level_size	= 8;
	
	// Load a new set
	loadSet( level_set, level_size );
	
	endure_rows++;
	endureSetRows();
}

/**
 * Display the current number of rows
 */
function endureSetRows ( ) {
	// Set the clicks
	var new_rows	= Builder.node( "span", {}, [ endure_rows ] );
	$( "currows" ).replaceChild( new_rows, $( "currows" ).firstChild );
}

/**
 * Utility functions below htis point
 */

/**
 * Format a number of seconds in mins/secs
 * @param	integer		the_time	Secondsto formar
 * @return	string		return		The formatted time in mm:ss format
 */
function formatTime ( the_time ) {
	timer_mins		= Math.floor( the_time / 60 );
	timer_secs		= ( the_time % 60 );
	return zero_pad( timer_mins ) + ":" + zero_pad( timer_secs );
}

/**
 * Output a debug message by appending it to the 
 * element with an ID of debug
 * @param	string	msg	The message to output
 */
function debug ( msg ) {
	$( "debug" ).appendChild( Builder.node( "p", {}, msg ) );
	return true;
}

/**
 * Set a select box to a given value
 * @param	string	elem	The ID of the element
 * @param	string	val		The value to set it to
 */
function setSelect ( elem, val ) {
	if ( $( elem ) ) { 
		the_elem		= $( elem );
		for ( var i = 0 ; i < the_elem.options.length ; i++ ) {
			if ( the_elem.options[ i ].value == val ) {
				the_elem.options[ i ].selected	= true;
			}
			else {
				the_elem.options[ i ].selected	= false;
			}
		}
	}
}

/**
 * Get a parameter from the URL, or return false
 * @param	string	strParamName	The value to find
 * @return	mixed	strReturn		The value, or false
 */
function getURLParam( name ) {
	var regexS	= "[\\?&]"+name+"=([^&#]*)";
	var regex	= new RegExp( regexS );
	var tmpURL	= window.location.href;
	var results	= regex.exec( tmpURL );

	if ( results == null ) {
		return false;
	}
	else {
		return unescape( results[1] );
	}
}

/**
 * Randomise an array using Fisher-Yates
 * @param	array	myArray		Array to randomise
 */
function fisherYates ( myArray ) {
  var i = myArray.length;
  if ( i == 0 ) return false;
  while ( --i ) {
     var j = Math.floor( Math.random() * ( i + 1 ) );
     var tempi = myArray[i];
     var tempj = myArray[j];
     myArray[i] = tempj;
     myArray[j] = tempi;
   }
}

/**
 * Clear the contents of a node
 * @param	mixed	target	The node to clear
 */
function clearNode ( target ) {
	var end	= target.childNodes.length;
	for ( i = ( end - 1 ) ; i >= 0 ; i-- ) {
		target.removeChild( target.childNodes[ i ] )
	}
}

/**
 * Pick a random item from a list of objects
 * @param	mixed	name_list	Possible list of [name1,name2,...]
 */
function randomFromList ( name_list ) {
	var length	= name_list.length;
	var rnum	= Math.random();
	var which	= Math.floor( ( rnum * length ) );
	var name	= name_list[ which ];
	return name;
}

/**
 * Zero pad a number
 * @param	string	input	The number to zero pad
 * @return	string	output	The number with one zero padding
 */
function zero_pad ( input ) {
	var output	= new Number( input );
	if ( output <= 9 ) { output = '0' + output; }
	return output;
}
