// 
// rline/rlpresence.cpp 
// 
// Copyright (C) 1999-2006 Rockstar Games.  All Rights Reserved. 
// 

#include "rlpresence.h"
#include "clan/rlclan.h"
#include "data/rson.h"
#include "diag/output.h"
#include "diag/seh.h"
#include "net/netfuncprofiler.h"
#include "net/nethardware.h"
#include "net/task.h"
#include "rldiag.h"
#include "rlfriendsreader.h"
#include "rlfriendsmanager.h"
#include "rlsessionmanager.h"
#include "ros/rlros.h"
#include "ros/rlroscredtasks.h"
#include "scpresence/rlscpresence.h"
#include "string/string.h"
#include "string/stringhash.h"
#include "system/memory.h"
#include "system/nelem.h"

#if RSG_DURANGO
#include "durango/rlxbl_interface.h"
#elif RSG_NP
#include "rlnp.h"
#include <np.h>
#include <user_service.h>
#elif __RGSC_DLL
#include "../../../suite/src/rgsc/rgsc/rgsc/rgsc.h"
#include "parser/manager.h"
#include "rline/socialclub/rlsocialclub.h"
#include "rlpc.h"
#elif RSG_PC
#include "net/task.h"
#include "parser/manager.h"
#include "rlpc.h"
#include "rline/socialclub/rlsocialclub.h"
#include "ros/rlroscredtasks.h"
#endif

/*  The Signin Bug
    --------------
    When non-Live-enabled profiles sign in/out it's possible that two
    XN_SYS_SIGNINCHANGED will be generated.  Only the second should be
    processed.
    According to MS, when we receive a XN_SYS_SIGNINCHANGED we should
    check if it's affecting a non-Live-enabled profile, and if it is
    and all gamers appear signed out then defer processing the event
    for 1500 to 2000 milliseconds, or until the second XN_SYS_SIGNINCHANGED
    is received.
*/

//FIXME (KB) - remove this when MS fixes the signin bug
#include "system/timer.h"

namespace rage
{


#if !__FINAL && RSG_DURANGO
PARAM(xuid, "If present, the Xuid will be used to create the ticket");
#endif // !__FINAL && RSG_DURANGO

PARAM(netFakeGamerName, "Specify a fake name to use for the local player");

RAGE_DEFINE_SUBCHANNEL(rline, presence);
#undef __rage_channel
#define __rage_channel rline_presence

extern netSocket* g_rlSkt;

namespace internal
{
static bool IsSignedIn(const int localGamerIndex);
static bool IsOnline(const int localGamerIndex);
static bool GetGamerId(const int localIndex, u64* gamerId);
static bool GetGamerHandle(const int localIndex, rlGamerHandle *handle);
static bool GetOfflineName(const int localGamerIndex,
                            char* name,
                            const unsigned nameLen);
static bool GetName(const int localGamerIndex,
                     char* name,
                     const unsigned nameLen);
static void ValidateName(char* nameBuf, const unsigned bufLen);

#if FAKE_DROP_LATENCY
static bool s_FakeUplinkOffline = false; // When true IsOnline will return false
#endif
}

AUTOID_IMPL(rlPresenceEvent);
AUTOID_IMPL(rlPresenceEventNetworkStatusChanged);
AUTOID_IMPL(rlPresenceEventPeerAddressChanged);
AUTOID_IMPL(rlPresenceEventSigninStatusChanged);
AUTOID_IMPL(rlPresenceEventProfileChanged);
AUTOID_IMPL(rlPresenceEventInviteAccepted);
AUTOID_IMPL(rlPresenceEventInviteAcceptedWhileOffline);
AUTOID_IMPL(rlPresenceEventInviteUnavailable);
AUTOID_IMPL(rlPresenceEventJoinedViaPresence);
AUTOID_IMPL(rlPresenceEventInviteReceived);
AUTOID_IMPL(rlPresenceEventPartyInviteReceived);
AUTOID_IMPL(rlPresenceEventGameSessionReady);
AUTOID_IMPL(rlPresenceEventMsgReceived);
AUTOID_IMPL(rlPresenceEventPartyChanged);
AUTOID_IMPL(rlPresenceEventPartySessionInvalid);
AUTOID_IMPL(rlPresenceEventScMessage);
AUTOID_IMPL(rlPresenceEventMuteListChanged);
AUTOID_IMPL(rlPresenceEventPartyMembersAvailable);
AUTOID_IMPL(rlPresenceOnlinePermissionsInvalid);
AUTOID_IMPL(rlPresenceOnlinePermissionsChanged);
#if RSG_ORBIS
AUTOID_IMPL(rlPresenceEventPlayTogetherHost)
#endif

rlPresence::GamerPresence rlPresence::m_GamerPresences[RL_MAX_LOCAL_GAMERS];

#if __STEAM_BUILD
NOSTRIP_PC_PARAM(steamjvp, "Steam JVP connect string containing session info. Format: -steamjvp={sessioninfo}");
#define STEAM_JVP_PREFIX "-steamjvp="
#define STEAM_JVP_PREFIX_LEN (sizeof(STEAM_JVP_PREFIX)-1)
#endif

#if __STEAM_BUILD
rlPresence::rlSteamPresence rlPresence::sm_SteamPresence;

rlPresence::rlSteamPresence::rlSteamPresence()
	: m_SteamCallbackHandle(this, &rlPresence::rlSteamPresence::OnSteamPresenceCb)
{

}


void rlPresence::rlSteamPresence::CheckForBootableInvites()
{
	static bool bHandledBootableInvite = false;
	if (!bHandledBootableInvite)
	{
		const int localGamerIndex = rlPresence::GetActingUserIndex();

		// Wait for login to check steam JVP
		if (RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex) &&
			rlPresence::IsSignedIn(localGamerIndex) && 
			rlPresence::IsOnline(localGamerIndex))
		{
			const char * jvpSessionBlob;
			if (PARAM_steamjvp.Get(jvpSessionBlob))
			{
				rlPresence::OnSteamPresenceInvite(jvpSessionBlob);
			}

			bHandledBootableInvite = true;
		}
	}
}

void rlPresence::rlSteamPresence::OnSteamPresenceCb( GameRichPresenceJoinRequested_t *pParam )
{
	rlDebug3("OnSteamPresenceCb - \"%s\" from \"%" I64FMT "u\"", pParam ? pParam->m_rgchConnect : "", pParam ? pParam->m_steamIDFriend.ConvertToUint64() : 0);
	if (pParam)
	{
		rlPresence::OnSteamPresenceInvite(pParam->m_rgchConnect);
	}
}

void rlPresence::OnSteamPresenceInvite(const char * sessionBlob)
{
	rtry
	{
		rlDebug3("rlPresence::OnSteamPresenceInvite : %s", sessionBlob);

		rcheck(RL_IS_VALID_LOCAL_GAMER_INDEX(m_ActingUserIndex), catchall, );
		rcheck(IsSignedIn(m_ActingUserIndex), catchall, );

		rverify(strlen(sessionBlob) > STEAM_JVP_PREFIX_LEN, catchall, rlError("Steam Session Blob was too short."));
			
		rlSessionInfo sessionInfo;
		if (!strncmp(STEAM_JVP_PREFIX, sessionBlob, STEAM_JVP_PREFIX_LEN))
		{
			rverify(sessionInfo.FromString(sessionBlob + STEAM_JVP_PREFIX_LEN), catchall, );
		}
		else
		{
			rverify(sessionInfo.FromString(sessionBlob), catchall, );
		}

		rverify(sessionInfo.IsValid(), catchall, );

		rlPresenceEventJoinedViaPresence e(m_GamerPresences[m_ActingUserIndex].m_GamerInfo, m_GamerPresences[m_ActingUserIndex].m_GamerInfo.GetGamerHandle(), sessionInfo, rlPresenceInviteFlags::Flags_None);
		m_Delegator.Dispatch(&e);
	}
	rcatchall
	{

	}
}

#endif

//////////////////////////////////////////////////////////////////////////
//  rlPresence::GamerPresence
//////////////////////////////////////////////////////////////////////////
void
rlPresence::GamerPresence::Clear()
{

#if SC_PRESENCE
    if(m_GetMessagesStatus.Pending())
    {
        rlScPresence::Cancel(&m_GetMessagesStatus);
    }
#endif

#if RSG_ORBIS
	m_UserServiceId = SCE_USER_SERVICE_USER_ID_INVALID;
#endif

    m_GamerInfo.Clear();

#if SC_PRESENCE
    m_RetrieveMessagesTimer.Clear();
    m_MsgIter.ReleaseResources();
#endif

    m_RetrySigninTimeout = 0;

    m_AgeGroup = RL_AGEGROUP_INVALID;
    m_Age = -1;

	m_HasNetwork = false;
    m_IsSignedIn = false;
    m_IsOnline = false;
    m_RetrySignin = false;
    m_ProcessProfileChange = false;
    m_RetrievingMessages = false;
    m_SignInChangeFlags = rlSignInStatusChangeFlags::NONE;
}

///////////////////////////////////////////////////////////////////////////////
//  rlPresence
///////////////////////////////////////////////////////////////////////////////
rlPresence::Delegator rlPresence::m_Delegator;

//Registered with netRelay to listen for events.
netRelay::Delegate rlPresence::m_RelayDelegate[NET_RELAYEVENT_NUM_EVENTS];
netNatDetector::Delegate rlPresence::m_NatDetectorDelegate;
netHardware::Delegate rlPresence::m_NetHardwareDelegate;
rlRos::Delegate rlPresence::m_RosDelegate;

#if RSG_NP
rlNpEventDelegator::Delegate rlPresence::m_NpDlgt;
#endif

bool rlPresence::m_Initialized = false;
bool rlPresence::m_HaveNetwork = false;
int rlPresence::m_ActingUserIndex = -1;
rlGamerInfo rlPresence::m_ActingGamerInfo;
unsigned rlPresence::m_PresenceMessageDelayTimeMs = rlPresence::DEFAULT_RETRIEVE_MESSAGE_DELAY_MS;
unsigned rlPresence::m_PresenceMessageMaxToRetrieve = rlPresence::DEFAULT_MAX_PRESENCE_MESSAGES_TO_RETRIEVE;

bool
rlPresence::Init()
{
    rlDebug("Initializing...");

    bool success = false;
    
    if(rlVerifyf(!m_Initialized, "Already initialized")
        && rlPresence::NativeInit())
    {
		netRelayEventType relayEventTypes[] = {NET_RELAYEVENT_MESSAGE_RECEIVED,
											   NET_RELAYEVENT_ADDRESS_OBTAINED,
											   NET_RELAYEVENT_ADDRESS_CHANGED};

		for(unsigned i = 0; i < COUNTOF(relayEventTypes); ++i)
		{
			m_RelayDelegate[relayEventTypes[i]].Bind(&rlPresence::OnRelayEvent);
			netRelay::AddDelegate(relayEventTypes[i], &m_RelayDelegate[relayEventTypes[i]]);
		}

		m_NatDetectorDelegate.Bind(&rlPresence::OnNatDetectorEvent);
		netNatDetector::AddDelegate(&m_NatDetectorDelegate);

		m_NetHardwareDelegate.Bind(&rlPresence::OnNetHardwareEvent);
		netHardware::AddDelegate(&m_NetHardwareDelegate);

		m_RosDelegate.Bind(&rlPresence::OnRosEvent);
		rlRos::AddDelegate(&m_RosDelegate);
		
        //Initialize the signin state for all local gamers.
        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
        {
            m_GamerPresences[i].Clear();
            rlPresence::RefreshSigninState(i);
			rlPresence::RefreshNetworkStatus(i);
        }

        success = m_Initialized = true;

        rlDebug("Initialized");
    }

    return success;
}

void
rlPresence::Shutdown()
{
    if(m_Initialized)
    {
        rlDebug("Shutting down...");

        rlPresence::NativeShutdown();

        m_Delegator.Clear();

        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
        {
            m_GamerPresences[i].Clear();
        }

		netRelayEventType relayEventTypes[] = {NET_RELAYEVENT_MESSAGE_RECEIVED,
											   NET_RELAYEVENT_ADDRESS_OBTAINED,
											   NET_RELAYEVENT_ADDRESS_CHANGED};

		for(unsigned i = 0; i < COUNTOF(relayEventTypes); ++i)
		{
			netRelay::RemoveDelegate(relayEventTypes[i], &m_RelayDelegate[relayEventTypes[i]]);
		}

		netNatDetector::RemoveDelegate(&m_NatDetectorDelegate);
		netHardware::RemoveDelegate(&m_NetHardwareDelegate);
		rlRos::RemoveDelegate(&m_RosDelegate);


        m_Initialized = m_HaveNetwork = false;

        rlDebug("Shut down");
    }
}

bool
rlPresence::IsInitialized()
{
    return m_Initialized;
}

void
rlPresence::Update()
{
    if(!rlVerifyf(m_Initialized, "Not initialized"))
    {
        return;
    }

#if FAKE_DROP_LATENCY
    if (internal::s_FakeUplinkOffline && !netSocket::GetFakeUplinkDisconnection())
    {
        rlDebug1("GetFakeUplinkDisconnection is false so turing s_FakeUplinkOffline off again");
        FakeOfflineDueToUplink(false);
    }
#endif

    rlPresence::NativeUpdate();

    const bool hadNetwork = m_HaveNetwork;

    m_HaveNetwork = rlPeerAddress::HasNetwork();

    for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
    {
        bool doRefreshSignin = false;

        if(hadNetwork && !m_HaveNetwork)
        {
            //Set any online gamers to offline.
            //
            //Do this only for non-Live platforms.  The reason is that
            //not all XDK subsystems detect the disconnect at the same time.
            //If we were to call, for example, XNetGetTitleXnAddr() at this
            //point it would return the XNET_GET_XNADDR_ONLINE bit in its
            //return value, when clearly that's not the case.  The result
            //is that the player appears connected to Live for a short time
            //after being disconnected.  This can have undesirable effects
            //on multiplayer games.
            //
            //To handle this the game should have a call to
            //rlPeerAddress::HasNetwork() in its main loop, which detects
            //disconnects instantly.  The game should bail on any networking
            //features if HasNetwork() returns false.

            doRefreshSignin = true;
        }

        GamerPresence& gp = m_GamerPresences[i];

        if(gp.m_ProcessProfileChange)
        {
            //One of the things that can change in a profile
            //is the gamer's name.  Refreshing the signin state
            //will also refresh the gamer's name.
            doRefreshSignin = true;
        }
        else if(gp.m_RetrySignin
                && int(sysTimer::GetSystemMsTime() - gp.m_RetrySigninTimeout) >= 0)
        {
            doRefreshSignin = true;
        }

        if(doRefreshSignin)
        {
            rlPresence::RefreshSigninState(i);
			rlPresence::RefreshNetworkStatus(i);
        }

#if SC_PRESENCE
		UpdateMessagesSync(i);
#endif
#if __STEAM_BUILD
		sm_SteamPresence.CheckForBootableInvites();
#endif

        UpdateScPresence(i);
    }
}

bool
rlPresence::IsSignedIn(const int localGamerIndex)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        return m_GamerPresences[localGamerIndex].m_IsSignedIn;
    }

    return false;
}

bool
rlPresence::IsOnline(const int localGamerIndex)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        return m_GamerPresences[localGamerIndex].m_IsOnline;
    }

    return false;
}

bool
rlPresence::GetName(const int localGamerIndex,
                     char* name,
                     const unsigned sizeofName)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(m_GamerPresences[localGamerIndex].m_IsSignedIn)
        {
            const char* n = m_GamerPresences[localGamerIndex].m_GamerInfo.GetName();
            safecpy(name, n, sizeofName);
            return true;
        }
    }

    return false;
}

bool rlPresence::GetDisplayName(const int localGamerIndex,
								char* name,
								const unsigned nameLen)
{
	bool success = false;
#if RSG_DURANGO
	if(rlVerify(name && (nameLen >= RL_MAX_DISPLAY_NAME_BUF_SIZE)))
	{
		*name = '\0';

		if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
			"Invalid gamer index: %d", localGamerIndex))
		{
			const char *displayName = g_rlXbl.GetPresenceManager()->GetDisplayName(localGamerIndex);

			if (PARAM_netFakeGamerName.Get())
			{
				PARAM_netFakeGamerName.Get(displayName);
			}

			if(rlVerify(displayName))
			{
				const unsigned len = ustrlen(displayName);
				if(rlVerify((len > 0) && (len < (int)nameLen)))
				{
					safecpy(name, displayName, nameLen);
					success = true;
				}
			}

		}
	}
#else
	success = GetName(localGamerIndex, name, nameLen);
#endif

	return success;
}

bool
rlPresence::GetGamerId(const int localGamerIndex, rlGamerId* gamerId)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(m_GamerPresences[localGamerIndex].m_IsSignedIn)
        {
            *gamerId = m_GamerPresences[localGamerIndex].m_GamerInfo.GetGamerId();
            return true;
        }
    }

    return false;
}

bool
rlPresence::GetGamerHandle(const int localGamerIndex,
                            rlGamerHandle* gamerHandle)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(m_GamerPresences[localGamerIndex].m_IsSignedIn)
        {
#if !__FINAL && RSG_DURANGO
			// Command line gamertag and xuid to impersonate someone else
			if (PARAM_xuid.Get())
			{
				const char* xuid = 0;
				PARAM_xuid.Get(xuid);
				static rlGamerHandle forged;
				forged.FromUserId(xuid);
				*gamerHandle = forged;
				return true;
			}
			else
#endif // !__FINAL && RSG_DURANGO
			{
				*gamerHandle = m_GamerPresences[localGamerIndex].m_GamerInfo.GetGamerHandle();
				return true;
			}
        }
    }

    return false;
}

bool
rlPresence::GetLocalGamerHandleFromScGamerHandle(const char* scGamerHandleStr, rlGamerHandle* gamerHandle)
{
    const RockstarId rockstarId = rlGamerHandle::GetRockstarId(scGamerHandleStr);
    return GetLocalGamerHandleFromRockstarId(rockstarId, gamerHandle);
}

bool
rlPresence::GetLocalGamerHandleFromRockstarId(const RockstarId rockstarId, rlGamerHandle* gamerHandle)
{
    if(InvalidRockstarId != rockstarId)
    {
        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
        {
            const rlRosCredentials& creds = rlRos::GetCredentials(i);
            if(creds.IsValid() && creds.GetRockstarId() == rockstarId)
            {
                return rlPresence::GetGamerHandle(i, gamerHandle)
                        && rlVerify(gamerHandle->IsValid());
            }
        }
    }
    else
    {
        rlError("Invalid rockstar ID");
    }

    return false;
}

