// Copyright 2007, Greggy Bits Software, LLC. All Rights Reserved.
//
// www.greggybits.com
//

// globals (er, um, I mean locals for our containing object)
//

var deckOfCards;
var topCardValue;

var gameInProgress = false;
var placingCards = false;

var placedCardCount;
var faceCardCount;

var messageTimer = 0;
var savedMessage;

var alertTimer;

// getElements(classname, tagname, root)
//
// Return an array of DOM elements that are members of the specified class,
// have the specified tagname, and are descendants of the specified root.
// 
// If no classname is specified, elements are returned regardless of class.
// If no tagname is specified, elements are returned regardless of tagname.
// If no root is specified, the document object is used.  If the specified
// root is a string, it is an element id, and the root
// element is looked up using getElementsById()
//
// This function is from the book JavaScript: The Definitive Guide, 5th Edition,
// by David Flanagan. Copyright 2006 O'Reilly Media, Inc. (ISBN #0596101996)
//
function getElements(classname, tagname, root) 
	{
	// If no root was specified, use the entire document
	// If a string was specified, look it up
	//
	if (!root)
		root = document;
	else if (typeof root == "string")
		root = document.getElementById(root);
	
	// if no tagname was specified, use all tags
	//
	if (!tagname)
		tagname = "*";

	// Find all descendants of the specified root with the specified tagname
	//
	var all = root.getElementsByTagName(tagname);

	// If no classname was specified, we return all tags
	//
	if (!classname)
		return all;

	// Otherwise, we filter the element by classname
	//
	var elements = [];
	for (var i = 0; i < all.length; i++)
		{
		var element = all[i];
		if (isMember(element, classname))
			elements.push(element);
		}

	// Note that we always return an array, even if it is empty
	//
	return elements;

	// isMember(element, classname)
	// Determine whether the specified element is a member of the specified
	// class.  This function is optimized for the common case in which the 
	// className property contains only a single classname.  But it also 
	// handles the case in which it is a list of whitespace-separated classes.
	//
	function isMember(element, classname)
		{
		var classes = element.className;
		if (!classes)
			return false;
		if (classes == classname)
			return true;

		// We didn't match exactly, so if there is no whitespace, then 
		// this element is not a member of the class
		//
		var whitespace = /\s+/;
		if (!whitespace.test(classes))
			return false;

		// If we get here, the element is a member of more than one class and
		// we've got to check them individually.
		//
		var c = classes.split(whitespace);
		for(var i = 0; i < c.length; i++)
			{
			if (c[i] == classname)
				return true;
			}

		return false;
		}
	}

// cardRankOfCardInPlace(place)
//
// dig down and find the rank of the card in this place
//
function cardRankOfCardInPlace(place)
	{
	var ranks = getElements("cardrank", "", place);

	if (ranks.length == 0)
		return "";

	var rank = ranks[0].innerHTML;
	if (rank == "A")
		return "1";
	else
		return rank;
	}

// cardRankOfCardInDeck()
//
// returns: the rank string of the card on the deck place
//
function cardRankOfCardInDeck()
	{
	return cardRankOfCardInPlace(deckPlace());
	}

// makeCardFromValue(value)
//
// make a playing card from a card value
//
//	input: integer value
//		a number from 1 to 52, representing a unique card in the deck
//
//	return: string
//		a DIV element suitable (ha!) for insertion into a card place element
//
function makeCardFromValue(value)
	{
	var rank = ((value - 1) % 13) + 1;
	var rankString;

	var suit = Math.floor(((value - 1) / 13) + 1);
	var suitString;
	var suitColorString;

	if (suit == 1)
		{
		suitString = "&spades;";
		suitColorString = "black";
		}
	else if (suit == 2)
		{
		suitString = "&clubs;";
		suitColorString = "black";
		}
	else if (suit == 3)
		{
		suitString = "&hearts;";
		suitColorString = "red";
		}
	else if (suit == 4)
		{
		suitString = "&diams;";
		suitColorString = "red";
		}

	if (rank <= 10 && rank > 1)
		{
		rankString = rank.toString();
		}
	else if (rank == 11)
		{
		rankString = "J";
		}
	else if (rank == 12)
		{
		rankString = "Q";
		}
	else if (rank == 13)
		{
		rankString = "K";
		}
	else if (rank == 1)
		{
		rankString = "A";
		}

	var cardString = "<div class='playingcard'\>";
	cardString = cardString + "<div class='" + suitColorString + "'\>";
	cardString = cardString + "<div class='cardrank'\>" + rankString + "<\/div\>";
	cardString = cardString + "<div class='cardsuit'\>" + suitString + "<\/div\>";
	cardString = cardString + "<\/div\><\/div\>";
	
	return cardString;
	}

