//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS3_Trophies_System.cpp
//
// Copyright (c) 2009 Rebellion. All rights reserved.
//
// Description:
//
//     Wrapper class for PS3 trophy SDK. Called via Asura_Rewards_System, do
//     not use directly
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

#ifdef ASURA_USE_REWARDS
// This file's header.
#include "Asura_PS3_Trophies_System.h"

#include "Core/Memory/Asura_Mem.h"
#include "Core/Threads/Asura_ProducerConsumerQueue.h"
#include "Debugging/Asura_Logfile.h"

#include <cell/sysmodule.h>
#include <sysutil/sysutil_gamecontent.h>
#include <../../common/include/sdk_version.h>

Asura_ProducerConsumerQueue* Asura_PS3_Trophies_System::s_pxWorkerThread = 0;
Asura_Semaphore Asura_PS3_Trophies_System::s_xInitCompleteSemaphore("AP3T_InitComplete", 0, 1);
bool Asura_PS3_Trophies_System::s_bInitComplete(false);
bool Asura_PS3_Trophies_System::s_bDiskSpaceCheckPassed(false);
bool Asura_PS3_Trophies_System::s_bDiskSpaceErrorOccured(false);
u_int Asura_PS3_Trophies_System::s_uDiskSpaceTotalKRequired(0);
u_int Asura_PS3_Trophies_System::s_uDiskSpaceExtraKRequired(0);
bool Asura_PS3_Trophies_System::s_bGeneralErrorOccured(false);
int Asura_PS3_Trophies_System::s_iGeneralErrorCode(0);
u_int Asura_PS3_Trophies_System::s_uNumTrophies(0);
SceNpTrophyFlagArray Asura_PS3_Trophies_System::s_xTrophiesUnlocked;
SceNpTrophyContext Asura_PS3_Trophies_System::s_uNPTrophyContext = SCE_NP_TROPHY_INVALID_CONTEXT;
SceNpTrophyHandle Asura_PS3_Trophies_System::s_uNPTrophyHandle = SCE_NP_TROPHY_INVALID_HANDLE;


void Asura_PS3_Trophies_System::Initialise()
{
	s_pxWorkerThread = new Asura_ProducerConsumerQueue(8, 1, ASURA_MULTITHREAD_STACK_SIZE_SMALL);

	int iStatus = cellSysmoduleLoadModule(CELL_SYSMODULE_SYSUTIL_NP_TROPHY);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("cellSysmoduleLoadModule failed (0x%08x)", iStatus));
		return;
	}

	iStatus = sceNpTrophyInit(NULL, 0, SYS_MEMORY_CONTAINER_ID_INVALID, 0);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("sceNpTrophyInit failed (0x%08x)", iStatus));
		return;
	}

	iStatus = sceNpTrophyCreateContext(&s_uNPTrophyContext, Project_GetCommunicationID(), Project_GetCommunicationSig(), 0);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("sceNpTrophyCreateContext failed (0x%08x)", iStatus));
		return;
	}

	iStatus = sceNpTrophyCreateHandle(&s_uNPTrophyHandle);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("sceNpTrophyCreateHandle failed (0x%08x)", iStatus));
		return;
	}

	s_pxWorkerThread->AddToQueue(Threaded_Init, 0, 0);
}

void Asura_PS3_Trophies_System::Shutdown()
{
	// NY - 19 March 2009 - do this first, as it won't return until the thread is killed, meaning we're safe to shut the sdk stuff down.
	delete s_pxWorkerThread;
	s_pxWorkerThread = 0;

	// NY - 19 March 2009 - don't bail if the function fail, it makes more sense to keep going.
#ifdef ASURA_ASSERT 
	int iStatus =
#endif // #ifdef ASURA_ASSERT 
		sceNpTrophyDestroyHandle(s_uNPTrophyHandle);
	Asura_Assert(iStatus == CELL_OK,"sceNpTrophyDestroyHandle failed");

#ifdef ASURA_ASSERT 
	iStatus =
#endif // #ifdef ASURA_ASSERT 
		sceNpTrophyDestroyContext(s_uNPTrophyContext);
	Asura_Assert(iStatus == CELL_OK,"sceNpTrophyDestroyContext failed");

#ifdef ASURA_ASSERT 
	iStatus =
#endif // #ifdef ASURA_ASSERT 
		sceNpTrophyTerm();
	Asura_Assert(iStatus == CELL_OK,"sceNpTrophyTerm failed");

#ifdef ASURA_ASSERT 
	iStatus =
#endif // #ifdef ASURA_ASSERT 
		cellSysmoduleUnloadModule(CELL_SYSMODULE_SYSUTIL_NP_TROPHY);
	Asura_Assert(iStatus == CELL_OK,"cellSysmoduleUnloadModule failed");
}

