/*
**	Command & Conquer Generals(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/>.
*/

#include "global.h"
#include "matcher.h"
#include "encrypt.h"
#include "timezone.h"
#include "debug.h"

#ifdef _WINDOWS
#define usleep(x) Sleep((x)/100000)
#endif

MatcherClass::MatcherClass(void)
{
	m_lastRotation = 0;
	m_baseNick = "matcher";
	m_joinSuccess = false;
	done = 0;

	m_rotateLogs = false;
	quiet = false;
}

Wstring MatcherClass::getString(const Wstring& key)
{
	Wstring res;
	Global.GetString(key, res);
	return res;
}

void logIt(const char *Txt)
{
	// intentionally crash if we can't open it
	FILE *fp = fopen("logIt.txt", "w");
	fputs(Txt, fp);
	fclose(fp);
}

void MatcherClass::readLoop(void)
{
	int delay = -1;
	Global.config.getInt("ROTATEDELAY", delay);
	DBGMSG("ROTATEDELAY: " << delay);

	do
	{
		static time_t lastLogTime = 0;
		time_t now = time(NULL);
		if (now > lastLogTime + 300)
		{
			lastLogTime = now;
			INFMSG("still here" << endl);
		}

		logIt("peerThink\n");
		peerThink(m_peer);

		logIt("peerIsConnected\n");
		if (peerIsConnected(m_peer))
		{
			logIt("checkMatches()\n");
			checkMatches();
			logIt("checkMatches() done\n");
		}
		else
			done = true;

		msleep(1);

		// rotate logs if it's time
		if (delay != -1)
		{
#ifdef _UNIX
			Xtime xtime;
			time_t curtime;
			curtime = time(NULL);
			// get the number of seconds that have passed since midnight
			// of the current day.
			curtime -= TimezoneOffset();
			time_t timeofday = curtime % (delay);
			if ((timeofday > 0) && (timeofday <= 300))
			{
				rotateOutput();
				rotateParanoid();
			}
#endif
		}
	}
	while (!done);
	DBGMSG("Bailing out of readLoop!" << endl);
	INFMSG("Bailing out of readLoop!" << endl);
	ERRMSG("Bailing out of readLoop!" << endl);
}

/////////////////////////////////////////////////////////////////////////////

static void DisconnectedCallback ( PEER peer, const char * reason, void * param)
{
	DBGMSG("Disconnected: " << reason);
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handleDisconnect( reason );
}

static void RoomMessageCallback ( PEER peer, RoomType roomType, const char * nick, const char * message, MessageType messageType, void * param)
{
	DBGMSG("(PUBLIC) " << nick << ": " << message);
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handleRoomMessage( nick, message, messageType );
}

static void PlayerMessageCallback ( PEER peer, const char * nick, const char * message, MessageType messageType, void * param)
{
	DBGMSG("(PRIVATE) " << nick << ": " << message);
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handlePlayerMessage( nick, message, messageType );
}

static void PlayerJoinedCallback ( PEER peer, RoomType roomType, const char * nick, void * param)
{
	DBGMSG(nick << " joined the room");
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handlePlayerJoined( nick );
}

static void PlayerLeftCallback ( PEER peer, RoomType roomType, const char * nick, const char * reason, void * param)
{
	DBGMSG(nick << " left the room");
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handlePlayerLeft( nick );
}

static void PlayerChangedNickCallback ( PEER peer, RoomType roomType, const char * oldNick, const char * newNick, void * param)
{
	INFMSG(oldNick << " changed nicks to " << newNick);
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handlePlayerChangedNick( oldNick, newNick );
}

static void EnumPlayersCallback ( PEER peer, PEERBool success, RoomType roomType, int index, const char * nick, int flags, void * param)
{
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handlePlayerEnum( success == PEERTrue, index, nick, flags);
}