// emptyCardPlaceContents(place)
//
// generate the content information for an empty card place
//
//	input: a card place element
//
//	return: string
//		a DIV element suitable for insertion into a card place element
//
function emptyCardPlaceContents(place)
	{
	return "<div class=\"emptyplace\">" + place.getAttribute("defaultrank") + "<\/div>";
	}

// placeHasCard(place)
//
//	returns: true if the place isn't empty
//
function placeHasCard(place)
	{
	return place.innerHTML != emptyCardPlaceContents(place);
	}

// setCommonCardPlaceStyles(place)
//
// apply the common style information for a card place
//
//	input: a card place element
//
function setCommonCardPlaceStyles(place)
	{
	place.style.textAlign = "center";
	place.style.width = "3em";
	place.style.height = "4em";
	place.style.minWidth = "3em";
	place.style.minHeight = "4em";
	place.style.maxWidth = "3em";
	place.style.maxHeight = "4em";
	place.style.color = "#BBB";
	place.style.visibility = "visible";
	}

// setEmptyCardPlaceStyles(place)
//
// apply the proper style information for an empty card place
//
//	input: a card place element
//
function setEmptyCardPlaceStyles(place)
	{
	place.style.border = "1px dashed gray";
	place.style.backgroundColor = defaultBackgroundColor;
	setCommonCardPlaceStyles(place);
	}

// setFilledCardPlaceStyles(place)
//
// apply the proper style information for a card place with a card in it
//
//	input: a card place element
//
function setFilledCardPlaceStyles(place)
	{
	place.style.border = "1px solid black";
	setCommonCardPlaceStyles(place);
	}

// setCardPlaceContents(place, contents)
//
// set the content information for an empty card place, setting styles appropriately
//
//	input: a card place element, and its contents
//
function setCardPlaceContents(place, contents)
	{
	if (contents == emptyCardPlaceContents(place))
		setEmptyCardPlaceStyles(place);
	else
		setFilledCardPlaceStyles(place);
	place.innerHTML = contents;
	}

// deckPlace()
//
//	returns: the deck place element
//
function deckPlace()
	{
	return document.getElementById("deck")
	}

// makeCardPlaceEmpty(place)
//
// set the content and style of card place for being an empty place
//
//	input: a card place element
//
function makeCardPlaceEmpty(place)
	{
	setCardPlaceContents(place, emptyCardPlaceContents(place));
	}

// makeDeckPlaceEmpty()
//
// set the content and style of deck place for being an empty place
//
function makeDeckPlaceEmpty()
	{
	makeCardPlaceEmpty(deckPlace());
	}

// makeAllCardPlacesEmpty()
//
// iterate through the card places and set their content and styles for being an empty place
//
function makeAllCardPlacesEmpty()
	{
	var places = getElements("cardplace", "", "cardplaces");
	for (var i in places)
		makeCardPlaceEmpty(places[i]);

	makeDeckPlaceEmpty();
	}

var defaultBackgroundColor = "#FFF";
var markedBackgroundColor = "#CFF";

// markedForDeletion(place)
//
//	returns: true if the place is marked, else false
//
function markedForDeletion(place)
	{
	return place.style.backgroundColor == markedBackgroundColor;
	}

