/*==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/PubUtilLib/plNetGameLib/Private/plNglGame.cpp
*   
***/

#include "../Pch.h"
#pragma hdrstop

// This file excluded from pre-compiled header because it is auto-generated by the build server.
#include "pnNetBase/pnNbGameKey.hpp"

namespace Ngl { namespace Game {
/*****************************************************************************
*
*   Private
*
***/

struct CliGmConn : AtomicRef {
    LINK(CliGmConn) link;

	CCritSect		critsect;
    AsyncSocket     sock;
    AsyncCancelId   cancelId;
    NetCli *        cli;
    NetAddress      addr;
    unsigned        seq;
    bool			abandoned;

	// ping
	AsyncTimer *	pingTimer;
	unsigned		pingSendTimeMs;
	unsigned		lastHeardTimeMs;

    CliGmConn ();
    ~CliGmConn ();

	void Send (const unsigned_ptr fields[], unsigned count);
};


//============================================================================
// JoinAgeRequestTrans
//============================================================================
struct JoinAgeRequestTrans : NetGameTrans {
    FNetCliGameJoinAgeRequestCallback   m_callback;
    void *                              m_param;
    // sent
	unsigned							m_ageMcpId;
    Uuid                                m_accountUuid;
    unsigned                            m_playerInt;

    JoinAgeRequestTrans (
		unsigned							ageMcpId,
        const Uuid &                        accountUuid,
        unsigned                            playerInt,
        FNetCliGameJoinAgeRequestCallback   callback,
        void *                              param
    );

    bool Send ();
    void Post ();
    bool Recv (
        const byte  msg[],
        unsigned    bytes
    );
};

//============================================================================
// RcvdPropagatedBufferTrans
//============================================================================
struct RcvdPropagatedBufferTrans : NetNotifyTrans {

	unsigned		bufferType;
	unsigned		bufferBytes;
	byte *			bufferData;

	RcvdPropagatedBufferTrans () : NetNotifyTrans(kGmRcvdPropagatedBufferTrans) {}
	~RcvdPropagatedBufferTrans ();
    void Post ();
};

//============================================================================
// RcvdGameMgrMsgTrans
//============================================================================
struct RcvdGameMgrMsgTrans : NetNotifyTrans {

	unsigned		bufferBytes;
	byte *			bufferData;