bool
rlPresence::GetGamerInfo(const int localGamerIndex, rlGamerInfo* gamerInfo)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        *gamerInfo = m_GamerPresences[localGamerIndex].m_GamerInfo;
#if !__FINAL && RSG_DURANGO
		// Command line gamertag and xuid to impersonate someone else
		if (PARAM_xuid.Get())
		{
			const char* xuid = 0;
			PARAM_xuid.Get(xuid);
			static rlGamerHandle forged;
			forged.FromUserId(xuid);
			gamerInfo->m_GamerHandle = forged;
		}
#endif // !__FINAL && RSG_DURANGO
        return true;
    }

    return false;
}

bool
rlPresence::GetLocalGamerInfoFromScGamerHandle(const char* scGamerHandleStr, rlGamerInfo* gamerInfo)
{
    const RockstarId rockstarId = rlGamerHandle::GetRockstarId(scGamerHandleStr);
    return GetLocalGamerInfoFromRockstarId(rockstarId, gamerInfo);
}

bool
rlPresence::GetLocalGamerInfoFromRockstarId(const RockstarId rockstarId, rlGamerInfo* gamerInfo)
{
    if(InvalidRockstarId != rockstarId)
    {
        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
        {
            const rlRosCredentials& creds = rlRos::GetCredentials(i);
            if(creds.IsValid() && creds.GetRockstarId() == rockstarId)
            {
                return rlPresence::GetGamerInfo(i, gamerInfo)
                        && rlVerify(gamerInfo->IsValid());
            }
        }
    }
    else
    {
        rlError("Invalid rockstar ID");
    }

    return false;
}

int
rlPresence::GetAge(const int localGamerIndex)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(m_GamerPresences[localGamerIndex].m_IsOnline)
        {
            return m_GamerPresences[localGamerIndex].m_Age;
        }
    }

    return -1;
}

rlAgeGroup
rlPresence::GetAgeGroup(const int localGamerIndex)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(m_GamerPresences[localGamerIndex].m_IsOnline)
        {
            return m_GamerPresences[localGamerIndex].m_AgeGroup;
        }
    }

    return RL_AGEGROUP_INVALID;
}

bool
rlPresence::IsGuest(const int localGamerIndex)
{
    bool isGuest = false;

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
#if RSG_DURANGO
		isGuest = g_rlXbl.GetPresenceManager() && g_rlXbl.GetPresenceManager()->IsGuest(localGamerIndex);
#endif
    }

    return isGuest;
}

int
rlPresence::GetLocalIndex(const rlGamerHandle& gh)
{
#if !__FINAL && RSG_DURANGO
	// Command line gamertag and xuid to impersonate someone else
	if (PARAM_xuid.Get())
	{
		int profilesSignedIn=0;
		for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
		{
			if(m_GamerPresences[i].m_IsSignedIn)
			{
				profilesSignedIn++;
			}
		}
		rlAssertf(profilesSignedIn==1, "You cannot impersonate a gamertag when you have more than one profile signed in");
		
		const char* xuid = 0;
		PARAM_xuid.Get(xuid);
		static rlGamerHandle forged;
		forged.FromUserId(xuid);
		if(gh==forged)
			return 0;
	}
#endif // !__FINAL && RSG_DURANGO
    for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
    {
        if(m_GamerPresences[i].m_IsSignedIn
            && m_GamerPresences[i].m_GamerInfo.GetGamerHandle() == gh)
        {
            return i;
        }
    }

    return -1;
}

bool
rlPresence::IsLocal(const rlGamerHandle& gh)
{
    return GetLocalIndex(gh) >= 0;
}

#if __RGSC_DLL
bool 
rlPresence::GetFriendsArray(const int localGamerIndex,
							rlFriend** friends,
							unsigned* numFriends)
{
	bool success = false;

	*numFriends = 0;

	if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
		"Invalid gamer index: %d", localGamerIndex)
		&& rlVerifyf(rlPresence::IsOnline(localGamerIndex),
		"Gamer: %d is not online", localGamerIndex))
	{
		rlFriendsPage* page = rlFriendsManager::GetDefaultFriendPage();
		*friends = page->m_Friends;
		*numFriends = page->m_NumFriends;
		success = true;
	}

	return success;
}

void 
rlPresence::SyncFriends()
{
	rlFriendsManager::RequestFriendSync();
}

void 
rlPresence::SetFriendIsOnline(const int localGamerIndex, rlFriend* f, const bool isOnline, const bool isPlayingSameTitle)
{
    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex)
        && rlVerifyf(rlPresence::IsOnline(localGamerIndex),
                    "Gamer: %d is not online", localGamerIndex))
	{
		rlAssert(f);
		f->SetIsOnline(isOnline, isPlayingSameTitle);
	
		rlFriendEventStatusChanged e(*f); 
		rlFriendsManager::DispatchEvent(&e);
	}
}

bool 
rlPresence::IsInitialFriendSyncDone()
{
	return rlFriendsManager::IsActivated() || rlFriendsManager::ActivationFailed();
}

void 
rlPresence::SetKickedByDuplicateSignIn(const int localGamerIndex)
{
	GamerPresence& gp = m_GamerPresences[0];
	gp.m_SignInChangeFlags |= rlSignInStatusChangeFlags::SIGN_IN_CHANGE_DUPLICATE_LOGIN;
	rlPresence::RefreshSigninState(localGamerIndex);
	rlPresence::RefreshNetworkStatus(localGamerIndex);
}

#endif

#if RSG_NP
bool 
rlPresence::AddFriend(const SceNpOnlineId& npOnlineId, const char* msg, netStatus* status)
{
	return g_rlNp.GetBasic().AddFriend(npOnlineId, msg, status);
}

bool 
rlPresence::AddFriend(const SceNpId& npId, const char* msg, netStatus* status)
{
	return g_rlNp.GetBasic().AddFriend(npId, msg, status);
}
#endif

#if RSG_NP || __STEAM_BUILD
bool 
rlPresence::SetBlob(const void* blob, const unsigned blobSize)
{
    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
#if RSG_NP
        return g_rlNp.GetBasic().SetPresenceBlob( blob, blobSize);
#elif __STEAM_BUILD	
		if (blobSize == 0)
		{
			return SteamFriends() && SteamFriends()->SetRichPresence("connect", "");
		}
		else
		{
			if (rlVerify(blobSize <= RL_PRESENCE_MAX_BUF_SIZE))
			{
				char presenceBuf[RL_PRESENCE_MAX_BUF_SIZE + STEAM_JVP_PREFIX_LEN]; // + 10 for "-steamjvp="
				formatf(presenceBuf, "%s%s", STEAM_JVP_PREFIX, (char*)blob);
				return SteamFriends() && SteamFriends()->SetRichPresence("connect", presenceBuf);
			}
		}
#endif
    }

    return false;
}

bool
rlPresence::SetStatusString(const char* statusStr)
{
    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
        const char* str = statusStr ? statusStr : "";
        rlDebug2("Setting status string to: \"%s\"", str);
#if RSG_NP
        return g_rlNp.GetBasic().SetPresenceStatusString(str);
#elif __STEAM_BUILD
		return SteamFriends() && SteamFriends()->SetRichPresence("status", str);
#endif
    }

    return false;
}

#elif RSG_DURANGO

bool rlPresence::SetStatusString(const rlGamerInfo& gamerInfo, const char * presenceStr, netStatus* status)
{
	bool success = false;

	if(rlVerifyf(m_Initialized, "Not initialized") && rlVerify(gamerInfo.IsLocal()) && rlVerify(gamerInfo.IsSignedIn()))
	{
		rlDebug2("Gamer:\"%s\"[%d] - setting presence:%s...", gamerInfo.GetName(), gamerInfo.GetLocalIndex(), presenceStr);
		success = g_rlXbl.GetPresenceManager()->SetRichPresence(gamerInfo, presenceStr, status);
	}
	else if(status)
	{
		status->SetPending();
		status->SetFailed();
	}

	return success;
}

void rlPresence::TriggerOnlinePermissionsChanged(const int gamerIndex)
{
	if (RL_VERIFY_LOCAL_GAMER_INDEX(gamerIndex))
	{
		if (rlVerify(m_GamerPresences[gamerIndex].m_GamerInfo.IsSignedIn()))
		{
			rlPresenceOnlinePermissionsChanged e(m_GamerPresences[gamerIndex].m_GamerInfo, false);
			m_Delegator.Dispatch(&e);
		}
	}
}

#else

bool
rlPresence::SetStatusString(const rlGamerInfo& gamerInfo,
                            const int presenceId,
                            const rlSchema& schema,
                            netStatus* status)
{
    bool success = false;

    if(rlVerifyf(m_Initialized, "Not initialized")
        && rlVerify(gamerInfo.IsLocal())
        && rlVerify(gamerInfo.IsSignedIn()))
    {
        rlDebug2("Gamer:\"%s\"[%d] - setting presence:%d...",
                    gamerInfo.GetName(),
                    gamerInfo.GetLocalIndex(),
                    presenceId);

        success = rlPresence::NativeSetStatusString(gamerInfo, presenceId, schema, status);
    }
    else if(status)
    {
        status->SetPending();
        status->SetFailed();
    }

    return success;
}

#endif

bool
rlPresence::GetStatusString(const rlGamerInfo& gamerInfo,
						    char* buf,
						    const unsigned sizeOfBuf,
						    netStatus* status)
{
    rlDebug2("Retreiving presence string for gamer:\"%s\"", gamerInfo.GetName());

    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
    	return rlPresence::NativeGetStatusString(gamerInfo, buf, sizeOfBuf, status);
    }

    return false;
}

extern bool NIY;

void
rlPresence::AddDelegate(Delegate* dlgt)
{
    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
        m_Delegator.AddDelegate(dlgt);
    }
}

void
rlPresence::RemoveDelegate(Delegate* dlgt)
{
    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
        m_Delegator.RemoveDelegate(dlgt);
    }
}

void
rlPresence::NotifyInviteAccepted(
	const int gamerIdx,
	const rlSessionInfo& sessionInfo,
	const rlGamerHandle inviter)
{
	rlAssert(gamerIdx >= 0 && gamerIdx < RL_MAX_LOCAL_GAMERS);

    if(rlVerifyf(m_Initialized, "Not initialized"))
    {
	    const GamerPresence& joinerInfo = m_GamerPresences[gamerIdx];

	    rlDebug("An invite has been accepted by %s", joinerInfo.m_GamerInfo.GetName());

	    rlPresenceEventInviteAccepted e(joinerInfo.m_GamerInfo, inviter, sessionInfo, rlPresenceInviteFlags::Flags_None);
	    m_Delegator.Dispatch(&e);
    }
}

#if RSG_PC && !__RGSC_DLL
// tasks that marshal presence calls to the DLL
class GetAttributesForGamerTask : public netTask
{
public:

	NET_TASK_DECL(GetAttributesForGamerTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	GetAttributesForGamerTask()
		: m_State(STATE_CALL)
        , m_LocalGamerIndexForCaller(-1)
		, m_Attrs(NULL)
		, m_NumAttrs(0)
		, m_AsyncStatus(NULL)
	{

	}

	~GetAttributesForGamerTask()
	{
		if(m_RgscAttrs)
		{
			RL_FREE(m_RgscAttrs);
			m_RgscAttrs = NULL;
		}
	}

	bool Configure(const int localGamerIndexForCaller,
                   const rlGamerHandle& gamerHandle,
				   rlScPresenceAttribute* attrs,
				   const unsigned numAttrs)
	{
        m_LocalGamerIndexForCaller = localGamerIndexForCaller;
		m_GamerHandle = gamerHandle;
		m_Attrs = attrs;
		m_NumAttrs = numAttrs;

		bool success = false;
		rtry
		{
			m_RgscAttrs = RL_NEW_ARRAY(GetAttributesForGamerTask, rgsc::PresenceAttribute, m_NumAttrs);
			rverify(m_RgscAttrs, catchall, );

			for(unsigned i = 0; i < m_NumAttrs; ++i)
			{
				m_RgscAttrs[i].SetName(m_Attrs[i].Name);

				if(m_Attrs[i].Type == RLSC_PRESTYPE_S64)
				{
					s64 value;
					rlVerify(m_Attrs[i].GetValue(&value));
					rlVerify(m_RgscAttrs[i].SetValue(value));
				}
				else if(m_Attrs[i].Type == RLSC_PRESTYPE_DOUBLE)
				{
					double value;
					rlVerify(m_Attrs[i].GetValue(&value));
					rlVerify(m_RgscAttrs[i].SetValue(value));
				}
				else if(m_Attrs[i].Type == RLSC_PRESTYPE_STRING)
				{
					char value[RLSC_PRESENCE_STRING_MAX_SIZE];
					rlVerify(m_Attrs[i].GetValue(value, sizeof(value)));
					rlVerify(m_RgscAttrs[i].SetValue(value));
				}
				else if(m_Attrs[i].Type == RLSC_PRESTYPE_INVALID)
				{
					m_RgscAttrs[i].SetType(rgsc::IPresenceAttributeLatestVersion::PRESTYPE_INVALID);
				}
				else
				{
					rlAssertf(false, "Unknown presence attribute type %d", (int)m_RgscAttrs[i].GetType());
				}
			}

			success = true;
		}
		rcatchall
		{
		}
		return success;
	}

	virtual void OnCancel()
	{
		netTaskDebug("Canceled while in state %d, PendingAsync: %s", m_State, m_AsyncStatus ? (m_AsyncStatus->Pending() ? "True" : "False") : "False");
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Cancel();
		}
	}

	virtual netTaskStatus OnUpdate(int* /*resultCode*/)
	{
		netTaskStatus status = NET_TASKSTATUS_PENDING;

		switch(m_State)
		{
		case STATE_CALL:
			if(WasCanceled())
			{
				netTaskDebug("Canceled - bailing...");
				status = NET_TASKSTATUS_FAILED;
			}
			else if(Call())
			{
				m_State = STATE_CALLING;
			}
			else
			{
				status = NET_TASKSTATUS_FAILED;
			}
			break;

		case STATE_CALLING:
			if(!m_AsyncStatus->Pending())
			{
				if(m_AsyncStatus->Succeeded())
				{
					status = NET_TASKSTATUS_SUCCEEDED;
				}
				else
				{
					status = NET_TASKSTATUS_FAILED;
				}
			}
			break;
		}

		if(NET_TASKSTATUS_PENDING != status)
		{
			this->Complete(status);
		}

		return status;
	}

private:

	bool Call()
	{
		bool success = true;

		rtry
		{
			rverify(g_rlPc.IsInitialized(), catchall, );

			HRESULT hr = g_rlPc.GetTaskManager()->CreateAsyncStatus(((rgsc::IAsyncStatus**)&m_AsyncStatus));
			rverify(SUCCEEDED(hr), catchall, );

			// TODO: NS - if we need m_LocalGamerIndexForCaller, then we need to make a new interface in the DLL (can't change existing interfaces)
			success = g_rlPc.GetPresenceManager()->GetAttributesForGamer(/*m_LocalGamerIndexForCaller, */m_GamerHandle.GetRockstarId(), m_RgscAttrs, m_NumAttrs, m_AsyncStatus);
		}
		rcatchall
		{

		}

		return success;
	}

	void Complete(const netTaskStatus status)
	{
		if(NET_TASKSTATUS_SUCCEEDED == status && !WasCanceled())
		{
			for(unsigned i = 0; i < m_NumAttrs; ++i)
			{
				if(m_RgscAttrs[i].GetType() == rgsc::IPresenceAttributeLatestVersion::PRESTYPE_S64)
				{
					s64 value;
					rlVerify(m_RgscAttrs[i].GetValue(&value));
					rlVerify(m_Attrs[i].SetValue(value));
				}
				else if(m_RgscAttrs[i].GetType() == rgsc::IPresenceAttributeLatestVersion::PRESTYPE_DOUBLE)
				{
					double value;
					rlVerify(m_RgscAttrs[i].GetValue(&value));
					rlVerify(m_Attrs[i].SetValue(value));
				}
				else if(m_RgscAttrs[i].GetType() == rgsc::IPresenceAttributeLatestVersion::PRESTYPE_STRING)
				{
					char value[RLSC_PRESENCE_STRING_MAX_SIZE];
					rlVerify(m_RgscAttrs[i].GetValue(value, sizeof(value)));
					rlVerify(m_Attrs[i].SetValue(value));
				}
				else if(m_RgscAttrs[i].GetType() == rgsc::IPresenceAttributeLatestVersion::PRESTYPE_INVALID)
				{
					netTaskDebug("Nothing returned for presence attribute %s", m_RgscAttrs[i].GetName());
				}
				else
				{
					rlAssertf(false, "Unknown attribute type %d", m_RgscAttrs[i].GetType());
				}
			}
		}

		if(m_AsyncStatus)
		{
			m_AsyncStatus->Release();
			m_AsyncStatus = NULL;
		}
	}

	enum State
	{
		STATE_CALL,
		STATE_CALLING,
	};

    int m_LocalGamerIndexForCaller;
	rlGamerHandle m_GamerHandle;
	rlScPresenceAttribute* m_Attrs;
	unsigned m_NumAttrs;
	rgsc::PresenceAttribute* m_RgscAttrs;
	rgsc::IAsyncStatusLatestVersion* m_AsyncStatus;

	State m_State;
};

class PostMessageTask : public netTask
{
public:

	NET_TASK_DECL(PostMessageTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	PostMessageTask()
		: m_State(STATE_CALL)
		, m_LocalGamerindex(-1)
		, m_Recipients(NULL)
		, m_NumRecipients(0)
		, m_TtlSeconds(0)
		, m_AsyncStatus(NULL)
	{

	}

	~PostMessageTask()
	{
		if(m_RgscRecipients)
		{
			RL_FREE(m_RgscRecipients);
			m_RgscRecipients = NULL;
		}
	}

	bool Configure(const int localGamerindex,
				   const rlGamerHandle* recipients,
				   const unsigned numRecipients,
				   const char* message,
				   const unsigned ttlSeconds)
	{
		m_LocalGamerindex = localGamerindex;
		m_Recipients = recipients;
		m_NumRecipients = numRecipients;
		m_RgscMessage.SetTimestamp(0);
		m_RgscMessage.SetContents(message);
		m_TtlSeconds = ttlSeconds;

		bool success = false;
		rtry
		{
			rverify(message != nullptr && istrlen(message) > 0, catchall, rlError("Empty messages aren't allowed"));

			m_RgscRecipients = RL_NEW_ARRAY(PostMessageTask, RockstarId, m_NumRecipients);
			rverify(m_RgscRecipients, catchall, );

			for(unsigned i = 0; i < m_NumRecipients; ++i)
			{
				m_RgscRecipients[i] = m_Recipients[i].GetRockstarId();
			}

			success = true;
		}
		rcatchall
		{

		}

		return success;
	}

	virtual void OnCancel()
	{
		netTaskDebug("Canceled while in state %d, PendingAsync: %s", m_State, m_AsyncStatus ? (m_AsyncStatus->Pending() ? "True" : "False") : "False");
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Cancel();
		}
	}

