//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=====================================================================================//

#ifndef _X360
#include "xbox/xboxstubs.h"
#endif

#include "mm_framework.h"
#include "filesystem.h"

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"



static CDlcManager g_DlcManager;
CDlcManager *g_pDlcManager = &g_DlcManager;

CON_COMMAND( mm_dlc_debugprint, "Shows information about dlc" )
{
	KeyValuesDumpAsDevMsg( g_pDlcManager->GetDataInfo(), 1 );
}

//////////////////////////////////////////////////////////////////////////

CDlcManager::CDlcManager() :
	m_pDataInfo( NULL ),
	m_eState( STATE_IDLE ),
	m_flTimestamp( 0.0f ),
	m_bNeedToDiscoverAllDlcs( true ),
	m_bNeedToUpdateFileSystem( false )
{
#ifdef _X360
	m_hEnumerator = NULL;
	memset( &m_xOverlapped, 0, sizeof( m_xOverlapped ) );
#endif
}

CDlcManager::~CDlcManager()
{
	if ( m_pDataInfo )
		m_pDataInfo->deleteThis();
	m_pDataInfo = NULL;
}

void CDlcManager::Update()
{
#ifdef _X360
	// Per TCR-126 we don't want to open DLC for users who haven't unlocked the game yet
	IXboxSystem *pXboxSystem = g_pMatchExtensions->GetIXboxSystem();
	if ( !pXboxSystem || !pXboxSystem->IsArcadeTitleUnlocked() )
	{
		return;
	}

	DWORD ret = 0;
	switch ( m_eState )
	{
	case STATE_XENUMERATE:
		if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
			return;

		ret = XGetOverlappedResult( &m_xOverlapped, ( DWORD * ) &m_dwNumItems, false );
		if ( ret != ERROR_SUCCESS )
		{
			Warning( "DLCMANAGER: XContentCreateEnumerator/XEnumerate async failed with error %d!\n", ret );
			m_dwNumItems = 0;
			m_bNeedToDiscoverAllDlcs = true;
		}
		
		CloseHandle( m_hEnumerator );
		m_hEnumerator = NULL;

		CreateNextContent();
		return;

	case STATE_XCONTENT_CREATE:
		if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
			return;

		ProcessNextContent();
		return;
	}
#endif

	// Once we are idle check if we need to update the file systems list of DLC
	if ( m_eState == STATE_IDLE )
	{
		if ( m_bNeedToUpdateFileSystem )
		{
			m_bNeedToUpdateFileSystem = false;
			g_pFullFileSystem->DiscoverDLC( XBX_GetPrimaryUserId() );
		}
	}
}

void CDlcManager::RequestDlcUpdate()
{
	if ( m_eState > STATE_IDLE )
		return;

	if ( m_eState == STATE_IDLE && !m_bNeedToDiscoverAllDlcs )
	{
		Msg( "DLCMANAGER: RequestDlcUpdate has no new content.\n" );
		return;
	}
		
	// If we specified dlc via the command line we can skip the actual enumeration
	const char *pCmdLine = CommandLine()->GetCmdLine();
	if ( V_stristr( pCmdLine, "-dlc" ) )
	{
		m_eState = STATE_IDLE;
		m_bNeedToUpdateFileSystem = true;
		m_bNeedToDiscoverAllDlcs = false;
		return;
	}

#if !defined( NO_STEAM ) && !defined( SWDS )
	// Client is requesting a DLC update
	m_CallbackOnDLCInstalled.Register( this, &CDlcManager::Steam_OnDLCInstalled );
	Steam_OnDLCInstalled( NULL );
#endif

#ifdef _X360
	if ( XBX_GetPrimaryUserIsGuest() )
	{
		Msg( "DLCMANAGER: RequestDlcUpdate will not update for guests.\n" );
		return;
	}

	DWORD nBufferSize = 0;
	DWORD ret = XContentCreateEnumerator( XBX_GetPrimaryUserId(), XCONTENTDEVICE_ANY,
		XCONTENTTYPE_MARKETPLACE, 0, 100, &nBufferSize, &m_hEnumerator );
	if ( ret )
	{
		Warning( "DLCMANAGER: XContentCreateEnumerator failed with error %d!\n", ret );
		return;
	}

	if ( nBufferSize )
	{
		m_dwNumItems = 0;
		m_arrContentData.EnsureCapacity( nBufferSize/sizeof( XCONTENT_DATA ) + 1 );
		m_arrContentData.RemoveAll();
		ret = XEnumerate( m_hEnumerator, m_arrContentData.Base(), nBufferSize, NULL, &m_xOverlapped );
		if ( ret && ret != ERROR_IO_PENDING )
		{
			Warning( "DLCMANAGER: XContentCreateEnumerator/XEnumerate failed with error %d!\n", ret );
		}
		Msg( "DLCMANAGER: XContentCreateEnumerator/XEnumerate initiated.\n" );
		m_eState = STATE_XENUMERATE;
		m_flTimestamp = Plat_FloatTime();
		m_bNeedToDiscoverAllDlcs = false;
		return;
	}

	Warning( "DLCMANAGER: XContentCreateEnumerator not starting enumeration!\n" );
	::CloseHandle( m_hEnumerator );
	m_hEnumerator = NULL;
#endif
}

