//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS3_NP_Basic.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     wrapper for PS3 NP Basic API, which provides friends list, block list,
//     recent players etc.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Network/Online/Asura_PS3_NP_Basic.h"

// Asura headers
#include "Interface/Asura_CommandConsole.h"
#include "Network/Online/Asura_PS3_NP.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Multithread.h"


// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_int uASURA_PS3_MIN_FRIENDLIST_SIZE = 100;
static const u_int uASURA_PS3_MAX_FRIENDLIST_SIZE = 256;
static const u_int uASURA_PS3_NP_BASIC_MAX_EVENTS = 128;
static const u_int uASURA_PS3_MAX_BLOCKLIST_SIZE = 50;

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
SceNpId* Asura_PS3_NP_Basic::s_pxFriends;
u_int Asura_PS3_NP_Basic::s_uNumFriends;
u_int Asura_PS3_NP_Basic::s_uFriendlistArraySize;
SceNpId* Asura_PS3_NP_Basic::s_pxBlockList;
u_int Asura_PS3_NP_Basic::s_uBlockListSize;
u_int Asura_PS3_NP_Basic::s_uInvitationCount;
bool Asura_PS3_NP_Basic::s_bNewInvitation;
Asura_PS3_InvitationInfo Asura_PS3_NP_Basic::s_xInviteInfo;
bool Asura_PS3_NP_Basic::s_bInvitePending;
Asura_RingBuffer<Asura_PS3_NP_Basic_Event>* Asura_PS3_NP_Basic::s_pxEventBuffer;
Asura_Mutex* Asura_PS3_NP_Basic::s_pxMutex;

// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////
void Asura_PS3_NP_Basic::Initialise()
{
	// build up list of friends
	u_int uNumFriends = 0;
	int iRet = sceNpBasicGetFriendListEntryCount(&uNumFriends);
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetFriendListEntryCount() failed 0x%x", iRet));

	// minimum of 100 friends allocated to ensure a stable memory footprint.
	// allow > 100 friends just in case it ever changes again.
	s_uFriendlistArraySize = Asura_Maths::ClampToRange(uNumFriends, uASURA_PS3_MIN_FRIENDLIST_SIZE, uASURA_PS3_MAX_FRIENDLIST_SIZE);
	s_pxFriends = new SceNpId[s_uFriendlistArraySize];

	s_pxBlockList = new SceNpId[uASURA_PS3_MAX_BLOCKLIST_SIZE];

	// setup event queue
	s_pxEventBuffer = new Asura_RingBuffer<Asura_PS3_NP_Basic_Event>;
	s_pxEventBuffer->Create(uASURA_PS3_NP_BASIC_MAX_EVENTS);
	s_pxMutex = new Asura_Mutex;

	// register callback
	iRet = sceNpBasicRegisterHandler(&Asura_PS3_NP::GetCommunicationID(), EventHandler, NULL);
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicRegisterHandler() failed 0x%x", iRet));

	// fill out the friends list and number of invitations
	Refresh();
}

void Asura_PS3_NP_Basic::Shutdown()
{
	delete s_pxMutex;
	s_pxMutex = NULL;

	delete s_pxEventBuffer;
	s_pxEventBuffer = NULL;

#ifdef ASURA_ASSERT
	int iRet = 
#endif
	sceNpBasicUnregisterHandler();
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicRegisterHandler() failed 0x%x", iRet));

	delete[] s_pxBlockList;
	s_uBlockListSize = 0;
	delete[] s_pxFriends;
	s_pxFriends = NULL;
	s_uNumFriends = 0;
	s_uFriendlistArraySize = 0;
}

