/*++

cacheimp.h -

	This file contains all the template function definitions required
	to make the cache manager work.


--*/

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
void
Cache< Data, Key, Constructor, fAtomic >::Schedule()	{
/*++

Routine Description :

	This function runs through all the items in the Cache
	bumping TTL's.  If there are any items ready to go
	then we dump them from the Cache.

Arguments :

	None.

Return Value :

	Nothing

--*/

	if( !m_fValid )
		return ;

	m_Lock.ShareLock() ;

	DWORD	cExpungable = 0 ;
	BOOL	fExpunge = m_ExpireList.Expire( cExpungable ) ;
	m_Lock.ShareUnlock() ;

	if( fExpunge ) {
		Expunge() ;
	}
}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL	
Cache<Data, Key, Constructor, fAtomic>::RemoveEntry(	
					CacheState*	pEntry
					)	{
/*++

Routine Description :

	This function removes an entry from the Cache.
	We call our hash table to delete the item.
	the CacheState destructor automagically removes
	the item from our linked lists.

	CALLER MUST HOLD APPROPRIATE LOCKS!


Arguments :

	pEntry - item to be removed from cache

Return Value :

	TRUE if successfully removed !

--*/


	CACHEENTRY	*pCacheEntry = (CACHEENTRY*)pEntry ;

	return	m_Lookup.DeleteData( pCacheEntry->GetKey(), pCacheEntry ) ;
}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL	
Cache<Data, Key, Constructor, fAtomic>::QueryRemoveEntry(	
					CacheState*	pEntry )	{
/*++

Routine Description :

	This function is called from CacheList object to
	determine whether we want to remove an item from the Cache.
	This function is used to implement the ExpungeSpecific
	function available to clients.

	CALLER MUST HOLD APPROPRIATE LOCKS!


Arguments :

	pEntry - item we want to determine whether it should remain !

Return Value :

	TRUE if successfully removed !

--*/


	CACHEENTRY	*pCacheEntry = (CACHEENTRY*) pEntry ;

	if( m_pCallbackObject ) {
		return	m_pCallbackObject->fRemoveCacheItem( *pCacheEntry->m_pData ) ;
	}
	return	FALSE ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL	
Cache<Data, Key, Constructor, fAtomic>::ExpungeInternal(
					const	CACHEENTRY*	pProtected,	
					BOOL	fDoCheap,
					Key*	key,
					CACHEENTRY*	pData
					)	{
/*++

Routine Description :

	This function is called when we want to force some thing
	out of the cache.  The caller can provide a key	
	to force a particular item out of the cache.

	CALLER MUST HOLD APPROPRIATE LOCKS!


Arguments :

	pProtected - an Element we want to make sure is not removed !
	key - Pointer to the key identifying the item to be removed
	pData - pointer to the CACHEENTRY object containing the data
		and key we wish to remove.

Return Value :

	TRUE something is successfully removed from the cache.

--*/


	if( key != 0 ) {
		return	m_Lookup.DeleteData( *key, pData ) ;
	}	
	return m_ExpireList.Expunge( this, fDoCheap, pProtected ) ;
}


#ifdef	DEBUG

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
long	Cache<Data, Key, Constructor, fAtomic>::s_cCreated = 0 ;

#endif

	
template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
Cache<Data, Key, Constructor, fAtomic>::Cache( ) : m_fValid( FALSE ) {
/*++

Routine Description :

	This function initializes our member variables.

Arguments :

	cMax - maximum number of elements the cache should hold

Return Value :

	Nothing

--*/

#ifdef	DEBUG

	InterlockedIncrement( &s_cCreated ) ;

#endif

	AddToSchedule() ;

}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
Cache<Data, Key, Constructor, fAtomic>::~Cache( ) {
/*++

Routine Description :

	This function destroys a Cache object !

Arguments :

	None

Return Value :

	Nothing

--*/

	RemoveFromSchedule() ;

	//
	//	Member and Base class destruction follows !!
	//

#ifdef	DEBUG

	InterlockedDecrement( &s_cCreated ) ;

#endif

}



template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
Cache<Data, Key, Constructor, fAtomic>::Init(	
				DWORD	(*pfnHash)( const Key& ),
				DWORD	dwLifetimeSeconds,
				DWORD	cMaxInstances,
				PSTOPHINT_FN pfnStopHint
		) {
/*++

Routine Description :

	This function initializes the cache so that it is ready
	to take entries.

Arguments :

	pfnHash - function to be used to compute hash values on keys
	dwLifetimeSeconds - The number of seconds objects should live in the Cache
	pfnStopHint - function to be used to send stop hints during
	  long spins so shutdown's don't time out.

Return Value :

	TRUE if successfull

--*/

	m_ExpireList.m_cMax = long(cMaxInstances) ;
	m_ExpireList.m_pfnStopHint = pfnStopHint;

	m_TTL = 1 + (dwLifetimeSeconds / CScheduleThread::dwNotificationSeconds) ;

	return	m_fValid = m_Lookup.Init( 100, 100, pfnHash ) ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
Cache<Data, Key, Constructor, fAtomic>::Expunge(	
				Key*	key,	
				CACHEENTRY*	pData
				)	{
/*++

Routine Description :

	This function is called when we want to force some thing
	out of the cache.  The caller can provide a key	
	to force a particular item out of the cache.

	WE WILL GRAB THE NECESSARY LOCKS !

Arguments :

	pfnHash - function to be used to compute hash values on keys

Return Value :

	TRUE if successfull

--*/

	_ASSERT( m_fValid ) ;

	m_Lock.ExclusiveLock() ;
	BOOL	fReturn = ExpungeInternal( 0, FALSE, key, pData ) ;
	m_Lock.ExclusiveUnlock() ;
	return	fReturn ;
}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
Cache<Data, Key, Constructor, fAtomic>::ExpungeSpecific(	
				CALLBACKOBJ* pCallbackObject,
				BOOL	fForced
				) {
/*++

Routine Description :

	This function is called when we want to force some thing
	out of the cache.  The caller can provide a key	
	to force a particular item out of the cache.

	WE WILL GRAB THE NECESSARY LOCKS !

Arguments :

	pfn - callback function used to determine what to remove
		from the Cache.
	fForced - if TRUE then we will remove from the Cache no matter
		what, even if other threads are using the object !

Return Value :

	TRUE if successfull

--*/



	m_Lock.ExclusiveLock() ;
	
	m_pCallbackObject = pCallbackObject ;
	
	BOOL	fReturn = m_ExpireList.ExpungeSpecific( this, fForced ) ;

	m_Lock.ExclusiveUnlock() ;
	return	fReturn ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
Cache<Data, Key, Constructor, fAtomic>::FindOrCreateInternal(
						DWORD	dwHash,
						Key&	key,
						Constructor&	constructor,
						CRefPtr< Data >&	pDataOut
						) {
/*++

Routine Description :

	This function is called when we want something out
	of the Cache.  We will either find the object or create it.

	WE WILL GRAB THE NECESSARY LOCKS !

Arguments :

	key - The unique key used to find the item in the Cache.
	constructor - An object to pass to the Data's constructor and Init
		functions.
	pDataOut - Smart pointer which receives the result

Return Value :

	TRUE if successfull

--*/


	BOOL	fReturn = FALSE ;
	pDataOut = 0 ;

	if( !m_fValid ) {
		SetLastError( 0 ) ;
		return	FALSE ;
	}	else	{
		m_Lock.ShareLock() ;

		//
		//	See if we can find an Entry in the cache for the item we want.
		//
		CACHEENTRY	*pEntry = m_Lookup.SearchKeyHash( dwHash, key ) ;
		if( pEntry ) {
			pDataOut = pEntry->m_pData ;
			fReturn = TRUE ;
			m_ExpireList.LiveLonger( pEntry, (m_TTL/2)+1 ) ;
		}

		m_Lock.ShareUnlock() ;

		if( pDataOut == 0 ) {

			m_Lock.ExclusiveLock() ;

			//
			//	Check that the Item we want in the Cache didn't make it
			//	into the Cache in the brief moment we switched from a shared
			//	to an exclusive lock.
			//

			pEntry = m_Lookup.SearchKeyHash( dwHash, key ) ;
			if( pEntry != 0 ) {
				//
				//	It's in the cache - return the cached object !
				//
				pDataOut = pEntry->m_pData ;
				fReturn = TRUE ;
				m_ExpireList.LiveLonger( pEntry, (m_TTL/2)+1 ) ;
				m_Lock.ExclusiveUnlock() ;
			}	else	{	

				//
				//	We're going to have to create one of the elements that goes
				//	in the cache.  Let's do that now !
				//

				const	CACHEENTRY	*pCacheEntry = 0 ;
				BOOL		fOverFlow = FALSE ;
				Data		*pData = 0 ;

				//
				//	We Add another brace here because we want to explicitly manage
				//	the lifetime of tempCacheEntry
				//
				{
					//
					//	tempCacheEntry will be copied by the FHash template into
					//	another CACHEENTRY object when it does its insertion.
					//
					CACHEENTRY	tempCacheEntry( m_TTL ) ;

					//
					//	The constructor of the Data object is expected to initialize
					//	only the 'key' data of the Data object so that the data can be
					//	found in the Cache.
					//
					tempCacheEntry.m_pData = pData = new	Data( key, constructor ) ;

					if( tempCacheEntry.m_pData ) {
	
						pCacheEntry = m_Lookup.InsertDataHash( dwHash, tempCacheEntry ) ;
						if( pCacheEntry ) {
							if( m_ExpireList.Append( pCacheEntry ) ) {
								if( !ExpungeInternal( pCacheEntry, TRUE ) ) {
									m_ExpireList.ForceExpunge( this, pCacheEntry ) ;
								}
							}
							pDataOut = pCacheEntry->m_pData ;
							pDataOut->ExclusiveLock() ;
						}	else	{

							//
							//	The Insertion failed !
							//	Falling through correctly handles this error case !
							//

						}

					}
					//
					//	Delete tempCacheEntry's reference to the Data object.
					//	if an error occurred inserting into hash tables etc...
					//	this will automatically destroy the data object.
					//
					tempCacheEntry.m_pData = 0 ;
				}	


				//
				//	Release the cache's lock.  We do this now - the constructor
				//	for the 'Data' object should do minimal work.  We will not call
				//	the init function for the Data object, with the 'cache' locks
				//	relingquished.  This lets others use the cache while expensive
				//	construction operations continue.
				//
				m_Lock.ExclusiveUnlock() ;

				//
				//	Complete the initialization of the new Data item if it exists !
				//
				if( pDataOut != 0 ) {
					BOOL	fSuccess = pDataOut->Init( constructor ) ;
					pDataOut->ExclusiveUnlock() ;

					if( fSuccess ) {

						fReturn = TRUE ;

					}	else	{

						pDataOut = 0 ;
			
						//
						//	Need to Expire the entry we just placed in the Cache !
						//
						//	NOTE : Expunge() manages its own locks !!!
						//
						Expunge( &key ) ;

					}
				}
			}
		}
	}
	return	fReturn ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
inline	BOOL
Cache<Data, Key, Constructor, fAtomic>::FindOrCreate(
						Key&	key,
						Constructor&	constructor,
						CRefPtr< Data >&	pDataOut  ) {
/*++

Routine Description :

	This function is called when we want something out
	of the Cache.  We will either find the object or create it.

	WE WILL GRAB THE NECESSARY LOCKS !

Arguments :

	key - The unique key used to find the item in the Cache.
	constructor - An object to pass to the Data's constructor and Init
		functions.
	pDataOut - Smart pointer which receives the result

Return Value :

	TRUE if successfull

--*/


	DWORD	dw = m_pfnHash( key ) ;

	return	FindOrCreateInternal(	dw,
									key,
									constructor,
									pDataOut
									) ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
MultiCache< Data, Key, Constructor, fAtomic >::MultiCache() :
	m_fValid( FALSE ),
	m_pCaches( 0 ) ,
	m_cSubCaches( 0 ),
	m_pfnHash( 0 ) {
/*++

Routine Description :

	This function initializes the MultiCache's data structures

Arguments :

	None.

Return Value :

	Nothing

--*/
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
MultiCache< Data, Key, Constructor, fAtomic >::~MultiCache()	{
/*++

Routine Description :

	This function destroys all of our data structures - release
	all of our subcaches !

Arguments :

	None.

Return Value :

	Nothing

--*/


	if( m_pCaches ) {

		delete[]	m_pCaches ;

	}

}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
MultiCache< Data, Key, Constructor, fAtomic >::Init(
				DWORD	(*pfnHash)( const	Key & ),
				DWORD	dwLifetimeSeconds,
				DWORD	cSubCaches,
				DWORD	cMaxElements,
				PSTOPHINT_FN pfnStopHint) {

/*++

Routine Description :

	This function initializes the MultiCache - we use
	multiple independent Caches to split the work
	of caching all the data.

Arguments :

	None.

Return Value :

	Nothing

--*/


	//	
	//	Check that we're in the right state for this !
	//
	_ASSERT( !m_fValid ) ;
	_ASSERT( m_pCaches == 0 ) ;

	//
	//	Validate our arguments !!!
	//
	_ASSERT(	pfnHash != 0 ) ;
	_ASSERT(	dwLifetimeSeconds != 0 ) ;
	_ASSERT(	cSubCaches != 0 ) ;
	_ASSERT(	cMaxElements != 0 ) ;

	m_pfnHash = pfnHash ;
	m_cSubCaches = cSubCaches ;

	//
	//	Allocate the necessary subcaches !
	//

	m_pCaches = new	CACHEINSTANCE[m_cSubCaches] ;

	if( !m_pCaches ) {
		return	FALSE ;
	}	else	{

		for( DWORD	i=0; i<cSubCaches; i++ ) {
			
			if( !m_pCaches[i].Init( m_pfnHash, dwLifetimeSeconds, (cMaxElements / cSubCaches) + 1, pfnStopHint ) ) {
				delete	m_pCaches ;
				return	FALSE ;
			}
		}
	}
	m_fValid = TRUE ;
	return	TRUE ;
}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
DWORD
MultiCache< Data, Key, Constructor, fAtomic >::ChooseInstance(
				DWORD	dwHash
				) {
/*++

Routine Description :

	Given a Key figure out which of our subinstances we wish to use.

Arguments :

	None.

Return Value :

	Nothing

--*/


	//	
	//	Check that we're in the right state for this !
	//
	_ASSERT( m_fValid ) ;
	_ASSERT( m_pCaches != 0 ) ;

	//
	//	Validate our arguments !!!
	//
	_ASSERT(	m_pfnHash != 0 ) ;
	_ASSERT(	m_cSubCaches != 0 ) ;

	
	//DWORD	dwHash = m_pfnHash( k ) ;
	
	//
	//	Constants below stolen from C-runtime rand() function !
	//

	dwHash = (((dwHash * 214013) +2531011) >> 8) % m_cSubCaches ;

	return	dwHash ;

}



template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
MultiCache< Data, Key, Constructor, fAtomic >::Expunge(
				Key*	pk,
				CACHEENTRY*	pData
				) {
/*++

Routine Description :

	Either force a specified element out of the cache,
	or force out all those that are ready to go !

Arguments :

	None.

Return Value :

	Nothing

--*/

	if( pk != 0 ) {

		DWORD	dw = m_pfnHash( *pk ) ;

		CACHEINSTANCE*	pInstance = &m_pCacges[ChooseInstance( dw )] ;
		return	pInstance->Expunge( pk, pData ) ;

	}	else	{

		BOOL	fReturn = TRUE ;
		for( DWORD i=0; i<m_cSubCaches; i++ ) {

			fReturn &= m_pCaches[i].Expunge() ;
		
		}
	}
	return	fReturn ;
}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
MultiCache< Data, Key, Constructor, fAtomic >::ExpungeSpecific(
				CALLBACKOBJ* pCallbackObject,
				BOOL	fForced
				) {
/*++

Routine Description :

	Either force a specified element out of the cache,
	or force out all those that are ready to go !

Arguments :

	None.

Return Value :

	Nothing

--*/

	BOOL	fReturn = TRUE ;
	for( DWORD i=0; i<m_cSubCaches; i++ ) {

		fReturn &= m_pCaches[i].ExpungeSpecific( pCallbackObject, fForced ) ;
	
	}
	return	fReturn ;
}

template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
MultiCache< Data, Key, Constructor, fAtomic >::FindOrCreate(
				Key&	key,
				Constructor&	constructor,
				CRefPtr< Data >&	pDataOut
				) {
/*++

Routine Description :


Arguments :

	None.

Return Value :

	Nothing

--*/

	DWORD	dw = m_pfnHash( key ) ;

	CACHEINSTANCE*	pInstance = &m_pCaches[ChooseInstance(dw)] ;

	return	pInstance->FindOrCreateInternal( dw, key, constructor, pDataOut ) ;

}


template	<	class	Data,	
				class	Key,	
				class	Constructor,	
				BOOL	fAtomic
				>
BOOL
MultiCache< Data, Key, Constructor, fAtomic >::FindOrCreate(
				Key&	key,
				DWORD	dw,
				Constructor&	constructor,
				CRefPtr< Data >&	pDataOut
				) {
/*++

Routine Description :


Arguments :

	None.

Return Value :

	Nothing

--*/

	_ASSERT( dw == m_pfnHash( key ) ) ;

	CACHEINSTANCE*	pInstance = &m_pCaches[ChooseInstance(dw)] ;

	return	pInstance->FindOrCreateInternal( dw, key, constructor, pDataOut ) ;

}