//========= Copyright Valve Corporation, All rights reserved. ============//
//
//	FILEIO.CPP
//
//	File I/O Service Routines
//=====================================================================================//
#include "vxconsole.h"

//-----------------------------------------------------------------------------
// SystemTimeToString
//
// mm/dd/yyyy hh:mm:ss am
//-----------------------------------------------------------------------------
char *SystemTimeToString( SYSTEMTIME *systemTime, char *buffer, int bufferSize )
{
	char	timeString[256];
	char	dateString[256];
	int		length;

	GetDateFormat( LOCALE_SYSTEM_DEFAULT, 0, systemTime, "MM'/'dd'/'yyyy", dateString, sizeof( dateString ) );
	GetTimeFormat( LOCALE_SYSTEM_DEFAULT, 0, systemTime, "hh':'mm':'ss tt", timeString, sizeof( timeString ) );
	length = _snprintf( buffer, bufferSize, "%s  %s", dateString, timeString );
	if ( length == -1 )
		buffer[bufferSize-1] = '\0';

	return buffer;
}

//-----------------------------------------------------------------------------
// CompareFileTimes_NTFStoFATX
//
//-----------------------------------------------------------------------------
int CompareFileTimes_NTFStoFATX( FILETIME* ntfsFileTime, char *ntfsTimeString, int ntfsStringSize, FILETIME* fatxFileTime, char *fatxTimeString, int fatxStringSize )
{
	SYSTEMTIME	ntfsSystemTime;
	SYSTEMTIME	fatxSystemTime;
	int			diff;
	int			ntfsSeconds;
	int			fatxSeconds;

	TIME_ZONE_INFORMATION tzInfo;
	GetTimeZoneInformation( &tzInfo );

	// cannot compare UTC file times directly
	// disjoint filesystems - xbox has a +/- 2s error
	// daylight savings time handling on each file system may cause problems
	FileTimeToSystemTime( ntfsFileTime, &ntfsSystemTime );
	FileTimeToSystemTime( fatxFileTime, &fatxSystemTime );

	// operate on local times, assumes xbox and pc are both set for same time zone and daylight saving
	SYSTEMTIME	ntfsLocalTime;
	SYSTEMTIME	fatxLocalTime;
	SystemTimeToTzSpecificLocalTime( &tzInfo, &ntfsSystemTime, &ntfsLocalTime );
	SystemTimeToTzSpecificLocalTime( &tzInfo, &fatxSystemTime, &fatxLocalTime );

	if ( ntfsTimeString )
	{
		SystemTimeToString( &ntfsLocalTime, ntfsTimeString, ntfsStringSize );
	}

	if ( fatxTimeString )
	{
		SystemTimeToString( &fatxLocalTime, fatxTimeString, fatxStringSize );
	}

	diff = ntfsLocalTime.wYear-fatxLocalTime.wYear;
	if ( diff )
		return diff;

	diff = ntfsLocalTime.wMonth-fatxLocalTime.wMonth;
	if ( diff )
		return diff;

	diff = ntfsLocalTime.wDay-fatxLocalTime.wDay;
	if ( diff )
		return diff;

	diff = ntfsLocalTime.wHour-fatxLocalTime.wHour;
	if ( diff )
		return diff;

	// allow for +/- 3s error
	ntfsSeconds = ntfsLocalTime.wHour*60*60 + ntfsLocalTime.wMinute*60 + ntfsLocalTime.wSecond;
	fatxSeconds = fatxLocalTime.wHour*60*60 + fatxLocalTime.wMinute*60 + fatxLocalTime.wSecond;

	diff = ntfsSeconds-fatxSeconds;
	if ( diff > 3 || diff < -3 )
		return diff;
	
	// times are considered equal
	return 0;
}

//-----------------------------------------------------------------------------
//	FreeTargetFileList
//
//-----------------------------------------------------------------------------
void FreeTargetFileList( fileNode_t* pFileList )
{
	fileNode_t	*nodePtr;
	fileNode_t	*nextPtr;

	if ( !pFileList )
		return;

	nodePtr = pFileList;
	while ( nodePtr )
	{
		nextPtr = nodePtr->nextPtr;

		Sys_Free( nodePtr->filename );
		Sys_Free( nodePtr );

		nodePtr = nextPtr;
	}
}