	virtual netTaskStatus OnUpdate(int* /*resultCode*/)
	{
		netTaskStatus status = NET_TASKSTATUS_PENDING;

		switch(m_State)
		{
		case STATE_CALL:
			if(WasCanceled())
			{
				netTaskDebug("Canceled - bailing...");
				status = NET_TASKSTATUS_FAILED;
			}
			else if(Call())
			{
				m_State = STATE_CALLING;
			}
			else
			{
				status = NET_TASKSTATUS_FAILED;
			}
			break;

		case STATE_CALLING:
			if(!m_AsyncStatus->Pending())
			{
				if(m_AsyncStatus->Succeeded())
				{
					status = NET_TASKSTATUS_SUCCEEDED;
				}
				else
				{
					status = NET_TASKSTATUS_FAILED;
				}
			}
			break;
		}

		if(NET_TASKSTATUS_PENDING != status)
		{
			this->Complete(status);
		}

		return status;
	}

private:

	bool Call()
	{
		bool success = true;

		rtry
		{
			rverify(g_rlPc.IsInitialized(), catchall, );

			HRESULT hr = g_rlPc.GetTaskManager()->CreateAsyncStatus(((rgsc::IAsyncStatus**)&m_AsyncStatus));
			rverify(SUCCEEDED(hr), catchall, );
			
			success = g_rlPc.GetPresenceManager()->PostMessage(m_LocalGamerindex, m_RgscRecipients, m_NumRecipients, &m_RgscMessage, m_TtlSeconds, m_AsyncStatus);
		}
		rcatchall
		{

		}

		return success;
	}

	void Complete(const netTaskStatus /*status*/)
	{
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Release();
			m_AsyncStatus = NULL;
		}
	}

	enum State
	{
		STATE_CALL,
		STATE_CALLING,
	};

	int m_LocalGamerindex;
	const rlGamerHandle* m_Recipients;
	unsigned m_NumRecipients;
	unsigned m_TtlSeconds;
	rgsc::PresenceMessage m_RgscMessage;
	RockstarId* m_RgscRecipients;
	rgsc::IAsyncStatusLatestVersion* m_AsyncStatus;

	State m_State;
};

class QueryTask : public netTask
{
public:

	NET_TASK_DECL(QueryTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	QueryTask()
		: m_State(STATE_CALL)
        , m_LocalGamerIndex(-1)
		, m_Offset(0)
		, m_Count(0)
		, m_RecordsBuf(NULL)
		, m_SizeofRecordsBuf(0)
		, m_QueryName(NULL)
		, m_ParamNameValueCsv(NULL)
		, m_Records(NULL)
        , m_NumRecordsRetrieved(NULL)
		, m_NumRecords(NULL)
		, m_AsyncStatus(NULL)
	{

	}

	QueryTask::~QueryTask()
	{
		if(m_QueryName)
		{
			StringFree(m_QueryName);
			m_QueryName = NULL;
		}

		if(m_ParamNameValueCsv)
		{
			StringFree(m_ParamNameValueCsv);
			m_ParamNameValueCsv = NULL;
		}

		if(m_AsyncStatus)
		{
			m_AsyncStatus->Release();
			m_AsyncStatus = NULL;
		}
	}

	bool Configure(const int localGamerIndex,
                   const char* queryName,
				   const char* paramNameValueCsv,
				   const int offset,
				   const int count,
                   char* recordsBuf,
                   const unsigned sizeofRecordsBuf,
				   char** records,
                   unsigned* numRecordsRetrieved,
				   unsigned* numRecords)
	{
        m_LocalGamerIndex = localGamerIndex;
        m_QueryName = StringDuplicate(queryName);
		m_ParamNameValueCsv = StringDuplicate(paramNameValueCsv);
		m_Offset = offset;
		m_Count = count;
		m_RecordsBuf = recordsBuf;
        m_SizeofRecordsBuf = sizeofRecordsBuf;
        m_NumRecordsRetrieved = numRecordsRetrieved;
		m_Records = records;
		m_NumRecords = numRecords;

		return true;
	}

	virtual void OnCancel()
	{
		netTaskDebug("Canceled while in state %d, PendingAsync: %s", m_State, m_AsyncStatus ? (m_AsyncStatus->Pending() ? "True" : "False") : "False");
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Cancel();
		}
	}

	virtual netTaskStatus OnUpdate(int* /*resultCode*/)
	{
		netTaskStatus status = NET_TASKSTATUS_PENDING;

		switch(m_State)
		{
		case STATE_CALL:
			if(WasCanceled())
			{
				netTaskDebug("Canceled - bailing...");
				status = NET_TASKSTATUS_FAILED;
			}
			else if(Call())
			{
				m_State = STATE_CALLING;
			}
			else
			{
				status = NET_TASKSTATUS_FAILED;
			}
			break;

		case STATE_CALLING:
			if(!m_AsyncStatus->Pending())
			{
				if(m_AsyncStatus->Succeeded())
				{
					status = NET_TASKSTATUS_SUCCEEDED;
				}
				else
				{
					status = NET_TASKSTATUS_FAILED;
				}
			}
			break;
		}

		if(NET_TASKSTATUS_PENDING != status)
		{
			this->Complete(status);
		}

		return status;
	}

private:

	bool Call()
	{
		bool success = true;

		rtry
		{
			rverify(g_rlPc.IsInitialized(), catchall, );

			HRESULT hr = g_rlPc.GetTaskManager()->CreateAsyncStatus(((rgsc::IAsyncStatus**)&m_AsyncStatus));
			rverify(SUCCEEDED(hr), catchall, );
			
			// TODO: NS - if we need m_LocalGamerIndex, then we need to make a new interface in the DLL (can't change existing interfaces)
			success = g_rlPc.GetPresenceManager()->Query(/*m_LocalGamerIndex,*/
                                                         m_QueryName,
														 m_ParamNameValueCsv,
														 m_Offset,
														 m_Count,
														 m_RecordsBuf,
														 m_SizeofRecordsBuf,
														 m_Records,
                                                         m_NumRecordsRetrieved,
														 m_NumRecords,
														 m_AsyncStatus);
		}
		rcatchall
		{

		}

		return success;
	}

	void Complete(const netTaskStatus /*status*/)
	{
	}

	enum State
	{
		STATE_CALL,
		STATE_CALLING,
	};

    int m_LocalGamerIndex;
	char* m_QueryName;
	char* m_ParamNameValueCsv;
	int m_Offset;
	int m_Count;
	char* m_RecordsBuf;
	unsigned m_SizeofRecordsBuf;
	char** m_Records;
    unsigned* m_NumRecordsRetrieved;
	unsigned* m_NumRecords;

	rgsc::IAsyncStatusLatestVersion* m_AsyncStatus;
	State m_State;
};

class QueryCountTask : public netTask
{
public:

	NET_TASK_DECL(QueryCountTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	QueryCountTask()
		: m_State(STATE_CALL)
        , m_LocalGamerIndex(-1)
		, m_Count(NULL)
		, m_QueryName(NULL)
		, m_ParamNameValueCsv(NULL)
		, m_AsyncStatus(NULL)
	{

	}

	QueryCountTask::~QueryCountTask()
	{
		if(m_QueryName)
		{
			StringFree(m_QueryName);
			m_QueryName = NULL;
		}

		if(m_ParamNameValueCsv)
		{
			StringFree(m_ParamNameValueCsv);
			m_ParamNameValueCsv = NULL;
		}

		if(m_AsyncStatus)
		{
			m_AsyncStatus->Release();
			m_AsyncStatus = NULL;
		}
	}


	bool Configure(const int localGamerIndex,
                   const char* queryName,
				   const char* paramNameValueCsv,
				   unsigned* count)
	{
        m_LocalGamerIndex = localGamerIndex;
        m_QueryName = StringDuplicate(queryName);
		m_ParamNameValueCsv = StringDuplicate(paramNameValueCsv);
		m_Count = count;

		return true;
	}

	virtual void OnCancel()
	{
		netTaskDebug("Canceled while in state %d, PendingAsync: %s", m_State, m_AsyncStatus ? (m_AsyncStatus->Pending() ? "True" : "False") : "False");
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Cancel();
		}
	}

	virtual netTaskStatus OnUpdate(int* /*resultCode*/)
	{
		netTaskStatus status = NET_TASKSTATUS_PENDING;

		switch(m_State)
		{
		case STATE_CALL:
			if(WasCanceled())
			{
				netTaskDebug("Canceled - bailing...");
				status = NET_TASKSTATUS_FAILED;
			}
			else if(Call())
			{
				m_State = STATE_CALLING;
			}
			else
			{
				status = NET_TASKSTATUS_FAILED;
			}
			break;

		case STATE_CALLING:
			if(!m_AsyncStatus->Pending())
			{
				if(m_AsyncStatus->Succeeded())
				{
					status = NET_TASKSTATUS_SUCCEEDED;
				}
				else
				{
					status = NET_TASKSTATUS_FAILED;
				}
			}
			break;
		}

		if(NET_TASKSTATUS_PENDING != status)
		{
			this->Complete(status);
		}

		return status;
	}

private:

	bool Call()
	{
		bool success = true;

		rtry
		{
			rverify(g_rlPc.IsInitialized(), catchall, );

			HRESULT hr = g_rlPc.GetTaskManager()->CreateAsyncStatus(((rgsc::IAsyncStatus**)&m_AsyncStatus));
			rverify(SUCCEEDED(hr), catchall, );

			// TODO: NS - if we need m_LocalGamerIndex, then we need to make a new interface in the DLL (can't change existing interfaces)
			success = g_rlPc.GetPresenceManager()->QueryCount(/*m_LocalGamerIndex, */m_QueryName, m_ParamNameValueCsv, m_Count, m_AsyncStatus);
		}
		rcatchall
		{

		}

		return success;
	}

	void Complete(const netTaskStatus /*status*/)
	{

	}

	enum State
	{
		STATE_CALL,
		STATE_CALLING,
	};

    int m_LocalGamerIndex;
    char* m_QueryName;
	char* m_ParamNameValueCsv;
	unsigned* m_Count;

	rgsc::IAsyncStatusLatestVersion* m_AsyncStatus;
	State m_State;
};

class SetRichPresenceTask : public netTask
{
public:

	NET_TASK_DECL(SetRichPresenceTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	SetRichPresenceTask()
		: m_State(STATE_CALL)
		, m_AsyncStatus(NULL)
	{

	}

	bool Configure(const rlGamerInfo& gamerInfo,
				   const int presenceId,
				   const rlSchema& schema)
	{
		m_GamerHandle = gamerInfo.GetGamerHandle();
		m_PresenceId = presenceId;
		m_Schema = schema;

		return true;
	}

	virtual void OnCancel()
	{
		netTaskDebug("Canceled while in state %d, PendingAsync: %s", m_State, m_AsyncStatus ? (m_AsyncStatus->Pending() ? "True" : "False") : "False");
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Cancel();
		}
	}

	virtual netTaskStatus OnUpdate(int* /*resultCode*/)
	{
		netTaskStatus status = NET_TASKSTATUS_PENDING;

		switch(m_State)
		{
		case STATE_CALL:
			if(WasCanceled())
			{
				netTaskDebug("Canceled - bailing...");
				status = NET_TASKSTATUS_FAILED;
			}
			else if(Call())
			{
				m_State = STATE_CALLING;
			}
			else
			{
				status = NET_TASKSTATUS_FAILED;
			}
			break;

		case STATE_CALLING:
			if(!m_AsyncStatus->Pending())
			{
				if(m_AsyncStatus->Succeeded())
				{
					status = NET_TASKSTATUS_SUCCEEDED;
				}
				else
				{
					status = NET_TASKSTATUS_FAILED;
				}
			}
			break;
		}

		if(NET_TASKSTATUS_PENDING != status)
		{
			this->Complete(status);
		}

		return status;
	}

private:

	bool Call()
	{
		bool success = true;

		rtry
		{
			rverify(g_rlPc.IsInitialized(), catchall, );

			HRESULT hr = g_rlPc.GetTaskManager()->CreateAsyncStatus(((rgsc::IAsyncStatus**)&m_AsyncStatus));
			rverify(SUCCEEDED(hr), catchall, );

			int numAttrs = m_Schema.GetFieldCount();
			rlAssert(numAttrs < COUNTOF(m_RgscAttrs));

			for(int i = 0; i < numAttrs; ++i)
			{
				rverify(m_Schema.IsNil(i) == false, catchall, );

				// the name is the field id (i.e. the context id or the property id).
				// the determination of whether it's a property or a context is made by the bits in the id itself.
				unsigned fid = m_Schema.GetFieldId(i);
				char fieldName[32];
				formatf(fieldName, "%X", fid);
				m_RgscAttrs[i].SetName(fieldName);

				int fieldType = m_Schema.GetFieldType(i);

				if(fieldType == rlSchema::FIELDTYPE_INT32)
				{
					s32 value;
					rverify(m_Schema.GetFieldData(i, &value, sizeof(value)), catchall, );
					rverify(m_RgscAttrs[i].SetValue((s64)value), catchall, );
				}
				else if(fieldType == rlSchema::FIELDTYPE_INT64)
				{
					s64 value;
					rverify(m_Schema.GetFieldData(i, &value, sizeof(value)), catchall, );
					rverify(m_RgscAttrs[i].SetValue(value), catchall, );
				}
				else if(fieldType == rlSchema::FIELDTYPE_FLOAT)
				{
					float value;
					rverify(m_Schema.GetFieldData(i, &value, sizeof(value)), catchall, );
					rverify(m_RgscAttrs[i].SetValue((double)value), catchall, );
				}
				else if(fieldType == rlSchema::FIELDTYPE_DOUBLE)
				{
					double value;
					rverify(m_Schema.GetFieldData(i, &value, sizeof(value)), catchall, );
					rverify(m_RgscAttrs[i].SetValue(value), catchall, );
				}
				else
				{
					rlAssertf(false, "Unknown schema field type %d", fieldType);
				}
			}

			success = g_rlPc.GetPresenceManager()->SetRichPresence(m_PresenceId, m_RgscAttrs, numAttrs, m_AsyncStatus);
		}
		rcatchall
		{

		}

		return success;
	}

	void Complete(const netTaskStatus /*status*/)
	{
		if(m_AsyncStatus)
		{
			m_AsyncStatus->Release();
			m_AsyncStatus = NULL;
		}
	}

	enum State
	{
		STATE_CALL,
		STATE_CALLING,
	};

	rlGamerHandle m_GamerHandle;
	int m_PresenceId;
	rlSchema m_Schema;

	// Xbox supports a maximum of 8 contexts and properties (combined) per rich presence string
	rgsc::PresenceAttribute m_RgscAttrs[8];
	rgsc::IAsyncStatusLatestVersion* m_AsyncStatus;

	State m_State;
};

#endif

class rlPresencePublishToManyFriendsTask : public rlTaskBase
{
public:

	NET_TASK_DECL(rlPresencePublishToManyFriendsTask);
	NET_TASK_USE_CHANNEL(rline_presence);

	rlPresencePublishToManyFriendsTask()
		: m_LocalGamerindex(-1)
		, m_State(STATE_GET_ONLINE_FRIENDS)
	{
		m_Message[0] = '\0';
		m_FriendPage.Init();
	}

	~rlPresencePublishToManyFriendsTask()
	{

	}

	bool Configure(const int localGamerindex, const char* message)
	{
		m_LocalGamerindex = localGamerindex;

		bool success = true;
		rtry
		{
			rverify(message != nullptr && istrlen(message) > 0, catchall, rlError("Empty messages aren't allowed"));
			rverify(m_FriendReader.Init(0), catchall, rlError("Could not initialize friend reader"));
			safecpy(m_Message, message);
			success = true;
		}
		rcatchall
		{

		}

		return success;
	}

	virtual void Update(const unsigned )
	{
		switch(m_State)
		{
		case STATE_GET_ONLINE_FRIENDS:
			{
				int flags = rlFriendsReader::FRIENDS_ONLINE_IN_TITLE
					| rlFriendsReader::FRIENDS_FAVORITES
					| rlFriendsReader::FRIENDS_TWOWAY;

				if (m_FriendReader.Read(m_LocalGamerindex, 0, m_FriendPage.m_Friends, m_FriendPage.m_MaxFriends, &m_FriendPage.m_NumFriends, &m_TotalFriends, flags, &m_FriendStatus))
				{
					m_State = STATE_GETTING_ONLINE_FRIENDS;
				}
				else
				{
					this->Finish(FINISH_FAILED);
				}
			}
			break;
		case STATE_GETTING_ONLINE_FRIENDS:
			if (!m_FriendStatus.Pending())
			{
				if (m_FriendStatus.Succeeded())
				{
					if (m_FriendPage.m_NumFriends == 0)
					{
						this->Finish(FINISH_SUCCEEDED);
					}
					else
					{
						m_State = STATE_PUBLISH;
					}
				}
				else
				{
					this->Finish(FINISH_FAILED);
				}
			}
			break;
		case STATE_PUBLISH:
			if (BeginPublish())
			{
				this->Finish(FINISH_SUCCEEDED);
			}
			else
			{
				this->Finish(FINISH_FAILED);
			}
			break;
		}
	}

	virtual void DoCancel()
	{
		rlGetTaskManager()->CancelTask(&m_FriendStatus);
	}

private:

	bool BeginPublish()
	{
		//Collect the gamer handles of my friends.
		rlGamerHandle gamerHandles[rlFriendsPage::MAX_FRIEND_PAGE_SIZE];
		unsigned numGamerHandles = 0;

		for(int i = 0; i < (int)m_FriendPage.m_NumFriends; ++i)
		{
			// check we don't exceed the maximum message limit
			if(numGamerHandles >= RLSC_PRESENCE_MAX_MESSAGE_RECIPIENTS)
			{
				rlTaskDebug1("At maximum recipient count: %d - exiting", RLSC_PRESENCE_MAX_MESSAGE_RECIPIENTS);
				break;
			}

			if(m_FriendPage.m_Friends[i].IsPendingFriend())
			{
				//Not full friends yet
				continue;
			}

			if(!rlVerifyf(m_FriendPage.m_Friends[i].GetGamerHandle(&gamerHandles[numGamerHandles]), "Error getting friend's gamer handle"))
			{
				continue;
			}

			if(!m_FriendPage.m_Friends[i].IsOnline())
			{
				// friend is not online
				continue;
			}

			numGamerHandles++;
		}

		if(numGamerHandles > 0)
		{
			//Post a message to them, but make it look like it was
			//published to the "friends" channel.
			const rlScPresenceMessagePublish pubMsg("friends", m_Message);
			return rlPresence::PostMessage(m_LocalGamerindex, gamerHandles, numGamerHandles, pubMsg, 0);
		}
		else
		{
			return true;
		}
	}

	enum PublishState
	{
		STATE_GET_ONLINE_FRIENDS,
		STATE_GETTING_ONLINE_FRIENDS,
		STATE_PUBLISH,
	};

