/* Lecture Code 4.2
 *
 * The mindless computer hearts player!
 *
 */

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <deque>
#include <utility>
#include <cstdlib>
#include <string>
#include <ctime>
#include <sstream>
using namespace std;

/* Type: suitT
 * A type that stores the suit of a playing card.
 */
enum suitT {Hearts, Diamonds, Clubs, Spades};

/* Type: valueT
 * A type that stores the value of playing card.
 * Note that the values are sorted in increasing
 * order here, so you can compare cards by
 * value.
 */
enum valueT {Two, Three, Four, Five, Six, Seven,
             Eight, Nine, Ten, Jack, Queen, King,
			 Ace};

/* Type: cardT
 * A type that represents a playing card.
 */
struct cardT
{
	suitT suit;
	valueT value;
};

/* Type: deckT
 * A deque of cards.
 */
typedef deque<cardT> deckT;

/* The number of cards in each player's hand. */
const int NUM_CARDS = 13;

/* The number of players in this game. */
const int NUM_PLAYERS = 4;

/* The number of total points to play to. */
const int MAX_POINTS = 100;

/* The names of the players in this game. */
const string PLAYER_NAMES[] = {"Harun al-Rashid",
                               "Charlemagne",
							   "Herod the Great",
							   "Julius Caesar"};

/* Type: playerT
 * A player in the game.
 */
struct playerT
{
	deckT hand;
	int pointsTotal;
	string name;
};


/* Randomize()
 * Seeds the randomizer so random shuffling will work
 * correctly.  Consult a reference for more information on srand.
 */
void Randomize()
{
	srand((unsigned int)time(NULL));
}

/* GetLine()
 * Gets a string from the user.
 */
string GetLine()
{
	string result;
	getline(cin, result);
	return result;
}

/* PrintCard(cardT &card)
 * Prints a card's value and suit.
 */
void PrintCard(cardT &card)
{
	switch(card.value)
	{
	case Jack:
		cout << 'J'; break;
	case Queen:
		cout << 'Q'; break;
	case King:
		cout << 'K'; break;
	case Ace:
		cout << 'A'; break;
	default:
		cout << (card.value + 2);
	}
	
	/* The low-order ASCII characters 0x03 - 0x06 are the playing card suits. */
	cout << (char(card.suit + '\x03')) << ' ';
}

/* InitPlayers(players)
 * Initializes the players to have no points and the
 * correct name.
 */
void InitPlayers(vector<playerT> &players)
{
	for(vector<playerT>::iterator itr = players.begin();
		itr != players.end(); ++itr)
	{
		itr->pointsTotal = 0;
		
		/* This next line uses iterator subtraction (which is just like pointer subtraction)
		 * to get the index of the player name in the PLAYER_NAMES array.
		 */
		itr->name = PLAYER_NAMES[itr - players.begin()];
	}
}

/* FillDeck(deck)
 * Fills the deck of cards with all 52 cards, in sorted
 * order.
 */
void FillDeck(deckT &deck)
{
	for(suitT suit = Hearts; suit <= Spades; suit = suitT(suit + 1))
		for(valueT value = Two; value <= Ace; value = valueT(value + 1))
		{
			cardT card;
			card.suit = suit;
			card.value = value;
			deck.push_back(card);
		}
}

/* GetWinningPlayerIndex(deck)
 * Returns the zero-based index of the player who
 * won the trick.
 */
int GetWinningPlayerIndex(deckT &playedCards)
{
	/* Get the suit we're supposed to follow. */
	suitT toFollow = playedCards.front().suit;

	int index = 0;
	valueT value = Two;

	for(int i = 0; i < playedCards.size(); i++)
	{
		/* If the card matches the suit and is bigger, store it as the winner. */
		if(playedCards[i].suit == toFollow &&
			playedCards[i].value > value)
		{
			index = i;
			value = playedCards[i].value;
		}
	}

	return index;
}

/* GetPoints(trick)
 * Returns the number of points in the specified trick.  There are much better ways to do this
 * that we'll talk about when we cover functors, but for now this approach is totally valid.
 */
int GetPoints(deckT &trick)
{
	int total = 0;
	for(deckT::iterator itr = trick.begin();
		itr != trick.end(); ++itr)
	{
		if(itr->suit == Hearts)
			total++;
		if(itr->suit == Spades && itr->value == Queen)
			total += 13;
	}

	return total;
}

/* PrintAllHands(players)
 * Prints out each player's hand.
 */
void PrintAllHands(vector<playerT> &players)
{
	for(int i = 0; i < players.size(); i++)
	{
		cout << players[i].name << " has ";
		for(int j = 0; j < players[i].hand.size(); j++)
			PrintCard(players[i].hand[j]);
		cout << endl;
	}
}

/* Shuffles the deck - random_shuffle is wonderful, isn't it? */
void ShuffleDeck(deckT &deck)
{
	random_shuffle(deck.begin(), deck.end());
}

/* Compares two cards first by suit and then by value.  Remember that STL comparison
 * functions should mimic the < operator.
 */