// markOrUnmarkCardForDeletion(place)
//
// change the style of the card to indicate it's status, marked for deletion or not
//
//	input: the place to mark/unmark
//
function markOrUnmarkCardForDeletion(place)
	{
	if (markedForDeletion(place))
		place.style.backgroundColor = defaultBackgroundColor;
	else
		place.style.backgroundColor = markedBackgroundColor;
	}

// makeNewDeck()
//
// create and shuffle a new deck of cards (I picked 20 times. Whatever)
//
function makeNewDeck()
	{
	deckOfCards = new Array();
	for (var i = 0; i <= 51; i++)
		deckOfCards[i] = i + 1;

	for (i = 1; i <= 20; i++)
		deckOfCards.sort(randOrd);

	function randOrd()
		{
		return(Math.round(Math.random()) -0.5);
		}
	}

// topCard()
//
//	returns: the top card (its value, anyway), or 0 if there are no more cards
//
function topCard()
	{
	if (deckOfCards.length == 0)
		return 0;

	return deckOfCards.pop();
	}

// showTopCard()
//
// take the top card off the deck and give it to the deck place
//
function showTopCard()
	{
	topCardValue = topCard();
	var deckplace = deckPlace();

	if (topCardValue != 0)
		setCardPlaceContents(deckplace, makeCardFromValue(topCardValue));
	else
		setCardPlaceContents(deckplace, emptyCardPlaceContents(deckplace));
	}

// hideTopCard()
//
// put the top card back on the deck and empty the deck place
//
function hideTopCard()
	{
	var deckplace = deckPlace();

	if (topCardValue != 0)
		{
		setCardPlaceContents(deckplace, emptyCardPlaceContents(deckplace));
		deckOfCards.push(topCardValue);
		topCardValue = 0;
		}
	}

// hasOpenPlaceForRank(rank)
//
// check the card places for an open place of a given rank
//
//	input: a card rank to search for
//
//	returns: true is there is at least one open place, else false
//
function hasOpenPlaceForRank(rank)
	{
	var places = getElements("cardplace", "", "cardplaces");
	for (var i in places)
		{
		if (rank == places[i].getAttribute("defaultrank") && !placeHasCard(places[i]))
			return true;
		}

	return false;
	}

// canRemoveCards()
//
// check to see if there are any combinations of 10 to remove
//
//	returns: true if there are any combinations, else false
//
function canRemoveCards()
	{
	var buckets = new Array(false,false,false,false,false,false,false,false,false,false);	// 10 places

	var places = getElements("cardplace", "", "cardplaces");
	for (var i in places)
		{
		var rank = cardRankOfCardInPlace(places[i]);
		
		if (rank == 10)
			return true;

		if (rank < 10)
			{
			var rankValue = Number(rank);
			for (var j = 10 - rankValue; j > 0; --j)
				{
				if (buckets[j])
					{
					if (j + rankValue == 10)
						return true;
					else if (j + rankValue < 10)
						buckets[j + rankValue] = true;
					}
				}
			buckets[rankValue] = true;
			}
		}

	return false;
	}

// showTimedAlert(messageString)
//
// this is a workaround so that the screen updates before the alert shows... by waiting
// even a short amount of time, Safari on iPhone has time to draw the deck and the card
// just placed before showing the alert
//
function showTimedAlert(messageString)
	{
	alertTimer = setTimeout("alert(\"" + messageString + "\")", 10);
	}

// checkForWin()
//
// this rather optimistically named function tests current game conditions
// and alerts wins and losses
//
function checkForWin()
	{
	// got all the face cards in place? you win!
	//
	if (faceCardCount == 12)
		{
		setGameInProgress(false);
		showTimedAlert("You have won the game! In Bizarro World, that means you win $1 million!");
		return;
		}

	// no place to put the next card? you lose!
	//
	var rank = cardRankOfCardInDeck();
	if (rank == "K" || rank == "Q" || rank == "J")
		if (!hasOpenPlaceForRank(rank))
			{
			setGameInProgress(false);
			showTimedAlert("You have lost the game! There is no place to put the next card.");
			return;
			}

	// can't find any combinations of 10 to remove? you lose!
	// (only test this if the field is full)
	//
	if (placedCardCount == 16)
		if (!canRemoveCards())
			{
			setGameInProgress(false);
			showTimedAlert("You have lost the game! You can't remove any cards that add up to 10.");
			return;
			}
	}

