/*
**	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/WOLNetUtilObserver.cpp $
*
* DESCRIPTION
*
* PROGRAMMER
*     $Author: Denzil_l $
*
* VERSION INFO
*     $Revision: 25 $
*     $Modtime: 2/21/02 5:36p $
*
******************************************************************************/

#include <atlbase.h>
#include "WOLNetUtilObserver.h"
#include "WOLSession.h"
#include "WOLLadder.h"
#include "WOLServer.h"
#include "WOLErrorUtil.h"

namespace WWOnline {

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

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


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

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


/******************************************************************************
*
* NAME
*     NetUtilObserver::Init
*
* DESCRIPTION
*     Initialize Net utility observer
*
* INPUTS
*     WOLSession - Outer session
*
* RESULT
*     NONE
*
******************************************************************************/

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


/****************************************************************************
*
* NAME
*     IUnknown::QueryInterface
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
****************************************************************************/

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

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


/****************************************************************************
*
* NAME
*     IUnknown::AddRef
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*
****************************************************************************/

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


/****************************************************************************
*
* NAME
*     IUnknown::Release
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*
****************************************************************************/

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

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

	return mRefCount;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnPing
*
* DESCRIPTION
*     Handle ping result response to a ping request
*
* INPUTS
*     Result - Error / Status code
*     Time   - Ping time
*     IP     - IP Address
*     Handle - Ping request handle
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnPing(HRESULT result, int time, unsigned long ip, int handle)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

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

	// Find the ping request
	int pingIndex = -1;

	for (unsigned int index = 0; index < mOuter->mPingRequests.size(); index++)
		{
		if (mOuter->mPingRequests[index].GetHandle() == handle)
			{
			pingIndex = index;
			break;
			}
		}

