//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#ifndef UTLCACHEDFILEDATA_H
#define UTLCACHEDFILEDATA_H
#if defined( WIN32 )
#pragma once
#endif

#include "filesystem.h" // FileNameHandle_t
#include "utlrbtree.h"
#include "utlbuffer.h"
#include "UtlSortVector.h"
#include "tier1/strtools.h"

#include "tier0/memdbgon.h"

// If you change to serialization protocols, this must be bumped...
#define UTL_CACHE_SYSTEM_VERSION		2

#define UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO	(long)-2

// Cacheable types must derive from this and implement the appropriate methods...
abstract_class IBaseCacheInfo
{
public:
	virtual void Save( CUtlBuffer& buf ) = 0;
	virtual void Restore( CUtlBuffer& buf ) = 0;

	virtual void Rebuild( char const *filename ) = 0;
};

typedef unsigned int (*PFNCOMPUTECACHEMETACHECKSUM)( void );

typedef enum
{
	UTL_CACHED_FILE_USE_TIMESTAMP = 0,
	UTL_CACHED_FILE_USE_FILESIZE,
} UtlCachedFileDataType_t;

template <class T>
class CUtlCachedFileData
{
public:
	CUtlCachedFileData
	( 
		char const *repositoryFileName, 
		int version, 
		PFNCOMPUTECACHEMETACHECKSUM checksumfunc = NULL, 
		UtlCachedFileDataType_t fileCheckType = UTL_CACHED_FILE_USE_TIMESTAMP,
		bool nevercheckdisk = false,
		bool readonly = false,
		bool savemanifest = false
	)
		: m_Elements( 0, 0, FileNameHandleLessFunc ),
		m_sRepositoryFileName( repositoryFileName ),
		m_nVersion( version ),
		m_pfnMetaChecksum( checksumfunc ),
		m_bDirty( false ),
		m_bInitialized( false ),
		m_uCurrentMetaChecksum( 0u ),
		m_fileCheckType( fileCheckType ),
		m_bNeverCheckDisk( nevercheckdisk ),
		m_bReadOnly( readonly ),
		m_bSaveManifest( savemanifest )
	{
		Assert( !m_sRepositoryFileName.IsEmpty() );
	}

	virtual ~CUtlCachedFileData()
	{
		m_Elements.RemoveAll();
		int c = m_Data.Count();
		for ( int i = 0; i < c ; ++i )
		{
			delete m_Data[ i ];
		}
		m_Data.RemoveAll();
	}

	// If bExpectMissing is set, don't complain if this causes synchronous disk I/O to build the cache.
	T* Get( char const *filename );
	const T* Get( char const *filename ) const;

	T* operator[]( int i );
	const T* operator[]( int i ) const;

	int Count() const;

	void GetElementName( int i, char *buf, int buflen )
	{
		buf[ 0 ] = 0;
		if ( !m_Elements.IsValidIndex( i ) )
			return;

		g_pFullFileSystem->String( m_Elements[ i ].handle, buf, buflen );
	}

	bool EntryExists( char const *filename ) const
	{
		ElementType_t element;
		element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
		int idx = m_Elements.Find( element );
		return idx != m_Elements.InvalidIndex() ? true : false;
	}

