/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/******************************************************************************
*
* FILE
*     $Archive: /Commando/Code/WWOnline/WOLChatObserver.cpp $
*
* DESCRIPTION
*     Westwood Online Chat events handling / dispatcher.
*
* PROGRAMMER
*     Denzil E. Long, Jr.
*     $Author: Byon_g $
*
* VERSION INFO
*     $Revision: 76 $
*     $Modtime: 10/31/02 3:15p $
*
******************************************************************************/

#pragma warning (disable : 4530)

#include <atlbase.h>
#include "WOLChatObserver.h"
#include "WOLSession.h"
#include "WOLProduct.h"
#include "WOLServer.h"
#include "WOLDownload.h"
#include "WOLChannel.h"
#include "WOLSquad.h"
#include "WOLLadder.h"
#include "WOLChatMsg.h"
#include "WOLPageMsg.h"
#include "WOLGameOptions.h"
#include "WOLGame.h"
#include "WOLErrorUtil.h"
#include <wwlib\registry.h>
#include <commando\_globals.h>
#include "systimer.h"
#include "specialbuilds.h"
#include "simplevec.h"
#include "..\commando\cnetwork.h"
namespace WOL
{
#include <WOLAPI\chatdefs.h>
}

namespace WWOnline {

/******************************************************************************
*
* NAME
*     ChatObserver::ChatObserver
*
* DESCRIPTION
*     Constructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

ChatObserver::ChatObserver() :
		mRefCount(1),
		mOuter(NULL)
	{
	WWDEBUG_SAY(("WOL: ChatObserver Instantiated\n"));
	}


/******************************************************************************
*
* NAME
*     ChatObserver::~ChatObserver
*
* DESCRIPTION
*     Destructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

ChatObserver::~ChatObserver()
	{
	WWDEBUG_SAY(("WOL: ChatObserver Destroyed\n"));
	}


/******************************************************************************
*
* NAME
*     ChatObserver::Init
*
* DESCRIPTION
*     Initialize chat observer
*
* INPUTS
*     Session - Outer session to associate this observer with.
*
* RESULT
*     NONE
*
******************************************************************************/

void ChatObserver::Init(Session& outer)
	{
	mOuter = &outer;
	}


/****************************************************************************
*
* NAME
*     IUnknown::QueryInterface
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     Error / Status result
*
****************************************************************************/

STDMETHODIMP ChatObserver::QueryInterface(const IID& iid, void** ppv)
	{
	if ((iid == IID_IUnknown) || (iid == WOL::IID_IChatEvent))
		{
		*ppv = static_cast<WOL::IChatEvent*>(this);
		}
	else
		{
		*ppv = NULL;
		return E_NOINTERFACE;
		}

	static_cast<IUnknown*>(*ppv)->AddRef();
	return S_OK;
	}


/****************************************************************************
*
* NAME
*     IUnknown::AddRef
*
* DESCRIPTION
*     Increment objects reference count.
*
* INPUTS
*     NONE
*
* RESULT
*     RefCount - New reference count.
*
****************************************************************************/

ULONG STDMETHODCALLTYPE ChatObserver::AddRef(void)
	{
	InterlockedIncrement((LPLONG)&mRefCount);
	return mRefCount;
	}


/****************************************************************************
*
* NAME
*     IUnknown::Release
*
* DESCRIPTION
*     Release reference to object.
*
* INPUTS
*     NONE
*
* RESULT
*     RefCount - Remaining references
*
****************************************************************************/

ULONG STDMETHODCALLTYPE ChatObserver::Release(void)
	{
	InterlockedDecrement((LPLONG)&mRefCount);

	if (mRefCount == 0)
		{
		delete this;
		return 0;
		}

	return mRefCount;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnServerList
*
* DESCRIPTION
*     Handle
*
* INPUTS
*     Result  - Result / status code.
*     Servers - Server list.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnServerList(HRESULT result, WOL::Server* servers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnServerList '%s'\n", GetChatErrorString(result)));
		ServerError error(result, "WOL_SERVERLISTERROR");
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	mOuter->mRequestingServerList = false;

	// If update list received, don't provide server list
	if (mOuter->mPatchFiles.size() > 0)
		{
		return S_OK;
		}

	// If we didn't really want a server list anyway then ignore it.
	if (mOuter->mIgnoreServerLists)
		{
		return S_OK;
		}

	// Process new server list.
	mOuter->ClearServers();

	// Clear out old server info from the registry.
	RegistryClass reg(APPLICATION_SUB_KEY_NAME_SERVER_LIST);
	reg.Deleta_All_Values();

	WOL::Server* curServer = servers;

	while (curServer)
		{
		WWDEBUG_SAY(("WOL: Server '%s:%s'\n", curServer->connlabel, curServer->name));

		if ((strcmp((char*)curServer->connlabel, "IRC") == 0) ||
				(strcmp((char*)curServer->connlabel, "IGS") == 0))
			{
			mOuter->mIRCServers.push_back(IRCServerData::Create(*curServer));
			}
		else if (strcmp((char*)curServer->connlabel, "LAD") == 0)
			{
			mOuter->mLadderServer = LadderServerData::Create(*curServer);
			}
		else if (strcmp((char*)curServer->connlabel, "GAM") == 0)
			{
			mOuter->mGameResultsServer = GameResultsServerData::Create(*curServer);
			}
		else if (strcmp((char*)curServer->connlabel, "WDT") == 0)
			{
			mOuter->mWDTServer = WDTServerData::Create(*curServer);
			}
		else if (strcmp((char*)curServer->connlabel, "MGL") == 0)
			{
			mOuter->mMGLServers.push_back(MGLServerData::Create(*curServer));
			}
		else if (strcmp((char*)curServer->connlabel, "PNG") == 0)
			{
			RefPtr<PingServerData> pinger = PingServerData::Create(*curServer);
			WWASSERT(pinger.IsValid());

			if (pinger.IsValid())
				{
				mOuter->mPingServers.push_back(pinger);

				// Automatcally request the ping time to this server.
				const char* address = pinger->GetHostAddress();
				mOuter->RequestPing(address);
				}
			}

		curServer = curServer->next;
		}

	// Notify others about the server list.
	mOuter->NotifyObservers(mOuter->mIRCServers);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUpdateList
*
* DESCRIPTION
*     Handle application update requirement.
*
* INPUTS
*     Result  - Error / status code.
*     Updates - List of updates.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUpdateList(HRESULT result, WOL::Update* updates)
	{
	WWDEBUG_SAY(("WOL: OnUpdateList received\n"));

	// Make sure WOLSession is initialized
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Clear any previous patch files
	mOuter->mPatchFiles.clear();

	// Report any error condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUpdateList '%s'\n", GetChatErrorString(result)));

		const char* errorString = "WOL_UPDATELISTERROR";

		if (result == CHAT_E_STATUSERROR)
			{
			errorString = "WOL_BADINSTALL";
			}

		ServerError error(result, errorString);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	// Process the patch updates
	WOL::Update* update = updates;

	while (update)
		{
		WWDEBUG_SAY(("WOL: Patch file '%s\\%s\\%s'\n",
			update->server, update->patchpath, update->patchfile));

		mOuter->mPatchFiles.push_back(Download::Create(*update));
		update = update->next;
		}

	// If there are updates then notify the client.
	if (updates)
		{
		// If updates are received, do not provide list of servers
		mOuter->ClearServers();

		ServerError error(CHAT_E_MUSTPATCH, "WOL_PATCHREQUIRED");
		mOuter->NotifyObservers(error);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnServerError
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*     Text   - Error text description.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnServerError(HRESULT result, LPCSTR errorText)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (SUCCEEDED(result))
		{
		ServerError error(-1, "WOL_SERVERERROR");
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWDEBUG_SAY(("WOLERROR: OnServerError '%s' %s\n", GetChatErrorString(result), errorText));

	if (result != CHAT_E_UNKNOWNRESPONSE)
		{
		ServerError error(result, GetChatErrorString(result));
		mOuter->NotifyObservers(error);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnConnection
*
* DESCRIPTION
*
* INPUTS
*     Status - Connection error / status code.
*     MOTD   - Message of the day text.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnConnection(HRESULT result, LPCSTR motd)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	const char* errorText = NULL;

	switch (result)
		{
		// Connection successful
		case S_OK:
			{
			WWDEBUG_SAY(("WOL: Connected to server.\n"));
			mOuter->GetChatObject()->SetClientVersion(WOLAPI_BUILD_VERSION);
			
			mOuter->mCurrentConnectionStatus = ConnectionConnected;
			mOuter->mCurrentServer = mOuter->mPendingServer;
			mOuter->mCurrentLogin = mOuter->mPendingLogin;

			mOuter->mPendingServer.Release();
			mOuter->mPendingLogin.Release();

			// Check if the loginname is in the userlist. If not then create
			// a user for the login.
			RefPtr<LoginInfo> login = mOuter->mCurrentLogin;

			if (login.IsValid())
				{
				const WideStringClass& loginName = login->GetNickname();

				// Setup the current user for this session.
				RefPtr<UserData> user = mOuter->GetUserOrBuddy(loginName);

				if (!user.IsValid())
					{
					user = UserData::Create(loginName);
					}

				// Setup the current user
				WWASSERT(user.IsValid() && "OnConnection: Failed to create user");
				user->SetLocale(login->GetLocale());
				mOuter->mCurrentUser = user;

				// Request information about the current user.
				mOuter->RequestUserLocale(loginName);
				mOuter->RequestSquadInfoByMemberName(loginName);
				mOuter->RequestLadderInfo(loginName, LadderType_Team);
				mOuter->mLastUserDataRequestTime = TIMEGETTIME();
				}

			// Notify others about the connection
			mOuter->NotifyObservers(mOuter->mCurrentConnectionStatus);

			// Notify others about the message of the day.
			mOuter->mMessageOfTheDay = motd;
			MessageOfTheDayEvent motdEvent(mOuter->mMessageOfTheDay);
			mOuter->NotifyObservers(motdEvent);

			// Request insider status information and server time information
			mOuter->RequestInsiderStatus();
			mOuter->RequestServerTime();

			return S_OK;
			}
			break;

		case CHAT_E_NICKINUSE:
			errorText = "WOL_NICKINUSE";
			break;

		case CHAT_E_BADPASS:
			errorText = "WOL_BADPASSWORD";
			break;

		case CHAT_E_BANNED:
			errorText = "WOL_BANNED";
			break;

		case CHAT_E_DISABLED:
			errorText = "WOL_ACCOUNTDISABLED";
			break;

		case CHAT_E_SERIALBANNED:
			errorText = "WOL_SERIALBANNED";
			break;

		case CHAT_E_SERIALDUP:
			errorText = "WOL_SERIALDUPLICATE";
			break;

		case CHAT_E_SERIALUNKNOWN:
			errorText = "WOL_SERIALUNKNOWN";
			break;

		case CHAT_E_SKUSERIALMISMATCH:
			errorText = "WOL_SERIALSKUMISMATCH";
			break;

		default:
			errorText = "WOL_CONNECTERROR";
			break;
		}

	// We should only get here if the connection failed.
	mOuter->mPendingServer.Release();
	mOuter->mCurrentServer.Release();
	mOuter->mPendingLogin.Release();
	mOuter->mCurrentLogin.Release();
	mOuter->mCurrentConnectionStatus = ConnectionDisconnected;

	WWDEBUG_SAY(("WOLERROR: OnConnection '%s'\n", GetChatErrorString(result)));
	ServerError error(result, errorText);
	mOuter->NotifyObservers(error);
	mOuter->NotifyObservers(mOuter->mCurrentConnectionStatus);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnMessageOfTheDay
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnMessageOfTheDay(HRESULT, LPCSTR messageOfTheDay)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Notify other about the message of the day.
	mOuter->mMessageOfTheDay = messageOfTheDay;
	MessageOfTheDayEvent motdEvent(mOuter->mMessageOfTheDay);
	mOuter->NotifyObservers(motdEvent);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelList
*
* DESCRIPTION
*     Handle receipt of new channel list.
*
* INPUTS
*     Result   - Error / status code
*     Channels - Linked list of channels
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelList(HRESULT result, WOL::Channel* inChannels)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Report any error conditions
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelList '%s'\n", GetChatErrorString(result)));
		ChannelList empty;
		ChannelListEvent error(ChannelListEvent::Error, empty, -1);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	// If this is a ping list then update the latency of each channel.
	if (result == CHAT_S_PINGLIST)
		{
		WWDEBUG_SAY(("WOL: OnChannelList (PingList)\n"));

		WOL::Channel* wolChannel = inChannels;

		while (wolChannel)
			{
			const RefPtr<ChannelData>& channel = mOuter->FindChannel((const char*)wolChannel->name);

			if (channel.IsValid())
				{
				channel->SetLatency(wolChannel->latency);
				}

			wolChannel = wolChannel->next;
			}

		return S_OK;
		}

	// Get code for game channels
	RefPtrConst<Product> product = Product::Current();
	WWASSERT(product.IsValid() && "WOLProduct not initialized");

	int gameCode = -1;

	if (product.IsValid())
		{
		gameCode = product->GetGameCode();
		}
	else
		{
		WWDEBUG_SAY(("WOLERROR: WOLProduct not initialized.\n"));
		}

	// Create the new channel lists
	ChannelList chatChannels;
	ChannelList gameChannels;
	WOL::Channel* wolChannel = inChannels;

	while (wolChannel)
		{
		WWDEBUG_SAY(("WOL: Channel '%s:%ld'\n", (const char*)wolChannel->name, wolChannel->type));

		ChannelList* curList = NULL;
		ChannelList* newList = NULL;

		if (wolChannel->type == 0)
			{
			curList = &mOuter->mChatChannels;
			newList = &chatChannels;
			}
		else if (wolChannel->type == gameCode)
			{
			curList = &mOuter->mGameChannels;
			newList = &gameChannels;
			}
		else
			{
			continue;
			}

		WWASSERT(curList != NULL);
		WWASSERT(newList != NULL);

		ChannelList::iterator iter = FindChannelNode(*curList, (const char*)wolChannel->name);

		if (iter != curList->end())
			{
			RefPtr<ChannelData> channel = *iter;
			WWASSERT(channel.IsValid());
			channel->UpdateData(*wolChannel);

			ChannelList::iterator end = newList->end();
			newList->splice(end, *curList, iter);
			}
		else
			{
			RefPtr<ChannelData> channel = ChannelData::Create(*wolChannel);
			WWASSERT(channel.IsValid());
			newList->push_back(channel);
			}

		wolChannel = wolChannel->next;
		}

	// Replace lists
	mOuter->mChatChannels.swap(chatChannels);
	mOuter->mGameChannels.swap(gameChannels);

	// Notify other about the new channel lists
	ChannelListEvent chatListEvent(ChannelListEvent::NewList, mOuter->mChatChannels, 0);
	mOuter->NotifyObservers(chatListEvent);

	ChannelListEvent gameListEvent(ChannelListEvent::NewList, mOuter->mGameChannels, gameCode);
	mOuter->NotifyObservers(gameListEvent);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelCreate
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelCreate(HRESULT result, WOL::Channel* inChannel)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle failure condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelCreate '%s'\n", GetChatErrorString(result)));

		ChannelStatus status = GetChannelStatusFromHResult(result);
		mOuter->mCurrentChannelStatus = status;

		// Notify others about the channel creation failure
		ChannelEvent error(status, mOuter->mPendingChannel);
		mOuter->NotifyObservers(error);

		mOuter->mPendingChannel.Release();
		mOuter->mCurrentChannel.Release();
		return S_OK;
		}

	WWASSERT(inChannel != NULL && "OnChannelCreate parameter error");
	WWDEBUG_SAY(("WOL: Created channel '%s'\n", (char*)inChannel->name));

	// Make sure we have the channel to create pending.
	if (!mOuter->mPendingChannel.IsValid())
		{
		WWASSERT(mOuter->mPendingChannel.IsValid());
		return S_OK;
		}

	// Check if the created channel matches the pending one.
	wchar_t channelName[64];
	mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));
	const WideStringClass& pendingName = mOuter->mPendingChannel->GetName();

	if (pendingName.Compare_No_Case(channelName) != 0)
		{
		WWASSERT("OnChannelCreate: Channel name mismatch");
		return S_OK;
		}

	// Verify current login is valid
	RefPtr<LoginInfo> login = mOuter->mCurrentLogin;

	if (!login.IsValid())
		{
		WWASSERT(login.IsValid() && "OnChannelCreate: No login");
		return S_OK;
		}

	// Get or create user who created the channel
	RefPtr<UserData> user = mOuter->GetUserOrBuddy(login->GetNickname());

	if (!user.IsValid())
		{
		user = UserData::Create(login->GetNickname());
		}

	if (!user.IsValid())
		{
		WWASSERT(user.IsValid() && "OnChannelCreate: Unable to find or create user");
		return S_OK;
		}

	// The pending channel now becomes the current channel.
	mOuter->mCurrentChannel = mOuter->mPendingChannel;
	mOuter->mPendingChannel.Release();
	mOuter->mCurrentChannel->UpdateData(*inChannel);
	mOuter->mCurrentChannelStatus = ChannelJoined;

	// Notify others the channel was created
	ChannelEvent createdEvent(ChannelCreated, mOuter->mCurrentChannel);
	mOuter->NotifyObservers(createdEvent);

	// Add the logged in user to the userlist
	mOuter->mUsers.push_back(user);

	// Notify others that the logged in user has joined the newly created channel.
	UserEvent event(UserEvent::Join, user);
	mOuter->NotifyObservers(event);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelJoin
*
* DESCRIPTION
*     User has joined a channel.
*
* INPUTS
*     Result  - Error / status code
*     Channel - Channel joined
*     User    - User joining channel.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelJoin(HRESULT result, WOL::Channel* inChannel,
		WOL::User* inUser)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle failure conditions
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelJoin '%s'\n", GetChatErrorString(result)));
		ChannelStatus status = GetChannelStatusFromHResult(result);
		mOuter->mCurrentChannelStatus = status;

		// Get the channel we are attempting to join
		RefPtr<ChannelData> badChannel = mOuter->mPendingChannel;

		if (badChannel.IsValid() == false)
			{
			badChannel = mOuter->mCurrentChannel;
			}

		mOuter->mPendingChannel.Release();
		mOuter->mCurrentChannel.Release();

		// Notify others the reason for the channel join failure.
		ChannelEvent error(status, badChannel);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWASSERT(inChannel != NULL && "OnChannelJoin parameter error");
	WWASSERT(inUser != NULL && "OnChannelJoin parameter error");

  	struct in_addr addr;
	addr.S_un.S_addr = inUser->ipaddr;

	WWDEBUG_SAY(("WOL: User '%s' @ %s joining channel '%s'\n", (char*)inUser->name, inet_ntoa(addr), (char*)inChannel->name));

	// Verify that the current login is valid
	if (!mOuter->mCurrentLogin.IsValid())
		{
		WWASSERT(mOuter->mCurrentLogin.IsValid() && "OnChannelJoin: No login");
		return S_OK;
		}

	// Get or create the user who is joining.
	wchar_t inUsername[64];
	mbstowcs(inUsername, (char*)inUser->name, sizeof(inUser->name));
	RefPtr<UserData> user = mOuter->GetUserOrBuddy(inUsername);

	if (!user.IsValid())
		{
		user = UserData::Create(*inUser);
		}
	else
		{
		user->UpdateData(*inUser);
		}

	if (!user.IsValid())
		{
		WWASSERT(user.IsValid() && "OnChannelJoin: No user");
		return S_OK;
		}

	// Get channel name
	wchar_t channelName[64];
	mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));

	// Is the logged in user joining this channel?
	const WideStringClass& loginName = mOuter->mCurrentLogin->GetNickname();

	if (loginName.Compare_No_Case(inUsername) == 0)
		{
		// Verify that we are wanting to join a channel
		if (!mOuter->mPendingChannel.IsValid())
			{
			WWASSERT(mOuter->mPendingChannel.IsValid() && "OnChannelJoin: Unknown channel.");
			return S_OK;
			}

		// Verify that the channel joined is the one we were expecting to join.
		const WideStringClass& pendingChannelName = mOuter->mPendingChannel->GetName();

		if (pendingChannelName.Compare_No_Case(channelName) != 0)
			{
			WWASSERT(!"OnChannelJoin: Pending channel mismatch");
			return S_OK;
			}

		// The pending channel becomes the current channel
		mOuter->mPendingChannel->UpdateData(*inChannel);
		mOuter->mCurrentChannel = mOuter->mPendingChannel;
		mOuter->mPendingChannel.Release();

		mOuter->mCurrentChannelStatus = ChannelJoined;

		// Notify others that we have joined the channel
		ChannelEvent event(ChannelJoined, mOuter->mCurrentChannel);
		mOuter->NotifyObservers(event);
		}
	else
		{
		WWDEBUG_SAY(("WOL: OnChannelJoin other user '%S'\n", inUsername));
		}

	mOuter->AutoRequestUserDetails(user);

	// Make sure the current channel is valid
	if (!mOuter->mCurrentChannel.IsValid())
		{
		WWASSERT(mOuter->mCurrentChannel.IsValid());
		return S_OK;
		}

	// Verify that the channel joined is the current one.
	const WideStringClass& curChannelName = mOuter->mCurrentChannel->GetName();

	if (curChannelName.Compare_No_Case(channelName) != 0)
		{
		WWASSERT(!"OnChannelJoin: Channel mismatch");
		return S_OK;
		}

	// Increment user count for this channel
	mOuter->mCurrentChannel->GetData().currentUsers++;

	// Get the time they joined the channel. They have 2 mins to get into the game or they are kicked.
   user->mKickTimer = TIMEGETTIME();

	// Add the user to the user list
	mOuter->mUsers.push_back(user);

	// Notify others that a user joined the current channel.
	UserEvent event(UserEvent::Join, user);
	mOuter->NotifyObservers(event);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelLeave
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelLeave(HRESULT result, WOL::Channel* inChannel, WOL::User* inUser)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle error condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelLeave '%s'\n", GetChatErrorString(result)));
		ChannelStatus status = GetChannelStatusFromHResult(result);
		mOuter->mCurrentChannelStatus = status;

		ChannelEvent error(status, mOuter->mCurrentChannel);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWASSERT(inChannel != NULL && "OnChannelLeave: Parameter error");
	WWASSERT(inUser != NULL && "OnChannelLeave: Parameter error");
	WWDEBUG_SAY(("WOL: User '%s' leaving channel '%s'\n", (char*)inUser->name, (char*)inChannel->name));

	// If we are not logged in then ignore.
	if (!mOuter->mCurrentLogin.IsValid())
		{
		WWASSERT(mOuter->mCurrentLogin.IsValid());
		return S_OK;
		}

	// Get the current channel we are connected to. If we are not connected to any
	// channel then ignore.
	if (!mOuter->mCurrentChannel.IsValid())
		{
		WWASSERT(mOuter->mCurrentChannel.IsValid());
		return S_OK;
		}

	// There is a bug in WOLAPI (no surprise here!) that results in the leaving
	// channel being empty if the channel was join explicitly (without picking it
	// from the channel list) as in the case of quickmatch games. Therefore it is
	// necessary to check for this condition here. If the channel name is empty then
	// we MUST ASSUME that the channel we are leaving is the one we are currently
	// connected to.
	wchar_t channelName[64];
	const WideStringClass& curChannelName = mOuter->mCurrentChannel->GetName();

	if (strlen((const char*)inChannel->name) == 0)
		{
		wcsncpy(channelName, curChannelName, (sizeof(channelName) / sizeof(wchar_t)));
		}
	else
		{
		mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));
		}

	// If the leaving channel is not the channel we are connected to then ignore.
	if (curChannelName.Compare_No_Case(channelName) != 0)
		{
		WWASSERT(!"OnChannelLeave: Channel mismatch.");
		return S_OK;
		}

	// Decrement user count for this channel
	WWASSERT(mOuter->mCurrentChannel->GetCurrentUsers() > 0);
	mOuter->mCurrentChannel->GetData().currentUsers--;

	// If current user is leaving then disconnect from the channel clear the user list.
	wchar_t inUsername[64];
	mbstowcs(inUsername, (char*)inUser->name, sizeof(inUser->name));
	const WideStringClass& nickname = mOuter->mCurrentLogin->GetNickname();

	if (nickname.Compare_No_Case(inUsername) == 0)
		{
		RefPtr<ChannelData> leftChannel = mOuter->mCurrentChannel;

		mOuter->mCurrentChannelStatus = ChannelLeft;
		mOuter->mCurrentChannel.Release();

		// Notify others that we have left the channel
		ChannelEvent event(ChannelLeft, leftChannel);
		mOuter->NotifyObservers(event);

		mOuter->mUsers.clear();
		mOuter->NotifyObservers(mOuter->mUsers);
		}
	else
		{
		// If some other user is leaving then notify that he left the channel.
		RefPtr<UserData> leavingUser = RemoveUserInList(inUsername, mOuter->mUsers);

		if (leavingUser.IsValid())
			{
			UserEvent event(UserEvent::Leave, leavingUser);
			mOuter->NotifyObservers(event);
			}
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelTopic
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelTopic(HRESULT result, WOL::Channel* inChannel, LPCSTR topic)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle error condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelTopic '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// If inputs are invalid then ignore
	if (inChannel == NULL)
		{
		WWASSERT(inChannel);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: Channel '%s' Topic changed.\nExInfo: %s\nTopic: %s\n",
			(const char*)inChannel->name, (const char*)inChannel->exInfo, topic));

	wchar_t channelName[64];
	mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));

	RefPtr<ChannelData> channel = mOuter->FindChannel(channelName);

	if (channel.IsValid())
		{
#ifndef FREEDEDICATEDSERVER			
		channel->SetTopic(topic);
		channel->SetExtraInfo((const char*)inChannel->exInfo);

		ChannelEvent event(ChannelNewData, channel);
		mOuter->NotifyObservers(event);
#else
		bool bad_topic = false;
		if (strcmp(topic, channel->GetTopic()) != 0) {
			bad_topic = true;
			WWDEBUG_SAY(("Bad topic. Old = %s, new = %s\n", channel->GetTopic(), topic));
		}
		if (strcmp((char*)&inChannel->exInfo[0], channel->GetExtraInfo()) != 0) {
			bad_topic = true;
			WWDEBUG_SAY(("Bad exInfo. Old = %s, new = %s\n", channel->GetExtraInfo(), inChannel->exInfo));
		}
		if (bad_topic) {
			mOuter->SendChannelTopic();
		}
#endif
		}
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPrivateAction
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPrivateAction(HRESULT result, WOL::User* user, LPCSTR message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateAction '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, true, true);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPublicAction
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPublicAction(HRESULT result, WOL::Channel*, WOL::User* user, LPCSTR message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicAction '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, false, true);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserList
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserList(HRESULT result, WOL::Channel* inChannel, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserList '%s'\n", GetChatErrorString(result)));
		ServerError error(result, "WOL_USERLISTERROR");
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	if (inChannel == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: NULL channel\n"));
		WWASSERT(inChannel != NULL);
		return S_OK;
		}

	if (!mOuter->mCurrentChannel.IsValid())
		{
		WWDEBUG_SAY(("WOLERROR: UserList no current channel\n"));
		WWASSERT(!"OnUserList: No current channel");
		return S_OK;
		}

	// Verify the channel the user list is for against the channel we are in.
	wchar_t channelName[64];
	mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));
	const WideStringClass& curChannelName = mOuter->mCurrentChannel->GetName();

	if (curChannelName.Compare_No_Case(channelName) != 0)
		{
		WWDEBUG_SAY(("WOLERROR: UserList channel mismatch\n"));
		WWASSERT(!"OnUserList: Channel mismatch");
		return S_OK;
		}

	// Process the users.
	UserList userList;
	WOL::User* curUser = inUsers;

	while (curUser)
		{
		wchar_t name[64];
		mbstowcs(name, (const char*)curUser->name, sizeof(curUser->name));

		// If the user is not already in our list then create them.
		RefPtr<UserData> user = mOuter->GetUserOrBuddy(name);

		if (!user.IsValid())
			{
			user = UserData::Create(*curUser);
			}
		else
			{
			user->UpdateData(*curUser);
			}

		if (user.IsValid())
			{
			userList.push_back(user);

			// If we are connected to a game channel then request detailed
			// information about the users.
			if (mOuter->mCurrentChannel.IsValid() && (mOuter->mCurrentChannel->GetType() != 0))
				{
				mOuter->AutoRequestUserDetails(user);
				}
			}

		curUser = curUser->next;
		}

	// Update the user count for this channel.
	RefPtr<ChannelData> channel = mOuter->FindChannel((const char*)inChannel->name);

	if (channel.IsValid())
		{
		channel->GetData().currentUsers = userList.size();
		}

	// Update the user list
	mOuter->mUsers.swap(userList);
	userList.clear();

	mOuter->NotifyObservers(mOuter->mUsers);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPublicMessage
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPublicMessage(HRESULT result, WOL::Channel*, WOL::User* user, LPCSTR message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicMessage '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, false, false);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPrivateMessage
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPrivateMessage(HRESULT result, WOL::User* user, LPCSTR message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateMessage '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, true, false);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnSystemMessage
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnSystemMessage(HRESULT result, LPCSTR message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnSystemMessage '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(NULL, message, false, false);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnNetStatus
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnNetStatus(HRESULT result)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	ConnectionStatus status = mOuter->mCurrentConnectionStatus;

	switch (result)
		{
		case CHAT_E_CON_LOOKUP_FAILED:
		case CHAT_E_CON_ERROR:
		case CHAT_E_TIMEOUT:
		case CHAT_S_CON_DISCONNECTED:
			mOuter->mRequestingServerList = false;
			mOuter->mCurrentServer.Release();
			status = ConnectionDisconnected;

			if (mOuter->mCurrentChannelStatus == ChannelJoined)
				{
				// We can't be in a channel without a WOL connection.
				WWDEBUG_SAY(("WOL: Leaving channel due to chat connection loss\n"));
				mOuter->mCurrentChannel.Release();
				mOuter->mPendingChannel.Release();
				mOuter->mCurrentChannelStatus = ChannelLeft;
				}
			break;

		case CHAT_S_CON_DISCONNECTING:
			status = ConnectionDisconnecting;
			break;

		case CHAT_S_CON_CONNECTING:
			status = ConnectionConnecting;
			break;

		case CHAT_S_CON_CONNECTED:
			break;

		default:
			WWDEBUG_SAY(("WOL: OnNetStatus '%s'\n", GetChatErrorString(result)));
			break;
		}

	if (status != mOuter->mCurrentConnectionStatus)
		{
		mOuter->mCurrentConnectionStatus = status;

		if (status == ConnectionDisconnected)
			{
			mOuter->NotifyObservers(status);
			}
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnLogout
*
* DESCRIPTION
*     Handle other users that have logged out.
*
*     NOTE: This does not get called for the logged in user. When the logged in
*     user logs out OnNetStatus() is called with CHAT_S_CON_DISCONNECTED.
*
* INPUTS
*     Result - Error / status code.
*     User   - User that logged out.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnLogout(HRESULT result, WOL::User* inUser)
	{
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnLogout '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Remove the logged out user from the session
	if (inUser)
		{
		WWDEBUG_SAY(("WOL: User '%s' logged out\n", (char*)inUser->name));

		wchar_t name[64];
		mbstowcs(name, (char*)inUser->name, sizeof(inUser->name));

		// If some other user is leaving then notify that he left the channel.
		RefPtr<UserData> leavingUser = RemoveUserInList(name, mOuter->mUsers);

		if (leavingUser.IsValid())
			{
			// Decrement user count for this channel
			if (mOuter->mCurrentChannel.IsValid())
				{
				WWASSERT(mOuter->mCurrentChannel->GetData().currentUsers > 0);
				mOuter->mCurrentChannel->GetData().currentUsers--;
				}

			UserEvent event(UserEvent::Leave, leavingUser);
			mOuter->NotifyObservers(event);
			}
		}

	return S_OK;
	}




/*
** HACK. Private game options spam control.
*/
class PrivateGameOptionsTrackingClass
{
		public:
			char UserName[256];
			DynamicVectorClass<unsigned long> Times;

			bool operator == (PrivateGameOptionsTrackingClass const &whatever);
			bool operator != (PrivateGameOptionsTrackingClass const &whatever);
};

DynamicVectorClass<PrivateGameOptionsTrackingClass*> OptionsTracking;

bool PrivateGameOptionsTrackingClass::operator == (PrivateGameOptionsTrackingClass const &whatever)
{
	if (stricmp(UserName, whatever.UserName) == 0) {
		return(true);
	}
	return(false);
}			  

bool PrivateGameOptionsTrackingClass::operator != (PrivateGameOptionsTrackingClass const &whatever)
{
	if (stricmp(UserName, whatever.UserName) == 0) {
		return(false);
	}
	return(true);
}			  


bool Is_Options_Spammer(char *user_name, int &count)
{
	int index = -1;
	count = 0;
	for (int i=0 ; i<OptionsTracking.Count() ; i++) {
		if (stricmp(OptionsTracking[i]->UserName, user_name) == 0) {
			index = i;
			break;
		}
	}

	if (index == -1) {
		
		/*
		** See if there are any old ones we can remove.
		*/
		for (int i=OptionsTracking.Count() - 1 ; i >= 0 ; i--) {
			if (OptionsTracking[i]->Times.Count() == 0) {
				delete OptionsTracking[i];
				OptionsTracking.Delete(i);
				index = i;
				break;
			}
		}
		
		
		PrivateGameOptionsTrackingClass *options = new PrivateGameOptionsTrackingClass;
		strncpy(options->UserName, user_name, 255);
		options->UserName[255] = 0;
		OptionsTracking.Add(options);
		index = OptionsTracking.Count() - 1;
	}

	unsigned long time = TIMEGETTIME();
	OptionsTracking[index]->Times.Add(time);

	unsigned long old_time = time - 4000;
	if (old_time < time) {
		
		/*
		** Remove times older than 5 secs.
		*/
		int c = OptionsTracking[index]->Times.Count();
		for (int i=0 ; i<c ; i++) {
			if (OptionsTracking[index]->Times[0] < old_time) {
				OptionsTracking[index]->Times.Delete(0);
			} else {
				break;
			}
		}
		count = OptionsTracking[index]->Times.Count();

		if (OptionsTracking[index]->Times.Count() > 16) {
			return(true);
		}
		
		
	} else {
		while (OptionsTracking.Count()) {
			delete OptionsTracking[0];
			OptionsTracking.Delete(0);
		}
	}
	
	return(false);
	
}			  


void ChatObserver::Kick_Spammer(WOL::User *wol_user)
{		
	/*
	** Get the IP of the spammer.
	*/
	const UserList& user_list = mOuter->GetUserList();
	const unsigned int count = user_list.size();
	unsigned long ip = 0;

	for (unsigned int index = 0; index < count; index++) {
		const RefPtr<UserData>& user = user_list[index];
		if (user.IsValid()) {
			WOL::User userdata = user->GetData();
			if (stricmp((char*)wol_user->name, (char*)userdata.name) == 0) {
				ip = userdata.ipaddr;
				break;
			}
		}
	}
	
	/*
	** Ban em, ban em all.
	*/
	for (index = 0; index < count; index++) {
		const RefPtr<UserData>& user = user_list[index];
		if (user.IsValid()) {
			WOL::User userdata = user->GetData();
			if (userdata.ipaddr == ip) {
				WideStringClass widey((char*)userdata.name, true);

				const RefPtr<LoginInfo>& login = mOuter->GetCurrentLogin();

				WideStringClass myname(L"", true);
				if (login.IsValid()) {
					myname = login->GetNickname();
				}
				if (widey.Compare_No_Case(myname) != 0) {
					mOuter->BanUser(widey.Peek_Buffer(), true);
					mOuter->KickUser(widey.Peek_Buffer());
					WWDEBUG_SAY(("WOL: '%s' banned for options spamming\n", (char*)(char*)userdata.name));
				}
			}
		}
	}

	WideStringClass widey((char*)wol_user->name, true);
	mOuter->BanUser(widey.Peek_Buffer(), true);
	mOuter->KickUser(widey.Peek_Buffer());
	WWDEBUG_SAY(("WOL: '%s' banned for options spamming\n", (char*)wol_user->name));
}



/******************************************************************************
*
* NAME
*     ChatObserver::OnPrivateGameOptions
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPrivateGameOptions(HRESULT result, WOL::User* inUser, LPCSTR options)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateGameOptions '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Parameter check
	if ((inUser == NULL) || (options == NULL))
		{
		WWASSERT(inUser != NULL);
		WWASSERT(options != NULL);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: (time %d) PrivateGameOptions from '%s' : %s\n", TIMEGETTIME(), (char*)inUser->name, options));
	
	if (cNetwork::I_Am_Server()) {
	
		int count = 0;
		bool is_spammer = Is_Options_Spammer((char*)inUser->name, count);
		bool is_rginfo = (strstr(options, "RGINFO") == NULL) ? false : true;

		if (is_spammer) {
			if (count > 16 && count < 19) {
				Kick_Spammer(inUser);
			}
		} else {
			if (is_rginfo && count > 2) {
				WWDEBUG_SAY(("WOL: Ignoring RGINFO from '%s' \n", (char*)inUser->name));
			} else {
				GameOptionsMessage gameOptions(inUser, NULL, options, true);
				mOuter->NotifyObservers(gameOptions);
			}
		}
	} else {
		GameOptionsMessage gameOptions(inUser, NULL, options, true);
		mOuter->NotifyObservers(gameOptions);
	}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPublicGameOptions
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPublicGameOptions(HRESULT result, WOL::Channel* inChannel,
			WOL::User* inUser, LPCSTR options)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateGameOptions '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Parameter check
	if ((inChannel == NULL) || (inUser == NULL) || (options == NULL))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicGameOptions invalid parameters\n"));
		WWASSERT(inChannel != NULL);
		WWASSERT(inUser != NULL);
		WWASSERT(options != NULL);
		return S_OK;
		}

	// Only acknowledge options for the channel we are in.
	wchar_t inChannelName[64];
	mbstowcs(inChannelName, (char*)inChannel->name, sizeof(inChannel->name));
	RefPtr<ChannelData> channel = mOuter->FindChannel(inChannelName);

	if (!channel.IsValid() || (channel != mOuter->mCurrentChannel))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicGameOptions channel mismatch\n"));
		return S_OK;
		}

	// Make sure this is from a user we know about.
	wchar_t inUsername[64];
	mbstowcs(inUsername, (char*)inUser->name, sizeof(inUser->name));
	RefPtr<UserData> user = mOuter->FindUser(inUsername);

	if (!user.IsValid())
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicGameOptions unknown user\n"));
		WWASSERT(user.IsValid() && "Received game options from unknown user");
		return S_OK;
		}

	// Only handle options from other users.
	if (mOuter->mCurrentUser != user)
		{
		WWDEBUG_SAY(("WOL: PublicGameOptions [%s] from '%s': %s\n", (char*)inChannel->name, (char*)inUser->name, options));

#ifdef FREEDEDICATEDSERVER			
		int count = 0;
		if (Is_Options_Spammer((char*)inUser->name, count)) {
			if (count > 16 && count < 19) {
				Kick_Spammer(inUser);
			}
		}
#endif
		GameOptionsMessage gameOptions(inUser, inChannel, options, false);
		mOuter->NotifyObservers(gameOptions);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnGameStart
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnGameStart(HRESULT result, WOL::Channel* inChannel,
		WOL::User* inUsers, int gameID)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle error condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnGameStart '%s'\n", GetChatErrorString(result)));
		GameStartEvent gameStart(result);
		mOuter->NotifyObservers(gameStart);
		return S_OK;
		}

	// Ignore if parameters are invalid
	if (inChannel == NULL || inUsers == NULL)
		{
		WWDEBUG_SAY(("WOL: OnGameStart Invalid parameters\n"));
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: OnGameStart GameID %ld, Channel '%s'\n", gameID, (char*)inChannel->name));

	// Make sure the channel is the one we have joined.
	bool channelOkay = false;

	if (mOuter->mCurrentChannel.IsValid())
		{
		wchar_t chanName[64];
		mbstowcs(chanName, (const char*)inChannel->name, sizeof(inChannel->name));
		const WideStringClass& name = mOuter->mCurrentChannel->GetName();
		channelOkay = (name.Compare_No_Case(chanName) == 0);
		}

	// The game start is invalid if this is not the channel we expect.
	if (channelOkay == false)
		{
		WWDEBUG_SAY(("WOLERROR: OnGameStart channel mismatch\n"));
		GameStartEvent gameStart(E_INVALIDARG);
		mOuter->NotifyObservers(gameStart);
		return S_OK;
		}

	// Process the game start
	UserList userList;
	WOL::User* curUser = inUsers;

	while (curUser)
		{
		WWDEBUG_SAY(("WOL: OnGameStart Player '%s'\n", (char*)curUser->name));
		wchar_t name[64];
		mbstowcs(name, (const char*)curUser->name, sizeof(curUser->name));

		// Get user from current list. If not in list then create new user.
		RefPtr<UserData> user = mOuter->FindUser(name);

		if (!user.IsValid())
			{
			user = UserData::Create(*curUser);
			}

		if (user.IsValid())
			{
			userList.push_back(user);
			}

		curUser = curUser->next;
		}

	GameStartEvent gameStart(mOuter->mCurrentChannel, userList, gameID);
	mOuter->NotifyObservers(gameStart);
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserKick
*
* DESCRIPTION
*     Handle response to user kick request.
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserKick(HRESULT result, WOL::Channel* inChannel,
			WOL::User* inUser, WOL::User* kicker)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Handle error condition
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserKick '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Check parameters
	if ((inChannel == NULL) || (inUser == NULL))
		{
		WWASSERT(inChannel);
		WWASSERT(inUser);
		WWDEBUG_SAY(("WOLWARNING: Cannot handle user kick because CHANNEL or USER is NULL\n"));
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: '%s' has been kicked from channel '%s' by '%s'\n",
			(char*)inUser->name, (char*)inChannel->name, (char*)kicker->name));

	// If we are not connected to a channel then ignore.
	if (!mOuter->mCurrentChannel.IsValid())
		{
		WWDEBUG_SAY(("WOLWARNING: OnUserKick not in a channel\n"));
		return S_OK;
		}

	// If the channel is not the channel we are connected to then ignore.
	wchar_t channelName[64];
	mbstowcs(channelName, (const char*)inChannel->name, sizeof(inChannel->name));
	const WideStringClass& curChannelName = mOuter->mCurrentChannel->GetName();

	if (curChannelName.Compare_No_Case(channelName) != 0)
		{
		WWDEBUG_SAY(("WOLWARNING: OnUserKick channel mismatch\n"));
		return S_OK;
		}

	// Decrement user count for this channel
	WWASSERT(mOuter->mCurrentChannel->GetData().currentUsers > 0);
	mOuter->mCurrentChannel->GetData().currentUsers--;

	// If current user is kicked then disconnect from the channel and clear the user list.
	if (inUser->flags & CHAT_USER_MYSELF)
		{
		mOuter->mCurrentChannelStatus = ChannelKicked;

		// Release the current channel since we have been kicked
		RefPtr<ChannelData> channel = mOuter->mCurrentChannel;
		mOuter->mCurrentChannel.Release();

		// Notify others that we have been kicked from the channel.
		ChannelEvent event(ChannelKicked, channel);
		mOuter->NotifyObservers(event);

		// Clear the user list then notify others that the user list is now empty.
		mOuter->mUsers.clear();
		mOuter->NotifyObservers(mOuter->mUsers);
		}
	else
		{
		// Remove kicked user from the userlist
		wchar_t username[64];
		mbstowcs(username, (char*)inUser->name, sizeof(inUser->name));

		RefPtr<UserData> kickedUser = RemoveUserInList(username, mOuter->mUsers);

		// Notify others that the user was kicked
		if (kickedUser.IsValid())
			{
			UserEvent event(UserEvent::Kicked, kickedUser);
			mOuter->NotifyObservers(event);
			}
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserIP
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserIP(HRESULT result, WOL::User* user)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserIP '%s'\n", GetChatErrorString(result)));

		if (user)
			{
			UserIPEvent event(UserIPEvent::Error, *user);
			mOuter->NotifyObservers(event);
			}
	
		return S_OK;
		}

		if (user)
			{
			UserIPEvent event(UserIPEvent::GotIP, *user);
			mOuter->NotifyObservers(event);
			}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnFind
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnFind(HRESULT result, WOL::Channel* wolChannel)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnFind '%s'\n", GetChatErrorString(result)));
		UserEvent event(UserEvent::Located, RefPtr<UserData>());
		mOuter->NotifyObservers(event);
		mOuter->mLocatingUser.Release();
		return S_OK;
		}

	const RefPtr<UserData>& user = mOuter->mLocatingUser;

	if (user.IsValid())
		{
		UserLocation location = USERLOCATION_UNKNOWN;
		RefPtr<ChannelData> userChannel;

		if (wolChannel)
			{
			location = USERLOCATION_IN_CHANNEL;

			// Look for the channel in our existing list
			userChannel = mOuter->FindChannel((const char*)wolChannel->name);

			// Create the channel if we don't already have it.
			if (userChannel.IsValid() == false)
				{
				userChannel = ChannelData::Create(*wolChannel);
				}
			}
		else
			{
			switch (result)
				{
				default:
				case CHAT_S_FIND_NOCHAN:
					location = USERLOCATION_NO_CHANNEL;
				break;

				case CHAT_S_FIND_NOTHERE:
					location = USERLOCATION_OFFLINE;
					break;

				case CHAT_S_FIND_OFF:
					location = USERLOCATION_HIDING;
					break;
				}
			}

		user->SetLocation(location);
		user->SetChannel(userChannel);

		// Notify others about the users location.
		UserEvent event(UserEvent::Located, user);
		mOuter->NotifyObservers(event);
		}

	mOuter->mLocatingUser.Release();

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPageSend
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPageSend(HRESULT result)
	{
	PageSendStatus status = PAGESEND_ERROR;

	switch (result)
		{
		default:
		case E_FAIL:
			status = PAGESEND_ERROR;
			break;

		case CHAT_S_PAGE_NOTHERE:
			status = PAGESEND_OFFLINE;
			break;

		case CHAT_S_PAGE_OFF:
			status = PAGESEND_HIDING;
			break;

		case S_OK:
			status = PAGESEND_SENT;
			break;
		}

	mOuter->NotifyObservers(status);
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPaged
*
* DESCRIPTION
*     Handle being paged by user.
*
* INPUTS
*     Result - Error / status code.
*     User   - User who initiated page.
*     Text   - Page message
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPaged(HRESULT result, WOL::User* user, LPCSTR text)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPaged '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	if (user && text)
		{
		PageMessage page((const char*)&user->name[0], text);
		mOuter->NotifyObservers(page);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnServerBannedYou
*
* DESCRIPTION
*      The login has been banned from the server.
*
* INPUTS
*     Result     - Error / status code.
*     LiftedTime - Time banned will be lifted.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnServerBannedYou(HRESULT, WOL::time_t liftedTime)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: OnServerBannedYou\n"));

	ServerError event(CHAT_E_BANNED, "WOL_BANNED", liftedTime);
	mOuter->NotifyObservers(event);
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelBan
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelBan(HRESULT result, LPCSTR username, int banned)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelBan '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: '%s' has been %s\n", username, banned ? "banned" : "unbanned"));

	if (banned && username)
		{
		wchar_t name[64];
		mbstowcs(name, username, 63);

		RefPtr<UserData> user = mOuter->GetUserOrBuddy(name);

		if (!user.IsValid())
			{
			user = UserData::Create(name);
			}

		UserEvent event(UserEvent::Banned, user);
		mOuter->NotifyObservers(event);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserFlags
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserFlags(HRESULT result, LPCSTR username, unsigned int flags, unsigned int mask)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserFlags '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Find the user and update their flag settings
	wchar_t name[64];
	mbstowcs(name, username, 63);
	RefPtr<UserData> user = mOuter->GetUserOrBuddy(name);

	if (user.IsValid())
		{
		unsigned int userflags = user->GetData().flags;
		user->GetData().flags = ((userflags & mask) | flags);

		UserEvent event(UserEvent::NewData, user);
		mOuter->NotifyObservers(event);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnSquadInfo
*
* DESCRIPTION
*     Handle receipt of squad information.
*
* INPUTS
*     Result   - Error / status code.
*     WOLSquad - WOLAPI squad structure
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnSquadInfo(HRESULT result, unsigned long squadID, WOL::Squad* inSquad)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	// Requested squad not found.
	if (CHAT_E_NONESUCH == result)
		{
		if (!mOuter->mSquadPending.empty())
			{
			RefPtr<SquadData> squad;
			ProcessSquadRequest(squad);
			return S_OK;
			}
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnSquadInfo '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	WOL::Squad* wolSquad = inSquad;

	while (wolSquad)
		{
//		WWDEBUG_SAY(("WOL: OnSquadInfo ID %ld, Name '%s', Abbr '%s'\n", inSquad->id, inSquad->name, inSquad->abbreviation));

		// If the squad is already in the local cache then update it.
		// Otherwise create and cache a local copy if this squad information.
		RefPtr<SquadData> squad = SquadData::FindByID(wolSquad->id);

		if (squad.IsValid())
			{
			squad->UpdateData(*wolSquad);
			}
		else
			{
			squad = SquadData::Create(*wolSquad);

			// Automatically request ladder information for this squad.
			if (squad.IsValid())
				{
				wchar_t abbr[64];
				mbstowcs(abbr, squad->GetAbbr(), 64);
				abbr[63] = 0;

				mOuter->RequestLadderInfo(abbr, LadderType_Clan);
				}
			}

		if (squad.IsValid())
			{
			// Assign this squad to any users that are members.
			AssignSquadToUsers(mOuter->GetUserList(), squad);
			AssignSquadToUsers(mOuter->GetBuddyList(), squad);

			ProcessSquadRequest(squad);
			}

		wolSquad = wolSquad->next;
		}

	return S_OK;
	}


void ChatObserver::AssignSquadToUsers(const UserList& users, const RefPtr<SquadData>& squad)
	{
	// Assign the squad to users who are its members.
	const unsigned int userCount = users.size();

	for (unsigned int index = 0; index < userCount; ++index)
		{
		const RefPtr<UserData>& user = users[index];

		if (user.IsValid() && (user->GetSquadID() == squad->GetID()))
			{
			user->SetSquad(squad);

			// Notify others that this user received squad information
			UserEvent event(UserEvent::SquadInfo, user);
			mOuter->NotifyObservers(event);
			}
		}
	}


void ChatObserver::ProcessSquadRequest(const RefPtr<SquadData>& squad)
	{
	// Process any particular squad requests that have been made.
	if (!mOuter->mSquadPending.empty())
		{
		// Get the first pending squad request from the queue and see if it's a request by ID or Name.
		const WideStringClass& pending = mOuter->mSquadPending[0];

		if (squad.IsValid())
			{
			WWDEBUG_SAY(("WOL: Squad %s found for %S\n", squad->GetAbbr(), (const WCHAR*)pending));

			// First character of user names cannot be numbers. Therefore if it is a number
			// then process the request by ID. Otherwise process the request by name.
			wchar_t firstChar = pending[0];

			if (iswdigit(firstChar))
				{
				unsigned int pendingID = _wtoi(pending);

				if (squad->GetID() == pendingID)
					{
					SquadEvent squadEvent(NULL, squad);
					mOuter->NotifyObservers(squadEvent);
					}
				else
					{
					WWDEBUG_SAY(("WOLWARNING: OnSquadInfo PendingID '%u' doesn't match CurrentID '%u'\n", pendingID, squad->GetID()));
					}
				}
			else
				{
				RefPtr<UserData> user = mOuter->GetUserOrBuddy(pending);

				if (user.IsValid())
					{
					user->SetSquad(squad);
					UserEvent event(UserEvent::SquadInfo, user);
					mOuter->NotifyObservers(event);
					}

				// Must be a user name that we requested by
				SquadEvent squadEvent(pending, squad);
				mOuter->NotifyObservers(squadEvent);
				}
			}
		else
			{
			WWDEBUG_SAY(("WOL: Squad not found for '%S'\n", (const WCHAR*)pending));
			}

		Session::SquadRequestColl::iterator first = mOuter->mSquadPending.begin();
		mOuter->mSquadPending.erase(first);
		}
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserLocale
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserLocale(HRESULT result, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserLocale '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	WOL::User* wolUser = inUsers;

	while (wolUser)
		{
		WWDEBUG_SAY(("WOL: OnUserLocale '%s' %ld\n", wolUser->name, wolUser->locale));

		WOL::Locale locale = wolUser->locale;
		WWASSERT(locale >= WOL::LOC_UNKNOWN && locale <= WOL::LOC_TURKEY && "OnUserLocale Locale out of range!");

		// Update the users locale.
		wchar_t username[64];
		mbstowcs(username, (const char*)wolUser->name, sizeof(wolUser->name));
		RefPtr<UserData> user = mOuter->GetUserOrBuddy(username);

		if (user.IsValid() && (user->GetLocale() != locale))
			{
			// If the current user's requested locale is different than the player's
			// login locale then update the locale in the WWOnline database to match
			// the login locale.
			if (mOuter->IsCurrentUser(user))
				{
				const RefPtr<LoginInfo>& login = mOuter->GetCurrentLogin();
				WWASSERT(login.IsValid() && "OnUserLocale: Login not set");

				if (login.IsValid())
					{
					WOL::Locale loginLocale = login->GetLocale();

					if (locale != loginLocale)
						{
						mOuter->ChangeCurrentUserLocale(loginLocale);
						}
					}
				}

			// Update the locale for this user.
			user->SetLocale(locale);
			UserEvent event(UserEvent::Locale, user);
			mOuter->NotifyObservers(event);
			}

		wolUser = wolUser->next;
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnSetLocale
*
* DESCRIPTION
*     Handle response to IChat::RequestSetLocale
*
* INPUTS
*     Result - Error / status code.
*     Locale - New locale for current user.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnSetLocale(HRESULT result, WOL::Locale locale)
	{
	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnSetLocale '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	WWASSERT(mOuter && "Session not initialized");

	// Verify that the locale is what we expected.
	const RefPtr<LoginInfo>& login = mOuter->GetCurrentLogin();
	WWASSERT(login.IsValid() && "OnSetLocale: Invalid Login");

	if (login.IsValid() && (login->GetLocale() != locale))
		{
		WWDEBUG_SAY(("WOLERROR: OnSetLocale - Mismatch with login locale\n"));
		WWASSERT(login->GetLocale() == locale && "OnSetLocale: Mismatch with login locale");
		}

	// Set the current users locale to the newly requested one.
	RefPtr<UserData> user = mOuter->GetCurrentUser();

	if (user.IsValid())
		{
		user->SetLocale(locale);
		UserEvent event(UserEvent::Locale, user);
		mOuter->NotifyObservers(event);
		}
	else
		{
		WWDEBUG_SAY(("WOLERROR: OnSetLocale Invalid current user\n"));
		WWASSERT(user.IsValid() && "OnSetLocale Invalid current user");
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnUserTeam
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnUserTeam(HRESULT result, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnUserTeam '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	WOL::User* wolUser = inUsers;

	while (wolUser)
		{
		WWDEBUG_SAY(("WOL: OnUserTeam '%s' %ld\n", wolUser->name, wolUser->locale));

		// Update the users locale.
		wchar_t username[64];
		mbstowcs(username, (const char*)wolUser->name, sizeof(wolUser->name));
		RefPtr<UserData> user = mOuter->GetUserOrBuddy(username);

		if (user.IsValid())
			{
			user->SetTeam(wolUser->team);
			UserEvent event(UserEvent::NewData, user);
			mOuter->NotifyObservers(event);
			}

		wolUser = wolUser->next;
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnSetTeam
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnSetTeam(HRESULT result, int team)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnSetTeam '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	RefPtr<UserData> user = mOuter->GetCurrentUser();

	if (user.IsValid())
		{
		user->SetTeam(team);
		UserEvent event(UserEvent::NewData, user);
		mOuter->NotifyObservers(event);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnBuddyList
*
* DESCRIPTION
*
* INPUTS
*     Result  - Result / status code.
*     Buddies - User list of buddies.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnBuddyList(HRESULT result, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnBuddyList '%s'\n", GetChatErrorString(result)));
		UserList buddies;
		BuddyEvent event(BuddyEvent::NewList, buddies);
		mOuter->NotifyObservers(event);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: OnBuddyList\n"));

	// Generate new list
	UserList buddies;

	WOL::User* wolUser = inUsers;

	while (wolUser)
		{
		WideStringClass name(64, true);
		name = (char*)wolUser->name;

		// First look for buddy in our old list.
		RefPtr<UserData> buddy = mOuter->FindBuddy(name);

		// Create a new buddy if we don't already know about him.
		if (buddy.IsValid() == false)
			{
			buddy = UserData::Create(*wolUser);
			}

		if (buddy.IsValid())
			{
			buddies.push_back(buddy);
			}

		wolUser = wolUser->next;
		}

	mOuter->mBuddies = buddies;
	BuddyEvent event(BuddyEvent::NewList, mOuter->mBuddies);
	mOuter->NotifyObservers(event);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnBuddyAdd
*
* DESCRIPTION
*
* INPUTS
*     Result  - Result / status code.
*     Buddies - User list of buddies added.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnBuddyAdd(HRESULT result, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnBuddyAdd '%s'\n", GetChatErrorString(result)));
		UserList addList;
		BuddyEvent event(BuddyEvent::Added, addList);
		mOuter->NotifyObservers(event);
		return S_OK;
		}

	// Generate new list
	UserList addList;
	WOL::User* wolUser = inUsers;

	while (wolUser)
		{
		WideStringClass name(64, true);
		name = (char*)wolUser->name;

		// First look for buddy in our old list.
		RefPtr<UserData> buddy = mOuter->FindBuddy(name);

		// Create a new buddy if we don't already know about him.
		if (buddy.IsValid() == false)
			{
			buddy = UserData::Create(*wolUser);

			if (buddy.IsValid())
				{
				mOuter->mBuddies.push_back(buddy);
				addList.push_back(buddy);
				}
			}

		wolUser = wolUser->next;
		}

	if (addList.size() > 0)
		{
		BuddyEvent event(BuddyEvent::Added, addList);
		mOuter->NotifyObservers(event);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnBuddyDelete
*
* DESCRIPTION
*
* INPUTS
*     Result  - Result / status code.
*     Buddies - User list of buddies removed
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnBuddyDelete(HRESULT result, WOL::User* inUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnBuddyDelete '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	// Generate new list
	UserList deleteList;

	WOL::User* wolUser = inUsers;

	while (wolUser)
		{
		WideStringClass name(64, true);
		name = (char*)wolUser->name;

		RefPtr<UserData> buddy = RemoveUserInList(name, mOuter->mBuddies);

		if (buddy.IsValid())
			{
			deleteList.push_back(buddy);
			}

		wolUser = wolUser->next;
		}

	if (deleteList.size() > 0)
		{
		BuddyEvent event(BuddyEvent::Deleted, deleteList);
		mOuter->NotifyObservers(event);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPublicUnicodeMesssage
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPublicUnicodeMessage(HRESULT result, WOL::Channel*,
		WOL::User* user, const unsigned short* message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicUnicodeMessage '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, false, false);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPrivateUnicodeMessage
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPrivateUnicodeMessage(HRESULT result, WOL::User* user,
		const unsigned short* message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateUnicodeMessage '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, true, false);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPrivateUnicodeAction
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPrivateUnicodeAction(HRESULT result, WOL::User* user,
		const unsigned short* message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPrivateUnicodeAction '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, true, true);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPublicUnicodeAction
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPublicUnicodeAction(HRESULT result, WOL::Channel*,
		WOL::User* user, const unsigned short* message)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPublicUnicodeAction '%s'\n", GetChatErrorString(result)));
		}
	else
		{
		ChatMessage msg(user, message, false, true);
		mOuter->NotifyObservers(msg);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnPagedUnicode
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnPagedUnicode(HRESULT result, WOL::User* user, const unsigned short* text)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnPagedUnicode '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	if (user && text)
		{
		wchar_t name[64];
		mbstowcs(name, (const char*)&user->name[0], sizeof(user->name));
		PageMessage page(name, text);
		mOuter->NotifyObservers(page);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnServerTime
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnServerTime(HRESULT result, WOL::time_t server_time)
	{	
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnServerTime '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	mOuter->mServerTime = server_time;
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnInsiderStatus
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnInsiderStatus(HRESULT result, WOL::User* wolUsers)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnInsiderStatus '%s'\n", GetChatErrorString(result)));
		return S_OK;
		}

	if (wolUsers != NULL)
		{
		//	Get the name of the currently logged in user
		RefPtr<UserData> curr_user = mOuter->GetCurrentUser();
		
		// Convert the WOL user's name to a wide character string
		WideStringClass wide_name(0, true);
		wide_name.Convert_From((const char*)wolUsers[0].name);

		//	Check to see if we got information back about the current user
		if (curr_user->GetName().Compare_No_Case(wide_name) == 0)
			{
			mOuter->mIsInsider = wolUsers[0].squadname[0] != '0';
			}
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnSetLocalIP
*
* DESCRIPTION
*
* INPUTS
*     Result - Error / status code.
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnSetLocalIP(HRESULT, LPCSTR)
	{
	WWDEBUG_SAY(("WOLWARNING: OnSetLocalIP not implemented\n"));
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelBegin
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelListBegin(HRESULT result)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelListBegin '%s'\n", GetChatErrorString(result)));
		ChannelList empty;
		ChannelListEvent error(ChannelListEvent::Error, empty, -1);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: OnChannelListBegin %d\n", mOuter->mRequestedChannelType));

	mOuter->mIncommingChatChannels.clear();
	mOuter->mIncommingGameChannels.clear();
	WWASSERT(mOuter->mRequestedChannelType >= 0);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelListEntry
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelListEntry(HRESULT result, WOL::Channel* wolChannel)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result) || (wolChannel == NULL) || (wolChannel->type != mOuter->mRequestedChannelType))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelListEntry '%s'\n", GetChatErrorString(result)));
		ChannelList empty;
		ChannelListEvent error(ChannelListEvent::Error, empty, -1);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: ChannelEntry '%s:%ld'\n", (const char*)wolChannel->name, wolChannel->type));

	// Get code for game channels
	RefPtrConst<Product> product = Product::Current();
	WWASSERT(product.IsValid() && "WOLProduct not initialized");
	int gameCode = product->GetGameCode();

	ChannelList* list = NULL;

	if (wolChannel->type == 0)
		{
		list = &mOuter->mChatChannels;
		}
	else if (wolChannel->type == gameCode)
		{
		list = &mOuter->mGameChannels;
		}
	else
		{
		return S_OK;
		}

	// Process the channel
	ChannelList inList;
	ChannelList outList;

	// Check if the channel exists in the current list. If the channel exists in
	// the current list then update the channel. Otherwise the channel is new,
	// therefore we need to add it.
	ChannelList::iterator iter = FindChannelNode(*list, (const char*)wolChannel->name);

	// If the channel already exists then just update it.
	if (iter != list->end())
		{
		// Update the channel data.
		RefPtr<ChannelData> channel = *iter;
		WWASSERT(channel.IsValid());
		channel->UpdateData(*wolChannel);

		// Channels arrive alphabetically. Therefore if there are any channels
		// in the list before this one then those channels are no longer around.
		// So move those channels into the out list.
		if (iter != list->begin())
			{
			#ifdef _DEBUG
			ChannelList::iterator remIter = list->begin();

			while (remIter != iter)
				{
				WWDEBUG_SAY(("WOL: Removing Channel '%S'\n", (*remIter)->GetName()));
				remIter++;
				}
			#endif

			ChannelList::iterator end = outList.end();
			outList.splice(end, *list, list->begin(), iter);
			}

		// Move the channel into the new list.
		ChannelList::iterator end = inList.end();
		inList.splice(end, *list, iter);
		}
	else
		{
		// Create the new channel and add it to the new list.
		RefPtr<ChannelData> channel = ChannelData::Create(*wolChannel);
		WWASSERT(channel.IsValid());
		inList.push_back(channel);
		}

	// Notify others about the channel changes
	if (inList.empty() == false)
		{
		ChannelListEvent inEvent(ChannelListEvent::Update, inList, wolChannel->type);
		mOuter->NotifyObservers(inEvent);
		}

	if (outList.empty() == false)
		{
		ChannelListEvent outEvent(ChannelListEvent::Remove, outList, wolChannel->type);
		mOuter->NotifyObservers(outEvent);
		}

	if (wolChannel->type == 0)
		{
		ChannelList::iterator end = mOuter->mIncommingChatChannels.end();
		mOuter->mIncommingChatChannels.splice(end, inList);
		}
	else
		{
		ChannelList::iterator end = mOuter->mIncommingGameChannels.end();
		mOuter->mIncommingGameChannels.splice(end, inList);
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     ChatObserver::OnChannelListEnd
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     Error / Status result
*
******************************************************************************/

STDMETHODIMP ChatObserver::OnChannelListEnd(HRESULT result)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result))
		{
		WWDEBUG_SAY(("WOLERROR: OnChannelListEnd '%s'\n", GetChatErrorString(result)));
		ChannelList empty;
		ChannelListEvent error(ChannelListEvent::Error, empty, -1);
		mOuter->NotifyObservers(error);
		return S_OK;
		}

	WWDEBUG_SAY(("WOL: OnChannelListEnd %d\n", mOuter->mRequestedChannelType));
	WWASSERT(mOuter->mRequestedChannelType >= 0);

	// Replace lists
	ChannelList* channelList = NULL;
	ChannelList* newList = NULL;
	int channelType = 0;

	if (mOuter->mRequestedChannelType == 0)
		{
		channelList = &mOuter->mChatChannels;
		newList = &mOuter->mIncommingChatChannels;
		}
	else
		{
		RefPtr<Product> product = Product::Current();
		WWASSERT(product.IsValid());
		channelType = product->GetGameCode();

		channelList = &mOuter->mGameChannels;
		newList = &mOuter->mIncommingGameChannels;
		}

	WWASSERT(channelList != NULL);
	WWASSERT(newList != NULL);

	// Any leftover channels were not in the new list. So, send a
	// notification that these channels are being removed then remove them.
	if (!channelList->empty())
		{
		ChannelListEvent outEvent(ChannelListEvent::Remove, *channelList, channelType);
		mOuter->NotifyObservers(outEvent);
		channelList->clear();
		}
		
	// Swap in the new channel list
	channelList->swap(*newList);

	// Send notification about the new channel list.
	ChannelListEvent listEvent(ChannelListEvent::NewList, *channelList, channelType);
	mOuter->NotifyObservers(listEvent);

	mOuter->mRequestedChannelType = -1;

	return S_OK;
	}

} // namespace WWOnline