// checkAndRemoveTens()
//
// browse through the card places, removing cards if they're marked for deletion and add
// up to 10
//
function checkAndRemoveTens()
	{
	var sum = 0;
	var places = getElements("cardplace", "", "cardplaces");
	for (var i in places)
		if (markedForDeletion(places[i]))
			sum = sum + Number(cardRankOfCardInPlace(places[i]));

	if (sum == 10)
		for (i in places)
			{
			if (markedForDeletion(places[i]))
				{
				makeCardPlaceEmpty(places[i]);
				placedCardCount -= 1;
				showTapDeckMessage();
				}
			}
	}

// showStatus()
//
// show the status text
//
function showStatus()
	{
	document.getElementById("status").style.visibility = "visible";
	}

// hideStatus()
//
// hide the status text
//
function hideStatus()
	{
	document.getElementById("status").style.visibility = "hidden";
	}

// doMessageTimerFired()
//
// put back the original message
//
function doMessageTimerFired()
	{
	messageTimer = 0;
	document.getElementById("status").innerHTML = savedMessage;
	}

// stopMessageTimer()
//
// clear the timeout
// there may be something else to do in here, otherwise we don't really need a function
//
function stopMessageTimer()
	{
	if (messageTimer != 0)
		{
		clearTimeout(messageTimer);
		doMessageTimerFired();
		}
	}

// showTimedMessage(messageFunction())
//
// call the messageFunction(), and then start a timer
// when the timer fires it puts back the original message
//
function showTimedMessage(messageFunction)
	{
	stopMessageTimer();
	savedMessage = document.getElementById("status").innerHTML;
	messageFunction();
	messageTimer = setTimeout("doMessageTimerFired()", 2500);
	}

// showMessageCommon(messageName)
//
// show the message named messageName
//
function showMessageCommon(messageName)
	{
	stopMessageTimer();
	document.getElementById("status").innerHTML = document.getElementById(messageName).innerHTML;
	showStatus();
	}

// showPlacingMessage()
//
// set the status text to the hint about placing cards
//
function showPlacingMessage()
	{
	showMessageCommon("placingCards");
	}

// showRemovingMessage()
//
// set the status text to the hint about removing cards
//
function showRemovingMessage()
	{
	showMessageCommon("removingCards");
	}

// showTapDeckMessage()
//
// set the status text to the hint about tapping the deck
//
function showTapDeckMessage()
	{
	showMessageCommon("tapDeck");
	}

// showNewGameMessage()
//
// set the status text to the hint about starting a new game
//
function showNewGameMessage()
	{
	showMessageCommon("newGameMessage");
	}

// showFaceCardMessage()
//
// set the status text to the hint about starting a new game
//
function showFaceCardMessage()
	{
	showMessageCommon("faceCardMessage");
	}

// setGameInProgress(state)
//
// handle some side effects of changing the game state
//
//	input: boolean
//
function setGameInProgress(inProgress)
	{
	gameInProgress = inProgress;

	if (inProgress)
		setPlacingCards(placingCards);
	else
		showNewGameMessage();
	}

// setPlacingCards(state)
//
// handle some side effects of changing the game state
//
//	input: boolean
//
function setPlacingCards(placing)
	{
	placingCards = placing;

	if (!gameInProgress)
		return;

	if (placing)
		showPlacingMessage();
	else
		showRemovingMessage();
	}