	rlFriendsReader m_FriendReader;
	netStatus m_FriendStatus;
	unsigned m_TotalFriends;
	rlFriendsPage m_FriendPage;

	int m_LocalGamerindex;
	PublishState m_State;
	char m_Message[RLSC_PRESENCE_MESSAGE_MAX_SIZE];
};


bool 
rlPresence::SetIntAttribute(const int localGamerIndex,
							const char* name,
							const s64 value,
							const bool SC_PRESENCE_ONLY(bNeedsCommitIfDirty))
{
#if SC_PRESENCE
	return rlScPresence::SetIntAttribute(localGamerIndex, name, value, bNeedsCommitIfDirty);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->SetIntAttribute(localGamerIndex, name, value);
	}
	return false;
#endif
}

bool 
rlPresence::SetDoubleAttribute(const int localGamerIndex,
							   const char* name,
							   const double value,
							   const bool SC_PRESENCE_ONLY(bNeedsCommitIfDirty))
{
#if SC_PRESENCE
	return rlScPresence::SetDoubleAttribute(localGamerIndex, name, value,bNeedsCommitIfDirty);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->SetDoubleAttribute(localGamerIndex, name, value);
	}
	return false;
#endif
}

bool 
rlPresence::SetStringAttribute(const int localGamerIndex,
							   const char* name,
							   const char* value,
							   const bool SC_PRESENCE_ONLY(bNeedsCommitIfDirty))
{
#if SC_PRESENCE
	return rlScPresence::SetStringAttribute(localGamerIndex, name, value, bNeedsCommitIfDirty);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->SetStringAttribute(localGamerIndex, name, value);
	}
	return false;
#endif
}

bool
rlPresence::ClearAttribute(const int SC_PRESENCE_ONLY(localGamerIndex), const char* SC_PRESENCE_ONLY(name))
{
#if SC_PRESENCE
	return rlScPresence::ClearAttribute(localGamerIndex, name);
#else
    return false;
#endif
}

bool
rlPresence::HasAttribute(const int SC_PRESENCE_ONLY(localGamerIndex), const char* SC_PRESENCE_ONLY(name))
{
#if SC_PRESENCE
	return rlScPresence::HasAttribute(localGamerIndex, name);
#else
    return false;
#endif
}

bool
rlPresence::IsAttributeSet(const int SC_PRESENCE_ONLY(localGamerIndex), const char* SC_PRESENCE_ONLY(name))
{
#if SC_PRESENCE
	return rlScPresence::IsAttributeSet(localGamerIndex, name);
#else
    return false;
#endif
}

bool 
rlPresence::GetIntAttribute(const int localGamerIndex,
							const char* name,
							s64* value)
{
#if SC_PRESENCE
	return rlScPresence::GetIntAttribute(localGamerIndex, name, value);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->GetIntAttribute(localGamerIndex, name, value);
	}
	return false;
#endif
}

bool 
rlPresence::GetDoubleAttribute(const int localGamerIndex,
							   const char* name,
							   double* value)
{
#if SC_PRESENCE
	return rlScPresence::GetDoubleAttribute(localGamerIndex, name, value);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->GetDoubleAttribute(localGamerIndex, name, value);
	}
	return false;
#endif
}

bool 
rlPresence::GetStringAttribute(const int localGamerIndex,
							   const char* name,
							   char* value,
							   const unsigned sizeofValue)
{
#if SC_PRESENCE
	return rlScPresence::GetStringAttribute(localGamerIndex, name, value, sizeofValue);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->GetStringAttribute(localGamerIndex, name, value, sizeofValue);
	}
	return false;
#endif
}

bool 
rlPresence::GetAttributesForGamer(const int localGamerIndex,
                                  const rlGamerHandle& gamerHandle,
								  rlScPresenceAttribute* attrs,
								  const unsigned numAttrs,
								  netStatus* status)
{
#if SC_PRESENCE
	return rlScPresence::GetAttributesForGamer(localGamerIndex, gamerHandle, attrs, numAttrs, status);
#else
	bool success = false;

	GetAttributesForGamerTask* task;
	if(!netTask::Create(&task, status)
		|| !task->Configure(localGamerIndex, gamerHandle, attrs, numAttrs)
		|| !netTask::Run(task))
	{
		netTask::Destroy(task);
	}
	else
	{
		success = true;
	}

	return success;
#endif
}

bool
rlPresence::GetAttributesForGamers(const int SC_PRESENCE_ONLY(localGamerIndex),
									const rlGamerHandle* SC_PRESENCE_ONLY(gamerHandles),
									const unsigned SC_PRESENCE_ONLY(numGamerHandles),
									rlScPresenceAttribute* SC_PRESENCE_ONLY(attrs)[],
									const unsigned SC_PRESENCE_ONLY(numAttrs),
									netStatus* SC_PRESENCE_ONLY(status))
{
	// Note: this function is used by the Social Club DLL on GTA V PC. It will return false on PC if called by the game directly.
#if SC_PRESENCE
	return rlScPresence::GetAttributesForGamers(localGamerIndex, gamerHandles, numGamerHandles, attrs, numAttrs, status);
#else
	return false;
#endif
}

bool
rlPresence::Subscribe(const int localGamerIndex,
					  const char** channels,
					  const unsigned numChannels)
{
#if SC_PRESENCE
    return rlScPresence::Subscribe(localGamerIndex, channels, numChannels);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->Subscribe(localGamerIndex, channels, numChannels);
	}
	return false;
#endif
}

bool
rlPresence::Unsubscribe(const int localGamerIndex,
						const char** channels,
						const unsigned numChannels)
{
#if SC_PRESENCE
    return rlScPresence::Unsubscribe(localGamerIndex, channels, numChannels);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->Unsubscribe(localGamerIndex, channels, numChannels);
	}
	return false;
#endif
}

bool
rlPresence::UnsubscribeAll(const int localGamerIndex)
{
#if SC_PRESENCE
    return rlScPresence::UnsubscribeAll(localGamerIndex);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		return g_rlPc.GetPresenceManager()->UnsubscribeAll(localGamerIndex);
	}
	return false;
#endif
}

bool
rlPresence::Publish(const int localGamerIndex,
					const char** channels,
					const unsigned numChannels,
					const char* filterName,
					const char* paramNameValueCsv,
					const char* message)
{
#if SC_PRESENCE
    return rlScPresence::Publish(localGamerIndex,
                                channels,
                                numChannels,
                                filterName,
                                paramNameValueCsv,
                                message);
#else
	if (g_rlPc.GetPresenceManager() != NULL)
	{
		rgsc::PresenceMessage rgscMessage;
		rgscMessage.SetTimestamp(0);
		rgscMessage.SetContents(message);

		return g_rlPc.GetPresenceManager()->Publish(localGamerIndex,
													channels,
													numChannels,
													filterName,
													paramNameValueCsv,
													&rgscMessage);
	}
	return false;
#endif
}

bool rlPresence::PublishToManyFriends(const int localGamerIndex, const char* message)
{
	rlDebug("Local gamer %d publishing '%s' to many friends", localGamerIndex, message);

	bool success = false;

	rlFireAndForgetTask<rlPresencePublishToManyFriendsTask>* task = NULL;

	rtry
	{
		rverify(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex), catchall, rlError("Invalid gamer index: %d", localGamerIndex));
		rverify(rlGetTaskManager()->CreateTask(&task), catchall, rlError("Error creating task"));
		rverify(rlTaskBase::Configure(task, localGamerIndex, message, &task->m_Status), catchall, rlError("Error configuring task"));
		rverify(rlGetTaskManager()->AddParallelTask(task), catchall, rlError("Error queuing task"));

		success = true;
	}
	rcatchall
	{
		if(task)
		{
			rlGetTaskManager()->DestroyTask(task);
		}
	}

	return success;
}

bool
rlPresence::PublishToCrew(const int localGamerIndex,
					        const char* message)
{
    rlDebug("Local gamer %d publishing '%s' to crew",
            localGamerIndex,
            message);

    const rlClanDesc& clanDesc = rlClan::GetPrimaryClan(localGamerIndex);
    if(rlVerifyf(clanDesc.IsValid(),
                "Can't publish to crew - no primary crew available"))
    {
        char channel[RLSC_PRESENCE_CHANNEL_NAME_MAX_SIZE];
        const char* channels[] = {channel};
        rlClan::CreateMessageChannelName(channel, clanDesc.m_Id);

        return rlPresence::Publish(localGamerIndex, channels, 1, NULL, NULL, message);
    }

	return false;
}

bool 
rlPresence::PostMessage(const int localGamerindex,
						const rlGamerHandle* recipients,
						const unsigned numRecipients,
                        const char* message,
                        const unsigned ttlSeconds)
{
#if SC_PRESENCE
	return rlScPresence::PostMessage(localGamerindex, recipients, numRecipients, message, ttlSeconds);
#else
	bool success = false;

	netFireAndForgetTask<PostMessageTask>* task;
	if(!netTask::Create(&task)
		|| !task->Configure(localGamerindex, recipients, numRecipients, message, ttlSeconds)
		|| !netTask::Run(task))
	{
		netTask::Destroy(task);
	}
	else
	{
		success = true;
	}

	return success;
#endif
}

bool 
rlPresence::Query(const int localGamerIndex,
                    const char* queryName,
                    const char* paramNameValueCsv,
                    const int offset,
                    const int count,
                    char* recordsBuf,
                    const unsigned sizeofRecordsBuf,
                    char** records,
                    unsigned* numRecordsRetrieved,
                    unsigned* numRecords,
                    netStatus* status)
{
#if SC_PRESENCE
	return rlScPresence::Query(localGamerIndex,
                                queryName,
                                paramNameValueCsv,
                                offset,
                                count,
                                recordsBuf,
                                sizeofRecordsBuf,
                                records,
                                numRecordsRetrieved,
                                numRecords,
                                status);
#else
	bool success = false;

	QueryTask* task;
	if(!netTask::Create(&task, status)
		|| !task->Configure(localGamerIndex,
                            queryName,
                            paramNameValueCsv,
                            offset,
                            count,
                            recordsBuf,
                            sizeofRecordsBuf,
                            records,
                            numRecordsRetrieved,
                            numRecords)
		|| !netTask::Run(task))
	{
		netTask::Destroy(task);
	}
	else
	{
		success = true;
	}

	return success;
#endif
}

bool 
rlPresence::QueryCount(const int localGamerIndex,
                       const char* queryName,
					   const char* paramNameValueCsv,
					   unsigned* count,
					   netStatus* status)
{
#if SC_PRESENCE
	return rlScPresence::QueryCount(localGamerIndex,
                                    queryName,
                                    paramNameValueCsv,
                                    count,
                                    status);
#else
	bool success = false;

	QueryCountTask* task;
	if(!netTask::Create(&task, status)
		|| !task->Configure(localGamerIndex, queryName, paramNameValueCsv, count)
		|| !netTask::Run(task))
	{
		netTask::Destroy(task);
	}
	else
	{
		success = true;
	}

	return success;
#endif
}

void
rlPresence::CancelQuery(netStatus* status)
{
#if SC_PRESENCE
	rlScPresence::Cancel(status);
#else
    netTask::Cancel(status);
#endif
}

void rlPresence::SetActingUserIndex(const int localGamerIndex)
{
    if(m_ActingUserIndex != localGamerIndex)
    {
        rlDebug("SetActingUserIndex :: Was: %d, Now: %d", m_ActingUserIndex, localGamerIndex);
        m_ActingUserIndex = localGamerIndex;
    }
}

rlGamerInfo* rlPresence::GetActingGamerInfoPtr()
{
	return &m_ActingGamerInfo;
}

const rlGamerInfo& rlPresence::GetActingGamerInfo()
{
	return m_ActingGamerInfo;
}

bool rlPresence::IsActingGamerInfoValid()
{
	return m_ActingGamerInfo.IsValid();
}

void rlPresence::SetActingGamerInfo(const rlGamerInfo& gamerInfo)
{
	rlDebug("SetActingGamerInfo :: Index: %d -> %d, SignedIn: %s -> %s, Online: %s -> %s, Name: %s -> %s",
		m_ActingGamerInfo.GetLocalIndex(), gamerInfo.GetLocalIndex(),
		(m_ActingGamerInfo.IsLocal() && m_ActingGamerInfo.IsSignedIn()) ? "True" : "False", (gamerInfo.IsLocal() && gamerInfo.IsSignedIn()) ? "True" : "False",
		(m_ActingGamerInfo.IsLocal() && m_ActingGamerInfo.IsOnline()) ? "True" : "False", (gamerInfo.IsLocal() && gamerInfo.IsOnline()) ? "True" : "False",
		(m_ActingGamerInfo.IsLocal() && m_ActingGamerInfo.IsSignedIn()) ? m_ActingGamerInfo.GetName() : "None", (gamerInfo.IsLocal() && gamerInfo.IsSignedIn()) ? gamerInfo.GetName() : "None");

	m_ActingGamerInfo = gamerInfo;
}

void rlPresence::ClearActingGamerInfo()
{
	if(m_ActingGamerInfo.IsValid())
	{
		rlDebug("ClearActingGamerInfo");
		m_ActingGamerInfo.Clear();
	}
}

void 
rlPresence::SetPresenceMessageDelayTimeMs(const unsigned delayTimeMs)
{
	if(m_PresenceMessageDelayTimeMs != delayTimeMs)
	{
		rlDebug3("SetPresenceMessageDelayTimeMs :: Setting to %dms", delayTimeMs);
		m_PresenceMessageDelayTimeMs = delayTimeMs;
	}
}

void  
rlPresence::SetPresenceMessageMaxToRetrieve(const unsigned maxToRetrieve)
{
	if(m_PresenceMessageMaxToRetrieve != maxToRetrieve)
	{
		rlDebug3("SetPresenceMessageMaxToRetrieve :: Setting to %d", maxToRetrieve);
		m_PresenceMessageMaxToRetrieve = maxToRetrieve;
	}
}

#if FAKE_DROP_LATENCY
void
rlPresence::FakeOfflineDueToUplink(bool fakeOffline)
{
    if (internal::s_FakeUplinkOffline != fakeOffline)
    {
        internal::s_FakeUplinkOffline = fakeOffline;
        rlDebug1("Chaning s_FakeUplinkOffline to %s", fakeOffline ? "true" : "false");

        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
        {
            if (m_GamerPresences[i].m_IsSignedIn)
            {
                m_GamerPresences[i].m_ProcessProfileChange = true;
            }
        }
    }
}
#else
void
rlPresence::FakeOfflineDueToUplink(bool)
{
}
#endif

//private:

#if __RGSC_DLL
bool rlPresence::ShouldDelayPresenceRefreshDueToReload()
{
	if (GetRgscConcreteInstance()->_GetUiInterface()->IsReloadingUi())
	{
		// If we're expecting to sign back in and we're not signed in, wait.
		// The signin can fail and in that case the page will load, IsReloadingUi will return to false
		if (GetRgscConcreteInstance()->_GetUiInterface()->IsReloadingUiSignedIn() && 
			!GetRgscConcreteInstance()->_GetProfileManager()->IsSignedInInternal())
		{
			return true;
		}

		// If we're expecting to sign back in online and we're not signed online, wait.
		// The signin can fail and in that case the page will load, IsReloadingUi will return to false
		if (GetRgscConcreteInstance()->_GetUiInterface()->IsReloadingUiOnline() && 
			!GetRgscConcreteInstance()->_GetProfileManager()->IsOnlineInternal())
		{
			return true;
		}
	}

	return false;
}
#endif

void 
rlPresence::RefreshNetworkStatus(const int localGamerIndex)
{
	rlAssert(localGamerIndex >= 0 && localGamerIndex < RL_MAX_LOCAL_GAMERS);

	GamerPresence& gp = m_GamerPresences[localGamerIndex];

	netPeerAddress peerAddr;
	rlPeerAddress::GetLocalPeerAddress(localGamerIndex, &peerAddr);

	const netPeerAddress& oldPeerAddr = gp.m_GamerInfo.GetPeerInfo().HasValidPeerAddress() ?
										gp.m_GamerInfo.GetPeerInfo().GetPeerAddress() :
										netPeerAddress();

	const bool peerAddrChanged = (oldPeerAddr.IsValid() != peerAddr.IsValid())	// only 1 peer address is valid OR
							  || (oldPeerAddr.GetRelayAddress() != peerAddr.GetRelayAddress()) // relay address has changed OR
							  || (oldPeerAddr.GetPrivateAddress() != peerAddr.GetPrivateAddress())	// private address has changed OR
							  || (oldPeerAddr.GetNatType() != peerAddr.GetNatType()) // NAT type has changed OR
							  || ((oldPeerAddr.GetGamerHandle().IsValid() || peerAddr.GetGamerHandle().IsValid()) // either gamer handle is valid, but differs from the other.
										 && oldPeerAddr.GetGamerHandle() != peerAddr.GetGamerHandle());

	const bool hasNetwork = rlPeerAddress::HasNetwork();
	const bool hadNetwork = gp.m_HasNetwork;
	const bool isSignedIn = gp.m_IsSignedIn;

	const bool networkStatusChanged = (hasNetwork != hadNetwork) || peerAddrChanged;

	if(isSignedIn)
	{
		gp.m_HasNetwork = hasNetwork;
	}

	// send an event when our network connection changes. If a player signs in
	// before they have an IP, the game will have stored the gamer's peer address
	// as 0:0:0:0. If they then plug in a network cable, no new event will be
	// generated when the network hardware becomes available and their address
	// won't get updated. If they then try to host a LAN game, it will look like
	// everything is working correctly, but no one will be able to find the game.
	if(networkStatusChanged && isSignedIn)
	{
		rlPeerInfo::GetLocalPeerInfo(&gp.m_GamerInfo.m_PeerInfo, localGamerIndex);

		if(hasNetwork != hadNetwork)
		{
			rlDebug("Gamer:\"%s\" [%d] - Network status changed: HadNetwork: %s",
					gp.m_GamerInfo.GetName(),
					gp.m_GamerInfo.GetLocalIndex(),
					hadNetwork ? "True" : "False");
		}

		if(peerAddrChanged)
		{
#if !__NO_OUTPUT
			char oldHandleString[RL_MAX_GAMER_HANDLE_CHARS]; oldHandleString[0] = '\0';
			if(oldPeerAddr.GetGamerHandle().IsValid()) oldPeerAddr.GetGamerHandle().ToString(oldHandleString, RL_MAX_GAMER_HANDLE_CHARS);
			char handleString[RL_MAX_GAMER_HANDLE_CHARS]; handleString[0] = '\0';
			if(peerAddr.GetGamerHandle().IsValid()) peerAddr.GetGamerHandle().ToString(handleString, RL_MAX_GAMER_HANDLE_CHARS);

			rlDebug("Gamer:\"%s\" [%d] - Network status changed: PeerAddrValid: %s -> %s, RelayAddress: %s -> %s, PrivAddr: %s -> %s, Nat: %d -> %d, Handle: %s -> %s",
					gp.m_GamerInfo.GetName(),
					gp.m_GamerInfo.GetLocalIndex(),
					oldPeerAddr.IsValid() ? "True" : "False", peerAddr.IsValid() ? "True" : "False",
					oldPeerAddr.GetRelayAddress().ToString(), peerAddr.GetRelayAddress().ToString(),
					oldPeerAddr.GetPrivateAddress().ToString(), peerAddr.GetPrivateAddress().ToString(),
					oldPeerAddr.GetNatType(), peerAddr.GetNatType(), 
					oldHandleString, handleString);
#endif

			//Also fire specific event for peer address change
 			rlPresenceEventPeerAddressChanged e(gp.m_GamerInfo);
 			m_Delegator.Dispatch(&e);
		}
		
		rlPresenceEventNetworkStatusChanged e(gp.m_GamerInfo);
		m_Delegator.Dispatch(&e);
	}
}

