//////////////////////////////////////////////////////////////////////////////
//
// Asura_GameSpy_Chat.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     Asura interface class for GameSpy Chat SDK
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_MULTIPLAYER_COMMS
#ifdef ASURA_SUPPORT_GAMESPY

#include "Network/GameSpy/Asura_GameSpy_Chat.h"

#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Network/GameSpy/Asura_GameSpy.h"
#include "Network/Asura_LobbyChat.h"

#include "GameSpy/chat/chat.h"
#include "GameSpy/stringutil.h"

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
CHAT Asura_GameSpy_Chat::s_pxChatObject = NULL;
Asura_GameSpy_Chat::ConnectionState Asura_GameSpy_Chat::s_eConnectionState = Asura_GameSpy_Chat::ChatState_None;
char Asura_GameSpy_Chat::s_acChannelName[uASURA_CHAT_CHANNEL_NAME_MAXLEN + 1];
u_int Asura_GameSpy_Chat::s_uNicknameModifier = 1;
char Asura_GameSpy_Chat::s_szNickNameExtension[10];

// S T A T I C   C L A S S   F U N C T I O N S ///////////////////////////////
bool Asura_GameSpy_Chat::Initialise(const char* szServerName, const char* szChannel,
								const int iNameSpaceID, const Asura_UnicodeChar* wszNickNameExtension,
								const Asura_UnicodeChar* wszUniqueNickName, const Asura_UnicodeChar* wszPassword)
{
	Asura_Assert((s_eConnectionState == ChatState_None) && (!s_pxChatObject), "already initialised");

	// first create a UTF8 version of the player name, truncating to uASURA_CHAT_MAX_USERNAME_LEN (31) chars plus NULL.
	Asura_UnicodeChar wszTruncatedNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	char szNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	Asura_PrintfToBfr(wszTruncatedNickname, L"%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, wszUniqueNickName);
	UCS2ToUTF8String(reinterpret_cast<const u_short*>(wszTruncatedNickname), szNickname);

	//convert password
	char szPassword[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	char szFixedPassword[uASURA_CHAT_MAX_USERNAME_LEN + 2];
	UCS2ToUTF8String(reinterpret_cast<const u_short*>(wszPassword), szPassword);
	// fix up any illegal characters
	chatFixNick(reinterpret_cast<char*>(szFixedPassword), reinterpret_cast<char*>(szPassword));

	//convert extension
	Asura_PrintfToBfr(s_szNickNameExtension, "%S", 10, true, wszNickNameExtension);

	// create a callbacks structure
	chatGlobalCallbacks xGlobalCallbacks;
	memset(&xGlobalCallbacks, 0, sizeof(chatGlobalCallbacks));
	xGlobalCallbacks.raw = Callback_Raw;
	xGlobalCallbacks.disconnected = Callback_Disconnected;
	xGlobalCallbacks.privateMessage = Callback_PrivateMessage;
	xGlobalCallbacks.invited = Callback_Invited;
	xGlobalCallbacks.param = NULL;

	s_pxChatObject = chatConnectLogin(szServerName, 6667, iNameSpaceID,
		0, 0, szNickname, szFixedPassword, 0, Asura_GameSpy::GetGameName(), Asura_GameSpy::GetGameKey(),
		&xGlobalCallbacks, Callback_NicknameError, Callback_FillInUser, Callback_Connected, NULL, CHATFalse);
	Asura_Assert(s_pxChatObject, "chatConnectLogin failed");
	if (s_pxChatObject)
	{
		// copy the channel name so we can use it later
		Asura_Assert(strlen(szChannel) <= uASURA_CHAT_CHANNEL_NAME_MAXLEN, "channel name too long");
		strncpy(s_acChannelName, szChannel, uASURA_CHAT_CHANNEL_NAME_MAXLEN);

		s_eConnectionState = ChatState_ConnectingToServer;
	}

	// restart the nickname modifier
	s_uNicknameModifier = 1;
	
	return (s_pxChatObject != NULL);
}


void Asura_GameSpy_Chat::Update( void )
{
	if (s_pxChatObject)
	{
		// update the state machine. only work required is to initiate the 
		// channel joining attempt once the connection has succeeded.
		if (s_eConnectionState == ChatState_ConnectedToServer)
		{
			// Set channel callbacks.
			chatChannelCallbacks xChannelCallbacks;
			memset(&xChannelCallbacks, 0, sizeof(chatChannelCallbacks));
			xChannelCallbacks.channelMessage = Callback_ChannelMessage;
			xChannelCallbacks.channelModeChanged = Callback_ChannelModeChanged;
			xChannelCallbacks.kicked = Callback_Kicked;
			xChannelCallbacks.topicChanged = Callback_TopicChanged;
			xChannelCallbacks.userParted = Callback_UserParted;
			xChannelCallbacks.userJoined = Callback_UserJoined;
			xChannelCallbacks.userListUpdated = Callback_UserListUpdated;
			xChannelCallbacks.userModeChanged = Callback_UserModeChanged;
			xChannelCallbacks.userChangedNick = Callback_UserChangedNick;
			xChannelCallbacks.param = NULL;

			// connect to the channel
			chatEnterChannel(s_pxChatObject, s_acChannelName, NULL, &xChannelCallbacks, 
				Callback_EnteredChannel, NULL, CHATFalse);
			
			s_eConnectionState = ChatState_JoiningChannel;
		}

		chatThink(s_pxChatObject);
	}
}

void Asura_GameSpy_Chat::Shutdown( void )
{
	if (s_pxChatObject)
	{
		chatDisconnect(s_pxChatObject);
		s_pxChatObject = NULL;
		s_eConnectionState = ChatState_None;

	}
}



bool Asura_GameSpy_Chat::SendMessage( const Asura_UnicodeChar* wszMessage )
{
	if ( (s_eConnectionState == ChatState_InChannel) || (s_eConnectionState == ChatState_GettingUserList) )
	{
		// TODO - use unicode version instead of converting to ASCII
		char szMessage[uASURA_CHAT_MAX_MESSAGE_SIZE];
		Asura_PrintfToBfr(szMessage, "%S", uASURA_CHAT_MAX_MESSAGE_SIZE, true, wszMessage);
		
		chatSendChannelMessage(s_pxChatObject, s_acChannelName, szMessage, CHAT_MESSAGE);
		return true;
	}
	return false;
}


//////////////////////////////////////////////////////////////////////////
// private functions
//////////////////////////////////////////////////////////////////////////

// "global callbacks"
void Asura_GameSpy_Chat::Callback_Raw(CHAT pxChat, const gsi_char* szRaw, void* pParam)
{
	//Asura_OutputToDebugger(("RAW: %s\n", szRaw));
}

void Asura_GameSpy_Chat::Callback_Disconnected(CHAT pxChat, const gsi_char* szReason, void* pParam)
{
	Asura_OutputToDebugger(("Chat Disconnected: %s\n", szReason));
}

void Asura_GameSpy_Chat::Callback_PrivateMessage(CHAT pxChat, const gsi_char* szUser, const gsi_char* szMessage, int iType, void* pParam)
{
	Asura_OutputToDebugger(("Chat: Private message from %s: %s\n", szUser, szMessage));
}

void Asura_GameSpy_Chat::Callback_Invited(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, void* pParam)
{
	Asura_OutputToDebugger(("Chat: Invited by %s to %s\n", szUser, szChannel));
}

// callbacks for connection
void Asura_GameSpy_Chat::Callback_Connected(CHAT pxChat, CHATBool eSuccess, int iFailureReason, void* pParam)
{
	if (eSuccess == CHATFalse)
	{
		//CHAT_DISCONNECTED = 0
		//CHAT_NICK_ERROR = 1
		//CHAT_LOGIN_FAILED = 2
		Asura_OutputToDebugger(("Failed to connect to chat server(%d)\n", iFailureReason));
		s_eConnectionState = ChatState_ConnectionFailed;
	}
	else
	{
		Asura_OutputToDebugger(("Connected to chat server\n"));
		s_eConnectionState = ChatState_ConnectedToServer;
	}
}


void Asura_GameSpy_Chat::Callback_FillInUser(CHAT pxChat, unsigned int uIP, gsi_char aucUser[128], void* pParam)
{
// "fillInUserCallback" is a pointer to a function that the application uses 
// to fill in the user field after the actual network connection to the chat 
// server has been made.  This is used by the Peer SDK to encode the local 
// machine's IP address (as known to the chat server) in the user field.  

//#ifndef GSI_UNICODE
//	strcpy(user, chatUser);
//#else
//	wcscpy(user, chatUser);
//#endif
}

void Asura_GameSpy_Chat::Callback_NicknameError(CHAT pxChat, int iType, const gsi_char* szNick, int iNumSuggestedNicks, const gsi_char** aszSuggestedNicks, void* pParam)
{
	switch (iType)
	{
		case CHAT_IN_USE:
		{
			// add a number to the start of the name, the number is cycled 
			// so if this failed, next time we will try another number.
			char szNewNick[uASURA_CHAT_MAX_USERNAME_LEN + 1];
			Asura_PrintfToBfr(szNewNick, "[%u]%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, s_uNicknameModifier++, szNick);
			Asura_OutputToDebugger(("Chat Nickname error with %s; retrying with %s.\n", szNick, szNewNick));
			
			chatRetryWithNick(pxChat, szNewNick);
			break;
		}
		case CHAT_INVALID:
		case CHAT_UNIQUENICK_EXPIRED:
		case CHAT_NO_UNIQUENICK:
		case CHAT_INVALID_UNIQUENICK:
		default:
		{
			Asura_Assert(false, "unexpected nick error");
			break;
		}
	}
}

// channel callbacks
void Asura_GameSpy_Chat::Callback_EnteredChannel(CHAT pxChat, CHATBool eSuccess, CHATEnterResult eResult, const gsi_char* szChannel, void* pParam)
{
// possible values for eResult
//	CHATEnterSuccess,        // The channel was successfully entered.
//	CHATBadChannelName,      // The channel name was invalid.
//	CHATChannelIsFull,       // The channel is at its user limit.
//	CHATInviteOnlyChannel,   // The channel is invite only.
//	CHATBannedFromChannel,   // The local user is banned from this channel.
//	CHATBadChannelPassword,  // The channel has a password, and a bad password (or none) was given.
//	CHATTooManyChannels,     // The server won't allow this user in any more channels.
//	CHATEnterTimedOut,       // The attempt to enter timed out.
//	CHATBadChannelMask       // Not sure if any servers use this, or what it means! (ERR_BADCHANMASK)
	if (eSuccess == CHATTrue)
	{
		Asura_OutputToDebugger(("enter channel %s succeeded.\n", szChannel));
		s_eConnectionState = ChatState_GettingUserList;

		chatEnumUsers(pxChat, szChannel, Callback_EnumUsers, NULL, CHATFalse);
	}
	else
	{
		Asura_OutputToDebugger(("enter channel %s failed: reason = %d.\n", szChannel, eResult));
		s_eConnectionState = ChatState_JoinChannelFailed;
	}
}

void Asura_GameSpy_Chat::Callback_ChannelMessage(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, const gsi_char* szMessage, int iType, void* pParam)
{
	char szNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	Asura_PrintfToBfr(szNickname, "%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, szUser);
	if (chatTranslateNick(szNickname, s_szNickNameExtension))
	{
		Asura_LobbyChat::AddMessage(szNickname, szMessage);
	}
}

void Asura_GameSpy_Chat::Callback_Kicked(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, const gsi_char* szReason, void* pParam)
{
}

void Asura_GameSpy_Chat::Callback_UserJoined(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, int iMode, void* pParam)
{
	char szNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	Asura_PrintfToBfr(szNickname, "%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, szUser);
	if (!(chatTranslateNick(szNickname, s_szNickNameExtension) && Asura_LobbyChat::AddUser(szNickname)))
	{
		Asura_Assert(false, "failed to add user to chat user list");
	}
}

void Asura_GameSpy_Chat::Callback_UserParted(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, int iWhy, const gsi_char* szReason, const gsi_char* szKicker, void* pParam)
{
// possible values of iWhy
//#define CHAT_LEFT           0  // The user left the channel.
//#define CHAT_QUIT           1  // The user quit the chat network.
//#define CHAT_KICKED         2  // The user was kicked from the channel.
//#define CHAT_KILLED         3  // The user was kicked off the chat network.

	// TODO: add message
	char szNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
	Asura_PrintfToBfr(szNickname, "%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, szUser);
	if (!(chatTranslateNick(szNickname, s_szNickNameExtension) && Asura_LobbyChat::RemoveUser(szNickname)))
	{
		Asura_Assert(false, "failed to find user to remove");
	}
}

void Asura_GameSpy_Chat::Callback_UserChangedNick(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szOldNick, const gsi_char* szNewNick, void* pParam)
{
}

void Asura_GameSpy_Chat::Callback_UserModeChanged(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szUser, int iMode, void* pParam)
{
// possible values of iMode
//#define CHAT_NORMAL         0
//#define CHAT_VOICE          1
//#define CHAT_OP             2
}

void Asura_GameSpy_Chat::Callback_TopicChanged(CHAT pxChat, const gsi_char* szChannel, const gsi_char* szTopic, void* pParam)
{
}

void Asura_GameSpy_Chat::Callback_ChannelModeChanged(CHAT pxChat, const gsi_char* szChannel, CHATChannelMode* pxMode, void* pParam)
{
// members of pxMode
//	CHATBool InviteOnly;
//	CHATBool Private;
//	CHATBool Secret;
//	CHATBool Moderated;
//	CHATBool NoExternalMessages;
//	CHATBool OnlyOpsChangeTopic;
//	int Limit;
}

void Asura_GameSpy_Chat::Callback_UserListUpdated(CHAT pxChat, const gsi_char* szChannel, void* pParam)
{
	Asura_OutputToDebugger(("User list updated\n"));
}


void Asura_GameSpy_Chat::Callback_EnumUsers(CHAT pxChat, CHATBool eSuccess, const gsi_char* szChannel, int iNumUsers, const gsi_char** aszUsers, int* aiModes, void* pParam)
{
	if (eSuccess == CHATTrue)
	{
		char szNickname[uASURA_CHAT_MAX_USERNAME_LEN + 1];
		for (int i = 0; i < iNumUsers; ++i)
		{
			Asura_PrintfToBfr(szNickname, "%s", uASURA_CHAT_MAX_USERNAME_LEN + 1, true, aszUsers[i]);
			if (!(chatTranslateNick(szNickname, s_szNickNameExtension) && Asura_LobbyChat::AddUser(szNickname)))
			{
				Asura_Assertf(false, ("failed to add user %s.", aszUsers[i]));
			}
		}
	}
	else
	{
		Asura_OutputToDebugger(("chat enumusers failed.\n"));
	}

	s_eConnectionState = ChatState_InChannel;
}



#endif // ASURA_SUPPORT_GAMESPY

#endif // #ifdef ASURA_SUPPORT_MULTIPLAYER_COMMS