	void SetElement( char const *name, long fileinfo, T* src )
	{
		SetDirty( true );

		int idx = GetIndex( name );

		Assert( idx != m_Elements.InvalidIndex() );

		ElementType_t& e = m_Elements[ idx ];

		CUtlBuffer buf( 0, 0, 0 );

		Assert( e.dataIndex != m_Data.InvalidIndex() );

		T *dest = m_Data[ e.dataIndex ];

		Assert( dest );

		// I suppose we could do an assignment operator, but this should save/restore the data element just fine for
		//  tool purposes
		((IBaseCacheInfo *)src)->Save( buf );
		((IBaseCacheInfo *)dest)->Restore( buf );

		e.fileinfo = fileinfo;
		if ( ( e.fileinfo == -1 ) &&
			( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
		{
			e.fileinfo = 0;
		}
		// Force recheck
		e.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
	}

	// If you create a cache and don't call init/shutdown, you can call this to do a quick check to see if the checksum/version
	//  will cause a rebuild...
	bool	IsUpToDate();

	void	Shutdown();
	bool	Init();

	void	Save();

	void	Reload();

	void	ForceRecheckDiskInfo();
	// Iterates all entries and gets filesystem info and optionally causes rebuild on any existing items which are out of date
	void	CheckDiskInfo( bool force_rebuild, time_t cacheFileTime = 0L );

	void	SaveManifest();
	bool	ManifestExists();

	const char *GetRepositoryFileName() const { return m_sRepositoryFileName; }

	long	GetFileInfo( char const *filename )
	{
		ElementType_t element;
		element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
		int idx = m_Elements.Find( element );
		if ( idx == m_Elements.InvalidIndex() )
		{
			return 0L;
		}

		return m_Elements[ idx ].fileinfo;
	}

	int		GetNumElements()
	{
		return m_Elements.Count();
	}

	bool		IsDirty() const
	{
		return m_bDirty;
	}

	T *RebuildItem( const char *filename );

private:

	void		InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile );
	void		InitLargeBuffer( FileHandle_t& fh, bool& deleteFile );

	int			GetIndex( const char *filename )
	{
		ElementType_t element;
		element.handle = g_pFullFileSystem->FindOrAddFileName( filename );
		int idx = m_Elements.Find( element );
		if ( idx == m_Elements.InvalidIndex() )
		{
			T *data = new T();

			int dataIndex = m_Data.AddToTail( data );
			idx = m_Elements.Insert( element );
			m_Elements[ idx ].dataIndex = dataIndex;
		}

		return idx;
	}

	void		CheckInit();

	void		SetDirty( bool dirty )
	{
		m_bDirty = dirty;
	}

	void		RebuildCache( char const *filename, T *data );

	struct ElementType_t
	{
		ElementType_t() :
			handle( 0 ),
			fileinfo( 0 ),
			diskfileinfo( UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ),
			dataIndex( -1 )
		{
		}

		FileNameHandle_t	handle;
		long long			fileinfo;
		long long			diskfileinfo;
		int					dataIndex;
	};

	static bool FileNameHandleLessFunc( ElementType_t const &lhs, ElementType_t const &rhs )
	{
		return lhs.handle < rhs.handle;
	}

	CUtlRBTree< ElementType_t >		m_Elements;
	CUtlVector< T * >				m_Data;
	CUtlString						m_sRepositoryFileName;
	int								m_nVersion;
	PFNCOMPUTECACHEMETACHECKSUM		m_pfnMetaChecksum;
	unsigned int					m_uCurrentMetaChecksum;
	UtlCachedFileDataType_t			m_fileCheckType;
	bool							m_bNeverCheckDisk : 1;
	bool							m_bReadOnly		  : 1;
	bool							m_bSaveManifest   : 1;
	bool							m_bDirty          : 1;
	bool							m_bInitialized    : 1;

};


template <class T>
T* CUtlCachedFileData<T>::Get( char const *filename )
{
	int idx = GetIndex( filename );

	ElementType_t& e = m_Elements[ idx ];

	if ( e.fileinfo == -1 &&
		m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE )
	{
		e.fileinfo = 0;
	}
	long cachefileinfo = e.fileinfo;
	// Set the disk fileinfo the first time we encounter the filename
	if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
	{
		if ( m_bNeverCheckDisk )
		{
			e.diskfileinfo = cachefileinfo;
		}
		else
		{
			if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) 
			{
				e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
				// Missing files get a disk file size of 0
				if ( e.diskfileinfo == -1 )
				{
					e.diskfileinfo = 0;
				}
			}
			else
			{
				e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
			}
		}
	}

	Assert( e.dataIndex != m_Data.InvalidIndex() );

	T *data = m_Data[ e.dataIndex ];

	Assert( data );

	// Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
	if ( cachefileinfo != e.diskfileinfo )
	{
		if ( !m_bReadOnly )
		{
			RebuildCache( filename, data );
		}
		e.fileinfo = e.diskfileinfo;
	}

	return data;
}

template <class T>
const T* CUtlCachedFileData<T>::Get( char const *filename ) const
{
	return const_cast< CUtlCachedFileData<T> * >(this)->Get( filename );
}