	RcvdGameMgrMsgTrans () : NetNotifyTrans(kGmRcvdGameMgrMsgTrans) {}
	~RcvdGameMgrMsgTrans ();
    void Post ();
};


/*****************************************************************************
*
*   Private data
*
***/

enum {
    kPerfConnCount,
    kPingDisabled,
    kNumPerf
};

static bool								s_running;
static CCritSect						s_critsect;
static LISTDECL(CliGmConn, link)		s_conns;
static CliGmConn *						s_active;
static FNetCliGameRecvBufferHandler		s_bufHandler;
static FNetCliGameRecvGameMgrMsgHandler	s_gameMgrMsgHandler;
static long								s_perf[kNumPerf];


/*****************************************************************************
*
*   Internal functions
*
***/

//===========================================================================
static unsigned GetNonZeroTimeMs () {
	if (unsigned ms = TimeGetMs())
		return ms;
	return 1;
}

//============================================================================
static CliGmConn * GetConnIncRef_CS (const char tag[]) {
    if (CliGmConn * conn = s_active)
		if (conn->cli) {
			conn->IncRef(tag);
			return conn;
		}
    return nil;
}

//============================================================================
static CliGmConn * GetConnIncRef (const char tag[]) {
    CliGmConn * conn;
    s_critsect.Enter();
    {
        conn = GetConnIncRef_CS(tag);
    }
    s_critsect.Leave();
    return conn;
}

//============================================================================
static void UnlinkAndAbandonConn_CS (CliGmConn * conn) {
	s_conns.Unlink(conn);
	conn->abandoned = true;
	if (conn->cancelId) {
		AsyncSocketConnectCancel(nil, conn->cancelId);
		conn->cancelId  = 0;
	}
	else if (conn->sock) {
		AsyncSocketDisconnect(conn->sock, true);
	}
	else {
		conn->DecRef("Lifetime");
	}
}

//============================================================================
static bool ConnEncrypt (ENetError error, void * param) {
    CliGmConn * conn = (CliGmConn *) param;

    if (IS_NET_SUCCESS(error)) {
        s_critsect.Enter();
        {
            SWAP(s_active, conn);
        }
        s_critsect.Leave();
    }

    return IS_NET_SUCCESS(error);
}

//============================================================================
static void NotifyConnSocketConnect (CliGmConn * conn) {

	conn->TransferRef("Connecting", "Connected");
    conn->cli = NetCliConnectAccept(
        conn->sock,
        kNetProtocolCli2Game,
        true,
        ConnEncrypt,
        0,
        nil,
        conn
    );
}

//============================================================================
static void NotifyConnSocketConnectFailed (CliGmConn * conn) {
    bool notify;
    s_critsect.Enter();
    {
		conn->cancelId = 0;
        s_conns.Unlink(conn);
        
        notify
			=  s_running
			&& !conn->abandoned
			&& (!s_active || conn == s_active);
			
        if (conn == s_active)
            s_active = nil;
    }
    s_critsect.Leave();

    NetTransCancelByConnId(conn->seq, kNetErrTimeout);
    conn->DecRef("Connecting");
    conn->DecRef("Lifetime");

    if (notify)
        ReportNetError(kNetProtocolCli2Game, kNetErrConnectFailed);
}

//============================================================================
static void NotifyConnSocketDisconnect (CliGmConn * conn) {

    bool notify;
    s_critsect.Enter();
    {
        s_conns.Unlink(conn);

        notify
			=  s_running
			&& !conn->abandoned
			&& (!s_active || conn == s_active);

        if (conn == s_active)
            s_active = nil;
    }
    s_critsect.Leave();

    // Cancel all transactions in process on this connection.
    NetTransCancelByConnId(conn->seq, kNetErrTimeout);
    conn->DecRef("Connected");
    conn->DecRef("Lifetime");

    if (notify)
        ReportNetError(kNetProtocolCli2Game, kNetErrDisconnected);
}

//============================================================================
static bool NotifyConnSocketRead (CliGmConn * conn, AsyncNotifySocketRead * read) {
    // TODO: Only dispatch messages from the active game server
    conn->lastHeardTimeMs = GetNonZeroTimeMs();
    bool result = NetCliDispatch(conn->cli, read->buffer, read->bytes, nil);
    read->bytesProcessed += read->bytes;
    return result;
}

//============================================================================
static bool SocketNotifyCallback (
    AsyncSocket         sock,
    EAsyncNotifySocket  code,
    AsyncNotifySocket * notify,
    void **             userState
) {
    bool result = true;
    CliGmConn * conn;

    switch (code) {
        case kNotifySocketConnectSuccess:
            conn = (CliGmConn *) notify->param;
            *userState = conn;
			conn->TransferRef("Connecting", "Connected");
            bool abandoned;
            s_critsect.Enter();
            {
				conn->sock		= sock;
                conn->cancelId  = 0;
				abandoned		= conn->abandoned;
            }
            s_critsect.Leave();
            if (abandoned)
				AsyncSocketDisconnect(sock, true);
			else
				NotifyConnSocketConnect(conn);
        break;

        case kNotifySocketConnectFailed:
            conn = (CliGmConn *) notify->param;
            NotifyConnSocketConnectFailed(conn);
        break;

        case kNotifySocketDisconnect:
            conn = (CliGmConn *) *userState;
            NotifyConnSocketDisconnect(conn);
        break;

        case kNotifySocketRead:
            conn = (CliGmConn *) *userState;
            result = NotifyConnSocketRead(conn, (AsyncNotifySocketRead *) notify);
        break;
    }
    
    return result;
}

//============================================================================
static void Connect (
    const NetAddress &  addr
) {
    CliGmConn * conn = NEWZERO(CliGmConn);
    conn->addr				= addr;
    conn->seq				= ConnNextSequence();
    conn->lastHeardTimeMs	= GetNonZeroTimeMs();

    conn->IncRef("Lifetime");
	conn->IncRef("Connecting");

    s_critsect.Enter();
    {
		while (CliGmConn * conn = s_conns.Head())
			UnlinkAndAbandonConn_CS(conn);
        s_conns.Link(conn);
    }
    s_critsect.Leave();

    Cli2Game_Connect connect;
    connect.hdr.connType    = kConnTypeCliToGame;
    connect.hdr.hdrBytes    = sizeof(connect.hdr);
    connect.hdr.buildId     = BuildId();
    connect.hdr.buildType   = BuildType();
    connect.hdr.branchId	= BranchId();
    connect.hdr.productId   = ProductId();
    connect.data.dataBytes  = sizeof(connect.data);

    AsyncSocketConnect(
        &conn->cancelId,
        addr,
        SocketNotifyCallback,
        conn,
        &connect,
        sizeof(connect),
        0,
        0
    );
}


/*****************************************************************************
*
*   CliGmConn
*
***/

//============================================================================
CliGmConn::CliGmConn () {
    AtomicAdd(&s_perf[kPerfConnCount], 1);
}

//============================================================================
CliGmConn::~CliGmConn () {
	if (cli)
		NetCliDelete(cli, true);
    AtomicAdd(&s_perf[kPerfConnCount], -1);
}

//============================================================================
void CliGmConn::Send (const unsigned_ptr fields[], unsigned count) {
	critsect.Enter();
	{
		NetCliSend(cli, fields, count);
		NetCliFlush(cli);
	}
	critsect.Leave();
}


/*****************************************************************************
*
*   Cli2Game protocol
*
***/

//============================================================================
static bool Recv_PingReply (
    const byte      msg[],
    unsigned        bytes,
    void *          param
) {
	ref(msg);
	ref(bytes);
	ref(param);
    return true;
}

//============================================================================
static bool Recv_JoinAgeReply (
    const byte      msg[],
    unsigned        bytes,
    void *          param
) {
	ref(bytes);
	ref(param);
	
    const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *)msg;
    if (sizeof(reply) != bytes)
        return false;