void
rlPresence::RefreshSigninState(const int gamerIdx)
{
#if __RGSC_DLL
	// If we're reloading the SCUI
	if (ShouldDelayPresenceRefreshDueToReload())
		return;
#endif

    rlAssert(gamerIdx >= 0 && gamerIdx < RL_MAX_LOCAL_GAMERS);

    GamerPresence& gp = m_GamerPresences[gamerIdx];

    rlGamerHandle hGamer;
    if(!internal::GetGamerHandle(gamerIdx, &hGamer))
    {
        hGamer.Clear();
    }

#if RSG_ORBIS
	int userServiceId = g_rlNp.GetUserServiceId(gamerIdx);
#endif // RSG_ORBIS

    //A new profile signed in to the same slot where another
    //profile was already signed in.  This can occur when the Xbox sign
    //in UI is displayed for a slot where a profile is currently signed
    //in and a different profile is then selected for that slot.
	const bool replacedGamer =
		(gp.m_GamerInfo.IsValid()
		&& hGamer.IsValid()
#if RSG_ORBIS
		&& gp.m_UserServiceId != userServiceId);
#else
		&& gp.m_GamerInfo.GetGamerHandle() != hGamer);
#endif

#if !__NO_OUTPUT
    if(replacedGamer)
    {
        char oldHandleStr[RL_MAX_GAMER_HANDLE_CHARS];
        gp.m_GamerInfo.GetGamerHandle().ToString(oldHandleStr);

        char newHandleStr[RL_MAX_GAMER_HANDLE_CHARS];
        hGamer.ToString(newHandleStr);
        
        rlDebug("Gamer[localIndex=%d][handle=%s] REPLACED BY [handle=%s]", 
            gamerIdx,
            oldHandleStr,
            newHandleStr);
    }
#endif

#if __RGSC_DLL
	// don't sign into presence when we're in the launcher to 
	// prevent duplicate notifications (launcher and game)
	const bool disablePresence = !GetRgscConcreteInstance()->IsPresenceSupported();
#elif SC_PRESENCE
	const bool disablePresence = false;
#endif

    const bool wasSignedIn = gp.m_IsSignedIn;
    const bool isSignedIn = internal::IsSignedIn(gamerIdx);
    const bool wasOnline = gp.m_IsOnline;
    const bool isOnline = internal::IsOnline(gamerIdx);
    const bool profileChanged = gp.m_ProcessProfileChange && !replacedGamer;

    const bool signedIn = isSignedIn && (!wasSignedIn || replacedGamer);
    const bool signedOut = (!isSignedIn && wasSignedIn) || replacedGamer;
    const bool signedOnline = isOnline && (!wasOnline || replacedGamer);
	const bool signedOffline = (!isOnline && wasOnline) || replacedGamer;

    //Save the current gamer info in case we need to send it
    //in a sign-out notification, at which time info.m_GamerInfo
    //will be reset.
    rlGamerInfo oldGamerInfo = gp.m_GamerInfo;
    rlGamerInfo newGamerInfo;

    oldGamerInfo.m_IsSignedIn = (isSignedIn && !replacedGamer);
    oldGamerInfo.m_IsOnline = (isOnline && !replacedGamer);

	// Keep the sign-in flags for later use before they're reset
	const unsigned signInChangeFlags = gp.m_SignInChangeFlags;

    if(signedOut)
    {
        gp.Clear();
    }

    //If someone signed out, someone could have also
    //signed in simultaneously into the same slot, so
    //after checking signedOut also check all of the following.
    if(signedIn
        || signedOnline
        //signedOffline is superseded by signedOut, which is handled above
        || (signedOffline && !signedOut)
        || profileChanged)
    {
        if(rlVerifyf(PopulateGamerInfo(gamerIdx, &newGamerInfo),
                    "Error populating gamer info for gamer:%d", gamerIdx))
        {
#if RSG_ORBIS
			// If we're not changing profiles, try to persist any old valid account ID.
			if (!replacedGamer && oldGamerInfo.IsValid() && g_rlNp.GetNpAccountId(gamerIdx) == RL_INVALID_NP_ACCOUNT_ID)
			{
				newGamerInfo.m_GamerHandle.m_Data.m_NpData.m_AccountId = oldGamerInfo.GetGamerHandle().GetNpAccountId();
			}

			gp.m_UserServiceId = userServiceId;
#endif // RSG_ORBIS

            gp.m_GamerInfo = newGamerInfo;
            gp.m_IsSignedIn = isSignedIn;
            gp.m_IsOnline = isOnline;

            gp.m_ProcessProfileChange = false;
            gp.m_RetrySignin = false;
        }
        else
        {
            gp.m_RetrySignin = true;
            gp.m_RetrySigninTimeout = sysTimer::GetSystemMsTime() + GamerPresence::SIGNIN_RETRY_INTERVAL;
        }
    }
    else
    {
        gp.m_ProcessProfileChange = false;
        gp.m_RetrySignin = false;
    }

	//Send the profile changed event only if the gamer is still signed in.
    if(profileChanged && isSignedIn && newGamerInfo.IsValid())
    {
        rlDebug("Gamer:\"%s\" (was:\"%s\") [%d] - profile changed",
                newGamerInfo.GetName(),
                oldGamerInfo.GetName(),
                newGamerInfo.GetLocalIndex());

        rlPresenceEventProfileChanged e(gp.m_GamerInfo);
        m_Delegator.Dispatch(&e);
    }

    if(signedOut)
    {
        rlAssert(oldGamerInfo.IsValid());
        rlAssert(!oldGamerInfo.IsOnline());
        rlAssert(!oldGamerInfo.IsSignedIn());

#if !__NO_OUTPUT
        char handleStr[RL_MAX_GAMER_HANDLE_CHARS];
        oldGamerInfo.GetGamerHandle().ToString(handleStr);
        rlDebug("Gamer:\"%s\"[localIndex=%d][handle=%s] SIGNED OUT", 
                  oldGamerInfo.GetName(),
                  oldGamerInfo.GetLocalIndex(),
                  handleStr);
#endif

        rlPresenceEventSigninStatusChanged e(
			oldGamerInfo,
			rlPresenceEventSigninStatusChanged::SIGNED_OUT,
			signInChangeFlags);

        m_Delegator.Dispatch(&e);
    }
    else if(signedOffline && newGamerInfo.IsValid())
    {
        rlAssert(oldGamerInfo.IsValid());
        rlAssert(!oldGamerInfo.IsOnline());

#if !__NO_OUTPUT
		char handleStr[RL_MAX_GAMER_HANDLE_CHARS];
        oldGamerInfo.GetGamerHandle().ToString(handleStr);
        rlDebug("Gamer:\"%s\"[localIndex=%d][handle=%s] SIGNED OFFLINE", 
                  oldGamerInfo.GetName(),
                  oldGamerInfo.GetLocalIndex(),
                  handleStr);
#endif

        rlPresenceEventSigninStatusChanged e(
			oldGamerInfo,
			rlPresenceEventSigninStatusChanged::SIGNED_OFFLINE,
			signInChangeFlags);

        m_Delegator.Dispatch(&e);
    }

	if(signedOut || signedOffline)
	{
#if SC_PRESENCE
		const bool isDuplicateSignin = (signInChangeFlags & rlSignInStatusChangeFlags::SIGN_IN_CHANGE_DUPLICATE_LOGIN) != 0;
		if(disablePresence == false)
		{
			//Stop updating our SC presence information
			if(signedOut)
			{
				rlScPresence::SignOut(oldGamerInfo.GetLocalIndex(), isDuplicateSignin);
			}
			else if(signedOffline)
			{
				rlScPresence::SignOffline(oldGamerInfo.GetLocalIndex(), isDuplicateSignin);
			}
		}
#if __RGSC_DLL
		u32 state = 0;
		if(signedOut)
		{
			rlSocialClub::ClearLogin(0);
			state |= rgsc::STATE_SIGNED_OUT;
		}
		if(signedOffline)
		{
			state |= rgsc::STATE_SIGNED_OFFLINE;
		}
		if(isDuplicateSignin)
		{
			state |= rgsc::STATE_KICKED_BY_DUPLICATE_SIGN_IN;
		}
		GetRgscConcreteInstance()->HandleNotification(
			rgsc::NOTIFY_SIGN_IN_STATE_CHANGED,
			&state);
#endif
#endif
	}

    if(signedOnline && newGamerInfo.IsValid())
    {
        rlAssert(newGamerInfo.IsSignedIn());
        rlAssert(newGamerInfo.IsOnline());

#if !__NO_OUTPUT
        char handleStr[RL_MAX_GAMER_HANDLE_CHARS];
        newGamerInfo.GetGamerHandle().ToString(handleStr);
        rlDebug("Gamer:\"%s\"[localIndex=%d][handle=%s] SIGNED ONLINE", 
                  newGamerInfo.GetName(),
                  newGamerInfo.GetLocalIndex(),
                  handleStr);
#endif

#if !__SKIPDEBUGAUTH
        //Make sure we're preventing people from seeing secret
        //stuff in our games during dev.
        rlGamerInfo::CheckDevPrivacyPrivilegesAndBail(newGamerInfo.GetLocalIndex());
#endif

#if RSG_NP
		{
			int err = g_rlNp.GetAge(gamerIdx, &gp.m_Age);
			if(err < 0)
			{
				rlNpError("Error getting age for gamer", err);
				gp.m_Age = -1;
			}

			rlDebug("Age for gamer:%d is %d", newGamerInfo.GetLocalIndex(), gp.m_Age);
		}
#endif

        rlPresenceEventSigninStatusChanged e(
			newGamerInfo,
			rlPresenceEventSigninStatusChanged::SIGNED_ONLINE,
			rlSignInStatusChangeFlags::NONE);

        m_Delegator.Dispatch(&e);
    }
    else if(signedIn && newGamerInfo.IsValid())
    {
        rlAssert(newGamerInfo.IsSignedIn());

#if !__NO_OUTPUT
        char handleStr[RL_MAX_GAMER_HANDLE_CHARS];
        newGamerInfo.GetGamerHandle().ToString(handleStr);
        rlDebug("Gamer:\"%s\"[localIndex=%d][handle=%s] SIGNED IN", 
                  newGamerInfo.GetName(),
                  newGamerInfo.GetLocalIndex(),
                  handleStr);
#endif

        rlPresenceEventSigninStatusChanged e(
			newGamerInfo,
			rlPresenceEventSigninStatusChanged::SIGNED_IN,
			rlSignInStatusChangeFlags::NONE);

        m_Delegator.Dispatch(&e);
    }

#if SC_PRESENCE
    if(signedIn && (disablePresence == false))
    {
        //Start updating our SC presence information
        rlScPresence::SignIn(gamerIdx);
	}
#endif

	gp.m_SignInChangeFlags = rlSignInStatusChangeFlags::NONE;
}

#if SC_PRESENCE
void
rlPresence::UpdateMessagesSync(const int gamerIdx)
{
    GamerPresence& gp = m_GamerPresences[gamerIdx];

    gp.m_RetrieveMessagesTimer.Update();

    //Check the status of reading messages
    if(gp.m_RetrievingMessages)
    {
        gp.m_RetrievingMessages = gp.m_GetMessagesStatus.Pending();
        if(!gp.m_RetrievingMessages)
        {
            if(gp.m_GetMessagesStatus.Succeeded())
            {
                for(int i = 0; i < (int)gp.m_MsgIter.GetNumMessagesRetrieved(); ++i)
                {
                    const char* msg;
                    u64 ts;
					rlGamerHandle senderGamerHandle;
					rlScPresenceMessageSender::Source source;

                    if(!gp.m_MsgIter.NextMessage(&msg, &ts, &senderGamerHandle, &source))
                    {
                        continue;
                    }

                    rlScPresenceMessage scPresMsg(msg, ts);
					rlScPresenceMessageSender scPresMsgSender(senderGamerHandle, source);

                    OnScMessage(gp.m_GamerInfo.GetLocalIndex(), scPresMsg, scPresMsgSender);
                }

                //Are there still more messages in the queue?
                if(gp.m_MsgIter.GetTotalMessages() > 0
                    && !gp.m_RetrieveMessagesTimer.IsRunning())
                {
                    gp.m_RetrieveMessagesTimer.InitMilliseconds(m_PresenceMessageDelayTimeMs);
                    rlDebug("Need to retrieve more messages - will retrieve in %0.2f seconds",
                            gp.m_RetrieveMessagesTimer.GetMillisecondsUntilTimeout()/1000.0f);
                }
            }

            gp.m_MsgIter.ReleaseResources();
        }
    }
    //Do we need to read messages?
    else if(gp.m_RetrieveMessagesTimer.IsTimedOut())
    {
        gp.m_RetrieveMessagesTimer.Clear();

        if(gp.m_IsOnline
            && rlRos::GetCredentials(gamerIdx).IsValid())
        {
            gp.m_RetrievingMessages =
                rlScPresence::GetMessages(gp.m_GamerInfo.GetLocalIndex(),
                                        m_PresenceMessageMaxToRetrieve,
                                        &gp.m_MsgIter,
                                        &gp.m_GetMessagesStatus);

            rlAssert(gp.m_RetrievingMessages);
        }
    }
}
#endif

void
rlPresence::UpdateScPresence(const int localGamerIndex)
{
#if !__RGSC_DLL
	rtry
    {
        rlGamerInfo gamerInfo;

        rverify(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex), catchall,);

		rcheck(rlPresence::IsOnline(localGamerIndex),catchall,);

        rcheck(rlPresence::IsSignedIn(localGamerIndex),catchall,);
		
		rverify(rlPresence::GetGamerInfo(localGamerIndex, &gamerInfo),
                catchall,
                rlError("Error retrieving gamer info for gamer: %d", localGamerIndex));
		
		rcheck(gamerInfo.IsSignedIn(), catchall, 
			rlError("rlPresence says signed in, but gamer info doesn't"));

        const rlRosCredentials& creds = rlRos::GetCredentials(localGamerIndex);

        rcheck(creds.IsValid(), catchall,);

        //Set the ScNickname presence attribute
        const char* nick = "";

        if(creds.GetRockstarId() > 0)
        {
            nick = creds.GetRockstarAccount().m_Nickname;
        }

        //Set the nickname, even if it's blank, so it will
        //always have a value.
        rlPresenceAttributeHelper::SetScNickname(localGamerIndex, nick);

        rlPresenceAttributeHelper::SetGamerTag(localGamerIndex, gamerInfo.GetName());

        //Send our peer address up to the presence server
		if(gamerInfo.GetPeerInfo().GetPeerAddress().GetGamerHandle().IsValid())
		{
			rlPresenceAttributeHelper::SetPeerAddress(localGamerIndex, gamerInfo.GetPeerInfo().GetPeerAddress());
		}

		// set the matchmaking matchIds of the sessions we're hosting
		char hostedMatchIds[RLSC_PRESENCE_STRING_MAX_SIZE];
		rlSessionManager::GetHostedMatchIds(localGamerIndex, hostedMatchIds, sizeof(hostedMatchIds));
		rlPresenceAttributeHelper::SetHostedSessionMatchIds(localGamerIndex, hostedMatchIds);
    }
    rcatchall
    {
    }
#else
	localGamerIndex;
#endif  //!__RGSC_DLL
}

rlPresence::GamerPresence*
rlPresence::GetGamerPresence(const rlGamerHandle& gh)
{
    const int localIndex = GetLocalIndex(gh);
    return RL_IS_VALID_LOCAL_GAMER_INDEX(localIndex) ? &m_GamerPresences[localIndex] : NULL;
}

void
rlPresence::OnRelayEvent(const netRelayEvent& event)
{
    rtry
    {
		if((NET_RELAYEVENT_ADDRESS_OBTAINED == event.m_EventType)
			|| (NET_RELAYEVENT_ADDRESS_CHANGED == event.m_EventType))
		{
			for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
			{
				rlPresence::RefreshSigninState(i);
				rlPresence::RefreshNetworkStatus(i);
			}
			return;
		}

        rverify(NET_RELAYEVENT_MESSAGE_RECEIVED == event.m_EventType,
                catchall,);

#if SC_PRESENCE

        const netRelayEventMessageReceived& msgRecvd =
            (const netRelayEventMessageReceived&) event;

#if !__NO_OUTPUT
        char msg[1024];
        unsigned msgLen = sizeof(msg)-1;
        if(msgLen > msgRecvd.m_Length) msgLen = msgRecvd.m_Length;
        sysMemCpy(msg, msgRecvd.m_Message, msgLen);
        msg[msgLen] = '\0';
        rlDebug("Received message: %s", msg);
#endif

        RsonReader rr(msgRecvd.m_Message, msgRecvd.m_Length);

        if(rr.CheckName("ros.msgs"))
        {
            //The message is in the following format:
            //"ros.msgs":{"<gamerhandle>":<numMsgs>,"<gamerhandle>":<numMsgs>,...}
            rlGamerHandle gamerHandle;

            //Get the gamer list
            rverify(rr.GetFirstMember(&rr),
                    catchall,
                    rlError("Failed to read gamer list from message: \"%s\"",
                            msgRecvd.m_Message));

            bool done = false;
            for(int i = 0; i < RL_MAX_LOCAL_GAMERS && !done; ++i, done = !rr.GetNextSibling(&rr))
            {
                char ghStr[64];
                int numMsgs;
                GamerPresence* gp;

                if(!rlVerifyf(rr.GetName(ghStr),
                                "Failed to parse gamer handle %d from message: \"%s\"",
                                i,
                                msgRecvd.m_Message))
                {
                    continue;
                }

                if(!rlVerifyf(gamerHandle.FromString(ghStr),
                                "Failed to parse gamer handle %d from message: \"%s\"",
                                i,
                                msgRecvd.m_Message))
                {
                    continue;
                }

                if(!rlVerifyf(rr.AsInt(numMsgs),
                                "Failed to parse message count %d from message: \"%s\"",
                                i,
                                msgRecvd.m_Message))
                {
                    continue;
                }

                if(NULL == (gp = GetGamerPresence(gamerHandle)))
                {
                    rlWarning("Received a message for an unknown gamer");
                    continue;
                }

                if(gp->m_RetrievingMessages)
                {
                    rlDebug("Message retrieval is already in flight");
                    continue;
                }

                if(!gp->m_RetrieveMessagesTimer.IsRunning())
                {
                    gp->m_RetrieveMessagesTimer.InitMilliseconds(m_PresenceMessageDelayTimeMs);
                }

                rlDebug("Will retrieve messages in %0.2f seconds",
                        gp->m_RetrieveMessagesTimer.GetMillisecondsUntilTimeout()/1000.0f);
            }
        }
#endif
    }
    rcatchall
    {
    }
}

