/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, 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/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/FeatureLib/pfGameMgr/pfGameMgr.cpp
*   
***/

#include "Pch.h"
#pragma hdrstop


/*****************************************************************************
*
*   Local types
*
***/

//============================================================================
// pfGameCli factory
//============================================================================
struct Factory : THashKeyVal<Uuid> {

	HASHLINK(Factory)	link;
	const GameTypeReg &	reg;

	Factory (const GameTypeReg & reg);
	Factory& operator= (const Factory &);	// not impl
};

//============================================================================
// pfGameCli internal state
//============================================================================
struct IGameCli : THashKeyVal<unsigned> {

	HASHLINK(IGameCli)	link;
	pfGameCli *			gameCli;
	Factory *			factory;
	plKey				receiver;
	unsigned			playerCount;
	
	IGameCli (
		pfGameCli *	gameCli,
		unsigned	gameId,
		plKey		receiver
	);

	void Recv				(GameMsgHeader * msg, void * param);
	void RecvPlayerJoined	(const Srv2Cli_Game_PlayerJoined & msg, void * param);
	void RecvPlayerLeft		(const Srv2Cli_Game_PlayerLeft & msg, void * param);
	void RecvInviteFailed	(const Srv2Cli_Game_InviteFailed & msg, void * param);
	void RecvOwnerChange	(const Srv2Cli_Game_OwnerChange & msg, void * param);
};

//============================================================================
// Transaction states
//============================================================================
struct TransState : THashKeyVal<unsigned> {

	HASHLINK(TransState)	link;
	void *					param;
	TransState (unsigned transId, void * param);
};
struct JoinTransState {

	plKey receiver;
	JoinTransState (plKey receiver)
	:	receiver(receiver)
	{ }
};

//============================================================================
// IGameMgr
//============================================================================
struct IGameMgr {

	pfGameCli * CreateGameCli (const Uuid & gameTypeId, unsigned gameId, plKey receiver);

	void Recv				(GameMsgHeader * msg);
	void RecvGameInstance	(const Srv2Cli_GameMgr_GameInstance & msg, void * param);
	void RecvInviteReceived	(const Srv2Cli_GameMgr_InviteReceived & msg, void * param);
	void RecvInviteRevoked	(const Srv2Cli_GameMgr_InviteRevoked & msg, void * param);

	static void StaticRecv (GameMsgHeader * msg);
};


/*****************************************************************************
*
*   Local data
*
***/

static HASHTABLEDECL(TransState, THashKeyVal<unsigned>, link)	s_trans;
static HASHTABLEDECL(IGameCli, THashKeyVal<unsigned>, link)		s_games;
static HASHTABLEDECL(Factory, THashKeyVal<Uuid>, link)			s_factories;

static long				s_transId;
static ARRAYOBJ(plKey)	s_receivers;


/*****************************************************************************
*
*   Local functions
*
***/

//============================================================================
static void ShutdownFactories () {

	while (Factory * factory = s_factories.Head())
		DEL(factory);
}

//============================================================================
AUTO_INIT_FUNC (SetGameMgrMsgHandler) {

	NetCliGameSetRecvGameMgrMsgHandler(IGameMgr::StaticRecv);
	atexit(ShutdownFactories);
}

//============================================================================
static inline unsigned INextTransId () {

	unsigned transId = AtomicAdd(&s_transId, 1);
	while (!transId)
		transId = AtomicAdd(&s_transId, 1);
	return transId;
}


/*****************************************************************************
*
*   IGameMgr
*
***/

//============================================================================
pfGameCli * IGameMgr::CreateGameCli (const Uuid & gameTypeId, unsigned gameId, plKey receiver) {
	
	if (Factory * factory = s_factories.Find(gameTypeId)) {
		pfGameCli * gameCli = factory->reg.create(gameId, receiver);
		gameCli->internal->factory = factory;
		return gameCli;
	}
	
	return nil;
}

//============================================================================
void IGameMgr::RecvGameInstance (const Srv2Cli_GameMgr_GameInstance & msg, void * param) {

	JoinTransState * state = (JoinTransState *)param;

	pfGameCli *	cli = nil;
	IGameCli *	internal = nil;
	if (msg.result == kGameJoinSuccess) {
		if (nil == (internal = s_games.Find(msg.newGameId)))
			cli = CreateGameCli(msg.gameTypeId, msg.newGameId, state->receiver);
		else
			cli = internal->gameCli;
	}

	DEL(state);
}