#ifdef _X360
void CDlcManager::CreateNextContent()
{
	Msg( "DLCMANAGER: enumeration checkpoint after %.3f sec\n", Plat_FloatTime() - m_flTimestamp );
	while ( m_dwNumItems -- > 0 )
	{
		XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
		char chKey[ XCONTENT_MAX_FILENAME_LENGTH + 1 ];
		memcpy( chKey, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );
		chKey[ XCONTENT_MAX_FILENAME_LENGTH ] = 0;

		if ( m_pDataInfo->FindKey( chKey ) )
			continue; // Already processed that DLC

		// Kick off DLC processing
		m_dwLicenseMask = 0;
		DWORD ret = XContentCreate( XBX_GetPrimaryUserId(), "PKG", pContentData, XCONTENTFLAG_OPENEXISTING, NULL, &m_dwLicenseMask, &m_xOverlapped );
		if ( ret && ( ret != ERROR_IO_PENDING ) )
		{
			Warning( "DLCMANAGER:   [%.*s] is corrupt\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName );
			continue; // assume corrupt
		}

		m_eState = STATE_XCONTENT_CREATE;
		return;
	}

	// All done
	m_eState = STATE_IDLE;
	float flTime = Plat_FloatTime() - m_flTimestamp;
	Msg( "DLCMANAGER: Full update finished after %.3f sec\n", flTime );
	KeyValuesDumpAsDevMsg( m_pDataInfo, 1 );
}

void CDlcManager::ProcessNextContent()
{
	DWORD dwResult = 0;
	DWORD ret = XGetOverlappedResult( &m_xOverlapped, &dwResult, false );
	if( ret == ERROR_SUCCESS )
	{
		XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
		char chKey[ XCONTENT_MAX_FILENAME_LENGTH + 1 ];
		memcpy( chKey, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );
		chKey[ XCONTENT_MAX_FILENAME_LENGTH ] = 0;

		Msg( "DLCMANAGER:   [%.*s] has license mask = 0x%08X\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName, m_dwLicenseMask );

		if ( !m_pDataInfo )
		{
			m_pDataInfo = new KeyValues( "DlcManager" );
			m_pDataInfo->SetUint64( "@info/installed", 0 );
		}

		if ( KeyValues *pDlc = m_pDataInfo->FindKey( chKey, true ) )
		{
			pDlc->SetInt( "licensemask", m_dwLicenseMask );
			pDlc->SetWString( "displayname", pContentData->szDisplayName );
			pDlc->SetInt( "deviceid", pContentData->DeviceID );
		}

		m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | ( 1ull << DLC_LICENSE_ID( m_dwLicenseMask ) ) );
		m_bNeedToUpdateFileSystem = true;
	}
	else
	{
		XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
		Warning( "DLCMANAGER:   [%.*s] async open failed with error %d\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName, ret );
	}
	XContentClose( "PKG", NULL );
	CreateNextContent();
}
#endif

bool CDlcManager::IsDlcUpdateFinished( bool bWaitForFinish )
{
	if ( m_eState == STATE_IDLE )
		return true;
	if ( !bWaitForFinish )
		return false;

	float flTimestamp = Plat_FloatTime();
	while ( m_eState != STATE_IDLE )
	{
		Update();
		ThreadSleep( 1 );
	}
	float flEndTimestamp = Plat_FloatTime();

	Warning( "DLCMANAGER: Forcing wait for update to finish stalled for %.3f sec\n", flEndTimestamp - flTimestamp );
	return true;
}

KeyValues * CDlcManager::GetDataInfo()
{
	return m_pDataInfo;
}

void CDlcManager::OnEvent( KeyValues *kvEvent )
{
#ifdef _X360
	char const *szEvent = kvEvent->GetName();

	if ( !Q_stricmp( "OnDowloadableContentInstalled", szEvent ) )
	{
		m_bNeedToDiscoverAllDlcs = true;
	}
	else if ( !Q_stricmp( "OnLiveMembershipPurchased", szEvent ) )
	{
		m_bNeedToDiscoverAllDlcs = true;
	}
	else if ( !Q_stricmp( "OnSysSigninChange", szEvent ) )
	{
		m_bNeedToDiscoverAllDlcs = true;
	}
#endif
}

#if !defined( NO_STEAM ) && !defined( SWDS )
void CDlcManager::Steam_OnDLCInstalled( DlcInstalled_t *pParam )
{
	m_bNeedToDiscoverAllDlcs = false;

	TitleDlcDescription_t const *dlcs = g_pMatchFramework->GetMatchTitle()->DescribeTitleDlcs();
	if ( !dlcs )
		return;

	TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();

	uint64 uiOldDlcMask = m_pDataInfo->GetUint64( "@info/installed" );
	if ( !m_pDataInfo )
	{
		m_pDataInfo = new KeyValues( "DlcManager" );
		m_pDataInfo->SetUint64( "@info/installed", 0 );
	}
	IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetPrimaryUserId() );
	for ( ; dlcs->m_uiLicenseMaskId; ++ dlcs )
	{
		// Check if DLC already detected
		if ( ( uiOldDlcMask & dlcs->m_uiLicenseMaskId ) == dlcs->m_uiLicenseMaskId )
			continue;

		// Check player profile first
		TitleDataFieldsDescription_t const *pDlcField = dlcs->m_szTitleDataBitfieldStatName ?
			TitleDataFieldsDescriptionFindByString( fields, dlcs->m_szTitleDataBitfieldStatName ) : NULL;
		if ( pDlcField && pPlayerLocal &&
			TitleDataFieldsDescriptionGetBit( pDlcField, pPlayerLocal ) )
		{
			m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | dlcs->m_uiLicenseMaskId );
			continue;
		}

		// Check Steam subscription
		if ( steamapicontext->SteamApps()->BIsSubscribedApp( dlcs->m_idDlcAppId ) )
		{
			m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | dlcs->m_uiLicenseMaskId );

			// Set player profile bit
			if ( pDlcField && pPlayerLocal )
				TitleDataFieldsDescriptionSetBit( pDlcField, pPlayerLocal, true );
		}
	}

	// Send the event in case detected DLC installed changes
	uint64 uiNewDlcMask = m_pDataInfo->GetUint64( "@info/installed" );
	if ( uiNewDlcMask != uiOldDlcMask )
	{
		KeyValues *kvEvent = new KeyValues( "OnDowloadableContentInstalled" );
		kvEvent->SetUint64( "installed", uiNewDlcMask );
		g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent );
		m_bNeedToUpdateFileSystem = true;
	}
}
#endif