//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//

#include "ps3_saveutil_v2.h"
#include "fmtstr.h"
#include "checksum_crc.h"
#include "memdbgon.h"


CON_COMMAND( ps3_saveutil_showtoc, "" )
{
	AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );

	int numTocEntries = g_SaveUtilV2TOC.m_arrEntries.Count();
	Msg( "--------- SAVEUTILTOC -----------\n" );
	for ( int k = 0; k < numTocEntries; ++ k )
	{
		CSaveUtilV2ContainerTOC::TocEntry_t &e = g_SaveUtilV2TOC.m_arrEntries[k].m_entry;
		Msg( "%02d : %016llx  %s\n"
			 "               '%s' %u/%u\n"
			 "               '%s' %u/%u\n"
			 "                %s\n",
			k + 1, e.m_timeModification, e.m_chContainerName,
			e.m_chFile[0], e.m_numBytesFile[0], e.m_numBytesDecompressedFile[0],
			e.m_chFile[1], e.m_numBytesFile[1], e.m_numBytesDecompressedFile[1],
			e.m_chComment
			);
	}
	Msg( "--------- %02d ENTRIES -----------\n", numTocEntries );
}

void SaveUtilV2_GetFileInfoSync( CUtlVector< IPS3SaveRestoreToUI::PS3SaveGameInfo_t > &saveGameInfos, bool bFindAll )
{
	// This can be called after starting a save op but before it completes, so this will return old data in that case.
	// The caller should be aware that if SaveUtil is busy then it can check the operation TAG and know what is in
	// progress and whether it can affect the TOC after it's finished.
	// Currently only UI queries the TOC and ensures that writes of savegames are completed before queries.
	AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );

	if ( !g_SaveUtilV2TOC.m_arrEntries.Count() )
	{
		saveGameInfos.RemoveAll();
		return;
	}

	int numTocEntries = bFindAll ? g_SaveUtilV2TOC.m_arrEntries.Count() : 1;
	saveGameInfos.SetCount( numTocEntries );
	for ( int k = 0; k < numTocEntries; ++ k )
	{
		saveGameInfos[k].m_InternalName = CFmtStr( "!%s", g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chContainerName );
		saveGameInfos[k].m_Comment = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chComment;
		saveGameInfos[k].m_Filename = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chFile[0];
		saveGameInfos[k].m_ScreenshotFilename = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chFile[1];
		saveGameInfos[k].m_nFileTime = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_timeModification;
	}
}

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

bool SaveUtilV2_CanStartJob()
{
	bool bResult = ( g_pSaveUtilThreadPool && !g_pSaveUtilAsyncStatus );
	if ( !bResult )
	{
		Warning( "SaveUtilV2_CanStartJob : cannot start job now! Invalid usage!\n" );
		Assert( 0 );
	}
	return bResult;
}

void SaveUtilV2_EnqueueJob( CPS3SaveRestoreAsyncStatus *pAsync, ISaveUtilV2Job *pJob )
{
	if ( g_pSaveUtilAsyncStatus )
		Error( "SaveUtilV2_EnqueueJob while job already running ( %p running, %p attempted )!\n", g_pSaveUtilAsyncStatus, pAsync );

	g_pSaveUtilAsyncStatus = pAsync;

	// Prepare for saveutil operation
	const int numContainers = VALVE_CONTAINER_COUNT;
	pJob->m_bufSaveDirList.EnsureCapacity( numContainers * MAX( sizeof( CellSaveDataFileStat ), sizeof( CellSaveDataDirList ) ) );

	// Prepare save dir info
	memset( &pJob->m_SaveDirInfo, 0, sizeof(CellSaveDataSetBuf) );
	pJob->m_SaveDirInfo.dirListMax = numContainers;
	pJob->m_SaveDirInfo.fileListMax = numContainers;
	pJob->m_SaveDirInfo.bufSize = pJob->m_bufSaveDirList.Size();
	pJob->m_SaveDirInfo.buf = pJob->m_bufSaveDirList.Base();

	// Mark the job as pending
	g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_NOTSUPPORTED;
	g_pSaveUtilAsyncStatus->m_bDone = 0;

	// Let's notify the file system that a save is starting, that way the file system can try to reduce HDD accesses and use BluRay instead
	g_pFullFileSystem->OnSaveStateChanged( true );

	// Add the job to thread pool
	pJob->SetFlags( JF_SERIAL | JF_QUEUE );
	g_pSaveUtilThreadPool->AddJob( pJob );
	pJob->Release();
}

