//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS3_AVChat2.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
// Description:
//
//     wrapper for PS3 AVChat2 library
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

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

// Asura Independent includes
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// Asura PS3 includes
#include "Network/Online/Asura_PS3_NP.h"

// PS3 SDK includes
#include <cell/sysmodule.h>

// C L A S S   D E C L A R A T I O N S ///////////////////////////////////////
class Asura_PS3_AVChat2_Member
{
public:

	Asura_PS3_AVChat2_Member()
	{
		m_xRoomMemberId = 0;
		m_bIsSpeaking = false;
	}

	SceNpMatching2RoomMemberId m_xRoomMemberId;
	bool m_bIsSpeaking;
};

// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_short usASURA_PS3_VOICE_MAX_SIMULTANEOUS_SPEAKERS = 8;
static const u_int uASURA_PS3_VOICE_THRESHOLD = 8;	// volume scale from 0-10. Values >= this count as player speaking

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
Asura_PS3_AVChat2_Member* Asura_PS3_AVChat2::s_pxMembers;
u_int Asura_PS3_AVChat2::s_uMaxMembers;
u_int Asura_PS3_AVChat2::s_uNumMembers;

// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////
void Asura_PS3_AVChat2::Initialise(u_short usMaxPlayers)
{
	Asura_Assert(!s_pxMembers, "already initialised");
	s_pxMembers = new Asura_PS3_AVChat2_Member[usMaxPlayers];
	s_uMaxMembers = usMaxPlayers;
	s_uNumMembers = 0;

#ifdef ASURA_ASSERT
	int iRet = 
#endif
		cellSysmoduleLoadModule(CELL_SYSMODULE_SYSUTIL_AVCHAT2);

	Asura_Assertf(iRet == CELL_OK, ("cellSysmoduleLoadModule failed (0x%x)", iRet));

	// Initialize initial parameter structure
	CellSysutilAvc2InitParam xAvc2param;

#ifdef ASURA_ASSERT
	iRet = 
#endif
		cellSysutilAvc2InitParam(CELL_SYSUTIL_AVC2_INIT_PARAM_VERSION, &xAvc2param);
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2InitParam failed (0x%x)", iRet));

	// Specify parameters
	// Voice chat, maximum number of players, maximum number of simultaneous speakers, 
	// normal voice quality, SPU load utilization
	xAvc2param.media_type  = CELL_SYSUTIL_AVC2_VOICE_CHAT;
	xAvc2param.max_players = usMaxPlayers;
	xAvc2param.voice_param.max_speakers  = usASURA_PS3_VOICE_MAX_SIMULTANEOUS_SPEAKERS;
	xAvc2param.voice_param.voice_quality = CELL_SYSUTIL_AVC2_VOICE_QUALITY_NORMAL;
	xAvc2param.spu_load_average  = 100;

	// init the library.
#ifdef ASURA_ASSERT
	iRet = 
#endif
		cellSysutilAvc2Load(Asura_PS3_NP::GetContextID(), SYS_MEMORY_CONTAINER_ID_INVALID, AVC2Callback, NULL, &xAvc2param );
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2Load failed (0x%x)", iRet));

#ifdef ASURA_ASSERT
	iRet = 
#endif
		cellSysutilAvc2StartVoiceDetection();
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2StartVoiceDetection failed (0x%x)", iRet));
}

void Asura_PS3_AVChat2::Shutdown()
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
		cellSysutilAvc2Unload();
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2Unload failed (0x%x)", iRet));

#ifdef ASURA_ASSERT
	iRet = 
#endif
		cellSysmoduleUnloadModule(CELL_SYSMODULE_SYSUTIL_AVCHAT2);
	Asura_Assertf(iRet == CELL_OK, ("cellSysmoduleUnloadModule failed (0x%x)", iRet));

	delete[] s_pxMembers;
	s_pxMembers = NULL;
	s_uMaxMembers = 0;
	s_uNumMembers = 0;
}

#if CELL_SDK_VERSION < 0x240000
#include "Core/Asura_Multithread.h"
static bool g_bJoinChatRequestComplete;
static bool g_bLeaveChatRequestComplete;
#endif