void
rlPresence::OnNatDetectorEvent(const netNatEvent& evt)
{
	switch(evt.GetId())
	{
	case NET_NAT_EVENT_NAT_INFO_CHANGED:
		{
			for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
			{
				rlPresence::RefreshSigninState(i);
				rlPresence::RefreshNetworkStatus(i);
			}
		}
		break;
	}
}

void
rlPresence::OnNetHardwareEvent(const netHardwareEvent& evt)
{
	switch(evt.GetId())
	{
	case NET_HARDWARE_EVENT_NETWORK_AVAILABILITY_CHANGED:
		{
			for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
			{
				rlPresence::RefreshSigninState(i);
				rlPresence::RefreshNetworkStatus(i);
			}
		}
		break;
	case NET_HARDWARE_EVENT_NETWORK_SOCKET_BINDING_CHANGED:
		{
			// our peer address contains our private address, update the peer address when the socket is bound
			if(g_rlSkt != nullptr)
			{
				const netHardwareEventSocketBindingChanged& bindingChanged =
					(const netHardwareEventSocketBindingChanged&)evt;

				if(bindingChanged.m_Skt->GetId() == g_rlSkt->GetId())
				{
					for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
					{
						rlPresence::RefreshSigninState(i);
						rlPresence::RefreshNetworkStatus(i);
					}
				}
			}
		}
		break;
	}
}

void 
rlPresence::OnRosEvent(const rlRosEvent& evt)
{
	switch(evt.GetId())
	{
	case RLROS_EVENT_RETRIEVED_GEOLOC_INFO:
		break;

	case RLROS_EVENT_ONLINE_STATUS_CHANGED:
	case RLROS_EVENT_LINK_CHANGED:
		{
			// credentials are a condition for updating presence so refresh everything
			for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i) 
			{
				rlPresence::RefreshSigninState(i);
				rlPresence::RefreshNetworkStatus(i);
			}
		}
		break;
		
	default:
		break;
	}
}

void
rlPresence::OnScMessage(const int localGamerIndex,
                        const rlScPresenceMessage& message,
						const rlScPresenceMessageSender& sender)
{
    rlDebug("Received message for gamer: %d from %s, msg: %s",
            localGamerIndex,
			(sender.m_Source == rlScPresenceMessageSender::Source::SENDER_PLAYER) ?
			sender.m_GamerHandle.ToString() :
			rlScPresenceMessageSender::GetSourceName(sender.m_Source),
			message.m_Contents);

    if(!rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                    "Invalid gamer index: %d", localGamerIndex))
    {
        return;
    }

#if __RGSC_DLL
	if(message.IsA<rlScPresenceMessageRefreshAttributes>())
	{
		if(GetRgscConcreteInstance()->IsPresenceSupported())
		{
			rlScPresenceMessageRefreshAttributes msg;
			if(msg.Import(message))
			{
				rlDebug("Refreshing attributes for gamer: %d", localGamerIndex);
				rlScPresence::ForceRefresh(localGamerIndex);
			}
		}
	}
	else
	{
		GetRgscConcreteInstance()->_GetPresenceManager()->OnSocialClubEvent(message);
	}
#elif RSG_PC
	// messages that don't get handled in the Social Club DLL get sent up to the game.
	rlPresenceEventScMessage e(m_GamerPresences[localGamerIndex].m_GamerInfo, message, sender);
	m_Delegator.Dispatch(&e);
#else

    if(message.IsA<rlScPresenceMessageMpInvite>())
    {
		rlScPresenceMessageMpInvite msg;
        if(msg.Import(message, sender))
        {
            rlDebug("Received an invite from: \"%s\"", msg.m_InviterName);
            rlPresenceEventInviteReceived e(m_GamerPresences[localGamerIndex].m_GamerInfo,
                                            msg.m_SessionInfo,
                                            msg.m_InviterGamerHandle,
                                            msg.m_InviterName,
                                            NULL);
			m_Delegator.Dispatch(&e);
        }
    }
	else if(message.IsA<rlScPresenceMessageRefreshAttributes>())
    {
        rlScPresenceMessageRefreshAttributes msg;
        if(msg.Import(message))
        {
            rlDebug("Refreshing attributes for gamer: %d", localGamerIndex);
            rlScPresence::ForceRefresh(localGamerIndex);
        }
    }
    else
    {
        rlPresenceEventScMessage e(m_GamerPresences[localGamerIndex].m_GamerInfo, message, sender);
		m_Delegator.Dispatch(&e);
    }
#endif
}

#if RSG_NP

bool
rlPresence::NativeInit()
{
    g_rlNp.AddDelegate(&m_NpDlgt);
    m_NpDlgt.Bind(&rlPresence::OnNpEvent);

    return true;
}

//enum
//{
//    MSG_FRIEND_INVITE   = 1,
//    MSG_FRIEND_MSG      = 2,
//};

void 
rlPresence::OnNpEvent(rlNp* /*np*/, const rlNpEvent* evt)
{
    const unsigned evtId = evt->GetId();

#define VALIDATE_LOCAL_INDEX(x)                                                         \
    int localGamerIndex = m_ActingUserIndex;                                            \
    if(!RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex))                                 \
    {                                                                                   \
        rlDebug2("rlPresence::OnNpEvent(%s) - Invalid acting index", x);                \
        for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)                                    \
        {                                                                               \
            if(IsSignedIn(i))                                                           \
            {                                                                           \
                rlDebug2("rlPresence::OnNpEvent(%s) - Using signed in index: %d", x, i);\
                localGamerIndex = i;                                                    \
                break;                                                                  \
            }                                                                           \
        }                                                                               \
    }                                                                                   \
    if(!RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex))                                 \
    {                                                                                   \
        rlDebug2("rlPresence::OnNpEvent(%s) - Found no valid index. Bailing", x);       \
        return;                                                                         \
    }

	if(RLNP_EVENT_ONLINE_STATUS_CHANGED == evtId)
    {
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_ONLINE_STATUS_CHANGED)");
        const rlNpEventOnlineStatusChanged* osc = (const rlNpEventOnlineStatusChanged*)evt;
		if(osc)
		{
			m_GamerPresences[osc->m_LocalGamerIndex].m_SignInChangeFlags = rlSignInStatusChangeFlags::NONE;

			// if this wasn't due to sign out, assume we lost connection
			if(!osc->IsOfflineDueToSignOut())
				m_GamerPresences[osc->m_LocalGamerIndex].m_SignInChangeFlags |= rlSignInStatusChangeFlags::SIGN_IN_CHANGE_CONNECTION_LOST;

			if(osc->KickedByDuplicateLogin())
				m_GamerPresences[osc->m_LocalGamerIndex].m_SignInChangeFlags |= rlSignInStatusChangeFlags::SIGN_IN_CHANGE_DUPLICATE_LOGIN;

			rlPresence::RefreshSigninState(osc->m_LocalGamerIndex);
			rlPresence::RefreshNetworkStatus(osc->m_LocalGamerIndex);
		}
    }
	else if(RLNP_EVENT_PRESENCE_STATUS_CHANGED == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_PRESENCE_STATUS_CHANGED)");
		const rlNpEventPresenceStatusChanged* osc = (const rlNpEventPresenceStatusChanged*)evt;
		if (osc)
		{
			rlPresence::RefreshSigninState(osc->m_LocalGamerIndex);
			rlPresence::RefreshNetworkStatus(osc->m_LocalGamerIndex);
		}
	}
    else if(RLNP_EVENT_MSG_RECEIVED == evtId)
    {
        rlDebug2("rlPresence::OnNpEvent(MSG_RECEIVED)");

        VALIDATE_LOCAL_INDEX("MSG_RECEIVED");

        rlNpEventMsgReceived* msg = (rlNpEventMsgReceived*)evt;

		char senderName[RL_MAX_NAME_BUF_SIZE];
		safecpy(senderName, msg->m_FromOnlineId->data, sizeof(senderName));   

        rlPresenceEventMsgReceived e(m_GamerPresences[localGamerIndex].m_GamerInfo,
                                     senderName,
                                     msg->m_Data,
                                     msg->m_Size);
        m_Delegator.Dispatch(&e);

    }
    else if(RLNP_EVENT_INVITE_RECEIVED == evtId)
    {
        rlDebug("rlPresence::OnNpEvent(INVITE_RECEIVED)");

        VALIDATE_LOCAL_INDEX("INVITE_RECEIVED");
        
        rlNpEventInviteReceived* msg = (rlNpEventInviteReceived*)evt;

		rlGamerHandle inviter;
		inviter.ResetNp(g_rlNp.GetEnvironment(), msg->m_FromAccountId, msg->m_FromOnlineId);

        rlPresenceEventInviteReceived e(m_GamerPresences[localGamerIndex].m_GamerInfo,
                                        *msg->m_SessionInfo,
                                        inviter,
                                        msg->m_FromOnlineId->data,
                                        msg->m_Salutation);
        m_Delegator.Dispatch(&e);
    }
	else if(RLNP_EVENT_JOIN_SESSION == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(JOIN_SESSION)");

        VALIDATE_LOCAL_INDEX("JOIN_SESSION");
        
        rlNpEventJoinSession* msg = (rlNpEventJoinSession*)evt;

		rlGamerHandle inviter;
		inviter.ResetNp(g_rlNp.GetEnvironment(), msg->m_Target, msg->m_TargetOnlineId);

		// build invite flags
		unsigned inviteFlags = 0;
		if(msg->m_MemberType == rlNpGameIntentMemberType::Spectator)
			inviteFlags |= rlPresenceInviteFlags::Flag_IsSpectator;

		// if the invite target is not valid, use the acting local player
		rlPresenceEventJoinedViaPresence e(
			m_GamerPresences[localGamerIndex].m_GamerInfo,
			inviter.IsValid() ? inviter : m_GamerPresences[localGamerIndex].m_GamerInfo.GetGamerHandle(),
			*msg->m_SessionInfo,
			inviteFlags);

		m_Delegator.Dispatch(&e);
	}
    else if(RLNP_EVENT_INVITE_ACCEPTED == evtId)
    {
        rlDebug("rlPresence::OnNpEvent(INVITE_ACCEPTED)");

        VALIDATE_LOCAL_INDEX("INVITE_ACCEPTED");
        
        rlNpEventInviteAccepted* msg = (rlNpEventInviteAccepted*)evt;

		rlGamerHandle inviter;
		inviter.ResetNp(g_rlNp.GetEnvironment(), msg->m_From, msg->m_OnlineId);

        rlPresenceEventInviteAccepted e(
			m_GamerPresences[localGamerIndex].m_GamerInfo,
			inviter,
			*msg->m_SessionInfo,
			rlPresenceInviteFlags::Flags_None);

        m_Delegator.Dispatch(&e);
    }
    else if(RLNP_EVENT_INVITE_ACCEPTED_WHILE_OFFLINE == evtId)
    {
        rlDebug("rlPresence::OnNpEvent(INVITE_ACCEPTED_WHILE_OFFLINE)");

        VALIDATE_LOCAL_INDEX("INVITE_ACCEPTED_WHILE_OFFLINE");
        
        rlPresenceEventInviteAcceptedWhileOffline e(m_GamerPresences[localGamerIndex].m_GamerInfo);
        m_Delegator.Dispatch(&e);
    }
	else if(RLNP_EVENT_NP_UNAVAILABLE == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_NP_UNAVAILABLE)");
		rlNpEventNpUnavailable* msg = (rlNpEventNpUnavailable*)evt;
		if(msg)
		{
			rlPresenceOnlinePermissionsChanged e(m_GamerPresences[msg->m_LocalGamerIndex].m_GamerInfo, false);
			m_Delegator.Dispatch(&e);
		}
	}
	else if(RLNP_EVENT_USER_SERVICE_STATUS_CHANGED == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_USER_SERVICE_STATUS_CHANGED)");
		rlNpEventUserServiceStatusChanged* msg = (rlNpEventUserServiceStatusChanged*)evt;
		if (msg)
		{
			RefreshSigninState(msg->m_LocalGamerIndex);
			RefreshNetworkStatus(msg->m_LocalGamerIndex);
		}
	}
	else if(RLNP_EVENT_PLAYSTATION_PLUS_INVALID == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_PLAYSTATION_PLUS_INVALID)");
		rlNpEventPlayStationPlusInvalid* msg = (rlNpEventPlayStationPlusInvalid*)evt;
		if (msg)
		{
			rlPresenceOnlinePermissionsInvalid e(m_GamerPresences[msg->m_LocalGamerIndex].m_GamerInfo);
			m_Delegator.Dispatch(&e);
		}
	}
	else if(RLNP_EVENT_PLAYSTATION_PLUS_UPDATE == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_PLAYSTATION_PLUS_UPDATE)");
		rlNpEventPlayStationPlusUpdate* msg = (rlNpEventPlayStationPlusUpdate*)evt;
		if (msg)
		{
			rlPresenceOnlinePermissionsChanged e(m_GamerPresences[msg->m_LocalGamerIndex].m_GamerInfo, msg->m_HadActivePromotion);
			m_Delegator.Dispatch(&e);
		}
	}
	else if(RLNP_EVENT_INVITE_REJECTED_NP_UNAVAILABLE == evtId)
	{
		rlDebug("rlPresence::OnNpEvent(RLNP_EVENT_INVITE_REJECTED_NP_UNAVAILABLE)");
        VALIDATE_LOCAL_INDEX("RLNP_EVENT_INVITE_REJECTED_NP_UNAVAILABLE");
        rlPresenceEventInviteUnavailable e(m_GamerPresences[localGamerIndex].m_GamerInfo, rlPresenceInviteUnavailableReason::Reason_NotOnline);
		m_Delegator.Dispatch(&e);
	}
#if RL_NP_SUPPORT_PLAY_TOGETHER
	else if(RLNP_EVENT_PLAY_TOGETHER_HOST == evtId)
	{
		rlDebug2("rlPresence::OnNpEvent(RLNP_EVENT_PLAY_TOGETHER_HOST)");

		rlNpEventPlayTogetherHost* playTogetherEvt = (rlNpEventPlayTogetherHost*)evt;

		rlGamerHandle invitees[RL_MAX_PLAY_TOGETHER_GROUP];
		for(unsigned i = 0; i < playTogetherEvt->m_nInvitees; i++)
		{
			rlGamerHandle inviter;
			invitees[i].ResetNp(g_rlNp.GetEnvironment(), RL_INVALID_NP_ACCOUNT_ID, &playTogetherEvt->m_Invitees[i]);
		}

		rlPresenceEventPlayTogetherHost e(g_rlNp.GetUserServiceIndex(playTogetherEvt->m_UserId), playTogetherEvt->m_nInvitees, invitees);
		m_Delegator.Dispatch(&e);
	}
#endif
#if RSG_PROSPERO
	else if (RLNP_EVENT_BLOCKLIST_RETRIEVED == evtId)
	{
		rlDebug2("rlPresence::OnNpEvent(RLNP_EVENT_BLOCKLIST_RETRIEVED)");
		VALIDATE_LOCAL_INDEX("RLNP_EVENT_BLOCKLIST_RETRIEVED");

		rlPresenceEventBlocklistChanged e(localGamerIndex);
		m_Delegator.Dispatch(&e);
	}
#endif
}

void
rlPresence::NativeShutdown()
{
    g_rlNp.RemoveDelegate(&m_NpDlgt);
}

void
rlPresence::NativeUpdate()
{
}

bool
rlPresence::NativeSetStatusString(const rlGamerInfo& /*gamerInfo*/,
                                  const int /*presenceId*/,
                                  const rlSchema& /*schema*/,
                                  netStatus* /*status*/)
{
    rlError("NativeSetStatusString() not supported on this platform");
    return false;
}

bool
rlPresence::NativeGetStatusString(const rlGamerInfo& /*gamerInfo*/,
							      char* /*buf*/,
							      const unsigned /*sizeOfBuf*/,
							      netStatus* /*status*/)
{
    rlError("NativeGetStatusString() not supported on this platform");
	return false;
}

#elif RSG_PC

// this is here to avoid including windows platform headers.
static rlPcEventDelegator::Delegate m_PcDlgt;

bool
rlPresence::NativeInit()
{
 	g_rlPc.AddDelegate(&m_PcDlgt);
 	m_PcDlgt.Bind(&rlPresence::OnPcEvent);

	return true;
}

static bool ParseRosCredentials(const int localGamerIndex, const char* xmlResponse, rlRosCredentials* rosCredentials)
{
	bool success = false;

	int xmlResponseLen = xmlResponse ? istrlen(xmlResponse) : 0;
	if(xmlResponseLen == 0)
	{
		return false;
	}

	INIT_PARSER;

	parTree* tree = NULL;

	rtry
	{
#if RSG_PC && !__RGSC_DLL
		if (g_rlPc.GetDownloaderPipe() && g_rlPc.GetDownloaderPipe()->IsConnected())
		{
			g_rlPc.GetDownloaderPipe()->ForwardCredentialsToLauncher(xmlResponse);
		}
#endif

		// treat the response as a memory file so we can use the XML parser
		char filename[64];
		fiDeviceMemory::MakeMemoryFileName(filename, sizeof(filename), xmlResponse, xmlResponseLen, false, NULL);

		PARSER.Settings().SetFlag(parSettings::READ_SAFE_BUT_SLOW, true);
		tree = PARSER.LoadTree(filename, "xml");
		PARSER.Settings().SetFlag(parSettings::READ_SAFE_BUT_SLOW, false);

		rverify(tree && tree->GetRoot(), catchall, );

		parTreeNode* pRoot = tree->GetRoot();
		rverify(pRoot, catchall, );

		rlRosCreateTicketResponse response;
		success = rlRosXmlCreateTicketResponseParser::Parse(pRoot, &response) &&
			rlRosCreateTicketResponseHelper::ApplyCreateTicketResponse(response, localGamerIndex, rosCredentials);
	}
	rcatchall
	{
	}

	if(tree)
	{
		delete tree;
		tree = NULL;
	}

	SHUTDOWN_PARSER;

	return success;
}