bool CompareTwoCards(cardT one, cardT two)
{
	if(one.suit < two.suit)
		return true;
	if(one.suit > two.suit)
		return false;
	return one.value < two.value;
}

/* Deals the cards out to the player.  Note that the number of cards in the deck isn't
 * modified.
*/
void DealCards(deckT &deck, vector<playerT> &players)
{
	for(int i = 0; i < players.size(); i++)
	{
		/* Deal the cards out. */
		copy(deck.begin() + NUM_CARDS * i, deck.begin() + NUM_CARDS * (i + 1),
			back_inserter(players[i].hand));

		/* Sort the cards by suit, then value. */
		sort(players[i].hand.begin(), players[i].hand.end(), CompareTwoCards);
	}
}

/* A comparison function that only compares the value of two cards. */
bool CompareByValueOnly(cardT one, cardT two)
{
	return one.value < two.value;
}

/* Called when the first player needs to lead a card.  For now, this simply picks
 * The lowest card in the player's hand.
 */
void PlayFirstCard(playerT &player, deckT& playedCards)
{
	deckT::iterator toPlay = min_element(player.hand.begin(), player.hand.end(), CompareByValueOnly);

	/* Add the card to the deck and remove it from the player's hand. */
	playedCards.push_back(*toPlay);
	player.hand.erase(toPlay);
}

/* A comparison function that only compares the suit of two cards. */
bool CompareBySuitOnly(cardT one, cardT two)
{
	return one.suit < two.suit;
}

/* Called to play a card that follows suit.  If there aren't any cards that follow suit, the computer
 * plays the largest card in its hand.
 */
void PlayFollowingCard(playerT &player, deckT& playedCards)
{
	/* equal_range returns iterators to the first and one-past-the-last element in a sorted container
	 * that compare equal to the specified element.
	 */
	pair<deckT::iterator, deckT::iterator> range =
		equal_range(player.hand.begin(), player.hand.end(), playedCards.front(), CompareBySuitOnly);


	/* Stores the card we'll play. */
	deckT::iterator toPlay;

	/* If there are no cards in range, pick the largest value we have. */
	if(range.first == range.second)
		toPlay = max_element(player.hand.begin(), player.hand.end(), CompareByValueOnly);
	
	/* Otherwise, pick the smallest card of that suit. */
	else
		toPlay = min_element(range.first, range.second, CompareByValueOnly);

	/* Play the card! */
	playedCards.push_back(*toPlay);
	player.hand.erase(toPlay);
}

/* Finishes the hand by awarding points to the winner and rotating the deal. */
void FinishHand(deckT &playedCards, vector<playerT> &players)
{
	int winnerIndex = GetWinningPlayerIndex(playedCards);

	players[winnerIndex].pointsTotal += GetPoints(playedCards);

	cout << players[winnerIndex].name << " takes the trick." << endl;
	
	/* Using iterator arithmetic (which is like pointer arithmetic), rotate the cards around. */
	rotate(players.begin(), players.begin() + winnerIndex, players.end());
}

/* Plays all thirteen hands. */
void PlayRound(vector<playerT> &players)
{
	for(int i = 0; i < NUM_CARDS; i++)
	{
		/* Stores the cards the players have played. */
		deckT playedCards;
		for(vector<playerT>::iterator itr = players.begin();
			itr != players.end(); ++itr)
		{
			/* First play can be anything, afterwards must follow suit. */
			if(itr == players.begin())
				PlayFirstCard(*itr, playedCards);
			else
				PlayFollowingCard(*itr, playedCards);

			/* Announce what card was played. */
			cout << itr->name << " played ";
			PrintCard(playedCards.back());
			cout << endl;
		}

		FinishHand(playedCards, players);
		cout << endl;
	}
}

/* Compares two players by name only. */
bool CompareNamesOnly(playerT one, playerT two)
{
	return one.name < two.name;
}

void PrintScores(vector<playerT> players)
{
	/* Sort alphabetically and print out the names. */
	sort(players.begin(), players.end(), CompareNamesOnly);
	for(vector<playerT>::iterator itr = players.begin(); itr != players.end(); ++itr)
		cout << itr->name << " has " << itr->pointsTotal << " points." << endl;

}

/* Returns whether a player has more than the maximum number of points. */
bool PlayerLost(playerT player)
{
	return player.pointsTotal >= MAX_POINTS;
}

/* Returns whether the game's over by checking if any players have lost. */
bool GameOver(vector<playerT> &players)
{
	return find_if(players.begin(), players.end(), PlayerLost) != players.end();
}

int main()
{
	deckT deck;
	vector<playerT> players(NUM_PLAYERS);
	InitPlayers(players);
	Randomize();
	FillDeck(deck);

	while(!GameOver(players))
	{
		ShuffleDeck(deck);
		DealCards(deck, players);
		PrintAllHands(players);
		PlayRound(players);
		PrintScores(players);
		GetLine();
	}
	
	return 0;
}