void Asura_PS3_NP_Basic::AddConsoleCmdsAndVars()
{
	const Asura_UnicodeChar* wszNewInvite = L"Asura.PS3NP.NewInvite";
	Asura_CommandConsole::AddVar( wszNewInvite, &s_bNewInvitation, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::MakeVarReadOnly( wszNewInvite );
	const Asura_UnicodeChar* wszNumInvites = L"Asura.PS3NP.NumInvites";
	Asura_CommandConsole::AddVar( wszNumInvites, &s_uInvitationCount, 0, INT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::MakeVarReadOnly( wszNumInvites );
	Asura_CommandConsole::AddCmd( L"Asura.PS3NP.ResetNewInviteFlag", ConsoleCmd_ResetNewInviteFlag, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddCmd( L"Asura.PS3NP.StartInvitationGui", ConsoleCmd_StartInvitationGui, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
}

bool Asura_PS3_NP_Basic::IsPlayerAFriend(const SceNpId& xNpId)
{
	for (u_int u = 0; u < s_uNumFriends; ++u)
	{
		if (sceNpUtilCmpNpId(&xNpId, &s_pxFriends[u]) == 0)
		{
			return true;
		}
	}
	return false;
}

bool Asura_PS3_NP_Basic::IsPlayerOnBlockList(const SceNpId& xNpId)
{
	for (u_int u = 0; u < s_uBlockListSize; ++u)
	{
		if (sceNpUtilCmpNpId(&xNpId, &s_pxBlockList[u]) == 0)
		{
			return true;
		}
	}
	return false;
}

void Asura_PS3_NP_Basic::AddPlayerToRecentPlayersList( const SceNpId& xID )
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		sceNpBasicAddPlayersHistory(&xID, NULL);
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicAddPlayersHistory() failed 0x%x", iRet));
}

void Asura_PS3_NP_Basic::AddPlayersToRecentPlayersList( const SceNpId* pxIDs, u_int uNumPlayers )
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		sceNpBasicAddPlayersHistoryAsync(pxIDs, uNumPlayers, NULL, NULL);
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicAddPlayersHistoryAsync() failed 0x%x", iRet));
}

void Asura_PS3_NP_Basic::Update()
{
	Asura_Assert(s_pxMutex, "not initialised");
	// pull events off the queue to be processed
	Asura_PS3_NP_Basic_Event* pxEvent = NULL;
	do 
	{
		Asura_PS3_NP_Basic_Event xEventCopy;
		s_pxMutex->Lock();
		pxEvent = s_pxEventBuffer->Peek();
		if (pxEvent)
		{
			// make copy of event data so we can remove it from queue and unlock the mutex
			xEventCopy = *pxEvent;
			pxEvent = &xEventCopy;
			s_pxEventBuffer->Drop();
		}
		s_pxMutex->Unlock();

		if (pxEvent)
		{
			ProcessEvent(pxEvent);
		}
	} 
	while (pxEvent);
}

int Asura_PS3_NP_Basic::EventHandler( int iEvent, int iRetCode, u_int uReqId, void* pArg )
{
	if (!s_pxMutex)
	{
		// maybe shutting down?
		return 0;
	}
	// lock the mutex and add to the event queue.
	s_pxMutex->Lock();
	Asura_PS3_NP_Basic_Event* pxEvent = s_pxEventBuffer->Alloc();
	Asura_Assert(pxEvent, "failed to allocate event from queue");
	if (pxEvent)
	{
		pxEvent->m_iEvent = iEvent;
		pxEvent->m_iRetCode = iRetCode;
	}
	s_pxMutex->Unlock();
	return 0;
}

#ifdef ASURA_NO_DEBUG_OUTPUT
#define OUTPUT_CASE_TO_DEBUGGER( x )
#else
#define OUTPUT_CASE_TO_DEBUGGER( x ) \
		case (x): Asura_OutputToDebugger(("Received " #x "\n")); break
#endif

union Asura_PS3_NP_Basic_EventUnion
{
	u_char m_aucBuffer[SCE_NP_BASIC_MAX_MESSAGE_SIZE];
	SceNpBasicPresenceDetails m_xPresenceDetails;
	SceNpBasicExtendedAttachmentData m_xExtendedAttachmentData;
};

void Asura_PS3_NP_Basic::ProcessEvent(const Asura_PS3_NP_Basic_Event* pxEvent)
{
	// process the event
	switch (pxEvent->m_iEvent)
	{
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_UNKNOWN);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_OFFLINE);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_PRESENCE);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_MESSAGE);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_ADD_FRIEND_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_INCOMING_ATTACHMENT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_INCOMING_INVITATION);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_END_OF_INITIAL_PRESENCE);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_SEND_ATTACHMENT_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_RECV_ATTACHMENT_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_OUT_OF_CONTEXT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_FRIEND_REMOVED);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_ADD_BLOCKLIST_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_SEND_MESSAGE_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_SEND_INVITATION_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_RECV_INVITATION_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_MESSAGE_MARKED_AS_USED_RESULT);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_INCOMING_CUSTOM_INVITATION);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_INCOMING_CLAN_MESSAGE);
		OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_EVENT_ADD_PLAYERS_HISTORY_RESULT);
		default:
		{
			Asura_OutputToDebugger(("received unknown event %d\n", pxEvent->m_iEvent));
			break;
		}

	}

	SceNpUserInfo xUser;	//Buffer to store contact's NP user info
	Asura_PS3_NP_Basic_EventUnion xBuffer;
	int iEvent;
	size_t uSize = sizeof(xBuffer);

	int iRet = sceNpBasicGetEvent(&iEvent, &xUser, &xBuffer, &uSize);
	if ( iRet < 0 )
	{
		//Error handling
		switch (iRet)
		{
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_NOT_INITIALIZED);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_NOT_SUPPORTED);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_OUT_OF_MEMORY);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_INVALID_ARGUMENT);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_NO_EVENT);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_NOT_REGISTERED);
			OUTPUT_CASE_TO_DEBUGGER(SCE_NP_BASIC_ERROR_DATA_LOST );
			default:
			{
				Asura_OutputToDebugger(("sceNpBasicGetEvent returned unknown error code %d\n", iRet));
				break;
			}
		}
	}
	else
	{
		switch (iEvent)
		{
			case SCE_NP_BASIC_EVENT_OFFLINE:
			case SCE_NP_BASIC_EVENT_PRESENCE:
			{
				AddFriend(xUser.userId);
				break;
			}

			case SCE_NP_BASIC_EVENT_INCOMING_CUSTOM_INVITATION:
			{
				// record the existence of another invitation
				++s_uInvitationCount;
				s_bNewInvitation = true;
				break;
			}

			case SCE_NP_BASIC_EVENT_RECV_INVITATION_RESULT:
			{
				const SceNpBasicExtendedAttachmentData& xAttachmentDetails = xBuffer.m_xExtendedAttachmentData;
				if (xAttachmentDetails.userAction == SCE_NP_BASIC_MESSAGE_ACTION_ACCEPT)
				{
					u_int uSize = sizeof(s_xInviteInfo);
					iRet = sceNpBasicRecvMessageAttachmentLoad(xAttachmentDetails.data.id, &s_xInviteInfo, &uSize);
					Asura_Assertf(iRet == CELL_OK, ("sceNpBasicRecvMessageCustom() failed 0x%x", iRet));
					if (iRet == CELL_OK)
					{
						s_bInvitePending = true;
					}
				}

				// refresh the invitation count. could perhaps just decrement it if the action wasn't "cancel".
				iRet = sceNpBasicGetCustomInvitationEntryCount( &s_uInvitationCount );
				Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetCustomInvitationEntryCount() failed 0x%x", iRet));

				break;
			}

			default:
			{
				// not interested
				break;
			}
		}
	}

}