//-----------------------------------------------------------------------------
//	GetTargetFileList_r
//
//-----------------------------------------------------------------------------
bool GetTargetFileList_r( char* targetPath, bool recurse, int attributes, int level, fileNode_t** pFileList )
{
	HRESULT				hr;
	PDM_WALK_DIR		pWalkDir = NULL;
	DM_FILE_ATTRIBUTES	fileAttr;
	bool				valid;
	char				filename[MAX_PATH];
	fileNode_t*			nodePtr;
	bool				bGetNormal;
	int					fixedAttributes;

	if ( !level )
		*pFileList = NULL;

	fixedAttributes = attributes;
	if ( fixedAttributes & FILE_ATTRIBUTE_NORMAL )
	{
		fixedAttributes &= ~FILE_ATTRIBUTE_NORMAL;
		bGetNormal = true;
	}
	else
		bGetNormal = false;

	while ( 1 )
	{
		hr = DmWalkDir( &pWalkDir, targetPath, &fileAttr );
		if ( hr != XBDM_NOERR )
			break;

		strcpy( filename, targetPath );
		Sys_AddFileSeperator( filename, sizeof( filename ) );
		strcat( filename, fileAttr.Name );

		// restrict to desired attributes
		if ( ( bGetNormal && !fileAttr.Attributes ) ||	( fileAttr.Attributes & fixedAttributes ) )
		{
			Sys_NormalizePath( filename, false );

			// create a new file node
			nodePtr = ( fileNode_t* )Sys_Alloc( sizeof( fileNode_t ) );

			// link it in
			nodePtr->filename     = Sys_CopyString( filename );
			nodePtr->changeTime   = fileAttr.ChangeTime;
			nodePtr->creationTime = fileAttr.CreationTime;
			nodePtr->sizeHigh     = fileAttr.SizeHigh;
			nodePtr->sizeLow      = fileAttr.SizeLow;
			nodePtr->attributes   = fileAttr.Attributes;
			nodePtr->level        = level;
			nodePtr->nextPtr      = *pFileList;
			*pFileList            = nodePtr;
		}

		if ( fileAttr.Attributes & FILE_ATTRIBUTE_DIRECTORY )
		{
			if ( recurse )
			{
				// descend into directory
				valid = GetTargetFileList_r( filename, recurse, attributes, level+1, pFileList );
				if ( !valid )
					return false;
			}
		}
	}
	DmCloseDir( pWalkDir );

	if ( hr != XBDM_ENDOFLIST )
	{
		// failure
		return false;
	}

	// ok
	return true;
}