    NetTransRecv(reply.transId, msg, bytes);

    return true;
}

//============================================================================
static bool Recv_PropagateBuffer (
    const byte      msg[],
    unsigned        bytes,
    void *          param
) {
    ref(bytes);
    ref(param);

    const Game2Cli_PropagateBuffer & reply = *(const Game2Cli_PropagateBuffer *)msg;

	RcvdPropagatedBufferTrans * trans = NEW(RcvdPropagatedBufferTrans);
	trans->bufferType	= reply.type;
	trans->bufferBytes	= reply.bytes;
	trans->bufferData	= (byte *)ALLOC(reply.bytes);
	MemCopy(trans->bufferData, reply.buffer, reply.bytes);
	NetTransSend(trans);

    return true;
}

//============================================================================
static bool Recv_GameMgrMsg (
    const byte      msg[],
    unsigned        bytes,
    void *          param
) {
    ref(bytes);
    ref(param);

    const Game2Cli_GameMgrMsg & reply = *(const Game2Cli_GameMgrMsg *)msg;

	RcvdGameMgrMsgTrans * trans = NEW(RcvdGameMgrMsgTrans);
	trans->bufferBytes	= reply.bytes;
	trans->bufferData	= (byte *)ALLOC(reply.bytes);
	MemCopy(trans->bufferData, reply.buffer, reply.bytes);
	NetTransSend(trans);

    return true;
}


//============================================================================
// Send/Recv protocol handler init
//============================================================================
#define MSG(s)  kNetMsg_Cli2Game_##s
static NetMsgInitSend s_send[] = {
    { MSG(PingRequest),         },
    { MSG(JoinAgeRequest),      },
    { MSG(PropagateBuffer),     },
    { MSG(GameMgrMsg),			},
};
#undef MSG

#define MSG(s)  kNetMsg_Game2Cli_##s, Recv_##s
static NetMsgInitRecv s_recv[] = {
    { MSG(PingReply)            },
    { MSG(JoinAgeReply),        },
    { MSG(PropagateBuffer),     },
    { MSG(GameMgrMsg),			},
};
#undef MSG


/*****************************************************************************
*
*   JoinAgeRequestTrans
*
***/

//============================================================================
JoinAgeRequestTrans::JoinAgeRequestTrans (
	unsigned							ageMcpId,
    const Uuid &                        accountUuid,
    unsigned                            playerInt,
    FNetCliGameJoinAgeRequestCallback   callback,
    void *                              param
) : NetGameTrans(kJoinAgeRequestTrans)
,	m_ageMcpId(ageMcpId)
,   m_accountUuid(accountUuid)
,   m_playerInt(playerInt)
,   m_callback(callback)
,   m_param(param)
{
}

//============================================================================
bool JoinAgeRequestTrans::Send () {
    if (!AcquireConn())
        return false;

    const unsigned_ptr msg[] = {
        kCli2Game_JoinAgeRequest,
						m_transId,
						m_ageMcpId,
        (unsigned_ptr) &m_accountUuid,
						m_playerInt,
    };

	m_conn->Send(msg, arrsize(msg));
    
    return true;
}

//============================================================================
void JoinAgeRequestTrans::Post () {
    m_callback(
        m_result,
        m_param
    );
}

//============================================================================
bool JoinAgeRequestTrans::Recv (
    const byte  msg[],
    unsigned    bytes
) {
	ref(bytes);
	
    const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *) msg;
    m_result        = reply.result;
    m_state         = kTransStateComplete;
    return true;
}

