//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_NetLinkManager.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
// Description:
//
//     360 implementation of network link class functions.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

#include "Network/Asura_NetLinkManager.h"
#include "Network/Online/Asura_Lobby.h"

#include "Debugging/Asura_Logfile.h"

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

// Asura 360
#include "Core/Asura_XBox360_Notifications.h"
#include "Core/Asura_Xbox360_OS.h"
#include "Network/Online/Asura_Xbox360_Stats.h"

// library headers
#include <Xbox.h>

// Enum
enum ASURA_XBOX360_NETLINK_PHASE
{
	ASURA_XBOX360_NETLINK_PHASE_IDLE,
	ASURA_XBOX360_NETLINK_PHASE_STARTING,

#ifdef ASURA_SUPPORT_LOBBY
	ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_START,
	ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_UPDATE,

	ASURA_XBOX360_NETLINK_PHASE_QOSLOOKUP_START,
#endif //ASURA_SUPPORT_LOBBY
};

// Globals
static Asura_Xbox360_StatsReader* g_xTrueSkillStatsReader;

///////////////////////////////////////////////////////////////////////////////
// Asura_Xbox360_QosServiceLookup
///////////////////////////////////////////////////////////////////////////////
class Asura_Xbox360_QosServiceLookup
{
public:
	Asura_Xbox360_QosServiceLookup()
		: m_pxQosResult(0)
		, m_bDoingQosLookup(false)
		, m_dwUpBitsPerSec(0)
		, m_dwDnBitsPerSec(0)
	{}

	~Asura_Xbox360_QosServiceLookup() { Release(); }

	bool IsDoingLookup() const { return m_bDoingQosLookup; }

	void Start();	// Start lookup.
	bool Update();	// Update lookup, return true when finished.

	void Release();	// Release the results struct.

	u_int GetUploadBandwidth()   const { return static_cast<u_int>(m_dwUpBitsPerSec); }
	u_int GetDownloadBandwidth() const { return static_cast<u_int>(m_dwDnBitsPerSec); }

private:
	XNQOS* m_pxQosResult;
	bool m_bDoingQosLookup;

	// Results
	DWORD m_dwUpBitsPerSec;
	DWORD m_dwDnBitsPerSec;
};


// Start lookup.
void Asura_Xbox360_QosServiceLookup::Start()
{
	// Make sure that any previous lookup is cancelled.
	Release();

	const INT iRet = XNetQosServiceLookup(0, NULL, &m_pxQosResult);
	if (iRet == ERROR_SUCCESS)
	{
		m_bDoingQosLookup = true;
	}
	else
	{
		Asura_Assertf(false, ( "Failed to start QoS Service Lookup, error 0x%08x\n", iRet ));
	}
}

// Update lookup, return true when finished.
bool Asura_Xbox360_QosServiceLookup::Update()
{
	if (m_bDoingQosLookup)
	{
		bool bRelease = true;
		if (m_pxQosResult)
		{
			const BYTE bFlags = m_pxQosResult->axnqosinfo[0].bFlags;
			if ( (bFlags & XNET_XNQOSINFO_COMPLETE) == 0 )
			{
				// Continue waiting.
				bRelease = false;
			}
		}

		if (bRelease)
		{
			Release(); // Finished.
		}
	}

	// Finished?
	return !m_bDoingQosLookup;
}

// Release the results struct.
void Asura_Xbox360_QosServiceLookup::Release()
{
	if (m_pxQosResult)
	{	
		const BYTE bFlags = m_pxQosResult->axnqosinfo[0].bFlags;
		if ( (bFlags & XNET_XNQOSINFO_COMPLETE) != 0 )
		{
			// Copy the data, before deleting the struct.
			m_dwUpBitsPerSec = m_pxQosResult->axnqosinfo[0].dwUpBitsPerSec;
			m_dwDnBitsPerSec = m_pxQosResult->axnqosinfo[0].dwDnBitsPerSec;
		}

#if defined(ASURA_ASSERT)
		const INT iRet =
#endif	//defined(ASURA_ASSERT)
			XNetQosRelease(m_pxQosResult);
		Asura_Assertf(iRet == ERROR_SUCCESS, ( "Failed to release QoS data struct, error 0x%08x\n", iRet ));

		m_pxQosResult = 0;
	}
	m_bDoingQosLookup = false;
}