//============================================================================
void IGameMgr::RecvInviteReceived (const Srv2Cli_GameMgr_InviteReceived & msg, void * param) {
	pfGameMgrMsg * gameMgrMsg = NEWZERO(pfGameMgrMsg);
	gameMgrMsg->Set(msg);
	for (unsigned i = 0; i < s_receivers.Count(); ++i)
		gameMgrMsg->AddReceiver(s_receivers[i]);
	gameMgrMsg->Send();
}

//============================================================================
void IGameMgr::RecvInviteRevoked (const Srv2Cli_GameMgr_InviteRevoked & msg, void * param) {
	pfGameMgrMsg * gameMgrMsg = NEWZERO(pfGameMgrMsg);
	gameMgrMsg->Set(msg);
	for (unsigned i = 0; i < s_receivers.Count(); ++i)
		gameMgrMsg->AddReceiver(s_receivers[i]);
	gameMgrMsg->Send();
}

//============================================================================
void IGameMgr::Recv (GameMsgHeader * msg) {

	// Look for transaction state associated with this message	
	void * param;
	if (TransState * trans = s_trans.Find(msg->transId)) {
		param		= trans->param;
		DEL(trans);
	}
	else {
		param		= nil;
	}

	// If the message has a receiver gameId specified, then
	// hand it off to that game client for dispatch.
	if (unsigned gameId = msg->recvGameId) {
		if (IGameCli * node = s_games.Find(gameId)) {
			node->Recv(msg, param);
		}
		return;
	}
	
	// The message was meant for the game manager (that's us); dispatch it.
	#define DISPATCH(a) case kSrv2Cli_GameMgr_##a: {						\
		const Srv2Cli_GameMgr_##a & m = *(const Srv2Cli_GameMgr_##a *)msg;	\
		Recv##a(m, param);													\
	}																		\
	break;																	//
	switch (msg->messageId) {
		DISPATCH(GameInstance);
		DISPATCH(InviteReceived);
		DISPATCH(InviteRevoked);
		DEFAULT_FATAL(msg->messageId);
	}
	#undef DISPATCH
}

//============================================================================
void IGameMgr::StaticRecv (GameMsgHeader * msg) {

	pfGameMgr::GetInstance()->internal->Recv(msg);
}


/*****************************************************************************
*
*	pfGameMgrMsg
*
***/

//============================================================================
pfGameMgrMsg::pfGameMgrMsg () {

	netMsg = nil;
}

//============================================================================
pfGameMgrMsg::~pfGameMgrMsg () {

	FREE(netMsg);
}

//============================================================================
void pfGameMgrMsg::Set (const GameMsgHeader & msg) {

	netMsg = (GameMsgHeader *)ALLOC(msg.messageBytes);
	MemCopy(netMsg, &msg, msg.messageBytes);
}


/*****************************************************************************
*
*	pfGameCliMsg
*
***/

//============================================================================
pfGameCliMsg::pfGameCliMsg () {

	gameCli	= nil;
	netMsg	= nil;
}

//============================================================================
pfGameCliMsg::~pfGameCliMsg () {

	FREE(netMsg);
}

//============================================================================
void pfGameCliMsg::Set (pfGameCli * cli, const GameMsgHeader & msg) {

	netMsg = (GameMsgHeader *)ALLOC(msg.messageBytes);
	MemCopy(netMsg, &msg, msg.messageBytes);
	gameCli		= cli;
}

/*****************************************************************************
*
*   pfGameMgr
*
***/

//============================================================================
pfGameMgr::pfGameMgr () {
}

//============================================================================
pfGameMgr * pfGameMgr::GetInstance () {

	static pfGameMgr s_instance;
	return &s_instance;
}

//============================================================================
void pfGameMgr::GetGameIds (ARRAY(unsigned) * arr) const {

	for (IGameCli * node = s_games.Head(); node; node = s_games.Next(node))
		arr->Add(node->GetValue());
}