void 
rlPresence::OnPcEvent(rlPc* /*pc*/, const rlPcEvent* evt)
{
	const unsigned evtId = evt->GetId();

	// log event
	rlDebug1("OnPcEvent :: %s received", evt->GetAutoIdNameFromId(evt->GetId()));

	if(RLPC_EVENT_SIGN_IN_STATE_CHANGED == evtId)
	{
		rlPcEventSignInStateChanged* msg = (rlPcEventSignInStateChanged*)evt;

		if(msg->m_State & rgsc::STATE_SIGNED_IN)
		{
			rlDebug2("\t Signed In");
			rlAssert(g_rlPc.IsInitialized() && g_rlPc.GetProfileManager()->IsSignedIn());
		}
		if(msg->m_State & rgsc::STATE_SIGNED_ONLINE)
		{
			rlDebug2("\t Signed Online");
			rlAssert(g_rlPc.IsInitialized() && g_rlPc.GetProfileManager()->IsOnline());
		}
		if(msg->m_State & rgsc::STATE_SIGNED_OUT)
		{
			rlDebug2("\t Signed Out");
			rlAssert(g_rlPc.IsInitialized() && (g_rlPc.GetProfileManager()->IsSignedIn() == false));
			rlAssert(g_rlPc.IsInitialized() && (g_rlPc.GetProfileManager()->IsOnline() == false));
		}
		if(msg->m_State & rgsc::STATE_SIGNED_OFFLINE)
		{
			rlDebug2("\t Signed Offline");
			rlAssert(g_rlPc.IsInitialized() && (g_rlPc.GetProfileManager()->IsOnline() == false));
		}
		if(msg->m_State & rgsc::STATE_KICKED_BY_DUPLICATE_SIGN_IN)
		{
			rlDebug2("\t Kicked by duplicate sign in");
			m_GamerPresences[0].m_SignInChangeFlags |= rlSignInStatusChangeFlags::SIGN_IN_CHANGE_DUPLICATE_LOGIN;
			rlAssert(g_rlPc.IsInitialized() && (g_rlPc.GetProfileManager()->IsOnline() == false));
		}
		if(msg->m_State & rgsc::STATE_SIGNED_CONNECTION_LOST)
		{
			rlDebug2("\t Connection Lost");
			rlAssert(g_rlPc.IsInitialized() && (g_rlPc.GetProfileManager()->IsOnline() == false));
		}

		for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
		{
			rlPresence::RefreshSigninState(i);
			rlPresence::RefreshNetworkStatus(i);
		}

		bool isOnline = g_rlPc.IsInitialized() && g_rlPc.GetProfileManager()->IsOnline();
		if(isOnline == false)
		{
			rlSocialClub::ClearLogin(0);
		}
	}
	else if(RLPC_EVENT_ROS_TICKET_CHANGED == evtId)
	{
		rlPcEventRosTicketChanged* msg = (rlPcEventRosTicketChanged*)evt;

		bool isOnline = g_rlPc.IsInitialized() && g_rlPc.GetProfileManager();
		if(rlVerify(isOnline))
		{
			rgsc::Profile profile;
			rgsc::RGSC_HRESULT hr = g_rlPc.GetProfileManager()->GetSignInInfo(&profile);
			if(rlVerify(hr == rgsc::RGSC_OK))
			{
				rlVerify(rlSocialClub::SetLogin(0, profile.GetSocialClubEmail(), profile.GetSocialClubAuthToken()));

				rlRosCredentials rosCredentials;
				if(rlVerify(ParseRosCredentials(0, msg->m_Xml, &rosCredentials)))
				{
					rlAssert(rosCredentials.IsValid());
					rlVerify(rlRos::SetCredentials(0, &rosCredentials));
					rlAssert(rlRos::IsOnline(0));
				}
			}
		}
	}
	else if(RLPC_EVENT_FRIEND_STATUS_CHANGED == evtId)
	{
		rlFriendsManager::RequestFriendSync();
	}
	else if(RLPC_EVENT_SOCIAL_CLUB_MESSAGE == evtId)
	{
		rlPcEventSocialClubMessage* msg = (rlPcEventSocialClubMessage*)evt;
		OnScMessage(0, *msg->m_ScMsg, *msg->m_Sender);
	}
	else if(RLPC_EVENT_GAME_INVITE_ACCEPTED == evtId)
	{
		rlPcEventGameInviteAccepted* msg = (rlPcEventGameInviteAccepted*)evt;
	
		if(msg->m_ScMsg->IsA<rlScPresenceMessageMpInvite>())
		{
			rlScPresenceMessageMpInvite invite;
			if(invite.Import(*msg->m_ScMsg, *msg->m_Sender))
			{
				rlDebug("Accepted an invite from: \"%s\"", invite.m_InviterName);
				rlPresence::NotifyInviteAccepted(0,
												 invite.m_SessionInfo,
												 invite.m_InviterGamerHandle);
			}
		}
	}
	else if(RLPC_EVENT_JOINED_VIA_PRESENCE == evtId)
	{
		rlPcEventJoinedViaPresence* msg = (rlPcEventJoinedViaPresence*)evt;

		if(msg->m_ScMsg->IsA<rlScPresenceMessageMpInvite>())
		{
			rlScPresenceMessageMpInvite invite;
			if(invite.Import(*msg->m_ScMsg, *msg->m_Sender))
			{
				rlDebug("Joining a game via presence");
				const GamerPresence& joinerInfo = m_GamerPresences[0];
				rlPresenceEventJoinedViaPresence e(joinerInfo.m_GamerInfo, invite.m_InviterGamerHandle, invite.m_SessionInfo, rlPresenceInviteFlags::Flag_IsJoin);
				m_Delegator.Dispatch(&e);
			}
		}
	}

	if (g_rlPc.GetDownloaderPipe() != NULL)
	{
		g_rlPc.GetDownloaderPipe()->OnPcEvent(evt);
	}
}

void
rlPresence::NativeShutdown()
{
	g_rlPc.RemoveDelegate(&m_PcDlgt);
}

void
rlPresence::NativeUpdate()
{
	for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
	{
		rlPresence::RefreshSigninState(i);
		rlPresence::RefreshNetworkStatus(i);
	}
}

bool
#if __RGSC_DLL
rlPresence::NativeSetStatusString(const rlGamerInfo& /*gamerInfo*/,
								  const int /*presenceId*/,
								  const rlSchema& /*schema*/,
								  netStatus* /*status*/)
#else
rlPresence::NativeSetStatusString(const rlGamerInfo& gamerInfo,
							      const int presenceId,
							      const rlSchema& schema,
							      netStatus* status)
#endif
{
	bool success = false;

#if !__RGSC_DLL

	SetRichPresenceTask* task;
	if(!netTask::Create(&task, status)
		|| !task->Configure(gamerInfo, presenceId, schema)
		|| !netTask::Run(task))
	{
		netTask::Destroy(task);
	}
	else
	{
		success = true;
	}

#endif

	return success;
}

bool
rlPresence::NativeGetStatusString(const rlGamerInfo& /*gamerInfo*/,
							      char* /*buf*/,
							      const unsigned /*sizeOfBuf*/,
							      netStatus* /*status*/)
{
    rlError("NativeGetStatusString() not supported on this platform");
	return false;
}

#elif RSG_DURANGO

static rlXblEventDelegator::Delegate m_XblDlgt;

bool
rlPresence::NativeInit()
{
	g_rlXbl.AddDelegate(&m_XblDlgt);
	m_XblDlgt.Bind(&rlPresence::OnXblEvent);
	return true;
}

void
rlPresence::NativeShutdown()
{
	g_rlXbl.RemoveDelegate(&m_XblDlgt);
}

void
rlPresence::NativeUpdate()
{

}

void 
rlPresence::OnXblEvent(rlXbl* /*xbl*/, const rlXblEvent* evt)
{
	const unsigned evtId = evt->GetId();

	if(RLXBL_PRESENCE_EVENT_SIGNIN_STATUS_CHANGED == evtId)
	{
		rlDebug2("rlPresence::OnXblEvent(RLXBL_PRESENCE_EVENT_SIGNIN_STATUS_CHANGED)");

		rlXblPresenceEventSigninStatusChanged* msg = (rlXblPresenceEventSigninStatusChanged*)evt;

		if(msg->IsSignedIn())
		{
			rlDebug2("\t Signed In");
		}
		else
		{
			rlDebug2("\t Signed Out");
		}

		rlPresence::RefreshSigninState(msg->GetLocalGamerIndex());
		rlPresence::RefreshNetworkStatus(msg->GetLocalGamerIndex());
	}
	else if(RLXBL_PRESENCE_EVENT_NETWORK_STATUS_CHANGED == evtId)
	{
		rlDebug2("rlPresence::OnXblEvent(RLXBL_PRESENCE_EVENT_NETWORK_STATUS_CHANGED)");

		for(int i = 0; i < RL_MAX_LOCAL_GAMERS; ++i)
		{
			NPROFILE(rlPresence::RefreshSigninState(i));
			NPROFILE(rlPresence::RefreshNetworkStatus(i));
		}
	}
	else if (RLXBL_PARTY_EVENT_GAME_SESSION_READY == evtId)
	{
		rlDebug2("rlPresence::OnXblEvent(RLXBL_PARTY_EVENT_GAME_SESSION_READY)");
		rlXblPartyGameSessionReadyEvent* msg = (rlXblPartyGameSessionReadyEvent*)evt;

		rlDebug("GameSessionReady :: Target: %s. Origin: %d", msg->m_Target.GetName(), msg->m_Origin);

		rlPresenceEventGameSessionReady e(msg->m_Target, msg->m_GameSession, msg->m_NumPartyPlayers, msg->m_PartyPlayers, static_cast<int>(msg->m_Origin));
		m_Delegator.Dispatch(&e);
	}
	else if (RLXBL_PARTY_EVENT_GAME_SESSION_UNAVAILABLE == evtId)
	{
		rlDebug2("rlPresence::OnXblEvent(RLXBL_PARTY_EVENT_GAME_SESSION_UNAVAILABLE)");
		rlXblPartyGameSessionUnavailableEvent* msg = (rlXblPartyGameSessionUnavailableEvent*)evt;

		rlDebug("GameSessionReady :: Target: %s", msg->m_Target.GetName());

		rlPresenceEventInviteUnavailable e(msg->m_Target, rlPresenceInviteUnavailableReason::Reason_ServiceError);
		m_Delegator.Dispatch(&e);
	}
	else if (RLXBL_PARTY_EVENT_GAME_SESSION_INVALID == evtId)
	{
		rlDebug2("rlPresence::OnXblEvent(RLXBL_PARTY_EVENT_GAME_SESSION_INVALID)");
		rlGamerInfo dummyInfo;
		rlPresenceEventPartySessionInvalid e(dummyInfo);
		m_Delegator.Dispatch(&e);
	}
	else if (RLXBL_PARTY_EVENT_ROSTER_CHANGED == evtId)
	{
		// Currently the gamerInfo provided by the event will be invalid and should be ignored.
		rlGamerInfo dummyInfo;
		dummyInfo.Clear();

		rlPresenceEventPartyChanged e(dummyInfo);
		m_Delegator.Dispatch(&e);
	}
	else if (RLXBL_PARTY_EVENT_GAME_AVAILABLE_PLAYERS == evtId)
	{
		rlXblPartyGamePlayersAvailableEvent* msg = (rlXblPartyGamePlayersAvailableEvent*)evt;
		if (msg->m_NumAvailablePlayers > 0)
		{
			rlGamerHandle availablePlayers[RL_MAX_PARTY_SIZE];
			u64 lastInvTime[RL_MAX_PARTY_SIZE];

			rlAssert(msg->m_NumAvailablePlayers <= RL_MAX_PARTY_SIZE);

			int numAvailablePlayers = 0;
			for (int i = 0; i < msg->m_NumAvailablePlayers && i < RL_MAX_PARTY_SIZE; i++)
			{
				availablePlayers[i].ResetXbl(msg->m_AvailablePlayers[i].XboxUserId);
				lastInvTime[i] = msg->m_AvailablePlayers[i].LastInvitedTime;
				numAvailablePlayers++;
			}

			int localGamerIndex = rlPresence::GetActingUserIndex();
			rlPresenceEventPartyMembersAvailable e(m_GamerPresences[localGamerIndex].m_GamerInfo, &availablePlayers[0], &lastInvTime[0], numAvailablePlayers);
			m_Delegator.Dispatch(&e);
		}
	}
	else if (RLXBL_INVITE_EVENT_ACCEPTED == evtId)
	{
		rlDebug("rlPresence::OnXblEvent(RLXBL_INVITE_EVENT_ACCEPTED)");

		rlXblInviteAcceptedEvent* msg = (rlXblInviteAcceptedEvent*)evt;

		if(msg->m_IsJoin)
		{
			rlPresenceEventJoinedViaPresence e(msg->m_gamerInfo,
				msg->m_inviter,
				msg->m_sessionInfo,
				rlPresenceInviteFlags::Flags_None);
			m_Delegator.Dispatch(&e);
		}
		else
		{
			rlPresenceEventInviteAccepted e(msg->m_gamerInfo,
				msg->m_inviter,
				msg->m_sessionInfo,
				rlPresenceInviteFlags::Flags_None);
			m_Delegator.Dispatch(&e);
		}
	}
	else if(RLXBL_INVITE_EVENT_UNAVAILABLE == evtId)
	{
		rlDebug("rlPresence::OnXblEvent(RLXBL_INVITE_EVENT_UNAVAILABLE)");

		rlXblEventInviteUnavailableEvent* msg = (rlXblEventInviteUnavailableEvent*)evt;

		if(!RL_IS_VALID_LOCAL_GAMER_INDEX(m_ActingUserIndex))
			return;

		rlPresenceEventInviteUnavailable e(m_GamerPresences[m_ActingUserIndex].m_GamerInfo, msg->m_Reason);
		m_Delegator.Dispatch(&e);
	}
}

bool
rlPresence::NativeSetStatusString(const rlGamerInfo& /*gamerInfo*/,
                                const int /*presenceId*/,
                                const rlSchema& /*schema*/,
                                netStatus* /*status*/)
{
    rlError("NativeSetStatusString() not supported on this platform");
    return false;
}

bool
rlPresence::NativeGetStatusString(const rlGamerInfo& /*gamerInfo*/,
							      char* /*buf*/,
							      const unsigned /*sizeOfBuf*/,
							      netStatus* /*status*/)
{
    rlError("NativeGetStatusString() not supported on this platform");
	return false;
}

#else

bool
rlPresence::NativeInit()
{
    return true;
}

void
rlPresence::NativeShutdown()
{
}

void
rlPresence::NativeUpdate()
{
}

bool
rlPresence::NativeSetStatusString(const rlGamerInfo& /*gamerInfo*/,
                                const int /*presenceId*/,
                                const rlSchema& /*schema*/,
                                netStatus* /*status*/)
{
    rlError("NativeSetStatusString() not supported on this platform");
    return false;
}

bool
rlPresence::NativeGetStatusString(const rlGamerInfo& /*gamerInfo*/,
							      char* /*buf*/,
							      const unsigned /*sizeOfBuf*/,
							      netStatus* /*status*/)
{
    rlError("NativeGetStatusString() not supported on this platform");
	return false;
}

#endif

namespace internal
{

#if RSG_NP
//PURPOSE
//  Returns number of bytes used (1-3) by first UTF-8 character in string.
static int Utf8CharSize(const char* str)
{
    if ((str[0] & 0x80) == 0) 
    {
        return 1;
    }
    else if ((str[0] & 0xE0) == 0xC0) 
    {
        return 2;
    }
    else if ((str[0] & 0xF0) == 0xE0) 
    {
        return 3;
    }
    rlAssertf(0 , "Invalid format for utf-8 character");
    return 0;
}

//PURPOSE
//  Copies as much of a UTF-8 source string as the destination
//  buffer allows, and adds a null terminator to the end.
//  The primary benefit of this over a simple copy is that it
//  takes care not to leave partial multi-byte characters when
//  the destination buffer is smaller than the source.
static void Utf8Safecpy(char* dest, 
                        const char* src,
                        const unsigned destSize)
{  
    rlAssert(dest && src && (destSize > 1));

    char* destEnd = (dest + destSize - 1) - 1;

    while((dest <= destEnd) && (*src != '\0'))
    {
        int charSize = Utf8CharSize(src);

        if(charSize > (destEnd - dest + 1))
        {
            break; //Would have broken in middle of UTF-8 char.
        }

        memcpy(dest, src, charSize);
        
        dest += charSize;
        src += charSize;
    }

    *dest = '\0';
}
#endif  //RSG_NP

static bool IsSignedIn(const int localGamerIndex)
{
    bool isSignedin = false;

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
#if RSG_DURANGO

		isSignedin = g_rlXbl.GetPresenceManager()->IsSignedIn(localGamerIndex);

#elif RSG_NP

        const char* signinName = g_rlNp.GetSignInName(localGamerIndex);
        isSignedin = (NULL != signinName) && ('\0' != signinName[0]);

#elif __RGSC_DLL

		isSignedin = GetRgscConcreteInstance()->_GetProfileManager()->IsSignedInInternal();

#elif RSG_PC

		isSignedin = g_rlPc.IsInitialized() && g_rlPc.GetProfileManager()->IsSignedIn();
#else

#endif
    }

    return isSignedin;
}

static bool IsOnline(const int localGamerIndex)
{
    bool isOnline = false;

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex)
        &&  rlPeerAddress::HasNetwork())
    {
#if RSG_DURANGO

		isOnline = g_rlXbl.GetPresenceManager()->IsOnline(localGamerIndex);

#elif RSG_NP

        isOnline = IsSignedIn(localGamerIndex) && g_rlNp.IsOnline(localGamerIndex);

#elif __RGSC_DLL

		isOnline = GetRgscConcreteInstance()->_GetProfileManager()->IsOnlineInternal();

#elif RSG_PC

		isOnline = g_rlPc.IsInitialized() && g_rlPc.GetProfileManager()->IsOnline() && rlRos::IsOnline(0);

#else
#endif
    }

#if FAKE_DROP_LATENCY
    if (s_FakeUplinkOffline)
    {
        isOnline = false;
    }
#endif

    return isOnline;
}

static bool GetGamerId(const int localGamerIndex, u64* gamerId)
{
    bool success = false;

    *gamerId = 0;

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(internal::IsSignedIn(localGamerIndex))
        {
            char name[RL_MAX_NAME_BUF_SIZE] = {0};

            //To create a unique gamer id hash the offline name,
            //which should always be available, with a unique id as the
            //seed.
            if(rlVerify(internal::GetOfflineName(localGamerIndex, name, sizeof(name))))
            {
                u32 hashA, hashB;

                u8 mac[6];

                if(netHardware::GetMacAddress(mac))
                {
                    *gamerId = u64(mac[0]) << 0;
                    *gamerId |= u64(mac[1]) << 8;
                    *gamerId |= u64(mac[2]) << 16;
                    *gamerId |= u64(mac[3]) << 24;
                    *gamerId |= u64(mac[4]) << 32;
                    *gamerId |= u64(mac[5]) << 40;
                }
                else if(!rlVerify(rlCreateUUID(gamerId)))
                {
                    //If all else fails
                    *gamerId = (u64) gamerId;
                }

                do 
                {
                    hashA = atDataHash(name, ::strlen(name), u32(*gamerId >> 32));
                    hashB = atDataHash(name, ::strlen(name), u32(*gamerId));

                    *gamerId = (u64(hashA) << 32) | hashB;

                } while(0 == *gamerId);

                success = true;
            }
        }
    }

    return success;
}