/*****************************************************************************
*
*   RcvdPropagatedBufferTrans
*
***/

//============================================================================
RcvdPropagatedBufferTrans::~RcvdPropagatedBufferTrans () {
	FREE(bufferData);
}

//============================================================================
void RcvdPropagatedBufferTrans::Post () {
    if (s_bufHandler)
        s_bufHandler(bufferType, bufferBytes, bufferData);
}

/*****************************************************************************
*
*   RcvdGameMgrMsgTrans
*
***/

//============================================================================
RcvdGameMgrMsgTrans::~RcvdGameMgrMsgTrans () {
	FREE(bufferData);
}

//============================================================================
void RcvdGameMgrMsgTrans::Post () {
    if (s_gameMgrMsgHandler)
        s_gameMgrMsgHandler((GameMsgHeader *)bufferData);
}


} using namespace Game;


/*****************************************************************************
*
*   NetGameTrans
*
***/

//============================================================================
NetGameTrans::NetGameTrans (ETransType transType)
:   NetTrans(kNetProtocolCli2Game, transType)
,   m_conn(nil)
{
}

//============================================================================
NetGameTrans::~NetGameTrans () {
    ReleaseConn();
}

//============================================================================
bool NetGameTrans::AcquireConn () {
    if (!m_conn)
        m_conn = GetConnIncRef("AcquireConn");
    return m_conn != nil;
}

//============================================================================
void NetGameTrans::ReleaseConn () {
    if (m_conn) {
        m_conn->DecRef("AcquireConn");
        m_conn = nil;
    }
}


/*****************************************************************************
*
*   Protected functions
*
***/

//============================================================================
void GameInitialize () {
    s_running = true;
    NetMsgProtocolRegister(
        kNetProtocolCli2Game,
        false,
        s_send, arrsize(s_send),
        s_recv, arrsize(s_recv),
        kDhGValue,
        BigNum(sizeof(kDhXData), kDhXData),
        BigNum(sizeof(kDhNData), kDhNData)
    );
}