template <class T>
T* CUtlCachedFileData<T>::operator[]( int i )
{
	return m_Data[ m_Elements[ i ].dataIndex ];
}

template <class T>
const T* CUtlCachedFileData<T>::operator[]( int i ) const
{
	return m_Data[ m_Elements[ i ].dataIndex ];
}

template <class T>
int CUtlCachedFileData<T>::Count() const
{
	return m_Elements.Count();
}

template <class T>
void CUtlCachedFileData<T>::Reload()
{
	Shutdown();
	Init();
}

template <class T>
bool CUtlCachedFileData<T>::IsUpToDate()
{
	// Don't call Init/Shutdown if using this method!!!
	Assert( !m_bInitialized );

	if ( m_sRepositoryFileName.IsEmpty() )
	{
		Error( "CUtlCachedFileData:  Can't IsUpToDate, no repository file specified." );
		return false;
	}

	// Always compute meta checksum
	m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;

	FileHandle_t fh;

	fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
	if ( fh == FILESYSTEM_INVALID_HANDLE )
	{
		return false;
	}

	// Version data is in first 12 bytes of file
	byte	header[ 12 ];
	g_pFullFileSystem->Read( header, sizeof( header ), fh );
	g_pFullFileSystem->Close( fh );

	int cacheversion = *( int *)&header[ 0 ];

	if ( UTL_CACHE_SYSTEM_VERSION != cacheversion )
	{
		DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
		Assert( !m_bReadOnly );
		if ( !m_bReadOnly )
		{
			g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
		}
		return false;
	}

	// Now parse data from the buffer
	int version = *( int *)&header[ 4 ];
	if ( version != m_nVersion )
	{
		DevMsg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
		Assert( !m_bReadOnly );
		if ( !m_bReadOnly )
		{
			g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
		}
		return false;
	}

	// This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
	//  meta data function
	unsigned int cache_meta_checksum = (unsigned int)*( int *)&header[ 8 ];

	if ( cache_meta_checksum != m_uCurrentMetaChecksum )
	{
		DevMsg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
		Assert( !m_bReadOnly );
		if ( !m_bReadOnly )
		{
			g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
		}
		return false;
	}

	// Looks valid
	return true;
}