// tapCardPlaceCommon(place)
//
//
//	input: a card place element that was tapped
//
function tapCardPlaceCommon(place)
	{
	if (!gameInProgress)
		return;

	if (placingCards)
		{
		if (placeHasCard(place))
			return;

		placedCardCount += 1;

		if (cardRankOfCardInDeck() == place.getAttribute("defaultrank"))
			faceCardCount += 1;

		setCardPlaceContents(place, deckPlace().innerHTML);
		showPlacingMessage();
		showTopCard();

		if (placedCardCount == 16)
			{
			setPlacingCards(false);
			hideTopCard();
			}
		}
	else
		{
		if (!placeHasCard(place))
			return;

		if (cardRankOfCardInPlace(place) == place.getAttribute("defaultrank"))
			return;

		markOrUnmarkCardForDeletion(place);
		checkAndRemoveTens();
		if (!canRemoveCards())
			{
			tapDeckPlace(deckPlace());
			return; // return to avoid calling checkForWin() again
			}
		}

	checkForWin();
	}

// tapMiddlePlace(place)
//
// handle tapping on a middle place, rejecting kings and queens and jacks
//
//	input: a card place element that was tapped
//
function tapMiddlePlace(place)
	{
	if (!gameInProgress)
		return;

	if (placingCards)
		{
		var deckCardRank = cardRankOfCardInDeck();
		if (deckCardRank == "K" || deckCardRank == "Q" || deckCardRank == "J")
			{
			showTimedMessage(showFaceCardMessage);
			return;
			}
		}

	tapCardPlaceCommon(place);
	}

// tapKingPlace(place)
//
// handle tapping on a king place, rejecting queens and jacks
//
//	input: a card place element that was tapped
//
function tapKingPlace(place)
	{
	if (!gameInProgress)
		return;

	if (placingCards)
		{
		var deckCardRank = cardRankOfCardInDeck();
		if (deckCardRank == "Q" || deckCardRank == "J")
			{
			showTimedMessage(showFaceCardMessage);
			return;
			}
		}

	tapCardPlaceCommon(place);
	}

// tapQueenPlace(place)
//
// handle tapping on a queen place, rejecting kings and jacks
//
//	input: a card place element that was tapped
//
function tapQueenPlace(place)
	{
	if (!gameInProgress)
		return;

	if (placingCards)
		{
		var deckCardRank = cardRankOfCardInDeck();
		if (deckCardRank == "K" || deckCardRank == "J")
			{
			showTimedMessage(showFaceCardMessage);
			return;
			}
		}

	tapCardPlaceCommon(place);
	}

// tapJackPlace(place)
//
// handle tapping on a jack place, rejecting kings and queens
//
//	input: a card place element that was tapped
//
function tapJackPlace(place)
	{
	if (!gameInProgress)
		return;

	if (placingCards)
		{
		var deckCardRank = cardRankOfCardInDeck();
		if (deckCardRank == "K" || deckCardRank == "Q")
			{
			showTimedMessage(showFaceCardMessage);
			return;
			}
		}

	tapCardPlaceCommon(place);
	}

// tapDeckPlace(deckplace)
//
// ignore taps while placing cards, but handle taps while removing cards to
// start placing cards again
//
//	input: the deck place element
//
function tapDeckPlace(deckplace)
	{
	if (placingCards || placedCardCount == 16 || !gameInProgress)
		return;

	setPlacingCards(true);
	showTopCard();
	checkForWin();
	}

// newGame()
//
// set up a new game, cleaning out everything and shuffling a new deck
//
function newGame()
	{
	if (gameInProgress)
		{
		var sure = confirm("The current game isn't over yet. Are you sure you want to start a new one?")
		if (sure == false)
			return;
		}

	makeAllCardPlacesEmpty();

	makeNewDeck();
	showTopCard();

	setGameInProgress(true);
	setPlacingCards(true);
	placedCardCount = 0;
	faceCardCount = 0;

	// funny business, so that some strings will match later
	//
	var deck = deckPlace();
	var tempColor = deck.style.color;
	deck.style.color = defaultBackgroundColor;
	defaultBackgroundColor = deck.style.color;
	deck.style.color = markedBackgroundColor;
	markedBackgroundColor = deck.style.color;
	deck.style.color = tempColor;
	}

// showHelp()
//
// make the rules visible
//
function showHelp()
	{
	document.getElementById("help").style.visibility = "visible";
	}

// hideHelp()
//
// make the rules invisible
//
function hideHelp()
	{
	document.getElementById("help").style.visibility = "hidden";
	}