//============================================================================
void GameDestroy (bool wait) {
    s_running = false;
    s_bufHandler = nil;
    s_gameMgrMsgHandler = nil;

	NetTransCancelByProtocol(
		kNetProtocolCli2Game,
		kNetErrRemoteShutdown
	);    
    NetMsgProtocolDestroy(
        kNetProtocolCli2Game,
        false
    );
    
    s_critsect.Enter();
    {
		while (CliGmConn * conn = s_conns.Head())
			UnlinkAndAbandonConn_CS(conn);
		s_active = nil;
	}
    s_critsect.Leave();
    
    if (!wait)
		return;

	while (s_perf[kPerfConnCount]) {
		NetTransUpdate();
        AsyncSleep(10);
	}
}

//============================================================================
bool GameQueryConnected () {
    bool result;
    s_critsect.Enter();
    {
		if (nil != (result = s_active))
			result &= (nil != s_active->cli);
    }
    s_critsect.Leave();
    return result;
}

//============================================================================
unsigned GameGetConnId () {
    unsigned connId;
    s_critsect.Enter();
    connId = (s_active) ? s_active->seq : 0;
    s_critsect.Leave();
    return connId;
}

//============================================================================
void GamePingEnable (bool enable) {
	s_perf[kPingDisabled] = !enable;
}

} using namespace Ngl;


/*****************************************************************************
*
*   Exported functions
*
***/

//============================================================================
void NetCliGameStartConnect (
    const NetAddressNode &  node
) {
    NetAddress addr;
    NetAddressFromNode(node, kNetDefaultClientPort, &addr);
    Connect(addr);
}

//============================================================================
void NetCliGameDisconnect () {
    
    s_critsect.Enter();
    {
		while (CliGmConn * conn = s_conns.Head())
			UnlinkAndAbandonConn_CS(conn);
		s_active = nil;
    }
    s_critsect.Leave();
}

//============================================================================
void NetCliGameJoinAgeRequest (
	unsigned							ageMcpId,
    const Uuid &                        accountUuid,
    unsigned                            playerInt,
    FNetCliGameJoinAgeRequestCallback   callback,
    void *                              param
) {
    JoinAgeRequestTrans * trans = NEWZERO(JoinAgeRequestTrans)(
		ageMcpId,
        accountUuid,
        playerInt,
        callback,
        param
    );
    NetTransSend(trans);
}

//============================================================================
void NetCliGameSetRecvBufferHandler (
    FNetCliGameRecvBufferHandler    handler
) {
    s_bufHandler = handler;
}

//============================================================================
void NetCliGamePropagateBuffer (
    unsigned                        type,
    unsigned                        bytes,
    const byte                      buffer[]
) {
    CliGmConn * conn = GetConnIncRef("PropBuffer");
    if (!conn)
        return;

    const unsigned_ptr msg[] = {
        kCli2Game_PropagateBuffer,
        type,
        bytes,
        (unsigned_ptr) buffer,
    };

	conn->Send(msg, arrsize(msg));

    conn->DecRef("PropBuffer");
}

//============================================================================
void NetCliGameSetRecvGameMgrMsgHandler (FNetCliGameRecvGameMgrMsgHandler handler) {
	s_gameMgrMsgHandler = handler;
}

//============================================================================
void NetCliGameSendGameMgrMsg (GameMsgHeader * msgHdr) {
    CliGmConn * conn = GetConnIncRef("GameMgrMsg");
    if (!conn)
        return;

	const unsigned_ptr msg[] = {
		kCli2Game_GameMgrMsg,
						msgHdr->messageBytes,
		(unsigned_ptr)	msgHdr,
	};
	
	conn->Send(msg, arrsize(msg));
	
	conn->DecRef("GameMgrMsg");
}