#undef OUTPUT_CASE_TO_DEBUGGER


void Asura_PS3_NP_Basic::Refresh()
{
	if (!s_pxFriends)
	{
		// probably called by offline mode, will be setup correctly when we become initialised, .
		return;
	}

	{
		u_int uNumFriends = 0;
		int iRet = sceNpBasicGetFriendListEntryCount(&uNumFriends);
		Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetFriendListEntryCount() failed 0x%x", iRet));

		if (iRet == CELL_OK)
		{
			// clamp to array size
			uNumFriends = Asura_Maths::Min(uNumFriends, s_uFriendlistArraySize);

			s_uNumFriends = 0;
			for (u_int u = 0; u < uNumFriends; ++u)
			{
				iRet = sceNpBasicGetFriendListEntry(u, &s_pxFriends[u]);
				Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetFriendListEntry() failed 0x%x", iRet));
				if (iRet < 0)
				{
					break;
				}
				++s_uNumFriends;
			}
		}
	}

	{
		Asura_Assert(s_pxBlockList, "friends list allocated but block list wasn't");
		u_int uBlockListEntries = 0;
		int iRet = sceNpBasicGetBlockListEntryCount(&uBlockListEntries);
		Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetBlockListEntryCount() failed 0x%x", iRet));
		if (iRet == CELL_OK)
		{
			// clamp to array size
			uBlockListEntries = Asura_Maths::Min(uBlockListEntries, uASURA_PS3_MAX_BLOCKLIST_SIZE);
			
			s_uBlockListSize = 0;
			for (u_int u = 0; u < uBlockListEntries; ++u)
			{
				iRet = sceNpBasicGetBlockListEntry(u, &s_pxBlockList[u]);
				Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetBlockListEntry() failed 0x%x", iRet));
				if (iRet < 0)
				{
					break;
				}
				++s_uBlockListSize;
			}
		}
	}

	// get number of invitations
	{
#ifdef ASURA_ASSERT
		int iRet = 
#endif
			sceNpBasicGetCustomInvitationEntryCount( &s_uInvitationCount );
		Asura_Assertf(iRet == CELL_OK, ("sceNpBasicGetCustomInvitationEntryCount() failed 0x%x", iRet));
	}
}

const Asura_PS3_InvitationInfo& Asura_PS3_NP_Basic::GetInviteInfo()
{
	Asura_Assert(s_bInvitePending, "should check for invite pending first");
	s_bInvitePending = false;
	return s_xInviteInfo;
}

// console interface for use in GUIMenus
bool Asura_PS3_NP_Basic::ConsoleCmd_StartInvitationGui(bool bHelp)
{
	if (!bHelp && IsInitialised())
	{
		StartInvitationGui();
	}
	return true;
}

bool Asura_PS3_NP_Basic::ConsoleCmd_ResetNewInviteFlag(bool bHelp)
{
	if (!bHelp && IsInitialised())
	{
		ResetNewInviteFlag();
	}
	return true;
}

void Asura_PS3_NP_Basic::StartInvitationGui()
{
	// display the PS3 UI which lets you choose an invitation and choose join/don't join.
	Asura_Assert(s_uInvitationCount > 0, "should only be called when there are outstanding invitations");
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		sceNpBasicRecvMessageCustom(SCE_NP_BASIC_MESSAGE_MAIN_TYPE_INVITE, 0, SYS_MEMORY_CONTAINER_ID_INVALID);
	Asura_Assertf(iRet == CELL_OK, ("sceNpBasicRecvMessageCustom() failed 0x%x", iRet));

	s_bNewInvitation = false;
}


void Asura_PS3_NP_Basic::AddFriend(const SceNpId& xNpId)
{
	if (!IsPlayerAFriend(xNpId) && s_uNumFriends < s_uFriendlistArraySize)
	{
		s_pxFriends[s_uNumFriends++] = xNpId;
	}
}