static bool GetGamerHandle(const int localGamerIndex, rlGamerHandle *handle)
{
    bool success = false;

    handle->Clear();

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex)
        && internal::IsSignedIn(localGamerIndex))
    {
#if RSG_DURANGO

		u64 xuid = g_rlXbl.GetPresenceManager()->GetXboxUserId(localGamerIndex);
		handle->ResetXbl(xuid);
		success = true;

#elif RSG_NP
		const char* localUserName = g_rlNp.GetSignInName(localGamerIndex);
		if (localUserName)
		{
			rlSceNpAccountId accountId = g_rlNp.GetNpAccountId(localGamerIndex);

			rlSceNpOnlineId onlineId;
			if (accountId != RL_INVALID_NP_ACCOUNT_ID)
			{
				onlineId.FromString(g_rlNp.GetNpOnlineId(localGamerIndex).data);
			}
			else
			{
				// Use hash of username+userserviceID for offline account ID, and use local name for the online id.
				accountId = atStringHash(localUserName, g_rlNp.GetUserServiceId(localGamerIndex));
				onlineId.FromString(localUserName);
			}

			handle->ResetNp(g_rlNp.GetEnvironment(), accountId, &onlineId);
			success = true;
		}
#elif __RGSC_DLL

		if(GetRgscConcreteInstance()->_GetProfileManager()->IsSignedInInternal())
		{
			const RgscProfile& profile = GetRgscConcreteInstance()->_GetProfileManager()->GetSignedInProfile();
			handle->ResetSc(profile.GetProfileId());
			success = true;
		}

#elif RSG_PC
		if(g_rlPc.IsInitialized())
		{
			rgsc::Profile profile;
			HRESULT hr = g_rlPc.GetProfileManager()->GetSignInInfo(&profile);
			if(hr == ERROR_SUCCESS)
			{
				rgsc::RockstarId profileId = profile.GetProfileId();
				handle->ResetSc((RockstarId) profileId);

				success = true;
			}
		}

#else
        rlError("Not implemented");

#endif
    }

    return success;
}

static bool GetOfflineName(const int localGamerIndex,
                            char* name,
                            const unsigned nameLen)
{
    rlAssert(nameLen >= RL_MAX_NAME_BUF_SIZE);

    bool success = false;

    *name = '\0';

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex)
                && internal::IsSignedIn(localGamerIndex))
    {
#if RSG_NP

        const char* signInName = g_rlNp.GetSignInName(localGamerIndex);

        if(rlVerify(signInName))
        {
            Utf8Safecpy(name, signInName, nameLen);
        }
        else
        {
            //Just compose a generic one.
            rlError("Could not get user sign-in name; on NP this shouldn't be possible");
            formatf(name, nameLen, "Player%d", localGamerIndex);
        }

        success = true;

#elif __RGSC_DLL

		if(GetRgscConcreteInstance()->_GetProfileManager()->IsSignedInInternal())
		{
			const RgscProfile& profile = GetRgscConcreteInstance()->_GetProfileManager()->GetSignedInProfile();
			const char *nickname = profile.GetName();
			if(rlVerify(nickname))
			{
				const unsigned len = (unsigned)strlen(nickname);
				if(rlVerify((len > 0) && (len < nameLen)))
				{
					safecpy(name, nickname, nameLen);
					success = true;
				}
			}
		}

#elif RSG_PC

		if(g_rlPc.IsInitialized())
		{
			rgsc::Profile profile;
			HRESULT hr = g_rlPc.GetProfileManager()->GetSignInInfo(&profile);
			if(hr == ERROR_SUCCESS)
			{
				const char *nickname = profile.GetNickname();
				if(rlVerify(nickname))
				{
					const unsigned len = ustrlen(nickname);
					if(rlVerify((len > 0) && (len < nameLen)))
					{
						safecpy(name, nickname, nameLen);
						success = true;
					}
				}
			}
		}

        /*
        char buf[MAX_COMPUTERNAME_LENGTH + 1];

        DWORD namelength = MAX_COMPUTERNAME_LENGTH + 1;
        if( !rlVerify(GetComputerName(buf, &namelength) ) )
        {
            //Just compose a generic one.
            formatf(buf, sizeof(buf), "Player%d", localGamerIndex);
        }

        //char buf[256];
        //size_t sizeofBuf = sizeof(buf);
        //if(!rlVerify(0 == getenv_s(&sizeofBuf, buf, sizeof(buf), "USERNAME")))
        //      {
        //          //Just compose a generic one.
        //          formatf(buf, sizeof(buf), "Player%d", localGamerIndex);
        //      }

        if(strlen(buf) >= nameLen)
        {
            rlWarning(("User name is too long"));
        }

        safecpy(name, buf, nameLen);

        success = true;
        */

#elif RSG_DURANGO
		const char *nickname = g_rlXbl.GetPresenceManager()->GetName(localGamerIndex);
		if(rlVerify(nickname))
		{
			const unsigned len = ustrlen(nickname);
			if(rlVerify((len > 0) && (len < nameLen)))
			{
				safecpy(name, nickname, nameLen);
				success = true;
			}
		}
#else

        rlAssert(0 == localGamerIndex);
        safecpy(name, "NONAME", nameLen);

        success = true;

#endif
    }

    return success;
}

static bool GetName(const int localGamerIndex,
                     char* name,
                     const unsigned nameLen)
{
    bool success = false;

#if RSG_NP
    bool isOfflineName = false;
#endif

    if(rlVerify(name && (nameLen >= RL_MAX_NAME_BUF_SIZE)))
    {
        *name = '\0';

        if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
        {
#if RSG_NP
			//If online, use the NP online ID.  Otherwise use the local sign-in name.
			if(g_rlNp.IsOnline(localGamerIndex))
			{
				const char* onlineId = g_rlNp.GetNpOnlineId(localGamerIndex).data;
				if(onlineId)
				{
					safecpy(name, onlineId, nameLen);
					success = true;
				}
				else
				{
					rlError("Failed to retrieve NP ID");
				}
			}
			else
			{
				success = internal::GetOfflineName(localGamerIndex, name, nameLen);
				isOfflineName = true;
			}
#elif __RGSC_DLL
			success = internal::GetOfflineName(localGamerIndex, name, nameLen);
#elif RSG_PC
            success = internal::GetOfflineName(localGamerIndex, name, nameLen);
#elif RSG_DURANGO
			success = internal::GetOfflineName(localGamerIndex, name, nameLen);
#else
            success = internal::GetOfflineName(localGamerIndex, name, nameLen);
#endif
        }
    }

	const char* fakeName = 0;
	if (PARAM_netFakeGamerName.Get(fakeName))
	{
		safecpy(name, fakeName, istrlen(name));
		success = true;
	}

    if(success)
    {
#if RSG_NP
        //NP has different rules for offline names, so don't
        //validate offline names using rules for online names.
        if(!isOfflineName)
        {
            internal::ValidateName(name, istrlen(name));
        }
#else
        internal::ValidateName(name, istrlen(name));
#endif
    }

    return success;
}

static void ValidateName(char* nameBuf, const unsigned bufLen)
{
    bool validName = true;
    int len = istrlen(nameBuf);

#if RSG_DURANGO
    /*
        Minimum size of one character.
        Maximum size of 15 characters.
        Cannot begin or end with a space character.
        Must begin with an alphabetic character (A-Z, a-z).
        Consists only of the following characters: A-Z, a-z, 0-9, and space (ASCII 0x20).
        Cannot have multiple contiguous space characters.
    */
    if(len > 15)
    {
        nameBuf[15] = '\0';
        len = 15;
    }

    if(len < 1)
    {
        validName = false;
    }
    else if(' ' == nameBuf[0] || ' ' == nameBuf[len-1])
    {
        validName = false;
    }
    else if((nameBuf[0] < 'a' || nameBuf[0] > 'z')
            && (nameBuf[0] < 'A' || nameBuf[0] > 'Z')

#if RSG_DURANGO
			// Xbox dev accounts can start with a number
			&& (rlGetEnvironment() == RL_ENV_DEV)
			&& (nameBuf[0] < '0' || nameBuf[0] > '9')
#endif
			)
    {
        validName = false;
    }
    else
    {
        for(int i = 0; i < len && validName; ++i)
        {
            const char c = nameBuf[i];
            validName =
                (c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z')
                || (c >= '0' && c <= '9')
                || (c == ' ');

            if(validName && i > 0 && ' ' == c)
            {
                //Multiple contiguous spaces?
                validName = !(nameBuf[i-1] == ' ');
            }
        }
    }

#elif RSG_NP
    /*
        The Online ID is selected by the user when signing up to
        PlayStation Network.  It is composed of 3 to 16 characters,
        and can use alphanumeric characters, hyphens, and underscores.
        An Online ID is guaranteed to be unique.
    */
    if(len > 16)
    {
        nameBuf[16] = '\0';
        len = 16;
    }

    if(len < 3)
    {
        validName = false;
    }
    else
    {
        for(int i = 0; i < len && validName; ++i)
        {
            const char c = nameBuf[i];
            validName =
                (c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z')
                || (c >= '0' && c <= '9')
                || (c == ' ')
                || (c == '-')
                || (c == '_');
        }
    }
#elif RSG_PC
	if(len > RL_MAX_NAME_LENGTH)
	{
		nameBuf[RL_MAX_NAME_LENGTH] = '\0';
		len = RL_MAX_NAME_LENGTH;
	}

	if(len < 3)
	{
		validName = false;
	}
#else
    if(len > RL_MAX_NAME_LENGTH)
    {
        nameBuf[RL_MAX_NAME_LENGTH] = '\0';
        len = RL_MAX_NAME_LENGTH;
    }

    if(len < 3)
    {
        validName = false;
    }
    else
    {
        for(int i = 0; i < len && validName; ++i)
        {
            const char c = nameBuf[i];
            validName =
                (c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z')
                || (c >= '0' && c <= '9')
                || (c == ' ')
                || (c == '-')
                || (c == '_');
        }
    }
#endif

    if(!validName)
    {
        safecpy(nameBuf, "Invalid Name", bufLen);
    }
}

}   //namespace internal

bool
rlPresence::PopulateGamerInfo(const int localGamerIndex, rlGamerInfo* gamerInfo)
{
    bool success = false;

    gamerInfo->Clear();

    if(rlVerifyf(RL_IS_VALID_LOCAL_GAMER_INDEX(localGamerIndex),
                "Invalid gamer index: %d", localGamerIndex))
    {
        if(internal::IsSignedIn(localGamerIndex)
            && rlPeerInfo::GetLocalPeerInfo(&gamerInfo->m_PeerInfo, localGamerIndex)
            && internal::GetGamerId(localGamerIndex, &gamerInfo->m_GamerId.m_Id)
            && internal::GetGamerHandle(localGamerIndex, &gamerInfo->m_GamerHandle)
            && internal::GetName(localGamerIndex, gamerInfo->m_Name, sizeof(gamerInfo->m_Name)))
        {
            gamerInfo->m_LocalIndex = localGamerIndex;
            gamerInfo->m_IsSignedIn = true;
            gamerInfo->m_IsOnline = internal::IsOnline(localGamerIndex);

            rlAssert(gamerInfo->IsValid());

            success = true;
        }
        else
        {
            gamerInfo->Clear();
        }
    }

    return success;
}

//////////////////////////////////////////////////////////////////////////
//  rlPresenceAttributeHelper
//////////////////////////////////////////////////////////////////////////
bool
rlPresenceAttributeHelper::SetGamerTag(const int localGamerIndex, const char* gamerTag)
{
    return rlVerifyf(rlPresence::SetStringAttribute(localGamerIndex,
                                                    rlScAttributeId::GamerTag.Name,
                                                    gamerTag),
                    "Error setting \"%s\" presence attribute",
                    rlScAttributeId::GamerTag.Name);
}

bool
rlPresenceAttributeHelper::SetScNickname(const int localGamerIndex, const char* scNickname)
{
    return rlVerifyf(rlPresence::SetStringAttribute(localGamerIndex,
                                                    rlScAttributeId::ScNickname.Name,
                                                    scNickname),
                    "Error setting \"%s\" presence attribute",
                    rlScAttributeId::ScNickname.Name);
}

bool
rlPresenceAttributeHelper::SetPeerAddress(const int localGamerIndex, const rlPeerAddress& peerAddr)
{
    rlPeerAddress tmpPeerAddr = peerAddr;
	tmpPeerAddr.ClearNonAdvertisableData();
	
    char paBuf[rlPeerAddress::TO_STRING_BUFFER_SIZE] = {""};

    return rlVerifyf(tmpPeerAddr.ToString(paBuf), "Error serializing peer address")
            && rlVerifyf(rlPresence::SetStringAttribute(localGamerIndex,
                                                        rlScAttributeId::PeerAddress.Name,
                                                        paBuf),
                "Error setting %s presence attribute", rlScAttributeId::PeerAddress.Name);
}

bool
rlPresenceAttributeHelper::SetCrewId(const int localGamerIndex, const s64 crewId)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::CrewId.Name,
                                                crewId),
                "Error setting \"%s\" presence attribute",
                rlScAttributeId::CrewId.Name);
}

bool
rlPresenceAttributeHelper::SetIsGameHost(const int localGamerIndex, const bool isHost)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::IsGameHost.Name,
                                                isHost ? 1 : 0),
                "Error setting %s presence attribute", rlScAttributeId::IsGameHost.Name);
}

bool
rlPresenceAttributeHelper::SetGameSessionToken(const int localGamerIndex, const rlSessionToken& token)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::GameSessionToken.Name,
                                                token.m_Value),
                "Error setting %s presence attribute", rlScAttributeId::GameSessionToken.Name);
}


bool
rlPresenceAttributeHelper::SetIsGameJoinable(const int localGamerIndex, const bool isJoinable)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::IsGameJoinable.Name,
                                                isJoinable ? 1 : 0),
                "Error setting %s presence attribute", rlScAttributeId::IsGameJoinable.Name);
}

bool
rlPresenceAttributeHelper::GetIsGameJoinable(const int localGamerIndex, bool& isJoinable)
{
    s64 value;
    if(rlPresence::GetIntAttribute(localGamerIndex,
                                    rlScAttributeId::IsGameJoinable.Name,
                                    &value))
    {
        isJoinable = (0 != value);
        return true;
    }

    return false;
}

bool
rlPresenceAttributeHelper::SetIsPartyHost(const int localGamerIndex, const bool isHost)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::IsPartyHost.Name,
                                                isHost ? 1 : 0),
                "Error setting %s presence attribute", rlScAttributeId::IsPartyHost.Name);
}

bool
rlPresenceAttributeHelper::SetPartySessionToken(const int localGamerIndex, const rlSessionToken& token)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::PartySessionToken.Name,
                                                token.m_Value),
                "Error setting %s presence attribute", rlScAttributeId::PartySessionToken.Name);
}

bool
rlPresenceAttributeHelper::SetPartySessionId(const int localGamerIndex, const u64 sessionId)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::PartySessionId.Name,
                                                sessionId),
                "Error setting %s presence attribute", rlScAttributeId::PartySessionId.Name);
}

bool
rlPresenceAttributeHelper::SetIsPartyJoinable(const int localGamerIndex, const bool isJoinable)
{
    return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                rlScAttributeId::IsPartyJoinable.Name,
                                                isJoinable ? 1 : 0),
                "Error setting %s presence attribute", rlScAttributeId::IsPartyJoinable.Name);
}

bool
rlPresenceAttributeHelper::GetIsPartyJoinable(const int localGamerIndex, bool& isJoinable)
{
    s64 value;
    if(rlPresence::GetIntAttribute(localGamerIndex,
                                    rlScAttributeId::IsPartyJoinable.Name,
                                    &value))
    {
        isJoinable = (0 != value);
        return true;
    }

    return false;
}

bool 
rlPresenceAttributeHelper::SetHostedSessionMatchIds(const int localGamerIndex, const char* hostedMatchIds)
{
	return rlVerifyf(rlPresence::SetStringAttribute(localGamerIndex,
                                                    rlScAttributeId::HostedMatchIds.Name,
		                                            hostedMatchIds),
		             "Error setting %s presence attribute", rlScAttributeId::HostedMatchIds.Name);
}

bool 
rlPresenceAttributeHelper::SetSessionType(const int localGamerIndex, const int sessionType)
{
	return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
												 rlScAttributeId::SessionType.Name,
												 sessionType),
					 "Error setting %s presence attribute", rlScAttributeId::SessionType.Name);
}

bool
rlPresenceAttributeHelper::SetBool(const int localGamerIndex, const bool value, const char* name)
{
	return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                 name,
		                                         value ? 1 : 0),
		             "Error setting %s presence attribute", name);
}

bool
rlPresenceAttributeHelper::GetBool(const int localGamerIndex, bool& value, const char* name)
{
	s64 currentValue;
	if(rlPresence::GetIntAttribute(localGamerIndex,
                                   name,
                                   &currentValue))
	{
		value = (0 != currentValue);
		return true;
	}

	return false;
}

bool
rlPresenceAttributeHelper::SetSessionToken(const int localGamerIndex, const rlSessionToken& token, const char* name)
{
	return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                 name,
                                                 token.m_Value),
                     "Error setting %s presence attribute", name);
}

bool
rlPresenceAttributeHelper::SetSessionId(const int localGamerIndex, const u64 sessionId, const char* name)
{
	return rlVerifyf(rlPresence::SetIntAttribute(localGamerIndex,
                                                 name,
                                                 (s64)sessionId),
                     "Error setting %s presence attribute", name);
}

bool
rlPresenceAttributeHelper::SetSessionInfo(const int localGamerIndex, const rlSessionInfo& origSessionInfo, const char* name)
{
	// exporting an rlSessionInfo already clears non-advertisable data, but just to be doubly sure...
	rlSessionInfo sessionInfo = origSessionInfo;
	sessionInfo.ClearNonAdvertisableData();

	char sinfoBuf[rlSessionInfo::TO_STRING_BUFFER_SIZE] = {""};
	if(sessionInfo.IsValid())
	{
		rlVerifyf(sessionInfo.ToString(sinfoBuf),
			      "Error serializing game session info");
	}

	return rlVerifyf(rlPresence::SetStringAttribute(localGamerIndex,
                                                    name,
		                                            sinfoBuf),
		             "Error setting %s presence attribute", name);
}

}   //namespace rage