JobStatus_t SaveUtilV2_JobDone( int nErrorCode )
{
	// Let's notify the file system that a save is finished, that way the file system can restart using the HDD
	g_pFullFileSystem->OnSaveStateChanged( false );

	// Set the job error code and set that the job is done
	if ( nErrorCode != CELL_SAVEDATA_ERROR_CBRESULT )
		g_pSaveUtilAsyncStatus->m_nSonyRetValue = nErrorCode;
	else if ( g_pSaveUtilAsyncStatus->m_nSonyRetValue >= 0 )
		g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
	
	CPS3SaveRestoreAsyncStatus *pAsync = g_pSaveUtilAsyncStatus;
	g_pSaveUtilAsyncStatus = NULL;
	pAsync->m_bDone = 1;
	return JOB_OK;
}

uint32 SaveUtilV2_ComputeBufferHash( void const *pvData, uint32 numBytes )
{
	return CRC32_ProcessSingleBuffer( pvData, numBytes );
}

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

void ISaveUtilV2Job::csDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
{
	ISaveUtilV2Job *pSelf = static_cast<ISaveUtilV2Job*>( cbResult->userdata );
	pSelf->DoDataStatCallback( SONY_SAVEUTIL_PARAMS );
}

void ISaveUtilV2Job::csDataFileCallback( SONY_SAVEUTIL_FILE_PARAMS )
{
	ISaveUtilV2Job *pSelf = static_cast<ISaveUtilV2Job*>( cbResult->userdata );
	if ( pSelf->m_pfnDoDataFileCallback )
	{
		(pSelf->*(pSelf->m_pfnDoDataFileCallback))( SONY_SAVEUTIL_PARAMS );
	}
	else
	{
		Msg( "ISaveUtilV2Job::csDataFileCallback finalizing save operation @%.3f\n", Plat_FloatTime() );
		cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
	}
}

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

void CSaveUtilV2ContainerTOC::SerializeIntoTocBuffer( void *pvBuffer )
{
	uint32 *pui32 = (uint32*) pvBuffer;
	*( pui32 ++ ) = m_idxNewSaveName;
	*( pui32 ++ ) = m_arrEntries.Count();
	V_memcpy( pui32, m_arrEntries.Base(), m_arrEntries.Count() * sizeof( TocStorageReserved_t ) );
}

void CSaveUtilV2ContainerTOC::SerializeFromTocBuffer( void *pvBuffer )
{
	uint32 *pui32 = (uint32*) pvBuffer;
	m_idxNewSaveName = *( pui32 ++ );
	uint32 uiEntriesCount = *( pui32 ++ );
	uiEntriesCount = MIN( uiEntriesCount, VALVE_CONTAINER_COUNT );
	m_arrEntries.AddMultipleToTail( uiEntriesCount, reinterpret_cast< TocStorageReserved_t * >( pui32 ) );
}

void CSaveUtilV2ContainerTOC::CopyInto( CSaveUtilV2ContainerTOC *pOther )
{
	pOther->m_idxNewSaveName = m_idxNewSaveName;
	pOther->m_arrEntries.RemoveAll();
	pOther->m_arrEntries.AddMultipleToTail( m_arrEntries.Count(), m_arrEntries.Base() );
}

int CSaveUtilV2ContainerTOC::FindByEmbeddedFileName( char const *szFilename, int *pnPartIndex )
{
	for ( int k = 0; k < m_arrEntries.Count(); ++ k )
	{
		if ( szFilename[0] == '!' )
		{
			if ( V_stricmp( m_arrEntries[k].m_entry.m_chContainerName, szFilename + 1 ) )
				continue;
			if ( pnPartIndex )
				*pnPartIndex = 0;
			return k;
		}
		for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
		{
			if ( !V_stricmp( m_arrEntries[k].m_entry.m_chFile[iPart], szFilename ) )
			{
				if ( pnPartIndex )
					*pnPartIndex = iPart;
				return k;
			}
		}
	}

	if ( pnPartIndex )
		*pnPartIndex = -1;
	return -1;
}