void Asura_PS3_Trophies_System::Reset()
{
}

bool Asura_PS3_Trophies_System::IsInitialised()
{
	return s_pxWorkerThread != 0;
}

bool Asura_PS3_Trophies_System::IsInitComplete()
{
	if (!s_bInitComplete)
	{
		if (s_xInitCompleteSemaphore.TryWait())
		{
			s_bInitComplete = true;
		}
	}
	return s_bInitComplete;
}

void Asura_PS3_Trophies_System::UnlockTrophy(int iTrophy)
{
	if (iTrophy >= 0)
	{
		s_pxWorkerThread->AddToQueue(Threaded_Unlock, 0, static_cast<u_int>(iTrophy));
	}
}

bool Asura_PS3_Trophies_System::IsTrophyUnlocked(int iTrophy)
{
	if (!IsInitComplete())
	{
		// NY - 18 March 2009 - assume false if asked before init complete
		return false;
	}
	return SCE_NP_TROPHY_FLAG_ISSET(iTrophy, &s_xTrophiesUnlocked);
}

//convert bytes to k, rounding up - sony's official maths for this, ported from their #define
//64bit version for trophy code
u_long Asura_PS3_Trophies_System::SizeInK(u_long ulBytes)
{
	return (ulBytes + 1023ul) / 1024ul;
}

void Asura_PS3_Trophies_System::Threaded_Init(void* pData, u_int uSize)
{
	u_int uType = 0;
	u_int uAttributes = 0;
	CellGameContentSize xSizeData;
	char szDirName[CELL_GAME_DIRNAME_SIZE] = "";

	Asura_Mem::Set(&xSizeData, 0, sizeof(CellGameContentSize));
	int iStatus = cellGameBootCheck(&uType, &uAttributes, &xSizeData, szDirName);
	if (iStatus != CELL_GAME_RET_OK)
	{
		Asura_Assertf(false, ("cellGameBootCheck failed (0x%08x)", iStatus));
		return;
	}

	char szBootContentInfoPath[CELL_GAME_PATH_MAX];
	char szBootUsrdirPath[CELL_GAME_PATH_MAX];
	iStatus = cellGameContentPermit( szBootContentInfoPath, szBootUsrdirPath );
	if (iStatus != CELL_GAME_RET_OK)
	{
		Asura_Assertf(false, ("cellGameContentPermit failed (0x%08x)", iStatus));
		return;
	}

	uint64_t ulRequiredBytes = 0;

#if (CELL_SDK_VERSION >= 0x270000)
	iStatus = sceNpTrophyGetRequiredDiskSpace(s_uNPTrophyContext, s_uNPTrophyHandle, &ulRequiredBytes, 0);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("sceNpTrophyGetRequiredDiskSpace failed (0x%08x)", iStatus));
		return;
	}
#else // #if (CELL_SDK_VERSION >= 0x270000)
	// NY - 01 May 2009 - prior to SDK 2.7, we had to check for 50MB free for trophies whether installed or not
	ulRequiredBytes = 50 * 1024 * 1024;