void Asura_PS3_AVChat2::JoinChat(const SceNpMatching2RoomId* pxRoomID, SceNpMatching2RoomMemberId xLocalRoomMemberID)
{
#if CELL_SDK_VERSION >= 0x240000
	CellSysutilAvc2EventId xEventId;
	CellSysutilAvc2EventParam xEventParam;
	int iRet = cellSysutilAvc2JoinChat(pxRoomID, &xEventId, &xEventParam);
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2JoinChat failed (0x%x)", iRet));

#else
	// pre SDK 240, had to use async function. TODO: remove this code.
	g_bJoinChatRequestComplete = false;
	int iRet = cellSysutilAvc2JoinChatRequest(pxRoomID);
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2JoinChatRequest failed (0x%x)", iRet));
	if (iRet == CELL_OK)
	{
		while (!g_bJoinChatRequestComplete)
		{
			cellSysutilCheckCallback();
			Asura_Thread_System::DelayCurrentThread(0.02f);
		}
	}
#endif
	if (iRet == CELL_OK)
	{
		PlayerJoined(xLocalRoomMemberID);
	}

	iRet = cellSysutilAvc2StartStreaming();
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2StartStreaming failed (0x%x)", iRet));
}

void Asura_PS3_AVChat2::LeaveChat( bool bForcedDisconnect )
{
	int iRet = cellSysutilAvc2StopStreaming();
	Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2StopStreaming failed (0x%x)", iRet));

	if (!bForcedDisconnect)
	{
#if CELL_SDK_VERSION >= 0x240000
		CellSysutilAvc2EventId xEventId;
		CellSysutilAvc2EventParam xEventParam;
		iRet = cellSysutilAvc2LeaveChat(&xEventId, &xEventParam);
		Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2LeaveChat failed (0x%x)", iRet));
#else
		// pre SDK 240, had to use async function. TODO: remove this code.
		g_bLeaveChatRequestComplete = false;
		iRet = cellSysutilAvc2LeaveChatRequest();
		Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2LeaveChatRequest failed (0x%x)", iRet));
		if (iRet == CELL_OK)
		{
			while (!g_bLeaveChatRequestComplete)
			{
				cellSysutilCheckCallback();
				Asura_Thread_System::DelayCurrentThread(0.02f);
			}
		}
#endif
	}

	// reset num members count as we won't get "player left" callbacks for anyone left in the room.
	s_uNumMembers = 0;
}

void Asura_PS3_AVChat2::PlayerJoined(SceNpMatching2RoomMemberId xRoomMemberID)
{
	Asura_Assert(s_pxMembers, "AVChat not initialised");
	Asura_Assert(s_uNumMembers < s_uMaxMembers, "too many chat room members");
	if (s_uNumMembers < s_uMaxMembers)
	{
		s_pxMembers[s_uNumMembers].m_xRoomMemberId = xRoomMemberID;
		s_pxMembers[s_uNumMembers].m_bIsSpeaking = false;
		++s_uNumMembers;
	}
}

void Asura_PS3_AVChat2::PlayerLeft(SceNpMatching2RoomMemberId xRoomMemberID)
{
	Asura_Assert(s_pxMembers, "AVChat not initialised");
	for (u_int u = 0; u < s_uNumMembers; ++u)
	{
		if (s_pxMembers[u].m_xRoomMemberId == xRoomMemberID)
		{
			// swap with last
			s_pxMembers[u] = s_pxMembers[--s_uNumMembers];
			return;
		}
	}
	// removed this assert since we sometimes get 2 callbacks for the same player.
	//Asura_Assert(false, "player not found");
}


bool Asura_PS3_AVChat2::IsPlayerSpeaking(SceNpMatching2RoomMemberId xRoomMemberID)
{
	Asura_Assert(s_pxMembers, "AVChat not initialised");
	for (u_int u = 0; u < s_uNumMembers; ++u)
	{
		if (s_pxMembers[u].m_xRoomMemberId == xRoomMemberID)
		{
			return s_pxMembers[u].m_bIsSpeaking;
		}
	}
	Asura_Assert(false, "player not found");
	return false;
}


void Asura_PS3_AVChat2::UpdatePlayerSpeakingStatus(SceNpMatching2RoomMemberId xRoomMemberID, u_int uVolume)
{
	Asura_Assert(s_pxMembers, "AVChat not initialised");
	for (u_int u = 0; u < s_uNumMembers; ++u)
	{
		if (s_pxMembers[u].m_xRoomMemberId == xRoomMemberID)
		{
			s_pxMembers[u].m_bIsSpeaking = (uVolume >= uASURA_PS3_VOICE_THRESHOLD);
			return;
		}
	}
	Asura_Assert(false, "player not found");
}