// Static data

static ASURA_XBOX360_NETLINK_PHASE g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_IDLE;

static Asura_Xbox360_QosServiceLookup s_xQosServiceLookup;


///////////////////////////////////////////////////////////////////////////////
// Asura_NetLinkManager
///////////////////////////////////////////////////////////////////////////////
// Platform_UpdateXXX() functions should call FinishedPlatformXXX() when finished.
void Asura_NetLinkManager::Platform_UpdateConnecting()
{
#ifdef ASURA_SUPPORT_LOBBY
	switch (g_eNetlinkPhase)
	{
	case ASURA_XBOX360_NETLINK_PHASE_STARTING:
		{	
			// while the system UI is open the player could be signed out of Live
			if (!Asura_Xbox360_OS::IsSystemUIOpen())
			{
				// success iff user is signed in to Live
				const u_int uUserIndex = Asura_Xbox360_OS::GetActivePlayerIndex();
				const bool bOk = (XUserGetSigninState(uUserIndex) == eXUserSigninState_SignedInToLive);
				if (bOk)
				{
					g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_START;
				}
				else
				{
					// Not signed in. Give up.
					g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_IDLE;
					FinishedPlatformConnecting(false);
				}
			}
			break;
		}

	case ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_START:
		{
			// fetch TrueSkill stats
			// first we need an xuid
			static XUID xXUID;
			#ifdef ASURA_ASSERT
			DWORD uRet = 
			#endif
				XUserGetXUID(Asura_Xbox360_OS::GetActivePlayerIndex(), &xXUID);
			Asura_Assert(uRet == ERROR_SUCCESS, "XUserGetXUID failed");
			// xStatsSpec must not be on the stack when it's used for FetchStatsForXuids().
			static XUSER_STATS_SPEC xStatsSpec;
			xStatsSpec.dwViewId = Asura_Xbox360_Stats::Project_GetTrueSkillLeaderboardID(true);
			xStatsSpec.dwNumColumnIds = 1;
			xStatsSpec.rgwColumnIds[0] = X_STATS_COLUMN_SKILL_SKILL;
			Asura_Assert(!g_xTrueSkillStatsReader, "stats reader still allocated");
			delete g_xTrueSkillStatsReader;
			g_xTrueSkillStatsReader = new Asura_Xbox360_StatsReader;
			if (!g_xTrueSkillStatsReader->FetchStatsForXuids(&xXUID, 1, xStatsSpec))
			{
				delete g_xTrueSkillStatsReader;
				g_xTrueSkillStatsReader = NULL;
			}

			// at the same time, fetch the game completion history information
			Asura_Lobby_System::GetGameCompletionHistory().Platform_GetData_Start();
			g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_UPDATE;
			break;
		}

	case ASURA_XBOX360_NETLINK_PHASE_FETCHONLINEDATA_UPDATE:
		{
			// Wait for the game completion history download and TrueSkill fetch to finish.
			const bool bGameCompletionDataDone = 
				Asura_Lobby_System::GetGameCompletionHistory().Platform_GetData_Update();
			bool bTrueSkillFetchDone = false;
			if (g_xTrueSkillStatsReader)
			{
				bTrueSkillFetchDone = g_xTrueSkillStatsReader->Update();
				if (bTrueSkillFetchDone)
				{
					int iTrueSkill = 0;
					// got the TrueSkill value.
					const XUSER_STATS_VIEW* pxData = g_xTrueSkillStatsReader->GetData();
					Asura_Assert(pxData && pxData->dwNumRows == 1 && pxData->pRows, "no TrueSkill data");
					if (pxData && pxData->dwNumRows == 1 && pxData->pRows)
					{
						const XUSER_STATS_ROW* pxRow = pxData->pRows;
						Asura_Assert(pxRow->dwNumColumns == 1 && pxRow->pColumns, "should have 1 TrueSkill column");
						if (pxRow->dwNumColumns == 1 && pxRow->pColumns)
						{
							Asura_Assert(pxRow->pColumns[0].wColumnId == X_STATS_COLUMN_SKILL_SKILL, "unexpected column ID");
							if (pxRow->pColumns[0].Value.type == XUSER_DATA_TYPE_INT64)
							{
								iTrueSkill = static_cast<int>(pxRow->pColumns[0].Value.i64Data);
							}
						}
					}
					g_xTrueSkillStatsReader->FreeData();
					delete g_xTrueSkillStatsReader;
					g_xTrueSkillStatsReader = NULL;

					Asura_OutputToDebugger_Retail(("using player TrueSkill value = %d\n", iTrueSkill));
					Asura_Lobby_System::SetLocalPlayerSkill(iTrueSkill / 3);
				}
			}
			else
			{
				bTrueSkillFetchDone = true;
			}

			if (bGameCompletionDataDone && bTrueSkillFetchDone)
			{
				g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_QOSLOOKUP_START;
			}
			break;
		}

	case ASURA_XBOX360_NETLINK_PHASE_QOSLOOKUP_START:
		{
			// Start the QoS lookup. It takes a while so we'll update
			// it in the UpdateConnected function rather than waiting here.
			s_xQosServiceLookup.Start();
			g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_IDLE;
			FinishedPlatformConnecting(true);
			break;
		}

	default:
		{
			// Unexpected state, abort.
			Asura_Assert(false, "unexpected connection phase");
			g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_IDLE;
			FinishedPlatformConnecting(false);
			break;
		}
	}
#else
	g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_IDLE;
	FinishedPlatformConnecting(true);
#endif
}