template <class T>
void CUtlCachedFileData<T>::InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile )
{
	deleteFile = false;

	CUtlBuffer loadBuf;
	g_pFullFileSystem->ReadToBuffer( fh, loadBuf );
	g_pFullFileSystem->Close( fh );

	int cacheversion = 0;
	loadBuf.Get( &cacheversion, sizeof( cacheversion ) );

	if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
	{
		// Now parse data from the buffer
		int version = loadBuf.GetInt();
		
		if ( version == m_nVersion )
		{
			// This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
			//  meta data function
			unsigned int cache_meta_checksum = loadBuf.GetInt();
			
			if ( cache_meta_checksum == m_uCurrentMetaChecksum )
			{
				int count = loadBuf.GetInt();
				
				Assert( count < 2000000 );

				CUtlBuffer buf( 0, 0, 0 );

				for ( int i = 0 ; i < count; ++i )
				{
					int bufsize = loadBuf.GetInt();
					Assert( bufsize < 1000000 );

					buf.Clear();
					buf.EnsureCapacity( bufsize );
					
					loadBuf.Get( buf.Base(), bufsize );

					buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
					buf.SeekPut( CUtlBuffer::SEEK_HEAD, bufsize );

					// Read the element name
					char elementFileName[ 512 ];
					buf.GetString( elementFileName );

					// Now read the element
					int slot = GetIndex( elementFileName );

					Assert( slot != m_Elements.InvalidIndex() );

					ElementType_t& element = m_Elements[ slot ];

					element.fileinfo = buf.GetInt();
					if ( ( element.fileinfo == -1 ) &&
						( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
					{
						element.fileinfo = 0;
					}

					Assert( element.dataIndex != m_Data.InvalidIndex() );

					T *data = m_Data[ element.dataIndex ];

					Assert( data );

					((IBaseCacheInfo *)data)->Restore( buf );
				}
			}
			else
			{
				Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
				deleteFile = true;
			}
		}
		else
		{
			Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
			deleteFile = true;
		}
	}
	else
	{
		DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
		deleteFile = true;
	}
}

template <class T>
void CUtlCachedFileData<T>::InitLargeBuffer( FileHandle_t& fh, bool& deleteFile )
{
	deleteFile = false;

	int cacheversion = 0;
	g_pFullFileSystem->Read( &cacheversion, sizeof( cacheversion ), fh );

	if ( UTL_CACHE_SYSTEM_VERSION == cacheversion )
	{
		// Now parse data from the buffer
		int version = 0;
		g_pFullFileSystem->Read( &version, sizeof( version ), fh );
		
		if ( version == m_nVersion )
		{
			// This is a checksum based on any meta data files which the cache depends on (supplied by a passed in
			//  meta data function
			unsigned int cache_meta_checksum = 0;
			
			g_pFullFileSystem->Read( &cache_meta_checksum, sizeof( cache_meta_checksum ), fh );

			if ( cache_meta_checksum == m_uCurrentMetaChecksum )
			{
				int count = 0;
				
				g_pFullFileSystem->Read( &count, sizeof( count ), fh );

				Assert( count < 2000000 );

				CUtlBuffer buf( 0, 0, 0 );

				for ( int i = 0 ; i < count; ++i )
				{
					int bufsize = 0;
					g_pFullFileSystem->Read( &bufsize, sizeof( bufsize ), fh );

					Assert( bufsize < 1000000 );
					if ( bufsize > 1000000 )
					{
						Msg( "Discarding repository '%s' due to corruption\n", m_sRepositoryFileName.String() );
						deleteFile = true;
						break;
					}


					buf.Clear();
					buf.EnsureCapacity( bufsize );
					
					int nBytesRead = g_pFullFileSystem->Read( buf.Base(), bufsize, fh );

					buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
					buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );

					// Read the element name
					char elementFileName[ 512 ];
					buf.GetString( elementFileName );

					// Now read the element
					int slot = GetIndex( elementFileName );

					Assert( slot != m_Elements.InvalidIndex() );

					ElementType_t& element = m_Elements[ slot ];

					element.fileinfo = buf.GetInt();
					if ( ( element.fileinfo == -1 ) &&
						( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) )
					{
						element.fileinfo = 0;
					}

					Assert( element.dataIndex != m_Data.InvalidIndex() );

					T *data = m_Data[ element.dataIndex ];

					Assert( data );

					((IBaseCacheInfo *)data)->Restore( buf );
				}
			}
			else
			{
				Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() );
				deleteFile = true;
			}
		}
		else
		{
			Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() );
			deleteFile = true;
		}
	}
	else
	{
		DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() );
		deleteFile = true;
	}

	g_pFullFileSystem->Close( fh );
}

template <class T>
bool CUtlCachedFileData<T>::Init()
{
	if ( m_bInitialized )
	{
		return true;
	}

	m_bInitialized = true;

	if ( m_sRepositoryFileName.IsEmpty() )
	{
		Error( "CUtlCachedFileData:  Can't Init, no repository file specified." );
		return false;
	}

	// Always compute meta checksum
	m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0;

	FileHandle_t fh;

	fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" );
	if ( fh == FILESYSTEM_INVALID_HANDLE )
	{
		// Nothing on disk, we'll recreate everything from scratch...
		SetDirty( true );
		return true;
	}
	time_t fileTime = g_pFullFileSystem->GetFileTime( m_sRepositoryFileName, "MOD" );
	int size = g_pFullFileSystem->Size( fh );

	bool deletefile = false;

	if ( size > 1024 * 1024 )
	{
		InitLargeBuffer( fh, deletefile );
	}
	else
	{
		InitSmallBuffer( fh, size, deletefile );
	}

	if ( deletefile )
	{
		Assert( !m_bReadOnly );
		if ( !m_bReadOnly )
		{
			g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" );
		}
		SetDirty( true );
	}
	CheckDiskInfo( false, fileTime );
	return true;
}