//============================================================================
pfGameCli * pfGameMgr::GetGameCli (unsigned gameId) const {

	if (IGameCli * node = s_games.Find(gameId))
		return node->gameCli;
	return nil;
}

//============================================================================
const wchar * pfGameMgr::GetGameNameByTypeId (const Uuid & gameTypeId) const {

	if (Factory * factory = s_factories.Find(gameTypeId))
		return factory->reg.name;
	return nil;
}

//============================================================================
void pfGameMgr::RemoveReceiver (plKey receiver) {

	for (unsigned i = 0; i < s_receivers.Count(); ++i) {
		if (s_receivers[i] == receiver) {
			s_receivers.DeleteUnordered(i);
			break;
		}
	}
}

//============================================================================
void pfGameMgr::AddReceiver (plKey receiver) {

	RemoveReceiver(receiver);
	s_receivers.Add(receiver);
}

//============================================================================
void pfGameMgr::JoinGame (
	plKey			receiver,
	unsigned		gameId
) {
	Cli2Srv_GameMgr_JoinGame msg;
	ZERO(msg);
	
	msg.messageId		= kCli2Srv_GameMgr_JoinGame;
	msg.recvGameId		= 0;			// send to GameMgr on server
	msg.newGameId		= gameId;		// the GameSrv we wish to join

	// Don't send "common game" message fields	
	unsigned msgBytes
		= sizeof(msg)
		- sizeof(msg.gameTypeId)
		- sizeof(msg.createDataBytes)
		- sizeof(msg.createData);
		
	msg.messageBytes	= msgBytes;

	GameMgrSend(&msg, NEWZERO(JoinTransState)(receiver));
}

//============================================================================
void pfGameMgr::CreateGame (
	plKey			receiver,
	const Uuid &	gameTypeId,
	unsigned		createOptions,
	unsigned		initBytes,
	const void *	initData
) {
	Cli2Srv_GameMgr_CreateGame * msg;

	unsigned msgBytes
		= sizeof(*msg)
		- sizeof(msg->createData)
		+ initBytes;
		
	msg = (Cli2Srv_GameMgr_CreateGame *)_alloca(msgBytes);
		
	msg->messageId			= kCli2Srv_GameMgr_CreateGame;
	msg->recvGameId			= 0;			// send to GameMgr on server
	msg->gameTypeId			= gameTypeId;	// The type of game we wish to create
	msg->createOptions		= createOptions;
	msg->messageBytes		= msgBytes;
	msg->createDataBytes	= initBytes;
	MemCopy(msg->createData, initData, initBytes);

	GameMgrSend(msg, NEWZERO(JoinTransState)(receiver));
}

//============================================================================
void pfGameMgr::JoinCommonGame (
	plKey			receiver,
	const Uuid &	gameTypeId,
	unsigned		gameNumber,
	unsigned		initBytes,
	const void *	initData
) {
	Cli2Srv_GameMgr_JoinGame * msg;

	unsigned msgBytes
		= sizeof(*msg)
		- sizeof(msg->createData)
		+ initBytes;
		
	msg = (Cli2Srv_GameMgr_JoinGame *)_alloca(msgBytes);
		
	msg->messageId			= kCli2Srv_GameMgr_JoinGame;
	msg->recvGameId			= 0;			// send to GameMgr on server
	msg->gameTypeId			= gameTypeId;	// the type of common game we with to join
	msg->newGameId			= gameNumber;	// the "table number" of th common game we wish to join
	msg->createOptions		= kGameJoinCommon;
	msg->messageBytes		= msgBytes;
	msg->createDataBytes	= initBytes;
	MemCopy(msg->createData, initData, initBytes);

	GameMgrSend(msg, NEWZERO(JoinTransState)(receiver));
}


/*****************************************************************************
*
*   pfGameCli
*
***/

//============================================================================
pfGameCli::pfGameCli (
	unsigned	gameId,
	plKey		receiver
) {
	internal = NEWZERO(IGameCli)(this, gameId, receiver);
}

//============================================================================
pfGameCli::~pfGameCli () {

	DEL(internal);
}

//============================================================================
unsigned pfGameCli::GetGameId () const {

	return internal->GetValue();
}

//============================================================================
const Uuid & pfGameCli::GetGameTypeId () const {

	return internal->factory->GetValue();
}