void Asura_PS3_AVChat2::AVC2Callback( CellSysutilAvc2EventId xEventId, CellSysutilAvc2EventParam xEventParam, void* pUserdata )
{
	switch(xEventId)
	{
#if CELL_SDK_VERSION < 0x240000
		case CELL_AVC2_EVENT_JOIN_FAILED:
		{
			Asura_Assertf(false, ("cellSysutilAvc2LeaveChatRequest failed (0x%Lx)", xEventParam));
		}
		// fallthrough
		case CELL_AVC2_EVENT_JOIN_SUCCEEDED:
		{
			g_bJoinChatRequestComplete = true;
		}
		break;
		case CELL_AVC2_EVENT_LEAVE_FAILED:
		{
			Asura_Assertf(false, ("cellSysutilAvc2LeaveChatRequest failed (0x%Lx)", xEventParam));
		}
		// fallthrough
		case CELL_AVC2_EVENT_LEAVE_SUCCEEDED:
		{
			g_bLeaveChatRequestComplete = true;
		}
		break;
#endif
		case CELL_AVC2_EVENT_SYSTEM_NEW_MEMBER_JOINED:
			Asura_OutputToDebugger(("AVC2: New player joined (0x%Lx)\n", xEventParam));
			PlayerJoined(static_cast<SceNpMatching2RoomMemberId>(xEventParam & 0xffff));
			break;

		case CELL_AVC2_EVENT_SYSTEM_MEMBER_LEFT:
			Asura_OutputToDebugger(("AVC2: Another player left AV chat session (0x%Lx)\n", xEventParam));
			PlayerLeft(static_cast<SceNpMatching2RoomMemberId>(xEventParam & 0xffff));
			break;

		case CELL_AVC2_EVENT_SYSTEM_SESSION_ESTABLISHED:
			Asura_OutputToDebugger(("AVC2: P2P session with specific player was established successfully (0x%Lx)\n", xEventParam));
			break;
			
		case CELL_AVC2_EVENT_SYSTEM_SESSION_CANNOT_ESTABLISHED:
			Asura_OutputToDebugger(("AVC2: Establishment of P2P session with specific player failed (0x%Lx)\n", xEventParam));
			break;
			
		case CELL_AVC2_EVENT_SYSTEM_SESSION_DISCONNECTED: 
			Asura_OutputToDebugger(("AVC2: P2P session with specific player was disconnected (0x%Lx)\n", xEventParam));
			break;
			
		case CELL_AVC2_EVENT_SYSTEM_VOICE_DETECTED:
		{
			//Asura_OutputToDebugger(("AVC2: Notification of chat audio event (0x%Lx)\n", xEventParam)); 
			SceNpMatching2RoomMemberId xPlayerID = static_cast<SceNpMatching2RoomMemberId>((xEventParam & 0xffffffff00000000ULL) >> 32);
			u_int uVolume = xEventParam & 0xffffffff;
			UpdatePlayerSpeakingStatus(xPlayerID, uVolume);
			break;
		}

		case CELL_AVC2_EVENT_SYSTEM_MIC_DETECTED:
			Asura_OutputToDebugger(("AVC2: microphone connection status (0x%Lx)\n", xEventParam));
			break;

		default:
			Asura_OutputToDebugger(("AVC2: callback for event %d (0x%Lx)\n", xEventId, xEventParam));
			break;
	}
}

bool Asura_PS3_AVChat2::IsMicrophoneAttached()
{
	if (!s_pxMembers)
	{
		// Chat system isn't initialised yet.
		return false;
	}

	uint8_t uStatus = 0;
	const int iRet = cellSysutilAvc2IsMicAttached(&uStatus);
	if (iRet != CELL_OK)
	{
		Asura_Assertf(iRet == CELL_OK, ("cellSysutilAvc2IsMicAttached failed (0x%x)", iRet));
		return false;
	}

	return (uStatus == CELL_AVC2_MIC_STATUS_ATTACHED_ON || uStatus == CELL_AVC2_MIC_STATUS_ATTACHED_OFF);
}