//-----------------------------------------------------------------------------
// FileSyncEx
//
// -1: failure, 0: nothing, 1: synced
//-----------------------------------------------------------------------------
int FileSyncEx( const char* localFilename, const char* targetFilename, int fileSyncMode, bool bVerbose, bool bNoWrite )
{
	bool						copy;
	bool						pathExist;
	WIN32_FILE_ATTRIBUTE_DATA	localAttributes;
	DM_FILE_ATTRIBUTES			targetAttributes;
	HRESULT						hr;
	int							errCode;
	int							deltaTime;
	char						localTimeString[256];
	char						targetTimeString[256];

	if ( ( fileSyncMode & FSYNC_TYPEMASK ) == FSYNC_OFF )
	{
		return 0;
	}

	if ( !GetFileAttributesEx( localFilename, GetFileExInfoStandard, &localAttributes ) )
	{
		// failed to get the local file's attributes
		if ( bVerbose )
		{
			ConsoleWindowPrintf( XBX_CLR_RED, "Sync Failure: Local file %s not available\n", localFilename );
		}
		return -1;
	}

	if ( localAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
	{
		// ignore directory
		return 0;
	}

	if ( fileSyncMode & FSYNC_ANDEXISTSONTARGET )
	{
		hr = DmGetFileAttributes( targetFilename, &targetAttributes );
		if ( hr != XBDM_NOERR )
		{
			// target doesn't exist, no sync operation should commence
			if ( bVerbose )
			{
				ConsoleWindowPrintf( XBX_CLR_DEFAULT, "No Update, Target file %s not available\n", targetFilename );
			}
			return 0;
		}
	}

	// default success, no operation
	errCode = 0;

	// default, create path and copy
	copy      = true;
	pathExist = false;

	if ( ( fileSyncMode & FSYNC_TYPEMASK ) == FSYNC_IFNEWER )
	{
		hr = DmGetFileAttributes( targetFilename, &targetAttributes );
		if ( hr == XBDM_NOERR )
		{
			// target path to file exists
			pathExist = true;

			// compare times
			deltaTime = CompareFileTimes_NTFStoFATX( &localAttributes.ftLastWriteTime, localTimeString, sizeof( localTimeString), &targetAttributes.ChangeTime, targetTimeString, sizeof( targetTimeString ) );
			if ( deltaTime < 0 )
			{
				// ntfs is older, fatx is newer, no update
				if ( bVerbose )
				{
					ConsoleWindowPrintf( XBX_CLR_DEFAULT, "No Update, %s [%s] is newer than %s [%s]\n", targetFilename, targetTimeString, localFilename, localTimeString );
				}
				copy = false;
			}
			else if ( !deltaTime )
			{
				// equal times, compare sizes
				if ( localAttributes.nFileSizeLow == targetAttributes.SizeLow &&
					localAttributes.nFileSizeHigh == targetAttributes.SizeHigh )
				{
					// file appears synced
					copy = false;
				}
				if ( bVerbose )
				{
					if ( copy )
					{
						ConsoleWindowPrintf( XBX_CLR_DEFAULT, "Update, %s [%s] [%d] has different size than %s [%s] [%d]\n", targetFilename, targetTimeString, targetAttributes.SizeLow, localFilename, localTimeString, localAttributes.nFileSizeLow );
					}
					else
					{
						ConsoleWindowPrintf( XBX_CLR_DEFAULT, "No Update, %s [%s] [%d] has same time and file size as %s [%s] [%d]\n", targetFilename, targetTimeString, targetAttributes.SizeLow, localFilename, localTimeString, localAttributes.nFileSizeLow );
					}
				}
			}
			else
			{
				// ntfs is newer, fatx is older, update
				if ( bVerbose )
				{
					ConsoleWindowPrintf( XBX_CLR_DEFAULT, "Update, %s [%s] is older than %s [%s]\n", targetFilename, targetTimeString, localFilename, localTimeString );
				}
			}
		}
	}
	else if ( ( fileSyncMode & FSYNC_TYPEMASK ) == FSYNC_ALWAYS )
	{
		if ( bVerbose )
		{
			ConsoleWindowPrintf( XBX_CLR_DEFAULT, "Force Update, %s\n", targetFilename );
		}
	}

	if ( copy && !bNoWrite )
	{
		if ( !pathExist )
		{
			CreateTargetPath( targetFilename );
		}

		hr = DmSendFile( localFilename, targetFilename );
		if ( hr == XBDM_NOERR )
		{
			// force the target to match the local attributes
			// to ensure sync
			memset( &targetAttributes, 0, sizeof( targetAttributes ) );
			targetAttributes.SizeHigh     = localAttributes.nFileSizeHigh;
			targetAttributes.SizeLow      = localAttributes.nFileSizeLow;
			targetAttributes.CreationTime = localAttributes.ftCreationTime;
			targetAttributes.ChangeTime   = localAttributes.ftLastWriteTime;
			DmSetFileAttributes( targetFilename, &targetAttributes );

			// success, file copied
			errCode = 1;
		}
		else
		{
			// failure
			if ( bVerbose )
			{
				ConsoleWindowPrintf( XBX_CLR_RED, "Sync Failed!\n" );
			}
			errCode = -1;
		}

		DebugCommand( "0x%8.8x = FileSyncEx( %s, %s )\n", hr, localFilename, targetFilename );
	}

	return errCode;
}

//-----------------------------------------------------------------------------
//	LoadTargetFile
//
//-----------------------------------------------------------------------------
bool LoadTargetFile( const char *pTargetPath, int *pFileSize, void **pData )
{
	DM_FILE_ATTRIBUTES	fileAttributes;
	HRESULT				hr;
	DWORD				bytesRead;
	char				*pBuffer;

	*pFileSize = 0;
	*pData     = (void *)NULL;

	hr = DmGetFileAttributes( pTargetPath, &fileAttributes );
	if ( hr != XBDM_NOERR || !fileAttributes.SizeLow )
		return false;

	// allocate for size and terminating null
	pBuffer = (char *)Sys_Alloc( fileAttributes.SizeLow+1 );

	hr = DmReadFilePartial( pTargetPath, 0, (LPBYTE)pBuffer, fileAttributes.SizeLow, &bytesRead );
  	if ( hr != XBDM_NOERR || ( bytesRead != fileAttributes.SizeLow ) )
	{
		Sys_Free( pBuffer );
		return false;
	}

	// add a terminating null
	pBuffer[fileAttributes.SizeLow] = '\0';

	*pFileSize = fileAttributes.SizeLow;
	*pData     = pBuffer;

	// success
	return true;
}

//-----------------------------------------------------------------------------
//	CreateTargetPath
//
//-----------------------------------------------------------------------------
bool CreateTargetPath( const char *pTargetFilename )
{
	// create path chain
	char	*pPath;
	char	dirPath[MAX_PATH];

	// prime and skip to first seperator
	strcpy( dirPath, pTargetFilename );
	pPath = strchr( dirPath, '\\' );
	while ( pPath )
	{		
		pPath = strchr( pPath+1, '\\' );
		if ( pPath )
		{
			*pPath = '\0';
			DmMkdir( dirPath );
			*pPath = '\\';
		}
	}

	return true;
}