//============================================================================
const wchar * pfGameCli::GetName () const {

	return internal->factory->reg.name;
}

//============================================================================
plKey pfGameCli::GetReceiver () const {

	return internal->receiver;
}

//============================================================================
unsigned pfGameCli::GetPlayerCount () const {

	return internal->playerCount;
}

//============================================================================
void pfGameCli::InvitePlayer (unsigned playerId) {

	Cli2Srv_Game_Invite msg;
	msg.messageId		= kCli2Srv_Game_Invite;
	msg.recvGameId		= GetGameId();	// send to GameSrv on server
	msg.playerId		= playerId;
	msg.messageBytes	= sizeof(msg);

	GameMgrSend(&msg);
}

//============================================================================
void pfGameCli::UninvitePlayer (unsigned playerId) {

	Cli2Srv_Game_Uninvite msg;
	msg.messageId		= kCli2Srv_Game_Uninvite;
	msg.recvGameId		= GetGameId();	// send to GameSrv on server
	msg.playerId		= playerId;
	msg.messageBytes	= sizeof(msg);

	GameMgrSend(&msg);
}

//============================================================================
void pfGameCli::LeaveGame () {
	
	Cli2Srv_Game_LeaveGame msg;
	msg.messageId		= kCli2Srv_Game_LeaveGame;
	msg.recvGameId		= GetGameId();	// send to GameSrv on server
	msg.messageBytes	= sizeof(msg);

	GameMgrSend(&msg);
}


/*****************************************************************************
*
*   IGameCli
*
***/

//============================================================================
IGameCli::IGameCli (
	pfGameCli *	gameCli,
	unsigned	gameId,
	plKey		receiver
) :	THashKeyVal<unsigned>(gameId)
,	gameCli(gameCli)
,	receiver(receiver)
{
	s_games.Add(this);
}

//============================================================================
void IGameCli::Recv (GameMsgHeader * msg, void * param) {

	#define DISPATCH(a)	case kSrv2Cli_Game_##a: {						\
		const Srv2Cli_Game_##a & m = *(const Srv2Cli_Game_##a *)msg;	\
		Recv##a(m, param);												\
	}																	\
	break;
	switch (msg->messageId) {
		DISPATCH(PlayerJoined);
		DISPATCH(PlayerLeft);
		DISPATCH(InviteFailed);
		DISPATCH(OwnerChange);
		default:
			gameCli->Recv(msg, param);
	}
	#undef DISPATCH
}

//============================================================================
void IGameCli::RecvPlayerJoined (const Srv2Cli_Game_PlayerJoined & msg, void * param) {
	++playerCount;
	gameCli->OnPlayerJoined(msg);
}

//============================================================================
void IGameCli::RecvPlayerLeft (const Srv2Cli_Game_PlayerLeft & msg, void * param) {
	--playerCount;
	gameCli->OnPlayerLeft(msg);
}

//============================================================================
void IGameCli::RecvInviteFailed (const Srv2Cli_Game_InviteFailed & msg, void * param) {
	gameCli->OnInviteFailed(msg);
}

//============================================================================
void IGameCli::RecvOwnerChange (const Srv2Cli_Game_OwnerChange & msg, void * param) {
	gameCli->OnOwnerChange(msg);
}


/*****************************************************************************
*
*   Factory
*
***/

//============================================================================
Factory::Factory (const GameTypeReg & reg)
:	reg(reg)
,	THashKeyVal<Uuid>(reg.typeId)
{
	s_factories.Add(this);
}


/*****************************************************************************
*
*   TransState
*
***/

//============================================================================
TransState::TransState (unsigned transId, void * param)
:	THashKeyVal<unsigned>(transId)
,	param(param)
{
	s_trans.Add(this);
}


/*****************************************************************************
*
*   Module functions
*
***/

//============================================================================
void GameMgrRegisterGameType (const GameTypeReg & reg) {

	(void)NEWZERO(Factory)(reg);
}

//============================================================================
void GameMgrSend (GameMsgHeader * msg, void * param) {

	if (param) {
		msg->transId = INextTransId();
		(void)NEW(TransState)(msg->transId, param);
	}
	else {
		msg->transId = 0;
	}

	NetCliGameSendGameMgrMsg(msg);
}