static int s_groupID = 0;
static void ListGroupRoomsCallback ( PEER peer, PEERBool success, int groupID, SBServer server, const char * name, int numWaiting, int maxWaiting, int numGames, int numPlaying, void * param)
{
	if (success && name && !strcasecmp(name, "QuickMatch"))
	{
		s_groupID = groupID;
	}
}

static void ConnectCallback ( PEER peer, PEERBool success, void * param)
{
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handleConnect( success == PEERTrue );
}

static void JoinCallback ( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void * param)
{
	MatcherClass *matcher = (MatcherClass *)param;
	if (matcher)
		matcher->handleJoin( success == PEERTrue );
}

static void NickErrorCallback ( PEER peer, int type, const char * badNick, void * param)
{
	ERRMSG("Nick error with " << badNick);

	if(type == PEER_IN_USE)
	{
		int len = strlen(badNick);
		std::string nickStr = badNick;
		int newVal = 0;
		if (badNick[len-1] == '}' && badNick[len-3] == '{' && isdigit(badNick[len-2]))
		{
			newVal = badNick[len-2] - '0' + 1;
			nickStr.erase(len-3, 3);
		}

		nickStr.append("{");
		char tmp[2];
		tmp[0] = '0'+newVal;
		tmp[1] = '\0';
		nickStr.append(tmp);
		nickStr.append("}");
		DBGMSG("Nickname taken: was "<<badNick<<", new val = "<<newVal<<", new nick = "<<nickStr.c_str());

		if (newVal < 10)
		{
			// Retry the connect with a similar nick.
			peerRetryWithNick(peer, nickStr.c_str());
		}
		else
		{
			// Cancel the connect.
			peerRetryWithNick(peer, NULL);
			MatcherClass *matcher = (MatcherClass *)param;
			if (matcher)
				matcher->handleNickError( badNick );
		}
	}
	else
	{
		// Cancel the connect.
		peerRetryWithNick(peer, NULL);
		MatcherClass *matcher = (MatcherClass *)param;
		if (matcher)
			matcher->handleNickError( badNick );
	}
}

/////////////////////////////////////////////////////////////////////////////

void callbackEach( CHAT chat, CHATBool success, int index, const char *channel,
                   const char *topic, int numUsers, void *param )
{
	DEBUG_LOG(("Chat channel success: %d\n", success));
	if (!success)
	{
		return;
	}
	DEBUG_LOG(("Channel[%d]: %s (%s), %d users\n",
	           index, channel, topic, numUsers));
}

void callbackAll( CHAT chat, CHATBool success, int numChannels, const char **channels,
                  const char **topics, int *numUsers, void *param )
{
	DEBUG_LOG(("Chat channels success: %d\n", success));
	if (!success)
	{
		return;
	}

	DEBUG_LOG(("%d channels found\n", numChannels));

	for (int i=0; i<numChannels; ++i)
	{
		DEBUG_LOG(("Channel[%d]: %s (%s), %d users\n",
		           i, channels[i], topics[i], numUsers[i]));
	}
}

void MatcherClass::handleConnect( bool success )
{
	m_connectSuccess = success;

	//DEBUG_LOG(("Enumerating chat channels\n"));
	//chatEnumChannels( peerGetChat(m_peer), "", callbackEach, callbackAll, NULL, CHATTrue );
	//DEBUG_LOG(("Done enumerating chat channels\n"));
}

void MatcherClass::handleGroupRoomList( bool success, int groupID, const char *name )
{
}

void MatcherClass::handleJoin( bool success )
{
	m_joinSuccess = success;
	if (m_joinSuccess)
	{
		DBGMSG("Joined room - listing players");
		peerEnumPlayers(m_peer, GroupRoom, EnumPlayersCallback, this);
	}
}

void MatcherClass::handleNickError( const char *badNick )
{
	exit(1);
}

static void AuthenticateCDKeyCallback
(
    PEER peer,
    int result,
    const char * message,
    void * param
)
{
	bool *val = (bool *)param;
	if (val)
	{
		*val = (result >= 1);
	}
}