void Asura_NetLinkManager::Platform_UpdateConnecting_PostRender()
{
}

void Asura_NetLinkManager::Platform_UpdateDisconnecting()
{
	FinishedPlatformDisconnecting();
}

void Asura_NetLinkManager::Platform_UpdateConnected()
{
	if (s_xQosServiceLookup.IsDoingLookup() && s_xQosServiceLookup.Update())
	{
		// Finished checking QoS.
		s_uUploadBitsPerSec   = s_xQosServiceLookup.GetUploadBandwidth();
		s_uDownloadBitsPerSec = s_xQosServiceLookup.GetDownloadBandwidth();

		Asura_OutputToDebugger_Retail(("Finished Qos Service Lookup: upload: %u kbps   download: %u kbps\n", (s_uUploadBitsPerSec/1000), (s_uDownloadBitsPerSec/1000)));
	}
}

// Platform_InitiateConnection() function should return true iff requested operation was started.
bool Asura_NetLinkManager::Platform_InitiateConnection()
{
	g_eNetlinkPhase = ASURA_XBOX360_NETLINK_PHASE_STARTING;

	// check if cable is plugged in and active
	if (XNetGetEthernetLinkStatus() == 0)
	{
		return false;
	}

#ifdef ASURA_SUPPORT_LOBBY
	// ensure that players are signed in to Live
	u_int uUserIndex = Asura_Xbox360_OS::GetActivePlayerIndex();
	if (XUserGetSigninState(uUserIndex) != eXUserSigninState_SignedInToLive)
	{
		const u_int uNumPanes = 1;
		if (XShowSigninUI(uNumPanes, XSSUI_FLAGS_SHOWONLYONLINEENABLED) == 0)
		{
			// wait for notification that the guide is up.
			do
			{ 
				Asura_XBox360_Notifications::Update();
				Sleep(250); 
			} while (!Asura_Xbox360_OS::IsSystemUIOpen());
		}
	}
#endif

	return true;
}

void Asura_NetLinkManager::Platform_InitiateDisconnection()
{
	// Make sure that we've released the QoS structs.
	s_xQosServiceLookup.Release();
}


#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

