/*
**	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/Commando/WOLQuickMatch.cpp $
*
* DESCRIPTION
*
* PROGRAMMER
*     $Author: Denzil_l $
*
* VERSION INFO
*     $Revision: 37 $
*     $Modtime: 2/20/02 5:30p $
*
******************************************************************************/

#include "WOLQuickMatch.h"
#include <WWOnline\PingProfile.h>
#include <WWOnline\WaitCondition.h>
#include <WWOnline\WOLProduct.h>
#include <WWOnline\WOLConnect.h>
#include <WWOnline\WOLServer.h>
#include <WWOnline\WOLLadder.h>
#include <WWLib\CPUDetect.h>
#include <WWDebug\WWDebug.h>
#include "cnetwork.h"
#include "translatedb.h"
#include "string_ids.h"


using namespace WWOnline;

typedef void (*QMDispatchFunc)(WOLQuickMatch*, const wchar_t*);

struct QMResponseDispatch
	{
	const wchar_t* Token;
	QMDispatchFunc Dispatch;
	};


#define QUICKMATCH_CHANNELNAME L"lob_39_0"
#define QUICKMATCH_BOTNAME L"matchbot"

/******************************************************************************
*
* NAME
*     WOLQuickMatch::Create
*
* DESCRIPTION
*     Create an instance to a quickmatch game matcher.
*
* INPUTS
*     NONE
*
* RESULT
*     Quickmatch - Quickmatch instance.
*
******************************************************************************/

