#include "RakPeerInterface.h"
#include "RakNetworkFactory.h"
#include "BitStream.h"
#include "RakSleep.h"
#include <stdlib.h> // For atoi
#include <cstring> // For strlen
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include "MessageIdentifiers.h"
#include "LobbyClientInterface.h"
// Database specifications of input data, passed to some functions in the Lobby Client
#include "LobbyDBSpec.h"
#include "RankingServerDBSpec.h"
#include "TitleValidationDBSpec.h"
#include "EpochTimeToString.h"

#ifdef _CONSOLE_1
#elif defined (_CONSOLE_2)
#else
#include "LobbyClientPC.h"
#endif

bool quit;

using namespace RakNet;

class MyLobbyCallbacks : public LobbyClientInterfaceCB
{
	/// --- FUNCTION CALL RESULTS ---
	virtual void Login_Result(LobbyClientCBResult result){printf("Login_Result: "); result.DebugPrintf();};
	virtual void SetTitleLoginId_Result(LobbyClientCBResult result){printf("SetTitleLoginId_Result: "); result.DebugPrintf();};
	virtual void Logoff_Result(LobbyClientCBResult result){printf("Logoff_Result: "); result.DebugPrintf();};
	virtual void RegisterAccount_Result(LobbyClientCBResult result){printf("RegisterAccount_Result: "); result.DebugPrintf();};
	virtual void UpdateAccount_Result(LobbyClientCBResult result){printf("UpdateAccount_Result: "); result.DebugPrintf();};
	virtual void ChangeHandle_Result(LobbyClientCBResult result){printf("ChangeHandle_Result: "); result.DebugPrintf();};
	virtual void RetrievePasswordRecoveryQuestion_Result(LobbyClientCBResult result){printf("RetrievePasswordRecoveryQuestion_Result: "); result.DebugPrintf();};
	virtual void RetrievePassword_Result(LobbyClientCBResult result){printf("RetrievePassword_Result: "); result.DebugPrintf();};
	virtual void DownloadFriends_Result(LobbyClientCBResult result){printf("DownloadFriends_Result: "); result.DebugPrintf();};
	virtual void SendAddFriendInvite_Result(LobbyClientCBResult result){printf("SendAddFriendInvite_Result: "); result.DebugPrintf();};
	virtual void AcceptAddFriendInvite_Result(LobbyClientCBResult result){printf("SendAddFriendInvite_Result: "); result.DebugPrintf();};
	virtual void DeclineAddFriendInvite_Result(LobbyClientCBResult result){printf("DeclineAddFriendInvite_Result: "); result.DebugPrintf();};
	virtual void DownloadIgnoreList_Result(LobbyClientCBResult result){printf("DownloadIgnoreList_Result: "); result.DebugPrintf();};
	virtual void AddToOrUpdateIgnoreList_Result(LobbyClientCBResult result){printf("AddToOrUpdateIgnoreList_Result: "); result.DebugPrintf();};
	virtual void RemoveFromIgnoreList_Result(LobbyClientCBResult result){printf("RemoveFromIgnoreList_Result: "); result.DebugPrintf();};
	virtual void DownloadEmails_Result(LobbyClientCBResult result){printf("DownloadEmails_Result: "); result.DebugPrintf();};
	virtual void SendEmail_Result(LobbyClientCBResult result){printf("SendEmail_Result: "); result.DebugPrintf(); /* User should redownload the list after this, if they want the list to update */};
	virtual void DeleteEmail_Result(LobbyClientCBResult result){printf("DeleteEmail_Result: "); result.DebugPrintf();};
	virtual void UpdateEmailStatus_Result(LobbyClientCBResult result){printf("UpdateEmailStatus_Result: "); result.DebugPrintf();};
	virtual void SendIM_Result(LobbyClientCBResult result){printf("SendIM_Result: "); result.DebugPrintf();};
	virtual void CreateRoom_Result(LobbyClientCBResult result){printf("CreateRoom_Result: "); result.DebugPrintf();};
	virtual void DownloadRooms_Result(LobbyClientCBResult result){printf("DownloadRooms_Result: "); result.DebugPrintf();};
	virtual void LeaveRoom_Result(LobbyClientCBResult result){printf("LeaveRoom_Result: "); result.DebugPrintf();};
	virtual void JoinRoom_Result(LobbyClientCBResult result){printf("JoinRoom_Result: "); result.DebugPrintf();};
	virtual void JoinRoomByFilter_Result(LobbyClientCBResult result){printf("JoinRoomByFilter_Result: "); result.DebugPrintf();};
	virtual void RoomChat_Result(LobbyClientCBResult result){printf("RoomChat_Result: "); result.DebugPrintf();};
	virtual void InviteToRoom_Result(LobbyClientCBResult result){printf("InviteToRoom_Result: "); result.DebugPrintf();};
	virtual void SetReadyToPlayStatus_Result(LobbyClientCBResult result){printf("SetReadyToPlayStatus_Result: "); result.DebugPrintf();};
	virtual void KickRoomMember_Result(LobbyClientCBResult result){printf("KickRoomMember_Result: "); result.DebugPrintf();};
	virtual void SetRoomIsHidden_Result(LobbyClientCBResult result){printf("SetRoomIsHidden_Result: "); result.DebugPrintf();};
	virtual void SetRoomAllowSpectators_Result(LobbyClientCBResult result){printf("SetRoomAllowSpectators_Result: "); result.DebugPrintf();};
	virtual void ChangeNumSlots_Result(LobbyClientCBResult result){printf("ChangeNumSlots_Result: "); result.DebugPrintf();};
	virtual void GrantModerator_Result(LobbyClientCBResult result){printf("GrantModerator_Result: "); result.DebugPrintf();};
	virtual void StartGame_Result(LobbyClientCBResult result){printf("StartGame_Result: "); result.DebugPrintf();};
	virtual void SubmitMatch_Result(LobbyClientCBResult result){printf("SubmitMatch_Result: "); result.DebugPrintf();};
	virtual void DownloadRating_Result(LobbyClientCBResult result){printf("DownloadRating_Result: "); result.DebugPrintf();};
	virtual void QuickMatch_Result(LobbyClientCBResult result){printf("QuickMatch_Result: "); result.DebugPrintf();};
	virtual void CancelQuickMatch_Result(LobbyClientCBResult result){printf("CancelQuickMatch_Result: "); result.DebugPrintf();};
	virtual void DownloadActionHistory_Result(LobbyClientCBResult result){printf("DownloadActionHistory_Result: "); result.DebugPrintf();};
	virtual void AddToActionHistory_Result(LobbyClientCBResult result){printf("AddToActionHistory_Result: "); result.DebugPrintf();};