void MatcherClass::connectAndLoop(void)
{
	// Game-specific initializations, if neccessary
	init();

	// Check for possible quit from init()-based self-tests
	if (done)
		return ;

	// Defaults.
	////////////
	Wstring title = "gmtest";
	Wstring secretKey = "HA6zkS";
	Wstring serialNo = "";
	m_profileID = 0;
	Global.config.getString("Nick", m_baseNick, "LOGIN");
	DBGMSG("base nick is " << m_baseNick.get());
	m_baseNick.toLower();
	Global.config.getString("Title", title, "LOGIN");
	Global.config.getString("SecretKey", secretKey, "LOGIN");
	Global.config.getInt("ProfileID", m_profileID, "LOGIN");
	Global.config.getString("CDKey", serialNo, "LOGIN");
	PEERCallbacks callbacks;
	PEERBool pingRooms[NumRooms];
	PEERBool crossPingRooms[NumRooms];

	// Setup the callbacks.
	///////////////////////
	memset(&callbacks, 0, sizeof(PEERCallbacks));
	callbacks.disconnected = DisconnectedCallback;
	callbacks.playerChangedNick = PlayerChangedNickCallback;
	callbacks.playerJoined = PlayerJoinedCallback;
	callbacks.playerLeft = PlayerLeftCallback;
	callbacks.roomMessage = RoomMessageCallback;
	callbacks.playerMessage = PlayerMessageCallback;
	callbacks.param = this;

	// Init.
	////////
	m_peer = peerInitialize(&callbacks);
	if(!m_peer)
	{
		ERRMSG("Failed to init peer object" << endl);
		return;
	}

	// Ping/cross-ping in no room.
	/////////////////////////////////
	pingRooms[TitleRoom] = PEERFalse;
	pingRooms[GroupRoom] = PEERFalse;
	pingRooms[StagingRoom] = PEERFalse;
	crossPingRooms[TitleRoom] = PEERFalse;
	crossPingRooms[GroupRoom] = PEERFalse;
	crossPingRooms[StagingRoom] = PEERFalse;

	// Set the title.
	/////////////////
	if(!peerSetTitle(m_peer, title.get(), secretKey.get(), title.get(), secretKey.get(), 0, 30, PEERTrue, pingRooms, crossPingRooms))
	{
		peerShutdown(m_peer);
		ERRMSG("Failed to set the title" << endl);
		return;
	}

	// Connect.
	///////////
	m_connectSuccess = false;
	m_nick = m_baseNick.get();
	peerConnect(m_peer, m_baseNick.get(), m_profileID, NickErrorCallback, ConnectCallback, this, PEERTrue);
	if(!m_connectSuccess)
	{
		peerShutdown(m_peer);
		ERRMSG("Failed to connect" << endl);
		return;
	}

	bool cdOk = false;
	peerAuthenticateCDKey(m_peer, serialNo.get(), AuthenticateCDKeyCallback, &cdOk, PEERTrue);
	if (!cdOk)
	{
		peerShutdown(m_peer);
		ERRMSG("Failed to auth CDKey " << serialNo.get() << endl);
		return;
	}

	m_groupID = 0;
	peerListGroupRooms(m_peer, NULL, ListGroupRoomsCallback, &m_groupID, PEERTrue);
	m_groupID = s_groupID;

	DBGMSG("QuickMatch room is " << m_groupID);

	// Join the title room.
	///////////////////////
	peerJoinGroupRoom(m_peer, m_groupID, JoinCallback, this, PEERTrue);
	if(!m_joinSuccess)
	{
		peerDisconnect(m_peer);
		peerShutdown(m_peer);
		ERRMSG("Failed to join the title room" << endl);
		return;
	}

	// Connected, so lets do our thing
	readLoop();

	peerDisconnect(m_peer);
	peerShutdown(m_peer);
}