WOLQuickMatch* WOLQuickMatch::Create(void)
	{
	WOLQuickMatch* match = new WOLQuickMatch;

	if (match)
		{
		if (match->FinalizeCreate())
			{
			return match;
			}

		match->Release_Ref();
		}

	return NULL;
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::WOLQuickMatch
*
* DESCRIPTION
*     Default constructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

WOLQuickMatch::WOLQuickMatch()
	{
	WWDEBUG_SAY(("WOLQuickMatch: Instantiating\n"));
	}


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

WOLQuickMatch::~WOLQuickMatch()
	{
	WWDEBUG_SAY(("WOLQuickMatch: Destructing\n"));
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::FinalizeCreate
*
* DESCRIPTION
*     Creation initializaton / finalization
*
* INPUTS
*     NONE
*
* RESULT
*     Success - True if successful
*
******************************************************************************/

bool WOLQuickMatch::FinalizeCreate(void)
	{
	mWOLSession = Session::GetInstance(false);

	if (!mWOLSession.IsValid())
		{
		WWDEBUG_SAY(("WOLQuickMatch: ERROR - WWOnline not instantiated\n"));
		return false;
		}

	return true;
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ConnectClient
*
* DESCRIPTION
*     Connect a game client to the quickmatch bot.
*
* INPUTS
*     ChannelName - Name of matching channel
*     BotName     - Name of matching bot
*
* RESULT
*     Wait - Wait condition to process.
*
******************************************************************************/

RefPtr<WaitCondition> WOLQuickMatch::ConnectClient(void)
	{
	WWDEBUG_SAY(("WOLQuickMatch: Connecting client to '%S' '%S'\n",
		QUICKMATCH_CHANNELNAME, QUICKMATCH_BOTNAME));

	// Make sure that we are logged on to WWOnline
	if (mWOLSession->GetConnectionStatus() != ConnectionConnected)
		{
		WWDEBUG_SAY(("WOLQuickMatch: ERROR - Not connected to WWOnline server\n"));
		RefPtr<SingleWait> wait = SingleWait::Create(TRANSLATE(IDS_WOL_CONNECTING));
		wait->EndWait(WaitCondition::Error, TRANSLATE(IDS_WOL_NOTCONNECTED));
		return wait;
		}

	// Generate client connect wait condition
	RefPtr<SerialWait> connectWait = SerialWait::Create();

	if (connectWait.IsValid())
		{
		Observer<ServerError>::NotifyMe(*mWOLSession);
		Observer<ChatMessage>::NotifyMe(*mWOLSession);

		// Request channel list
		RefPtr<WaitCondition> channelListWait = mWOLSession->GetNewChatChannelList();
		connectWait->Add(channelListWait);

		// Join the matching channel
		RefPtr<Product> product = Product::Current();
		WWASSERT(product.IsValid() && "WOLProduct not initialized.");
		const wchar_t* password = product->GetChannelPassword();

		RefPtr<WaitCondition> joinWait = mWOLSession->JoinChannel(QUICKMATCH_CHANNELNAME, password, 0);
		connectWait->Add(joinWait);

		// Make sure the matching bot is there.
		RefPtr<WaitCondition> findBotWait = GetUserWait::Create(mWOLSession, QUICKMATCH_BOTNAME);
		connectWait->Add(findBotWait);
		}

	return connectWait;
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::Disconnect
*
* DESCRIPTION
*     Disconnect from quickmatch
*
* INPUTS
*     NONE
*
* RESULT
*     Wait - Disconnect wait condition to process.
*
******************************************************************************/

RefPtr<WaitCondition> WOLQuickMatch::Disconnect(void)
	{
	WWDEBUG_SAY(("WOLQuickMatch: Disconnecting\n"));

	Observer<ServerError>::StopObserving();
	Observer<ChatMessage>::StopObserving();

	// If we are in the matching channel then disconnect.
	RefPtr<ChannelData> channel = mWOLSession->GetCurrentChannel();

	if (channel.IsValid())
		{
		const WideStringClass& name = channel->GetName();

		if (name.Compare_No_Case(QUICKMATCH_CHANNELNAME) == 0)
			{
			return mWOLSession->LeaveChannel();
			}
		}

	return NULL;	
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::SendClientInfo
*
* DESCRIPTION
*     Send client matching preferences to the quickmatch bot.
*
* INPUTS
*     NONE
*
* RESULT
*     True if successful.
*
******************************************************************************/

bool WOLQuickMatch::SendClientInfo(void)
	{
	unsigned long ver = cNetwork::Get_Exe_Key();

	// Get CPU speed
	int speed = CPUDetectClass::Get_Processor_Speed();

	// Get amount of physical memory
	MEMORYSTATUS memStatus;
	GlobalMemoryStatus(&memStatus);
	unsigned long memory = (memStatus.dwTotalPhys / 1048576);

	//-------------------------------------------------------------------------
	// Gather pings
	//-------------------------------------------------------------------------
	const PingProfile& pings = GetLocalPingProfile();
	char pseudoPings[18] = {0};
	EncodePingProfile(pings, pseudoPings);

	//-------------------------------------------------------------------------
	// Get clients ladder points
	//-------------------------------------------------------------------------
	RefPtr<UserData> client = mWOLSession->GetCurrentUser();
	WWASSERT(client.IsValid());

	if (client.IsValid() == false)
		{
		return false;
		}

	int tpoints = 0;
	unsigned int played = 0;

	RefPtr<LadderData> ladder = client->GetTeamLadder();

	if (ladder.IsValid())
		{
		tpoints = ladder->GetPoints();
		played = ladder->GetReserved2();
		}

	//-------------------------------------------------------------------------
	// Generate client information message
	//-------------------------------------------------------------------------
	WideStringClass clientMsg(256, true);
	clientMsg.Format(L"CINFO VER=%lu CPU=%lu MEM=%lu TPOINTS=%ld PLAYED=%lu PINGS=%S",
		ver, speed, memory, tpoints, played, pseudoPings);

	WWDEBUG_SAY(("WOLQuickMatch: '%S'\n", (const WCHAR*)clientMsg));
	return mWOLSession->SendPrivateMessage(QUICKMATCH_BOTNAME, (const WCHAR*)clientMsg);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::SendServerInfo
*
* DESCRIPTION
*     Send information describing the game,
*
* INPUTS
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::SendServerInfo(const char* exInfo, const char* topic)
	{
	if (exInfo && topic)
		{
#pragma message(__FILE__" *** HACK ALERT *** SINFO msg is imitating WOLAPI IRC topic!")

		// *** WARNING *** DANGER *** HACK ALERT ****
		//
		// The SINFO message sent to the matching bot is assembled in such
		// a way as to imitate the IRC topic string that WOLAPI produces.
		WideStringClass botMsg(0, true);
		botMsg.Format(L"SINFO %S%S", exInfo, topic);
		WWDEBUG_SAY(("WOLQuickMatch: '%S'\n", (const WCHAR*)botMsg));

		mWOLSession->SendPrivateMessage(QUICKMATCH_BOTNAME, botMsg);
		}
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::SendStatus
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::SendStatus(const wchar_t* statusMsg)
	{
	WWDEBUG_SAY(("WOLQuickMatch: Status '%S'\n", statusMsg));

	WideStringClass msg(256, true);
	msg = statusMsg;
	QuickMatchEvent status(QuickMatchEvent::QMMSG, msg);
	NotifyObservers(status);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ParseResponse
*
* DESCRIPTION
*     Handle response messages from the quickmatch bot.
*
* INPUTS
*     Message - Response message from quickmatch bot.
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::ParseResponse(const wchar_t* message)
	{
	if (message)
		{
		static QMResponseDispatch _dispatch[] =
			{
			{L"INFO ", WOLQuickMatch::ProcessInfo},
			{L"ERROR ", WOLQuickMatch::ProcessError},
			{L"START ", WOLQuickMatch::ProcessStart},
			{NULL, WOLQuickMatch::ProcessUnknown}
			};

		int index = 0;
		const wchar_t* token = _dispatch[index].Token;

		while (token)
			{
			// Find the first occurance of the token in the message
			wchar_t* cmd = wcsstr(message, token);

			// If the token was found and it is at the start of the message
			// then return the type of message this is.
			if (cmd && cmd == message)
				{
				const wchar_t* data = (message + wcslen(token));
				_dispatch[index].Dispatch(this, data);
				}

			index++;
			token = _dispatch[index].Token;
			}
		}
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ProcessInfo
*
* DESCRIPTION
*     Process information messages.
*
* INPUTS
*     Message - 
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::ProcessInfo(WOLQuickMatch* quickmatch, const wchar_t* data)
	{
	WideStringClass msg(255, true);
	msg = data;

	QuickMatchEvent status(QuickMatchEvent::QMINFO, msg);
	quickmatch->NotifyObservers(status);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ProcessError
*
* DESCRIPTION
*     Process error messages from the quickmatch bot.
*
* INPUTS
*     Message - Error message
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::ProcessError(WOLQuickMatch* quickmatch, const wchar_t* data)
	{
	WideStringClass msg(255, true);
	msg = data;

	QuickMatchEvent status(QuickMatchEvent::QMERROR, msg);
	quickmatch->NotifyObservers(status);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ProcessStart
*
* DESCRIPTION
*     Process start message from the quickmatch bot.
*
* INPUTS
*     Data - Start game parameters
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::ProcessStart(WOLQuickMatch* quickmatch, const wchar_t* data)
	{
	// Send message indicating successful match
	WideStringClass msg(255, true);
	msg.Format(TRANSLATE(IDS_MENU_QM_MATCHED_WITH), data);

	QuickMatchEvent status(QuickMatchEvent::QMMSG, msg);
	quickmatch->NotifyObservers(status);

	// Send message that we are matched.
	msg = data;
	QuickMatchEvent matchedEvent(QuickMatchEvent::QMMATCHED, msg);
	quickmatch->NotifyObservers(matchedEvent);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::ProcessUnknown
*
* DESCRIPTION
*     Process unknown messages from the quickmatch bot.
*
* INPUTS
*     Message - Unknown message
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::ProcessUnknown(WOLQuickMatch* quickmatch, const wchar_t* data)
	{
	WideStringClass msg(255, true);
	msg = data;

	QuickMatchEvent status(QuickMatchEvent::QMUNKNOWN, msg);
	quickmatch->NotifyObservers(status);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::HandleNotification(ServerError)
*
* DESCRIPTION
*     Handle server errors.
*
* INPUTS
*     Error - Server error
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::HandleNotification(ServerError& error)
	{
	const wchar_t* errorMsg = error.GetDescription();
	WWDEBUG_SAY(("WOLQuickMatch: ERROR - ServerError '%S'\n", errorMsg));
	QuickMatchEvent status(QuickMatchEvent::QMERROR, errorMsg);
	NotifyObservers(status);
	}


/******************************************************************************
*
* NAME
*     WOLQuickMatch::HandleNotification(ChatMessageEvent)
*
* DESCRIPTION
*     Handle private messages coming from the matchbot.
*
* INPUTS
*     Message - Chat message
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLQuickMatch::HandleNotification(ChatMessage& message)
	{
	const WideStringClass& sender = message.GetSendersName();

	if (sender.Compare_No_Case(QUICKMATCH_BOTNAME) == 0)
		{
		WWDEBUG_SAY(("WOLQuickMatch: BotMsg - '%S'\n", message.GetMessage()));
		ParseResponse(message.GetMessage());
		}
	}