	if ((pingIndex >= 0) && ((unsigned)pingIndex < mOuter->mPingRequests.size()))
		{
		RawPing* ping = &mOuter->mPingRequests[pingIndex];
		ping->SetTime(time);
		ping->SetIPAddress(ip);

		WWDEBUG_SAY(("WOL: OnPing %s = %d\n", ping->GetHostAddress(), time));

		// Automatically update ping time for ping servers.
		mOuter->UpdatePingServerTime(ping->GetHostAddress(), time);

		// Notify others about the ping result.
		mOuter->NotifyObservers(*ping);
	
		// Remove ping from request list.
		std::vector<RawPing>::iterator iter = mOuter->mPingRequests.begin();

		while (iter != mOuter->mPingRequests.end())
			{
			if (iter == ping)
				{
				mOuter->mPingRequests.erase(iter);
				mOuter->mPingsPending--;
				break;
				}

			iter++;
			}
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnLadderList
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnLadderList(HRESULT result, WOL::Ladder* list,
			int rungCount, long timeStamp, int keyRung)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

	if (FAILED(result) || (list == NULL))
		{
		WWDEBUG_SAY(("WOLERROR: OnLadderList '%s'\n", GetNetUtilErrorString(result)));

		// Clear the ladder type we requested (in order)
		if (mOuter->mLadderPending & LadderType_Individual)
			{
			mOuter->mLadderPending &= ~LadderType_Individual;
			}
		else if (mOuter->mLadderPending & LadderType_Team)
			{
			mOuter->mLadderPending &= ~LadderType_Team;
			}
		else if (mOuter->mLadderPending & LadderType_Clan)
			{
			mOuter->mLadderPending &= ~LadderType_Clan;
			}
		else
			{
			mOuter->mLadderPending &= ~LADDERTYPE_MASK;
			}

		// If there are no pending ladders then remove the satisfied requests
		if ((mOuter->mLadderPending & LADDERTYPE_MASK) == 0)
			{
			for (unsigned int count = 0; count < mOuter->mLadderPending; count++)
				{
				mOuter->mLadderRequests.pop_front();
				}

			mOuter->mLadderPending = 0;
			}

		return S_OK;
		}

	//---------------------------------------------------------------------------
	// Handle ladder information for individual users.
	//---------------------------------------------------------------------------
	if (keyRung == -1)
		{
		ProcessLadderListResults(list, timeStamp);
		return S_OK;
		}

	//---------------------------------------------------------------------------
	// Handle ladder search results
	//---------------------------------------------------------------------------
	LadderList ladders;

	WOL::Ladder* wolLadder = list;

	while (wolLadder)
		{
		RefPtr<LadderData> data = LadderData::Create(*wolLadder, timeStamp);

		if (data.IsValid())
			{
			ladders.push_back(data);
			}

		wolLadder = wolLadder->next;
		}

	mOuter->NotifyObservers(ladders);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::ProcessLadderListResults
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     NONE
*
******************************************************************************/

void NetUtilObserver::ProcessLadderListResults(WOL::Ladder* list, long timeStamp)
	{
	if (mOuter->mLadderPending)
		{
		// Within a given request, ladder results are guaranteed to be in order.
		WOL::Ladder* wolLadder = list;

		// Extract the ladder request type from the hiword of the SKU
		LadderType type = (LadderType)(wolLadder->sku & LADDERTYPE_MASK);

		if (type == 0)
			{
			type = LadderType_Individual;
			}

		Session::LadderRequestList::iterator request = mOuter->mLadderRequests.begin();
		unsigned int ladderCount = 0;

		while (wolLadder)
			{
			// Get the name of the user we requested information for.
			const WCHAR* requestName = wcschr(*request, L':');
			WWASSERT(requestName != NULL && "Invalid ladder request");
			requestName++;

			wchar_t ladderName[64];
			mbstowcs(ladderName, (const char*)wolLadder->login_name, sizeof(wolLadder->login_name));

			WWDEBUG_SAY(("WOL: LadderInfo [%08lX] Requested '%S', Received '%S'\n", type, requestName, ladderName));

			// If the ladder name matches the requested name then there is ladder info available.
			bool hasLadderData = (wcsicmp(requestName, ladderName) == 0);

			if (type == LadderType_Clan)
				{
				RefPtr<SquadData> squad = SquadData::FindByAbbr(requestName);

				if (squad.IsValid())
					{
					// Update / create ladder information for this clan
					RefPtr<LadderData> ladderData = squad->GetLadder();

					if (ladderData.IsValid())
						{
						ladderData->UpdateData(*wolLadder, timeStamp);
						}
					else if (hasLadderData)
						{
						ladderData = LadderData::Create(*wolLadder, timeStamp);
						WWASSERT(ladderData.IsValid());
						squad->SetLadder(ladderData);
						}

					// Notify others about updated clan ladder information
					NotifyClanLadderUpdate(mOuter->mUsers, squad);
					NotifyClanLadderUpdate(mOuter->mBuddies, squad);
					}
				}
			else
				{
				RefPtr<UserData> user = mOuter->GetUserOrBuddy(requestName);

				// Update users with new ladder information.
				if (user.IsValid())
					{
					RefPtr<LadderData> ladderData = user->GetLadderFromType(type);

					if (ladderData.IsValid())
						{
						ladderData->UpdateData(*wolLadder, timeStamp);
						}															 
					else if (hasLadderData)
						{
						ladderData = LadderData::Create(*wolLadder, timeStamp);
						WWASSERT(ladderData.IsValid());

						if (ladderData.IsValid())
							{
							user->SetLadderFromType(ladderData, type);
							}
						}

					// Notify others that the ladder information for this user has changed.
					UserEvent event(UserEvent::LadderInfo, user);
					mOuter->NotifyObservers(event);
					}
				}

			// Notify others about the raw ladder information
			LadderInfoEvent ladderEvent(ladderName, *wolLadder, timeStamp);
			mOuter->NotifyObservers(ladderEvent);

			request++;
			ladderCount++;
			wolLadder = wolLadder->next;
			}

		// Clear the ladder type we received
		mOuter->mLadderPending &= ~type;

		// If there are no pending ladders then remove the satisfied requests
		if ((mOuter->mLadderPending & LADDERTYPE_MASK) == 0)
			{
			WWASSERT(ladderCount == mOuter->mLadderPending && "LadderInfo received doesn't match number requested");

			for (unsigned int count = 0; count < mOuter->mLadderPending; ++count)
				{
				mOuter->mLadderRequests.pop_front();
				}

			mOuter->mLadderPending = 0;
			}
		}
	}


void NetUtilObserver::NotifyClanLadderUpdate(const UserList& users, const RefPtr<SquadData>& squad)
	{
	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()))
			{
			// Notify others that this user received squad information
			UserEvent event(UserEvent::LadderInfo, user);
			mOuter->NotifyObservers(event);
			}
		}
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnGameresSent
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnGameresSent(HRESULT)
	{
	WWDEBUG_SAY(("WOLWARNING: OnGameresSent() not implemented\n"));
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnNewNick
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

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

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

	RefPtr<LoginInfo> login;

	if (result == S_OK)
		{
		login = LoginInfo::Create(nickname, password, true);
		}
		
	NewLoginInfoEvent event(login, message);
	mOuter->NotifyObservers(event);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnAgeCheck
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnAgeCheck(HRESULT result, int years, int consent)
	{
	if (mOuter == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Session not initialized\n"));
		WWASSERT(mOuter && "Session not initialized");
		return S_OK;
		}

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

	AgeCheckEvent event(years, consent != 0);
	mOuter->NotifyObservers(event);

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnWDTState
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnWDTState(HRESULT result, unsigned char* , int )
	{
	WWDEBUG_SAY(("WOLWARNING: OnWDTState not implemented\n"));
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     NetUtilObserver::OnHighscore
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP NetUtilObserver::OnHighscore(HRESULT result, WOL::Highscore* list,
		int count, long time, int keyRung)
	{
	WWDEBUG_SAY(("WOLWARNING: OnHighscore not implemented\n"));
	return S_OK;
	}

} // namespace WWOnline