template <class T>
void CUtlCachedFileData<T>::Save()
{
	char path[ 512 ];
	Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
	Q_StripFilename( path );

	g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );

	if ( g_pFullFileSystem->FileExists( m_sRepositoryFileName, "MOD" ) && 
		!g_pFullFileSystem->IsFileWritable( m_sRepositoryFileName, "MOD" ) )
	{
		g_pFullFileSystem->SetFileWritable( m_sRepositoryFileName, true, "MOD" );
	}

	// Now write to file
	FileHandle_t fh;
	fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "wb" );
	if ( FILESYSTEM_INVALID_HANDLE == fh )
	{
		ExecuteNTimes( 25, Warning( "Unable to persist cache '%s', check file permissions\n", m_sRepositoryFileName.String() ) );
	}
	else
	{
		SetDirty( false );

		int v = UTL_CACHE_SYSTEM_VERSION;
		g_pFullFileSystem->Write( &v, sizeof( v ), fh );
		v = m_nVersion;
		g_pFullFileSystem->Write( &v, sizeof( v ), fh );
		v = (int)m_uCurrentMetaChecksum;
		g_pFullFileSystem->Write( &v, sizeof( v ), fh );

		// Element count
		int c = Count();

		g_pFullFileSystem->Write( &c, sizeof( c ), fh );

		// Save repository back out to disk...
		CUtlBuffer buf( 0, 0, 0 );

		for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
		{
			buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );

			ElementType_t& element = m_Elements[ i ];

			char fn[ 512 ];
			g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );

			buf.PutString( fn );
			buf.PutInt( element.fileinfo );

			Assert( element.dataIndex != m_Data.InvalidIndex() );

			T *data = m_Data[ element.dataIndex ];

			Assert( data );

			((IBaseCacheInfo *)data)->Save( buf );

			int bufsize = buf.TellPut();
			g_pFullFileSystem->Write( &bufsize, sizeof( bufsize ), fh );
			g_pFullFileSystem->Write( buf.Base(), bufsize, fh );
		}

		g_pFullFileSystem->Close( fh );
	}

	if ( m_bSaveManifest )
	{
		SaveManifest();
	}
}

template <class T>
void CUtlCachedFileData<T>::Shutdown()
{
	if ( !m_bInitialized )
		return;

	m_bInitialized = false;

	if ( IsDirty() )
	{
		Save();
	}
	// No matter what, create the manifest if it doesn't exist on the HD yet
	else if ( m_bSaveManifest && !ManifestExists() )
	{
		SaveManifest();
	}

	m_Elements.RemoveAll();
}

template <class T>
bool CUtlCachedFileData<T>::ManifestExists()
{
	char manifest_name[ 512 ];
	Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );

	Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );

	return g_pFullFileSystem->FileExists( manifest_name, "MOD" );
}

template <class T>
void CUtlCachedFileData<T>::SaveManifest()
{
	// Save manifest out to disk...
	CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );

	for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
	{
		ElementType_t& element = m_Elements[ i ];

		char fn[ 512 ];
		g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );

		buf.Printf( "\"%s\"\r\n", fn );
	}

	char path[ 512 ];
	Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) );
	Q_StripFilename( path );

	g_pFullFileSystem->CreateDirHierarchy( path, "MOD" );

	char manifest_name[ 512 ];
	Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) );

	Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) );

	if ( g_pFullFileSystem->FileExists( manifest_name, "MOD" ) && 
		!g_pFullFileSystem->IsFileWritable( manifest_name, "MOD" ) )
	{
		g_pFullFileSystem->SetFileWritable( manifest_name, true, "MOD" );
	}

	// Now write to file
	FileHandle_t fh;
	fh = g_pFullFileSystem->Open( manifest_name, "wb" );
	if ( FILESYSTEM_INVALID_HANDLE != fh )
	{
		g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
		g_pFullFileSystem->Close( fh );

		// DevMsg( "Persisting cache manifest '%s' (%d entries)\n", manifest_name, c );
	}
	else
	{
		Warning( "Unable to persist cache manifest '%s', check file permissions\n", manifest_name );
	}
}