	/// --- EVENT_NOTIFICATIONS ---
	virtual void ReceiveIM_Notify(const char *senderHandle, const char *body, LobbyClientUserId* senderId)	{
		printf("ReceiveIM (%i, %s): %s\n", *senderId, senderHandle, body);
	};
	virtual void FriendStatus_Notify(const char *friendName, LobbyClientUserId *friendId, FriendStatus status)	{
		printf("FriendStatus_Notify: %s status = ", friendName);
//		if (status==FRIEND_STATUS_NEW_FRIEND) printf("now friends.\n");
//		else 
			if (status==FRIEND_STATUS_NO_LONGER_FRIENDS) printf("no longer friends.\n");
		else if (status==FRIEND_STATUS_FRIEND_ONLINE) printf("online.\n");
		else if (status==FRIEND_STATUS_FRIEND_OFFLINE) printf("offline.\n");
	}
	virtual void IncomingEmail_Notify(LobbyClientUserId senderId, const char *senderHandle, const char *subject){printf("Incoming email from %s (%i), subject=%s\n", senderHandle, senderId, subject);};
	virtual void FriendInvitation_Notify(FriendInvitation_Notification *notification){
		if (notification->request)
			printf("%s has asked to be our friend.\n", notification->invitorHandle);
		else if (notification->accepted)
			printf("%s has accepted our friendship.\n", notification->inviteeHandle);
		else
			printf("%s has rejected our friendship.\n", notification->inviteeHandle);
	};
	virtual void RoomMemberDrop_Notify(RoomMemberDrop_Notification *notification){printf("Room member dropped.\n");};
	virtual void RoomMemberJoin_Notify(RoomMemberJoin_Notification *notification){printf("Room member joined.\n");};
	virtual void RoomMemberReadyStateChange_Notify(RoomMemberReadyStateChange_Notification *notification){
		printf("User %i readyState=%i\n", *notification->member, notification->isReady);};
	virtual void RoomNewModerator_Notify(RoomNewModerator_Notification *notification){printf("New moderator.\n");};
	virtual void RoomSetIsHidden_Notify(RoomSetroomIsHidden_Notification *notification){printf("RoomSetIsHidden_Notify");};
	virtual void RoomSetAllowSpectators_Notify(RoomSetAllowSpectators_Notification *notification){printf("RoomSetAllowSpectators_Notify");};
	virtual void RoomChangeNumSlots_Notify(RoomChangeNumSlots_Notification *notification){printf("RoomChangeNumSlots_Notify");};
	virtual void ChatMessage_Notify(LobbyClientUserId *senderId, const char *senderHandle, const char *message,
		void *binaryData,unsigned int binaryDataLength){printf("%s: %s\n", senderHandle, message);}
	virtual void RoomInvite_Notify(LobbyClientUserId *senderId, const char *senderHandle, const char *message, void *binaryData,
		unsigned int binaryDataLength, LobbyClientRoomId *roomId, const char *roomName, bool privateSlotInvitation){
			printf("%s: %s invited to room %s (%i)\n", senderHandle, message, roomName, *roomId);}
	virtual void RoomDestroyed_Notify(void){printf("Room destroyed. Going back to lobby.\n");};
	virtual void KickedOutOfRoom_Notify(KickedOutOfRoom_Notification *notification){
		printf("Kicked out of room by %s (%i)\n", notification->memberHandle, * notification->member);	};
	virtual void QuickMatchTimeout_Notify(QuickMatchTimeout_Notification *notification){printf("Quick match timed out. Back in lobby.\n");};
	virtual void StartGame_Notify(StartGame_Notification *notification){
		printf("Game should be started now.\n");
	};
};