#endif // #else // #if (CELL_SDK_VERSION >= 0x270000)

	Asura_OutputToDebugger(("Asura_PS3_Trophies_System::Threaded_Init - trophies require %u bytes\n", ulRequiredBytes));

	u_long ulRequiredK = SizeInK(ulRequiredBytes);

	s_uDiskSpaceTotalKRequired = Project_GetRequiredSpaceInK() + static_cast<u_int>(ulRequiredK);

	Asura_OutputToDebugger(("Asura_PS3_Trophies_System::Threaded_Init - title requires %u k\n", s_uDiskSpaceTotalKRequired));

	if(static_cast<u_int>(xSizeData.hddFreeSizeKB) < s_uDiskSpaceTotalKRequired)
	{
		//not enough space free - game needs to stop here and inform user to return to xmb and free up space
		s_uDiskSpaceExtraKRequired = s_uDiskSpaceTotalKRequired - xSizeData.hddFreeSizeKB;
		Asura_OutputToDebugger(("Asura_PS3_Trophies_System::Threaded_Init - title requires %u k extra free space\n", s_uDiskSpaceExtraKRequired));
		s_bDiskSpaceErrorOccured = true;
		return;
	}
	s_bDiskSpaceCheckPassed = true;

	do
	{
		iStatus = sceNpTrophyRegisterContext(s_uNPTrophyContext, s_uNPTrophyHandle, StatusCallback, NULL, 0);
		if (iStatus == static_cast<int>(CELL_SYSUTIL_ERROR_BUSY))
		{
			Asura_Thread_System::YieldCurrentThread();
		}
	}
	while (iStatus == static_cast<int>(CELL_SYSUTIL_ERROR_BUSY));

	if (iStatus != CELL_OK)
	{
		//this is valid at release apparently - game needs to stop here and inform user to return to xmb
		s_bGeneralErrorOccured = true;
		s_iGeneralErrorCode = iStatus;
		return;
	}

	size_t uCount = 0;
	 
	SCE_NP_TROPHY_FLAG_ZERO(&s_xTrophiesUnlocked);
	 
	iStatus = sceNpTrophyGetTrophyUnlockState(s_uNPTrophyContext, s_uNPTrophyHandle, &s_xTrophiesUnlocked, &uCount);
	if (iStatus != CELL_OK)
	{
		Asura_Assertf(false, ("sceNpTrophyGetTrophyUnlockState failed (0x%08x)", iStatus));
		return;
	}

	s_uNumTrophies = uCount;
	s_xInitCompleteSemaphore.Signal();
}

void Asura_PS3_Trophies_System::Threaded_Unlock(void* pData, u_int uTrophy)
{
	SceNpTrophyId iPlatinumId = SCE_NP_TROPHY_INVALID_TROPHY_ID;
	int iStatus = sceNpTrophyUnlockTrophy(s_uNPTrophyContext, s_uNPTrophyHandle, uTrophy, &iPlatinumId);
	// NY - 19 March 2009 -  note static casts in comparisons below - sony provide the constants in different types to what the function returns...
	if (iStatus == CELL_OK)
	{
		if (iPlatinumId != SCE_NP_TROPHY_INVALID_TROPHY_ID)
		{
			// Processing to be carried out when the platinum trophy is unlocked
		}
	}
	else if (iStatus == static_cast<int>(SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED))
	{
		//trophy already unlocked - silently carry on, making sure flag gets set.
	}
	else if (iStatus == static_cast<int>(SCE_NP_TROPHY_ERROR_SAVEDATA_USER_DOES_NOT_MATCH))
	{
		// wrong save data user - silently fail. fully TRC compliant behaviour will need to go in at project save/load level
		return;
	}
	else
	{
		Asura_Assertf(false, ("sceNpTrophyUnlockTrophy failed (0x%08x)", iStatus));
		return;
	}
	// NY - 19 March 2009 - may need to do more to get threads in sync here
	SCE_NP_TROPHY_FLAG_SET(uTrophy, &s_xTrophiesUnlocked);
}

int Asura_PS3_Trophies_System::StatusCallback(SceNpTrophyContext uContext, SceNpTrophyStatus eStatus, int iCompleted, int iTotal, void* pArg)
{
	Asura_OutputToDebugger(("Asura_PS3_Trophies_System::StatusCallback status %u %i / %i\n", eStatus, iCompleted, iTotal));
	return CELL_OK;
}

#endif // #ifdef ASURA_USE_REWARDS