template <class T>
T *CUtlCachedFileData<T>::RebuildItem( const char *filename )
{
	int idx = GetIndex( filename );
	ElementType_t& e = m_Elements[ idx ];

	ForceRecheckDiskInfo();

	long cachefileinfo = e.fileinfo;
	// Set the disk fileinfo the first time we encounter the filename
	if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
	{
		if ( m_bNeverCheckDisk )
		{
			e.diskfileinfo = cachefileinfo;
		}
		else
		{
			if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) 
			{
				e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" );
				// Missing files get a disk file size of 0
				if ( e.diskfileinfo == -1 )
				{
					e.diskfileinfo = 0;
				}
			}
			else
			{
				e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" );
			}
		}
	}

	Assert( e.dataIndex != m_Data.InvalidIndex() );

	T *data = m_Data[ e.dataIndex ];

	Assert( data );

	// Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct...
	if ( !m_bReadOnly )
	{
		RebuildCache( filename, data );
	}
	e.fileinfo = e.diskfileinfo;

	return data;
}

template <class T>
void CUtlCachedFileData<T>::RebuildCache( char const *filename, T *data )
{
	Assert( !m_bReadOnly );

	// Recache item, mark self as dirty
	SetDirty( true );

	((IBaseCacheInfo *)data)->Rebuild( filename );
}

template <class T>
void CUtlCachedFileData<T>::ForceRecheckDiskInfo()
{
	for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
	{
		ElementType_t& element = m_Elements[ i ];
		element.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO;
	}
}

class CSortedCacheFile
{
public:
	FileNameHandle_t	handle;
	int					index;

	 bool Less( const CSortedCacheFile &file0, const CSortedCacheFile &file1, void * )
	 {
		 char name0[ 512 ];
		 char name1[ 512 ];
		 g_pFullFileSystem->String( file0.handle, name0, sizeof( name0 ) );
		 g_pFullFileSystem->String( file1.handle, name1, sizeof( name1 ) );
		 return Q_stricmp( name0, name1 ) < 0 ? true : false;
	 }
};

// Iterates all entries and causes rebuild on any existing items which are out of date
template <class T>
void	CUtlCachedFileData<T>::CheckDiskInfo( bool forcerebuild, time_t cacheFileTime )
{
	char fn[ 512 ];
	int i;
	if ( forcerebuild )
	{
		for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
		{
			ElementType_t& element = m_Elements[ i ];
			g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
			Get( fn );
		}
		return;
	}

	CUtlSortVector<CSortedCacheFile, CSortedCacheFile> list;
	for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) )
	{
		ElementType_t& element = m_Elements[ i ];
		CSortedCacheFile insert;
		insert.handle = element.handle;
		insert.index = i;
		list.InsertNoSort( insert );
	}
	list.RedoSort();

	if ( !list.Count() )
		return;

	for ( int listStart = 0, listEnd = 0; listStart < list.Count(); listStart = listEnd+1 )
	{
		int pathIndex = g_pFullFileSystem->GetPathIndex( m_Elements[list[listStart].index].handle );
		for ( listEnd = listStart; listEnd < list.Count(); listEnd++ )
		{
			ElementType_t& element = m_Elements[ list[listEnd].index ];

			int pathTest = g_pFullFileSystem->GetPathIndex( element.handle );
			if ( pathTest != pathIndex )
				break;
		}
		g_pFullFileSystem->String( m_Elements[list[listStart].index].handle, fn, sizeof( fn ) );
		Q_StripFilename( fn );
		bool bCheck = true;
		
		if ( m_bNeverCheckDisk )
		{
			bCheck = false;
		}
		else 
		{
			time_t pathTime = g_pFullFileSystem->GetPathTime( fn, "GAME" );
			bCheck = (pathTime > cacheFileTime) ? true : false;
		}

		for ( i = listStart; i < listEnd; i++ )
		{
			ElementType_t& element = m_Elements[ list[i].index ];

			if ( element.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO )
			{
				if ( !bCheck )
				{
					element.diskfileinfo = element.fileinfo;
				}
				else
				{
					g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) );
					if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) 
					{
						element.diskfileinfo = g_pFullFileSystem->Size( fn, "GAME" );

						// Missing files get a disk file size of 0
						if ( element.diskfileinfo == -1 )
						{
							element.diskfileinfo = 0;
						}
					}
					else
					{
						element.diskfileinfo = g_pFullFileSystem->GetFileTime( fn, "GAME" );
					}
				}
			}
		}
	}
}

#include "tier0/memdbgoff.h"

#endif // UTLCACHEDFILEDATA_H