int main(void)
{
	RakPeerInterface *rakPeer=RakNetworkFactory::GetRakPeerInterface();

	SocketDescriptor socketDescriptor(0,0);
	if (rakPeer->Startup(1,30,&socketDescriptor, 1)==false)
	{
		printf("Start call failed!\n");
		return 0;
	}
	
	printf("Console based lobby client test (PC)\n");
	printf("Difficulty: Intermediate\n\n");

	// PC version of the lobby client (all you need unless you are doing consoles)
	LobbyClientPC lobbyClient;
	// LobbyClientPC is a plugin so it updates and checks messages automatically when you call RakPeer::Receive()
	rakPeer->AttachPlugin(&lobbyClient);

	// Setup that class we defined above to get the notification callbacks.
	// This is necessary as most operations are asynchronous
	MyLobbyCallbacks callbacks;
	lobbyClient.SetCallbackInterface(&callbacks);

	// In this sample we only connect to the lobby server.
	// This is set by the first system we connect to
	SystemAddress serverLobbyAddress = UNASSIGNED_SYSTEM_ADDRESS;

	unsigned short port;
	char str[512];
	printf("Enter port of lobby server: ");
	gets(str);
	if (str[0]!=0)
		port = atoi(str);
	else
		port=60000; // Arbitrary, Hardcoded into LobbyServerTest.cpp
	printf("Enter IP address of lobby server: ");
	gets(str);
	if (str[0]==0)
		strcpy(str, "127.0.0.1"); // Loopback
	SocketDescriptor sd(0,0); // Autochoose local port
	// Start RakNet
	rakPeer->Startup(1,100,&sd, 1);
	// Connect to the server. If and when we get back ID_CONNECTION_REQUEST_ACCEPTED we will login
	rakPeer->Connect(str,port,0,0,0);	


	// TODO - Clans database
	char ch;
	printf("Commands:\n"
		"(~) Quit\n"
		"(A) Login\n"
		"(B) Set TitleId\n"
		"(C) Logoff\n"
		"(D) Register an account\n"
		"(E) Retrieve password recovery question from handle\n"
		"(F) Retrieve password from password recovery question\n"
		"(G) Update account\n"
		"(H) Change handle\n"
		"(I) Download friends\n"
		"(J) Send add friend invite\n"
		"(K) Accept add friend invite\n"
		"(L) Decline add friend invite\n"
		"(M) Get friends list (print them)\n"
		"(N) Oops skipped this letter :)\n"
		"(O) Download ignore list\n"
		"(P) Add to or update ignore list\n"
		"(Q) Remove from ignore list\n"
		"(R) Get ignore list (print it)\n"
		"(S) Download emails\n"
		"(T) Send email\n"
		"(U) Delete email\n"
		"(V) Update email status\n"
		"(W) Read emails (print it)\n"
		"(X) Send IM\n"
		"(Y) Create room\n"
		"(Z) Download rooms\n"
		"(0) Get All rooms (print them)\n"
		"(1) Leave room\n"
		"(2) Join room\n"
		"(3) Room chat\n"
		"(4) Room invite\n"
		"(5) Set ready to play status\n"
		"(6) Kick room member\n"
		"(7) Set room invite only\n"
		"(8) Set room allow spectators\n"
		"(9) Change room slots\n"
		"(!) Grant moderator\n"
		"(@) Start game\n"
		"(#) Quick match\n"
		"($) Cancel Quick match\n"
		"(%%) Join room by filter\n"
		"(^) Download action history\n"
		"(&) Add to action history\n"
		"(*) Get action history (print it)\n"
		"(() Print recently met users (client-side only)\n"
		"()) Submit match\n"
		"(-) Get ranking\n"
		"(=) Print rating\n"
		);

	bool quit=false;
	Packet *packet;
	while (quit==false)
	{
		if (kbhit())
		{
			ch=getch();
			if (ch!='\n' && ch!='\r')
				printf("Command: (%c) ", ch);
			switch (ch)
			{
				case '~':
				{
					printf("Quitting...\n");
					quit=true;
					break;
				}

				case 'a':
				{
					// Login
					printf("Logging into lobby server.\n");
					char userHandle[512];
					char userPw[512];

					printf("Enter handle (string): ");
					gets(userHandle);
					if (userHandle[0]==0) strcpy(userHandle, "Rak'kar the great");

					printf("Enter user password (string): ");
					gets(userPw);
					if (userPw[0]==0) strcpy(userPw, "fakePW");

					lobbyClient.Login(userHandle,userPw,serverLobbyAddress);

					break;
				}

				case 'b':
				{
					// Set title ID
					char titleIdStr[512];
					char titlePw[512];
					printf("Set title ID.\n");
					printf("Enter title name (string): ");
					gets(titleIdStr);
					if (titleIdStr[0]==0) strcpy(titleIdStr, "Hangman EXTREME!");

					// For games in which users can host their own servers, the password is not secret so is just an identifier for the game.
					// For games where users cannot host their own servers, the password should be kept secure so that ranking can be uploaded accurately.
					printf("Enter title identifier / password.\nThis should have been setup on the database beforehand.\nSee the LobbyServerTest sample.\n(binary, entered here as string): ");
					gets(titlePw);
					if (titlePw[0]==0) strcpy(titlePw, "SECRET_PER_GAME_LOGIN_PW_PREVIOUSLY_SETUP_ON_THE_DB");
					int titleLoginPwLength = (int) strlen(titlePw);
					lobbyClient.SetTitleLoginId(titleIdStr,titlePw,titleLoginPwLength);

					break;
				}

				case 'c':
				{
					lobbyClient.Logoff();
					break;
				}

				case 'd':
				{
					// Register
					printf("Registering a new account.\n");
					LobbyDBSpec::CreateUser_Data accountData;

					// These two fields are mandatory
					printf("Enter what handle you want to use: ");
					gets(str);
					if (str[0]==0) strcpy(str, "Rak'kar the great");
					accountData.handle=str;
					printf("Enter your password: ");
					gets(str);
					if (str[0]==0) strcpy(str, "fakePW");
					accountData.password=str;
					// These two are optional, as are all the rest
					printf("Enter your password recovery question: ");
					gets(str);
					if (str[0]==0) strcpy(str, "2+2=?");
					accountData.passwordRecoveryQuestion=str;
					printf("Enter your password recovery answer: ");
					gets(str);
					if (str[0]==0) strcpy(str, "4");
					accountData.passwordRecoveryAnswer=str;
					printf("Enter your email address: ");
					gets(str);
					if (str[0]==0) strcpy(str, "johndoe@fakeEmailAddress.com");
					accountData.emailAddress=str;

					// There are a lot of other fields, we will leave them blank for now to keep the sample clear
					lobbyClient.RegisterAccount(&accountData, serverLobbyAddress);
					break;
				}

				case 'e':
				{
					// Retrieve password recovery question
					printf("Retrieving password recovery question.\n");
					char userHandle[512];

					printf("Enter handle (string): ");
					gets(userHandle);
					if (userHandle[0]==0) strcpy(userHandle, "Rak'kar the great");

					lobbyClient.RetrievePasswordRecoveryQuestion(userHandle, serverLobbyAddress);
					break;
				}

				case 'f':
				{
					// Retrieve password
					printf("Retrieving password.\n");

					char userHandle[512];
					printf("Enter handle (string): ");
					gets(userHandle);
					if (userHandle[0]==0) strcpy(userHandle, "Rak'kar the great");

					char pwRecoveryAnswer[512];
					printf("Enter password recovery answer (string): ");
					gets(pwRecoveryAnswer);
					if (pwRecoveryAnswer[0]==0) strcpy(pwRecoveryAnswer, "4");

					lobbyClient.RetrievePassword(userHandle, pwRecoveryAnswer, serverLobbyAddress);
					break;
				}

				case 'g':
				{
					printf("Updating an account.\n");
					LobbyDBSpec::UpdateUser_Data userData;
					
					userData.updateCCInfo=true;
					userData.updateBinaryData=false;
					userData.updatePersonalInfo=false;
					userData.updateEmailAddr=false;
					userData.updatePassword=false;
					userData.updateCaptions=false;
					userData.updateOtherInfo=false;
					userData.updatePermissions=false;
					userData.updateAccountNumber=true;
					userData.updateAdminLevel=false;
					userData.updateAccountBalance=false;

					char str[512];
					printf("Enter your CCV: ");
					gets(str);
					if (str[0]==0) strcpy(str, "12345");
					userData.input.creditCardCVV = str; // Should fail if defaultAllowUpdateCCInfo==false, or if the title wans't yet set

					printf("Enter your account number: ");
					gets(str);
					if (str[0]==0) strcpy(str, "98765");
					userData.input.accountNumber = atoi(str); // Should fail if defaultAllowUpdateAccountNumber==false, or if the title wans't yet set

					lobbyClient.UpdateAccount(&userData);
					break;
				}

				case 'h':
				{
					printf("Changing handle.\n");
					char str[512];
					printf("Enter handle (string): ");
					gets(str);
					if (str[0]==0) strcpy(str, "My new handle");

					lobbyClient.ChangeHandle(str);
					break;
				}

				case 'i':
				{
					printf("Download friends.\n");
					lobbyClient.DownloadFriends(true);
					break;
				}

				case 'j':
				{
					printf("Send add friend invite.\n");
					char str[512];
					char friendHandle[512];
					printf("Enter invitation text: ");
					gets(str); // Can be blank
					printf("Enter friend handle: ");
					gets(friendHandle);
					if (friendHandle[0]==0) strcpy(friendHandle, "Rak'kar the great");
					lobbyClient.SendAddFriendInvite(friendHandle, 0, str, 0);
					break;
				}

				case 'k':
				{
					printf("Accept add friend invite.\n");
					char str[512];
					char friendIdStr[512];
					printf("Enter acceptance text: ");
					gets(str); // Can be blank
					printf("Enter friend id (integer): ");
					gets(friendIdStr);
					if (friendIdStr[0]==0) strcpy(friendIdStr, "1");
					LobbyClientUserId id;
					id = atoi(friendIdStr);
					lobbyClient.AcceptAddFriendInvite(&id, str, 0);

					break;
				}

				case 'l':
				{
					printf("Decline add friend invite.\n");

					char str[512];
					char friendIdStr[512];
					printf("Enter declination text: ");
					gets(str); // Can be blank
					printf("Enter friend id (integer): ");
					gets(friendIdStr);
					if (friendIdStr[0]==0) strcpy(friendIdStr, "1");
					LobbyClientUserId id;
					id = atoi(friendIdStr);
					lobbyClient.DeclineAddFriendInvite(&id, str, 0);

					break;
				}

				case 'm':
				{
					printf("Get friends list (currently downloaded).\n");
					const DataStructures::List<LC_Friend *>& friendsList = lobbyClient.GetFriendsList();
					unsigned i;
					if (friendsList.Size()>0)
					{
						for (i=0; i < friendsList.Size(); i++)
						{
							printf("ID=%i isOnline=%i, Name=%s\n", *(friendsList[i]->id), friendsList[i]->isOnline, friendsList[i]->handle);
						}
					}
					else
					{
						printf("No friends downloaded.\n");
					}
					
					break;
				}

				case 'o':
				{
					printf("Downloading ignore list.\n");
					lobbyClient.DownloadIgnoreList(true);
					break;
				}

				case 'p':
				{
					printf("Add to or update ignore list.\n");

					char actionsStr[512];
					char userHandle[512];
					unsigned int userId;
					printf("Enter ignored user id (integer): ");
					gets(userHandle);
					userId = atoi(userHandle);
					printf("Enter actions string (arbitrary): ");
					gets(actionsStr);
					lobbyClient.AddToOrUpdateIgnoreList(0, &userId, actionsStr);
					break;
				}

				case 'q':
				{
					printf("Remove from ignore list.\n");
					char userHandle[512];
					unsigned int userId;
					printf("Enter ignored user id (integer): ");
					gets(userHandle);
					userId = atoi(userHandle);						
					lobbyClient.RemoveFromIgnoreList(&userId);
					break;

				}

				case 'r':
				{
					printf("Print ignore list (currently downloaded).\n");
					const DataStructures::List<LC_Ignored *>& ignoreList = lobbyClient.GetIgnoreList();
					unsigned i;
					if (ignoreList.Size()>0)
					{
						for (i=0; i < ignoreList.Size(); i++)
						{
							printf("ID=%i ActionString=%s\n", *(ignoreList[i]->id), ignoreList[i]->actionString);
						}
					}
					else
					{
						printf("Either nobody is ignored, or we didn't download the ignored users list yet.\n");
					}
					break;
				}

				case 's':
				{
					char str[256];
					printf("Downloading emails.\n");
					printf("Download from (I)nbox or (S)ent emails?: ");
					gets(str);
					bool inbox=(str[0]=='i');
					printf("Sort by date (A)scending or (D)escending?: ");
					gets(str);
					bool ascendingSort=(str[0]=='a');

					lobbyClient.DownloadEmails(ascendingSort, inbox, 0);
					
					break;
				}

				case 't':
				{
					printf("Sending an email.\n");

					LobbyDBSpec::SendEmail_Data data;
					char inputStr[4096];

					while (1)
					{
						LobbyDBSpec::UserIdOrHandle uioh;

						printf("Enter TO: recipient ID? (Enter to stop) (y/n): ");
						gets(inputStr);
						if (inputStr[0]=='y')
						{
							printf("Enter TO: user ID (integer): ");
							gets(inputStr);
							uioh.hasUserId=true;
							uioh.userId=atoi(inputStr);
						}
						else if (inputStr[0]==0)
							break;
						else
						{
							printf("Enter TO: user handle: ");
							gets(inputStr);
							uioh.hasUserId=false;
							uioh.userHandle=inputStr;
						}

						data.to.Insert(uioh);
					}

					printf("Enter subject: ");
					gets(inputStr);
					data.subject=inputStr;

					printf("Enter body: ");
					gets(inputStr);
					data.body=inputStr;

					// Test attachments
					data.attachment=new char [2];
					data.attachmentLength=2;
					data.attachment[0]='Z';
					data.attachment[1]=0;

					// Emails can be tagged with a status number. Can use for read, priority, etc.
					data.initialSenderStatus=63;
					data.initialRecipientStatus=64;

					// Mark sent email as initially opened.
					// Note: Received mail always marked as not opened until updated with UpdateEmailStatus
					data.wasOpened=true;

					/// Puts this functor on the processing queue. It will process sometime later in a thread.
					lobbyClient.SendEmail(&data, 0);

					// data.attachment automatically deallocated in the destructor of LobbyDBSpec::SendEmail_Data

					break;
				}

				case 'u':
				{
					printf("Deleting an email.\n");
					
					char str[512];
					EmailId emailId;
					printf("Enter email id (integer): ");
					gets(str);
					emailId = atoi(str);						
					lobbyClient.DeleteEmail(&emailId);

					break;
				}

				case 'v':
				{
					printf("Updating email status.\n");
					char str[512];
					EmailId emailId;
					printf("Enter email id (integer): ");
					gets(str);
					emailId = atoi(str);
					int newStatus;
					printf("Enter status (integer): ");
					gets(str);
					newStatus = atoi(str);
					bool wasOpened;
					printf("Mark the email as opened? (y/n): ");
					gets(str);
					if (str[0]=='y')
						wasOpened=true;
					else
						wasOpened=false;
					lobbyClient.UpdateEmailStatus(&emailId, newStatus, wasOpened);

					break;
				}

				case 'w':
				{
					printf("Read emails (print them)\n");

					const DataStructures::List<LobbyDBSpec::SendEmail_Data *> &inbox = lobbyClient.GetInboxEmailList();
					const DataStructures::List<LobbyDBSpec::SendEmail_Data *> &sentEmails = lobbyClient.GetSentEmailList();

					unsigned i;
					printf("INBOX:\n");
					if (inbox.Size()==0)
						printf("<EMPTY> (or none downloaded yet)\n");
					for (i=0; i < inbox.Size(); i++)
					{
						printf("%i. id.userId=%i\n", i+1, inbox[i]->id.userId);
						printf("%i. id.userHandle=%s\n", i+1, inbox[i]->id.userHandle.C_String());
						printf("%i. emailMessageID=%i\n", i+1, inbox[i]->emailMessageID);
						printf("%i. subject=%s\n", i+1, inbox[i]->subject.C_String());
						printf("%i. body=%s\n", i+1, inbox[i]->body.C_String());
						printf("%i. creationTime=%s\n", i+1, EpochTimeToString(inbox[i]->creationTime)); // Use EpochTimeToString.h
						printf("%i. status=%i opened=%i\n", i+1, inbox[i]->status, inbox[i]->wasOpened);
						printf("%i. attachmentLength=%i\n", i+1, inbox[i]->attachmentLength);
						printf("\n");
					}

					printf("SENT:\n");
					if (sentEmails.Size()==0)
						printf("<EMPTY> (or none downloaded yet)\n");
					for (i=0; i < sentEmails.Size(); i++)
					{
						printf("%i. id.userId=%i\n", i+1, sentEmails[i]->id.userId);
						printf("%i. id.userHandle=%s\n", i+1, sentEmails[i]->id.userHandle.C_String());
						printf("%i. emailMessageID=%i\n", i+1, sentEmails[i]->emailMessageID);
						printf("%i. subject=%s\n", i+1, sentEmails[i]->subject.C_String());
						printf("%i. body=%s\n", i+1, sentEmails[i]->body.C_String());
						printf("%i. creationTime=%s\n", i+1, EpochTimeToString(sentEmails[i]->creationTime)); // Use EpochTimeToString.h
						printf("%i. status=%i opened=%i\n", i+1, sentEmails[i]->status, sentEmails[i]->wasOpened);
						printf("%i. attachmentLength=%i\n", i+1, sentEmails[i]->attachmentLength);
						printf("\n");
					}

					break;
				}

				case 'x':
				{
					printf("Sending IM\n");

					printf("Send IM to user.\n");
					char userHandle[512];
					printf("Enter user handle: ");
					gets(userHandle);
					char message[4096];
					printf("Enter message: ");
					gets(message);
					lobbyClient.SendIM(userHandle, 0, message, 0, 0, 0);

					break;
				}

				case 'y':
				{
					bool roomIsHidden;
					int publicSlots;
					int privateSlots;
					char columnValue[256];
					bool allowSpectators;
					DataStructures::Table customPropertiesTable;
					printf("Create a room\n");
					printf("Enter room name: ");
					char roomName[512];
					gets(roomName);
					if (roomName[0]==0) strcpy(roomName, "Default room name");
					char roomPassword[512];
					printf("Enter room password: ");
					gets(roomPassword);
					char str[256];
					printf("Invite only? (y/n): ");
					gets(str);
					roomIsHidden=(str[0]=='y');
					printf("Number of public slots: ");
					gets(str); if (str[0]==0) strcpy(str, "10");
					publicSlots=atoi(str);
					printf("Number of private slots: ");
					gets(str); if (str[0]==0) strcpy(str, "5");
					privateSlots=atoi(str);
					printf("Allow spectators? (y/n): ");
					gets(str);
					allowSpectators=(str[0]!='n');

					// Just entering one column as a sample, but it can have any number of columns of string, binary, or numerical
					printf("Enter name of custom property column: ");
					gets(str); if (str[0]==0) strcpy(str, "My custom column");
					printf("Enter property value: ");
					gets(columnValue); if (columnValue[0]==0) strcpy(columnValue, "My custom value");
					customPropertiesTable.AddColumn(str, DataStructures::Table::STRING); // Note there is binary and numeric as well.
					DataStructures::Table::Row* row = customPropertiesTable.AddRow(0);
					row->cells[0]->Set(columnValue);

					lobbyClient.CreateRoom(roomName, roomPassword, roomIsHidden, publicSlots, privateSlots, allowSpectators, &customPropertiesTable);

					break;
				}

				case 'z':
				{
					printf("Download rooms\n");

					// Filters are AND
					// Filters are optional
					// Use filter indices if possible, for faster lookup
					DataStructures::Table::FilterQuery inclusionFilters[2];
					DataStructures::Table::Cell filterCells[2];

					// First filter sets only return rooms that allow spectators
					filterCells[0].Set(1); // 1 = integer value true
					inclusionFilters[0].cellValue=&filterCells[0];
					// Default columns are always at specific indices
					inclusionFilters[0].columnIndex=DefaultTableColumns::TC_ALLOW_SPECTATORS;
					inclusionFilters[0].operation=DataStructures::Table::QF_EQUAL;

					// Second filter searches our own custom column, string must match "My custom value"
					filterCells[1].Set("My custom value");
					inclusionFilters[1].cellValue=&filterCells[1];
					// Search by the name of the column
					strncpy(inclusionFilters[1].columnName,"My custom column",_TABLE_MAX_COLUMN_NAME_LENGTH);
					inclusionFilters[1].operation=DataStructures::Table::QF_EQUAL;

					// Download rooms using 2 search filters
					lobbyClient.DownloadRooms(inclusionFilters, 2);

					break;
				}

				case '0':
				{
					DataStructures::Table *table = lobbyClient.GetAllRooms();
					DataStructures::Page<unsigned, DataStructures::Table::Row*, _TABLE_BPLUS_TREE_ORDER> *cur = table->GetListHead();
					unsigned i;

					printf("Columns:\n");
					for (i=0; i < table->GetColumns().Size(); i++)
					{
						printf("%i. %s : ", i+1, table->GetColumns()[i].columnName);
						if (table->GetColumns()[i].columnType==DataStructures::Table::BINARY)
							printf("BINARY");
						else if (table->GetColumns()[i].columnType==DataStructures::Table::NUMERIC)
							printf("NUMERIC");
						else if (table->GetColumns()[i].columnType==DataStructures::Table::POINTER)
							printf("POINTER");
						else
							printf("STRING");
						printf("\n");
					}
					if (cur)
						printf("Rows:\n");
					else
						printf("Table has no rows.\n");
					while (cur)
					{
						for (i=0; i < (unsigned)cur->size; i++)
						{
							table->PrintRow(str, 256, ',', true, cur->data[i]);
							printf("RowID %i: %s\n", cur->keys[i], str );
						}
						cur=cur->next;
					}

					break;
				}

				case '1':
				{
					printf("Leave room\n");

					lobbyClient.LeaveRoom();
					break;
				}

				case '2':
				{
					printf("Join room\n");
					unsigned int roomId;
					bool joinAsSpectator;
					char str[256];
					printf("Enter room id (integer): ");
					gets(str); if (str[0]==0) strcpy(str,"0");
					roomId=atoi(str);
					printf("Enter as spectator? (y/n): ");
					gets(str);
					joinAsSpectator=(str[0]=='y');

					lobbyClient.JoinRoom(&roomId, 0, joinAsSpectator);
					break;
				}

				case '3':
				{
					printf("Enter chat message: \n");
					char str[4096];
					gets(str);
					lobbyClient.RoomChat(str,0,0,0);
					break;
				}

				case '4':
				{
					char userName[512];
					printf("Enter user to invite: ");
					gets(userName); if (userName[0]==0) strcpy(str,"Rak'kar the great");
					printf("Enter room invite message\n");
					char str[4096];
					gets(str);
					lobbyClient.InviteToRoom(userName,0, str,0,0,0,true);
					break;
				}

				case '5':
				{
					printf("Set ready to play status");
					bool isReady;
					char str[512];
					printf("Ready to play? (y/n)\n");
					gets(str);
					isReady = (str[0]=='y');
					lobbyClient.SetReadyToPlayStatus(isReady);
					break;
				}

				case '6':
				{
					printf("Kick room member\n");

					printf("Enter memberId to kick: ");
					gets(str); if (str[0]==0) strcpy(str,"1");
					LobbyClientUserId id;
					id = atoi(str);
					lobbyClient.KickRoomMember(&id);

					break;
				}

				case '7':
				{
					printf("Set room invite only\n");
					char str[512];
					printf("Invite only? (y/n)\n");
					gets(str);
					bool b = (str[0]=='y');
					lobbyClient.SetRoomIsHidden(b);

					break;
				}

				case '8':
				{
					printf("Set room allow spectators\n");
					char str[512];
					printf("Allow (new) spectators? (y/n)\n");
					gets(str);
					bool b = (str[0]=='y');
					lobbyClient.SetRoomAllowSpectators(b);

					break;
				}

				case '9':
				{
					printf("Change room slot counts\n");

					printf("Enter number of public slots: ");
					gets(str); if (str[0]==0) strcpy(str,"1");
					int publicSlots = atoi(str);

					printf("Enter number of reserved slots: ");
					gets(str); if (str[0]==0) strcpy(str,"1");
					int reservedSlots = atoi(str);

					lobbyClient.ChangeNumSlots(publicSlots, reservedSlots);

					break;
				}

				case '!':
				{
					printf("Grant moderator\n");
					printf("Enter moderator id: ");
					gets(str); if (str[0]==0) strcpy(str,"1");
					LobbyClientUserId id;
					id = atoi(str);
					lobbyClient.GrantModerator(&id);

					break;
				}

				case '@':
				{
					printf("Start game\n");
					lobbyClient.StartGame();

					break;
				}

				case '#':
				{
					printf("Quick match\n");
					printf("Enter required players: \n");
					gets(str); if (str[0]==0) strcpy(str,"2");
					int requiredPlayers = atoi(str);
					printf("Enter timeout in seconds: \n");
					gets(str); if (str[0]==0) strcpy(str,"30");
					int timeout = atoi(str);
					lobbyClient.QuickMatch(requiredPlayers,timeout);

					break;
				}

				case '$':
				{
					printf("Cancel Quick match\n");
					lobbyClient.CancelQuickMatch();
					break;
				}

				case '%':
				{
					printf("Join room by filter\n");

					// Filters are AND
					// Filters are optional
					// Use filter indices if possible, for faster lookup
					DataStructures::Table::FilterQuery inclusionFilters[2];
					DataStructures::Table::Cell filterCells[2];

					// First filter sets only return rooms that allow spectators
					filterCells[0].Set(1); // 1 = integer value true
					inclusionFilters[0].cellValue=&filterCells[0];
					// Default columns are always at specific indices
					inclusionFilters[0].columnIndex=DefaultTableColumns::TC_ALLOW_SPECTATORS;
					inclusionFilters[0].operation=DataStructures::Table::QF_EQUAL;

					// Second filter searches our own custom column, string must match "My custom value"
					filterCells[1].Set("My custom value");
					inclusionFilters[1].cellValue=&filterCells[1];
					// Search by the name of the column
					strncpy(inclusionFilters[1].columnName,"My custom column",_TABLE_MAX_COLUMN_NAME_LENGTH);
					inclusionFilters[1].operation=DataStructures::Table::QF_EQUAL;

					lobbyClient.JoinRoomByFilter(inclusionFilters, 2, false);
					break;
				}

				case '^':
				{
					printf("Download action history\n");
					printf("Sort by date (A)scending or (D)escending?: ");
					gets(str);
					bool ascendingSort=(str[0]=='a');
					lobbyClient.DownloadActionHistory(ascendingSort);
					break;
				}

				case '&':
				{
					printf("Add to action history\n");
					printf("defaultAllowClientsUploadActionHistory must be true in the titles table OR\n");
					printf("Your IP must be in the trustedIpList table\n");

					// history.id is not used from the lobbyClient
					// If you want to update the history for a particular user other than yourself, use AddToActionHistory_PostgreSQLImpl directly on the server.
					LobbyDBSpec::AddToActionHistory_Data history;

					char inputStr[512];
					printf("Enter actionTaken: ");
					gets(inputStr);
					history.actionTaken=inputStr;

					printf("Enter description: ");
					gets(inputStr);
					history.description=inputStr;

					history.actionTime; // Leave blank
					history.sourceIpAddress="127.0.0.1";

					/// Puts this functor on the processing queue. It will process sometime later in a thread.
					lobbyClient.AddToActionHistory(&history);

					break;
				}

				case '*':
				{
					printf("Print action history\n");

					const DataStructures::List<LobbyDBSpec::AddToActionHistory_Data *> &actionHistoryList = lobbyClient.GetActionHistoryList();
					unsigned i;
					if (actionHistoryList.Size()==0)
						printf("None.\n");
					for (i=0; i < actionHistoryList.Size(); i++)
					{
						printf("%i. id.userId=%i\n", i+1, actionHistoryList[i]->id.userId);
						printf("%i. id.userHandle=%s\n", i+1, actionHistoryList[i]->id.userHandle.C_String());
						printf("%i. actionTime=%s\n", i+1,actionHistoryList[i]->actionTime.C_String());
						printf("%i. actionTaken=%s\n", i+1, actionHistoryList[i]->actionTaken.C_String());
						printf("%i. description=%s\n", i+1, actionHistoryList[i]->description.C_String());
						printf("%i. sourceIpAddress=%s\n", i+1, actionHistoryList[i]->sourceIpAddress.C_String());
						printf("%i. creationTime=%s\n", i+1, EpochTimeToString(actionHistoryList[i]->creationTime));
					}

					break;
				}

				case '(':
				{
					printf("Print recently met users\n");
					const DataStructures::List<LobbyUserClientDetails *>& recentlyMetUsers = lobbyClient.GetRecentlyMetUsersList();
					unsigned i;
					if (recentlyMetUsers.Size()==0)
						printf("None.\n");
					for (i=0; i < recentlyMetUsers.Size(); i++)
					{
						printf("%i. %s\n", i+1, recentlyMetUsers[i]->userData->handle.C_String());
					}
					break;
				}

				case ')':
				{
					printf("Submit match\n");
					printf("Note: Your IP must be in the trustedIpList table\n");

					RankingServerDBSpec::SubmitMatch_Data *dat = new RankingServerDBSpec::SubmitMatch_Data;

					/// Every participant is represented by a pair of numbers.
					/// The primary ID can be used to reference a database primary key referring to another table holding all participant
					/// The secondary ID can be used to indicate type
					/// such as indicating the type of participant (player, squad, guild, clan, etc).
					char inputStr[512];
					printf("Enter the primary ID of participant A (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "0");
					dat->participantADbId.primaryKey = atoi(inputStr);

					printf("Enter the secondary ID of participant A (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "1");
					dat->participantADbId.secondaryKey = atoi(inputStr);

					printf("Enter the primary ID of participant B (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "2");
					dat->participantBDbId.primaryKey = atoi(inputStr);

					printf("Enter the secondary ID of participant B (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "3");
					dat->participantBDbId.secondaryKey = atoi(inputStr);

					// Score is tracked in case you want to check the database for cheaters, or to modify ratings, etc.
					// If you don't care about it, just use any number.
					printf("Enter the score of participant A (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "10.0");
					dat->participantAScore = (float) atof(inputStr);

					printf("Enter the score of participant B (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "21.0");
					dat->participantBScore = (float) atof(inputStr);

					// The rating of the player before the match started. Elo.h/.cpp is provided as one way to calculate ratings
					printf("Enter the pre-match rating of participant A (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "12.0");
					dat->participantAOldRating = (float) atof(inputStr);

					printf("Enter the post-match rating of participant A (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "5.0");
					dat->participantANewRating = (float) atof(inputStr);

					printf("Enter the pre-match rating of participant B (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "20.0");
					dat->participantBOldRating = (float) atof(inputStr);

					printf("Enter the post-match rating of participant B (float): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "30.0");
					dat->participantBNewRating = (float) atof(inputStr);

					/// Games are identified by a pair of integers the same way participants are
					/// The primary key could be the game title (such as Asteroids) while the secondary key could
					/// Indicate a submode (such as cooperative or deathmatch)
					printf("Enter the primary ID of the game (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "12");
					dat->gameDbId.primaryKey = atoi(inputStr);

					printf("Enter the secondary ID of the game (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "13");
					dat->gameDbId.secondaryKey = atoi(inputStr);

					/// A string is stored with each match.
					printf("Enter the match notes: ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "Default Match Notes");
					dat->matchNotes=inputStr;

					lobbyClient.SubmitMatch(dat);

					break;
				}

				case '-':
				{
					printf("Get rating\n");
					char inputStr[512];
					RankingServerDBSpec::GetRatingForParticipant_Data input;

					printf("Enter the primary ID of participant A (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "0");
					input.participantDbId.primaryKey = atoi(inputStr);

					printf("Enter the secondary ID of participant A (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "1");
					input.participantDbId.secondaryKey = atoi(inputStr);

					printf("Enter the primary ID of the game (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "12");
					input.gameDbId.primaryKey = atoi(inputStr);

					printf("Enter the secondary ID of the game (int): ");
					gets(inputStr);
					if (inputStr[0]==0) strcpy(inputStr, "13");
					input.gameDbId.secondaryKey = atoi(inputStr);

					lobbyClient.DownloadRating(&input);
					break;
				}

				case '=':
				{
					printf("Print rating\n");
					RankingServerDBSpec::GetRatingForParticipant_Data *rating = lobbyClient.GetRating();

					printf("[in] participantDbId.primaryKey=%i\n", rating->participantDbId.primaryKey);
					printf("[in] participantDbId.secondaryKey=%i\n", rating->participantDbId.secondaryKey);
					printf("[in] gameDbId.primaryKey=%i\n", rating->gameDbId.primaryKey);
					printf("[in] gameDbId.secondaryKey=%i\n", rating->gameDbId.secondaryKey);
					printf("[out] participantFound=%i\n", rating->participantFound); // False means no such participant
					if (rating->participantFound)
						printf("[out] rating=%f\n", rating->rating);
				}
			}
		}

		// Read all packets every tick. This also updates all plugins.
		for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
		{
			switch (packet->data[0])
			{
			case ID_CONNECTION_REQUEST_ACCEPTED:
				// Connected
				printf("Connected to lobby server.\n");
				serverLobbyAddress = packet->systemAddress;				
				break;

			case ID_DISCONNECTION_NOTIFICATION:
				// Connection lost normally
				printf("ID_DISCONNECTION_NOTIFICATION\n");
				break;

			case ID_NEW_INCOMING_CONNECTION:
				// Somebody connected.  We have their IP now
				printf("ID_NEW_INCOMING_CONNECTION from %s\n", packet->systemAddress.ToString());
				break;

			case ID_CONNECTION_LOST:
				// Couldn't deliver a reliable packet - i.e. the other system was abnormally
				// terminated
				printf("ID_CONNECTION_LOST\n");
				break;

			case ID_CONNECTION_ATTEMPT_FAILED:
				printf("Failed to connect!\nCheck that the server is running, and the IP and port is correct.\nThen restart this sample.\n");
				break;
			}

		}

		RakSleep(100);

	}



	RakNetworkFactory::DestroyRakPeerInterface(rakPeer);

	return 0;
}
