#ifndef _COLLECTION_HXX_INCLUDED
#define _COLLECTION_HXX_INCLUDED


//  asserts
//
//  #define COLLAssert to point to your favorite assert function per #include

#ifdef COLLAssert
#else  //  !COLLAssert
#define COLLAssert Assert
#endif  //  COLLAssert

#ifdef DHTAssert
#else  //  !DHTAssert
#define DHTAssert COLLAssert
#endif  //  DHTAssert


#include "dht.hxx"

#include <memory.h>
#include <minmax.h>

#pragma warning ( disable : 4786 )	//  we allow huge symbol names


namespace COLL {


//////////////////////////////////////////////////////////////////////////////////////////
//  CInvasiveList
//
//  Implements an "invasive" doubly linked list of objects.  The list is "invasive"
//  because part of its state is embedded directly in the objects it contains.  An
//  additional property of this list class is that the head of the list can be relocated
//  without updating the state of any of the contained objects.
//
//  CObject			= class representing objects in the list.  each class must contain
//					  storage for a CElement for embedded list state
//  OffsetOfILE		= inline function returning the offset of the CElement contained
//					  in the CObject

typedef SIZE_T (*PfnOffsetOf)();

template< class CObject, PfnOffsetOf OffsetOfILE >
class CInvasiveList
	{
	public:

		//  invasive list element state (embedded in linked objects)
	
		class CElement
			{
			public:

				//  ctor / dtor

				CElement() : m_pilePrev( (CElement*)-1 ), m_pileNext( (CElement*)-1 ) {}
				~CElement() {}

			private:

				CElement& operator=( CElement& );  //  disallowed

				friend class CInvasiveList< CObject, OffsetOfILE >;

				CElement*	m_pilePrev;
				CElement*	m_pileNext;
			};

	public:

		//  ctor / dtor

		CInvasiveList();
		~CInvasiveList();

		//  operators

		CInvasiveList& operator=( const CInvasiveList& il );

		//  API

		BOOL FEmpty() const;

		BOOL FMember( CObject* const pobj ) const;

		CObject* Prev( CObject* const pobj ) const;
		CObject* Next( CObject* const pobj ) const;

		CObject* PrevMost() const;
		CObject* NextMost() const;

		void InsertAsPrevMost( CObject* const pobj );
		void InsertAsNextMost( CObject* const pobj );

		void Remove( CObject* const pobj );

		void Empty();

	private:

		//  internal functions

		CObject* _PobjFromPile( CElement* const pile ) const;
		CElement* _PileFromPobj( CObject* const pobj ) const;
		
	private:

		CElement*	m_pilePrevMost;
		CElement*	m_pileNextMost;
	};

//  ctor

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::
CInvasiveList()
	{
	//  start with an empty list
	
	Empty();
	}

//  dtor

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::
~CInvasiveList()
	{
	}

//  assignment operator

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >& CInvasiveList< CObject, OffsetOfILE >::
operator=( const CInvasiveList& il )
	{
	m_pilePrevMost	= il.m_pilePrevMost;
	m_pileNextMost	= il.m_pileNextMost;
	return *this;
	}

//  returns fTrue if the list is empty

template< class CObject, PfnOffsetOf OffsetOfILE >
inline BOOL CInvasiveList< CObject, OffsetOfILE >::
FEmpty() const
	{
	return m_pilePrevMost == _PileFromPobj( NULL );
	}

//  returns fTrue if the specified object is a member of this list
//
//  NOTE:  this function currently returns fTrue if the specified object is a
//  member of any list!

template< class CObject, PfnOffsetOf OffsetOfILE >
inline BOOL CInvasiveList< CObject, OffsetOfILE >::
FMember( CObject* const pobj ) const
	{
#ifdef EXPENSIVE_DEBUG

	for ( CObject* pobjT = PrevMost(); pobjT && pobjT != pobj; pobjT = Next( pobjT ) )
		{
		}
	
	return pobjT == pobj;

#else  //  !DEBUG

	CElement* const pile = _PileFromPobj( pobj );

	COLLAssert( ( ( DWORD_PTR( pile->m_pilePrev ) + DWORD_PTR( pile->m_pileNext ) ) == -2 ) ==
			( pile->m_pilePrev == (CElement*)-1 && pile->m_pileNext == (CElement*)-1 ) );

	return ( DWORD_PTR( pile->m_pilePrev ) + DWORD_PTR( pile->m_pileNext ) ) != -2;

#endif  //  DEBUG
	}

//  returns the prev object to the given object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
Prev( CObject* const pobj ) const
	{
	return _PobjFromPile( _PileFromPobj( pobj )->m_pilePrev );
	}
	
//  returns the next object to the given object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
Next( CObject* const pobj ) const
	{
	return _PobjFromPile( _PileFromPobj( pobj )->m_pileNext );
	}

//  returns the prev-most object to the given object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
PrevMost() const
	{
	return _PobjFromPile( m_pilePrevMost );
	}

//  returns the next-most object to the given object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
NextMost() const
	{
	return _PobjFromPile( m_pileNextMost );
	}

//  inserts the given object as the prev-most object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
InsertAsPrevMost( CObject* const pobj )
	{
	CElement* const pile = _PileFromPobj( pobj );

	//  this object had better not already be in the list

	COLLAssert( !FMember( pobj ) );

	//  this object had better not already be in any list

	COLLAssert( pile->m_pilePrev == (CElement*)-1 );
	COLLAssert( pile->m_pileNext == (CElement*)-1 );

	//  the list is empty

	if ( m_pilePrevMost == _PileFromPobj( NULL ) )
		{
		//  insert this element as the only element in the list

		pile->m_pilePrev			= _PileFromPobj( NULL );
		pile->m_pileNext			= _PileFromPobj( NULL );

		m_pilePrevMost				= pile;
		m_pileNextMost				= pile;
		}

	//  the list is not empty

	else
		{
		//  insert this element at the prev-most position in the list

		pile->m_pilePrev			= _PileFromPobj( NULL );
		pile->m_pileNext			= m_pilePrevMost;

		m_pilePrevMost->m_pilePrev	= pile;

		m_pilePrevMost				= pile;
		}
	}

//  inserts the given object as the next-most object in the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
InsertAsNextMost( CObject* const pobj )
	{
	CElement* const pile = _PileFromPobj( pobj );

	//  this object had better not already be in the list

	COLLAssert( !FMember( pobj ) );

	//  this object had better not already be in any list

	COLLAssert( pile->m_pilePrev == (CElement*)-1 );
	COLLAssert( pile->m_pileNext == (CElement*)-1 );

	//  the list is empty

	if ( m_pileNextMost == _PileFromPobj( NULL ) )
		{
		//  insert this element as the only element in the list

		pile->m_pilePrev			= _PileFromPobj( NULL );
		pile->m_pileNext			= _PileFromPobj( NULL );

		m_pilePrevMost				= pile;
		m_pileNextMost				= pile;
		}

	//  the list is not empty

	else
		{
		//  insert this element at the next-most position in the list

		pile->m_pilePrev			= m_pileNextMost;
		pile->m_pileNext			= _PileFromPobj( NULL );

		m_pileNextMost->m_pileNext	= pile;

		m_pileNextMost				= pile;
		}
	}

//  removes the given object from the list

template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
Remove( CObject* const pobj )
	{
	CElement* const pile = _PileFromPobj( pobj );

	//  this object had better already be in the list

	COLLAssert( FMember( pobj ) );

	//  there is an element after us in the list

	if ( pile->m_pileNext != _PileFromPobj( NULL ) )
		{
		//  fix up its prev element to be our prev element (if any)
		
		pile->m_pileNext->m_pilePrev	= pile->m_pilePrev;
		}
	else
		{
		//  set the next-most element to be our prev element (if any)
		
		m_pileNextMost					= pile->m_pilePrev;
		}

	//  there is an element before us in the list

	if ( pile->m_pilePrev != _PileFromPobj( NULL ) )
		{
		//  fix up its next element to be our next element (if any)
		
		pile->m_pilePrev->m_pileNext	= pile->m_pileNext;
		}
	else
		{
		//  set the prev-most element to be our next element (if any)
		
		m_pilePrevMost					= pile->m_pileNext;
		}

	//  mark ourself as not in any list

	pile->m_pilePrev					= (CElement*)-1;
	pile->m_pileNext					= (CElement*)-1;
	}

//  resets the list to the empty state
	
template< class CObject, PfnOffsetOf OffsetOfILE >
inline void CInvasiveList< CObject, OffsetOfILE >::
Empty()
	{
	m_pilePrevMost	= _PileFromPobj( NULL );
	m_pileNextMost	= _PileFromPobj( NULL );
	}

//  converts a pointer to an ILE to a pointer to the object

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CObject* CInvasiveList< CObject, OffsetOfILE >::
_PobjFromPile( CElement* const pile ) const
	{
	return (CObject*)( (BYTE*)pile - OffsetOfILE() );
	}

//  converts a pointer to an object to a pointer to the ILE

template< class CObject, PfnOffsetOf OffsetOfILE >
inline CInvasiveList< CObject, OffsetOfILE >::CElement* CInvasiveList< CObject, OffsetOfILE >::
_PileFromPobj( CObject* const pobj ) const
	{
	return (CElement*)( (BYTE*)pobj + OffsetOfILE() );
	}


//////////////////////////////////////////////////////////////////////////////////////////
//  CApproximateIndex
//
//  Implements a dynamically resizable table of entries indexed approximately by key
//  ranges of a specified uncertainty.  Accuracy and exact ordering are sacrificied for
//  improved performance and concurrency.  This index is optimized for a set of records
//  whose keys occupy a fairly dense range of values.  The index is designed to handle
//  key ranges that can wrap around zero.  As such, the indexed key range can not span
//  more than half the numerical precision of the key.
//
//  CKey			= class representing keys used to order entries in the mesh table.
//					  this class must support all the standard math operators.  wrap-
//					  around in the key values is supported
//  CEntry			= class indexed by the mesh table.  this class must contain storage
//					  for a CInvasiveContext class
//  OffsetOfIC		= inline function returning the offset of the CInvasiveContext
//					  contained in the CEntry
//
//  You must use the DECLARE_APPROXIMATE_INDEX macro to declare this class.

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
class CApproximateIndex
	{
	public:

		//  class containing context needed per CEntry

		class CInvasiveContext
			{
			public:

				CInvasiveContext() {}
				~CInvasiveContext() {}

				static SIZE_T OffsetOfILE() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_ile ); }

			private:

				CInvasiveList< CEntry, OffsetOfILE >::CElement	m_ile;
			};

		//  API Error Codes

		enum ERR
			{
			errSuccess,
			errInvalidParameter,
			errOutOfMemory,
			errEntryNotFound,
			errNoCurrentEntry,
			errKeyRangeExceeded,
			};

		//  API Lock Context

		class CLock;

	public:

		//  ctor / dtor

		CApproximateIndex( const int Rank );
		~CApproximateIndex();

		//  API

		ERR ErrInit(	const CKey		dkeyPrecision,
						const CKey		dkeyUncertainty,
						const double	dblSpeedSizeTradeoff );
		void Term();

		void LockKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );
		void UnlockKeyPtr( CLock* const plock );

		long CmpKey( const CKey& key1, const CKey& key2 ) const;
		
		CKey KeyRangeFirst() const;
		CKey KeyRangeLast() const;

		CKey KeyInsertLeast() const;
		CKey KeyInsertMost() const;

		ERR ErrRetrieveEntry( CLock* const plock, CEntry** const ppentry ) const;
		ERR ErrInsertEntry( CLock* const plock, CEntry* const pentry, const BOOL fNextMost = fTrue );
		ERR ErrDeleteEntry( CLock* const plock );
		ERR ErrReserveEntry( CLock* const plock );
		void UnreserveEntry( CLock* const plock );

		void MoveBeforeFirst( CLock* const plock );
		ERR ErrMoveNext( CLock* const plock );
		ERR ErrMovePrev( CLock* const plock );
		void MoveAfterLast( CLock* const plock );

		void MoveBeforeKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );
		void MoveAfterKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock );

	public:

		//  bucket used for containing index entries that have approximately
		//  the same key

		class CBucket
			{
			public:

				//  bucket ID

				typedef unsigned long ID;
				
			public:

				CBucket() {}
				~CBucket() {}

				CBucket& operator=( const CBucket& bucket )
					{
					m_id	= bucket.m_id;
					m_cPin	= bucket.m_cPin;
					m_il	= bucket.m_il;
					return *this;
					}

			public:

				ID														m_id;
				unsigned long											m_cPin;
				CInvasiveList< CEntry, CInvasiveContext::OffsetOfILE >	m_il;
			};

		//  table that contains our buckets

		typedef CDynamicHashTable< CBucket::ID, CBucket > CBucketTable;

	public:

		//  API Lock Context

		class CLock
			{
			public:

				CLock() {}
				~CLock() {}

			private:

				friend class CApproximateIndex< CKey, CEntry, OffsetOfIC >;

				CBucketTable::CLock	m_lock;
				CBucket				m_bucket;
				CEntry*				m_pentryPrev;
				CEntry*				m_pentry;
				CEntry*				m_pentryNext;
			};

	private:

		CBucket::ID _IdFromKeyPtr( const CKey& key, CEntry* const pentry ) const;
		CBucket::ID _DeltaId( const CBucket::ID id, const long did ) const;
		long _SubId( const CBucket::ID id1, const CBucket::ID id2 ) const;
		long _CmpId( const CBucket::ID id1, const CBucket::ID id2 ) const;
		CInvasiveContext* _PicFromPentry( CEntry* const pentry ) const;
		BOOL _FExpandIdRange( const CBucket::ID idNew );
		
		ERR _ErrInsertBucket( CLock* const plock );
		ERR _ErrInsertEntry( CLock* const plock, CEntry* const pentry );
		ERR _ErrMoveNext( CLock* const plock );
		ERR _ErrMovePrev( CLock* const plock );

	private:

		//  never updated

		long				m_shfKeyPrecision;
		long				m_shfKeyUncertainty;
		long				m_shfBucketHash;
		long				m_shfFillMSB;
		CBucket::ID			m_maskBucketKey;
		CBucket::ID			m_maskBucketPtr;
		CBucket::ID			m_maskBucketID;
		long				m_didRangeMost;
		//BYTE				m_rgbReserved1[ 0 ];

		//  seldom updated

		CCriticalSection	m_critUpdateIdRange;
		long				m_cidRange;
		CBucket::ID			m_idRangeFirst;
		CBucket::ID			m_idRangeLast;
		BYTE				m_rgbReserved2[ 16 ];

		//  commonly updated

		CBucketTable		m_bt;
		//BYTE				m_rgbReserved3[ 0 ];
	};

//  ctor

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::
CApproximateIndex( const int Rank )
	:	m_critUpdateIdRange( CLockBasicInfo( CSyncBasicInfo( "CApproximateIndex::m_critUpdateIdRange" ), Rank - 1, 0 ) ),
		m_bt( Rank )
	{
	}

//  dtor

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::
~CApproximateIndex()
	{
	}

//  initializes the approximate index using the given parameters.  if the index
//  cannot be initialized, errOutOfMemory is returned

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrInit(	const CKey		dkeyPrecision,
			const CKey		dkeyUncertainty,
			const double	dblSpeedSizeTradeoff )
	{
	//  validate all parameters

	if (	dkeyPrecision <= dkeyUncertainty ||
			dkeyUncertainty < CKey( 0 ) ||
			dblSpeedSizeTradeoff < 0.0 || dblSpeedSizeTradeoff > 1.0 )
		{
		return errInvalidParameter;
		}
		
	//  init our parameters

	const CBucket::ID cbucketHashMin = CBucket::ID( ( 1.0 - dblSpeedSizeTradeoff ) * OSSyncGetProcessorCount() );

	CKey maskKey;
	for (	m_shfKeyPrecision = 0, maskKey = 0;
			dkeyPrecision > CKey( 1 ) << m_shfKeyPrecision && m_shfKeyPrecision < sizeof( CKey ) * 8;
			maskKey |= CKey( 1 ) << m_shfKeyPrecision++ )
		{
		}
	for (	m_shfKeyUncertainty = 0;
			dkeyUncertainty > CKey( 1 ) << m_shfKeyUncertainty && m_shfKeyUncertainty < sizeof( CKey ) * 8;
			m_shfKeyUncertainty++ )
		{
		}
	for (	m_shfBucketHash = 0, m_maskBucketPtr = 0;
			cbucketHashMin > CBucket::ID( 1 ) << m_shfBucketHash && m_shfBucketHash < sizeof( CBucket::ID ) * 8;
			m_maskBucketPtr |= CBucket::ID( 1 ) << m_shfBucketHash++ )
		{
		}

	m_maskBucketKey = CBucket::ID( maskKey >> m_shfKeyUncertainty );

	m_shfFillMSB = sizeof( CBucket::ID ) * 8 - m_shfKeyPrecision + m_shfKeyUncertainty - m_shfBucketHash;
	m_shfFillMSB = max( m_shfFillMSB, 0 );

	m_maskBucketID = ( ~CBucket::ID( 0 ) ) >> m_shfFillMSB;

	//  if our parameters leave us with too much or too little precision for
	//  our bucket IDs, fail.  "too much" precision would allow our bucket IDs
	//  to span more than half the precision of our bucket ID and cause our
	//  wrap-around-aware comparisons to fail.  "too little" precision would
	//  give us too few bucket IDs to allow us to hash efficiently
	//
	//  NOTE:  we check for hash efficiency in the worst case so that we don't
	//  suddenly return errInvalidParameter on some new monster machine

	const CBucket::ID cbucketHashMax = CBucket::ID( 1.0 * OSSyncGetProcessorCountMax() );

	for (	long shfBucketHashMax = 0;
			cbucketHashMax > CBucket::ID( 1 ) << shfBucketHashMax && shfBucketHashMax < sizeof( CBucket::ID ) * 8;
			shfBucketHashMax++ )
		{
		}

	long shfFillMSBMin;
	shfFillMSBMin = sizeof( CBucket::ID ) * 8 - m_shfKeyPrecision + m_shfKeyUncertainty - shfBucketHashMax;
	shfFillMSBMin = max( shfFillMSBMin, 0 );

	if (	shfFillMSBMin < 0 ||
			shfFillMSBMin > sizeof( CBucket::ID ) * 8 - shfBucketHashMax )
		{
		return errInvalidParameter;
		}

	//  limit the ID range to within half the precision of the bucket ID

	m_didRangeMost = m_maskBucketID >> 1;

	//  init our bucket ID range to be empty

	m_cidRange			= 0;
	m_idRangeFirst		= 0;
	m_idRangeLast		= 0;
	
	//  initialize the bucket table

	if ( m_bt.ErrInit( 5.0, 1.0 ) != errSuccess )
		{
		Term();
		return errOutOfMemory;
		}

	return errSuccess;
	}

//  terminates the approximate index.  this function can be called even if the
//  index has never been initialized or is only partially initialized
//
//  NOTE:  any data stored in the index at this time will be lost!

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
Term()
	{
	//  terminate the bucket table

	m_bt.Term();
	}

//  acquires a lock on the specified key and entry pointer and returns the lock
//  in the provided lock context

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
LockKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
	{
	//  compute the bucket ID for this key and entry pointer

	plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );

	//  write lock this bucket ID in the bucket table

	m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

	//  fetch this bucket from the bucket table if it exists.  if it doesn't
	//  exist, the bucket will start out empty and have the above bucket ID

	plock->m_bucket.m_cPin = 0;
	plock->m_bucket.m_il.Empty();
	(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

	//  the entry is in this bucket

	if ( plock->m_bucket.m_il.FMember( pentry ) )
		{
		//  set our currency to be on this entry in the bucket
		
		plock->m_pentryPrev	= NULL;
		plock->m_pentry		= pentry;
		plock->m_pentryNext	= NULL;
		}

	//  the entry is not in this bucket

	else
		{
		//  set our currency to be before the first entry in this bucket

		plock->m_pentryPrev	= NULL;
		plock->m_pentry		= NULL;
		plock->m_pentryNext	= plock->m_bucket.m_il.PrevMost();
		}

	//  if this bucket isn't pinned, it had better be represented by the valid
	//  bucket ID range of the index

	COLLAssert(	!plock->m_bucket.m_cPin ||
			(	_CmpId( plock->m_bucket.m_id, m_idRangeFirst ) >= 0 &&
				_CmpId( plock->m_bucket.m_id, m_idRangeLast ) <= 0 ) );
	}
	
//  releases the lock in the specified lock context

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
UnlockKeyPtr( CLock* const plock )
	{
	//  if this bucket isn't pinned, it had better be represented by the valid
	//  bucket ID range of the index

	COLLAssert(	!plock->m_bucket.m_cPin ||
			(	_CmpId( plock->m_bucket.m_id, m_idRangeFirst ) >= 0 &&
				_CmpId( plock->m_bucket.m_id, m_idRangeLast ) <= 0 ) );

	//  write unlock this bucket ID in the bucket table

	m_bt.WriteUnlockKey( &plock->m_lock );
	}

//  compares two keys as they would be seen relative to each other by the
//  approximate index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
CmpKey( const CKey& key1, const CKey& key2 ) const
	{
	return _CmpId( _IdFromKeyPtr( key1, NULL ), _IdFromKeyPtr( key2, NULL ) );
	}

//  returns the first key in the current key range.  this key is guaranteed to
//  be at least as small as the key of any record currently in the index given
//  the precision and uncertainty of the index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyRangeFirst() const
	{
	return CKey( m_idRangeFirst >> m_shfBucketHash ) << m_shfKeyUncertainty;
	}

//  returns the last key in the current key range.  this key is guaranteed to
//  be at least as large as the key of any record currently in the index given
//  the precision and uncertainty of the index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyRangeLast() const
	{
	return CKey( m_idRangeLast >> m_shfBucketHash ) << m_shfKeyUncertainty;
	}

//  returns the smallest key that could be successfully inserted into the index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyInsertLeast() const
	{
	const CBucket::ID cBucketHash = 1 << m_shfBucketHash;
	
	CBucket::ID idFirstLeast = m_idRangeLast - m_didRangeMost;
	idFirstLeast = idFirstLeast + ( cBucketHash - idFirstLeast % cBucketHash ) % cBucketHash;
	
	return CKey( idFirstLeast >> m_shfBucketHash ) << m_shfKeyUncertainty;
	}

//  returns the largest key that could be successfully inserted into the index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CKey CApproximateIndex< CKey, CEntry, OffsetOfIC >::
KeyInsertMost() const
	{
	const CBucket::ID cBucketHash = 1 << m_shfBucketHash;
	
	CBucket::ID idLastMost = m_idRangeFirst + m_didRangeMost;
	idLastMost = idLastMost - ( idLastMost + 1 ) % cBucketHash;
	
	return CKey( idLastMost >> m_shfBucketHash ) << m_shfKeyUncertainty;
	}

//  retrieves the entry corresponding to the key and entry pointer locked by the
//  specified lock context.  if there is no entry for this key, errEntryNotFound
//  will be returned

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrRetrieveEntry( CLock* const plock, CEntry** const ppentry ) const
	{
	//  return the current entry.  if the current entry is NULL, then there is
	//  no current entry

	*ppentry = plock->m_pentry;
	return *ppentry ? errSuccess : errEntryNotFound;
	}
	
//  inserts a new entry corresponding to the key and entry pointer locked by the
//  specified lock context.  fNextMost biases the position the entry will take
//  when inserted in the index.  if the new entry cannot be inserted,
//  errOutOfMemory will be returned.  if inserting the new entry will cause the
//  key space to become too large, errKeyRangeExceeded will be returned
//
//  NOTE:  it is illegal to attempt to insert an entry into the index that is
//  already in the index

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrInsertEntry( CLock* const plock, CEntry* const pentry, const BOOL fNextMost )
	{
	CBucketTable::ERR err;

	//  this entry had better not already be in the index

	COLLAssert( !plock->m_bucket.m_il.FMember( pentry ) );

	//  pin the bucket on behalf of the entry to insert

	plock->m_bucket.m_cPin++;
	
	//  insert this entry at the selected end of the current bucket

	if ( fNextMost )
		{
		plock->m_bucket.m_il.InsertAsNextMost( pentry );
		}
	else
		{
		plock->m_bucket.m_il.InsertAsPrevMost( pentry );
		}

	//  try to update this bucket in the bucket table

	if ( ( err = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
		{
		COLLAssert( err == CBucketTable::errNoCurrentEntry );
		
		//  the bucket does not yet exist, so try to insert it in the bucket table

		return _ErrInsertEntry( plock, pentry );
		}

	//  we succeeded in updating the bucket

	else
		{
		//  set the current entry to the newly inserted entry

		plock->m_pentryPrev	= NULL;
		plock->m_pentry		= pentry;
		plock->m_pentryNext	= NULL;
		return errSuccess;
		}
	}
	
//  deletes the entry corresponding to the key and entry pointer locked by the
//  specified lock context.  if there is no entry for this key, errNoCurrentEntry
//  will be returned

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrDeleteEntry( CLock* const plock )
	{
	//  there is a current entry

	if ( plock->m_pentry )
		{
		//  save the current entry's prev and next pointers so that we can
		//  recover our currency when it is deleted

		plock->m_pentryPrev	= plock->m_bucket.m_il.Prev( plock->m_pentry );
		plock->m_pentryNext	= plock->m_bucket.m_il.Next( plock->m_pentry );
		
		//  delete the current entry from this bucket

		plock->m_bucket.m_il.Remove( plock->m_pentry );

		//  unpin the bucket on behalf of this entry

		plock->m_bucket.m_cPin--;

		//  update the bucket in the bucket table.  it is OK if the bucket is
		//  empty because empty buckets are deleted in _ErrMoveNext/_ErrMovePrev

		const CBucketTable::ERR err = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket );
		COLLAssert( err == CBucketTable::errSuccess );

		//  set our currency to no current entry

		plock->m_pentry = NULL;
		return errSuccess;
		}

	//  there is no current entry

	else
		{
		//  return no current entry

		return errNoCurrentEntry;
		}
	}

//  reserves room to insert a new entry corresponding to the key and entry
//  pointer locked by the specified lock context.  if room for the new entry
//  cannot be reserved, errOutOfMemory will be returned.  if reserving the new
//  entry will cause the key space to become too large, errKeyRangeExceeded
//  will be returned
//
//  NOTE:  once room is reserved, it must be unreserved via UnreserveEntry()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrReserveEntry( CLock* const plock )
	{
	//  pin the locked bucket
	
	plock->m_bucket.m_cPin++;

	//  we failed to update the pin count on the bucket in the index because the
	//  bucket doesn't exist

	CBucketTable::ERR errBT;
	
	if ( ( errBT = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
		{
		COLLAssert( errBT == CBucketTable::errNoCurrentEntry );

		//  insert this bucket in the bucket table

		ERR err;

		if ( ( err = _ErrInsertBucket( plock ) ) != errSuccess )
			{
			COLLAssert( err == errOutOfMemory || err == errKeyRangeExceeded );

			//  we cannot insert the bucket so unpin the locked bucket and fail
			//  the reservation
			
			plock->m_bucket.m_cPin--;
			return err;
			}
		}

	return errSuccess;
	}

//  removes a reservation made with ErrReserveEntry()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
UnreserveEntry( CLock* const plock )
	{
	//  unpin the locked bucket
	
	plock->m_bucket.m_cPin--;

	//  update the pin count on the bucket in the index.  this cannot fail
	//  because we know the bucket exists because it is pinned

	CBucketTable::ERR errBT = m_bt.ErrReplaceEntry( &plock->m_lock, plock->m_bucket );
	COLLAssert( errBT == CBucketTable::errSuccess );
	}

//  sets up the specified lock context in preparation for scanning all entries
//  in the index by ascending key value, give or take the key uncertainty
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveBeforeFirst( CLock* const plock )
	{
	//  we will start scanning at the first bucket ID believed to be present in
	//  the index (it could have been emptied by now)

	plock->m_bucket.m_id = m_idRangeFirst;

	//  write lock this bucket ID in the bucket table

	m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

	//  fetch this bucket from the bucket table if it exists.  if it doesn't
	//  exist, the bucket will start out empty and have the above bucket ID

	plock->m_bucket.m_cPin = 0;
	plock->m_bucket.m_il.Empty();
	(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

	//  set our currency to be before the first entry in this bucket

	plock->m_pentryPrev	= NULL;
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= plock->m_bucket.m_il.PrevMost();
	}

//  moves the specified lock context to the next key and entry pointer in the
//  index by ascending key value, give or take the key uncertainty.  if the end
//  of the index is reached, errNoCurrentEntry is returned
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrMoveNext( CLock* const plock )
	{
	//  move to the next entry in this bucket

	plock->m_pentryPrev	= NULL;
	plock->m_pentry		=	plock->m_pentry ?
								plock->m_bucket.m_il.Next( plock->m_pentry ) :
								plock->m_pentryNext;
	plock->m_pentryNext	= NULL;

	//  we still have no current entry

	if ( !plock->m_pentry )
		{
		//  possibly advance to the next bucket

		return _ErrMoveNext( plock );
		}

	//  we now have a current entry

	else
		{
		//  we're done
		
		return errSuccess;
		}
	}

//  moves the specified lock context to the next key and entry pointer in the
//  index by descending key value, give or take the key uncertainty.  if the
//  start of the index is reached, errNoCurrentEntry is returned
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
ErrMovePrev( CLock* const plock )
	{
	//  move to the prev entry in this bucket

	plock->m_pentryNext	= NULL;
	plock->m_pentry		=	plock->m_pentry ?
								plock->m_bucket.m_il.Prev( plock->m_pentry ) :
								plock->m_pentryPrev;
	plock->m_pentryPrev	= NULL;

	//  we still have no current entry

	if ( !plock->m_pentry )
		{
		//  possibly advance to the prev bucket

		return _ErrMovePrev( plock );
		}

	//  we now have a current entry

	else
		{
		//  we're done
		
		return errSuccess;
		}
	}
	
//  sets up the specified lock context in preparation for scanning all entries
//  in the index by descending key value, give or take the key uncertainty
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveAfterLast( CLock* const plock )
	{
	//  we will start scanning at the last bucket ID believed to be present in
	//  the index (it could have been emptied by now)

	plock->m_bucket.m_id = m_idRangeLast;

	//  write lock this bucket ID in the bucket table

	m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

	//  fetch this bucket from the bucket table if it exists.  if it doesn't
	//  exist, the bucket will start out empty and have the above bucket ID

	plock->m_bucket.m_cPin = 0;
	plock->m_bucket.m_il.Empty();
	(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

	//  set our currency to be after the last entry in this bucket

	plock->m_pentryPrev	= plock->m_bucket.m_il.NextMost();
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= NULL;
	}

//  sets up the specified lock context in preparation for scanning all entries
//  greater than or approximately equal to the specified key and entry pointer
//  in the index by ascending key value, give or take the key uncertainty
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()
//
//  NOTE:  even though this function may land between two valid entries in
//  the index, the currency will not be on one of those entries until
//  ErrMoveNext() or ErrMovePrev() has been called

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveBeforeKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
	{
	//  we will start scanning at the bucket ID formed from the given key and
	//  entry pointer

	plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );

	//  write lock this bucket ID in the bucket table

	m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

	//  fetch this bucket from the bucket table if it exists.  if it doesn't
	//  exist, the bucket will start out empty and have the above bucket ID

	plock->m_bucket.m_cPin = 0;
	plock->m_bucket.m_il.Empty();
	(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

	//  set our currency to be before the first entry in this bucket

	plock->m_pentryPrev	= NULL;
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= plock->m_bucket.m_il.PrevMost();
	}

//  sets up the specified lock context in preparation for scanning all entries
//  less than or approximately equal to the specified key and entry pointer
//  in the index by descending key value, give or take the key uncertainty
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via UnlockKeyPtr()
//
//  NOTE:  even though this function may land between two valid entries in
//  the index, the currency will not be on one of those entries until
//  ErrMoveNext() or ErrMovePrev() has been called

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline void CApproximateIndex< CKey, CEntry, OffsetOfIC >::
MoveAfterKeyPtr( const CKey& key, CEntry* const pentry, CLock* const plock )
	{
	//  we will start scanning at the bucket ID formed from the given key and
	//  entry pointer

	plock->m_bucket.m_id = _IdFromKeyPtr( key, pentry );

	//  write lock this bucket ID in the bucket table

	m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

	//  fetch this bucket from the bucket table if it exists.  if it doesn't
	//  exist, the bucket will start out empty and have the above bucket ID

	plock->m_bucket.m_cPin = 0;
	plock->m_bucket.m_il.Empty();
	(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

	//  set our currency to be after the last entry in this bucket

	plock->m_pentryPrev	= plock->m_bucket.m_il.NextMost();
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= NULL;
	}

//  transforms the given key and entry pointer into a bucket ID

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CBucket::ID CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_IdFromKeyPtr( const CKey& key, CEntry* const pentry ) const
	{
	//  we compute the bucket ID such that each uncertainty range is split into
	//  several buckets, each of which are indexed by the pointer.  we do this
	//  to provide maximum concurrency while accessing any particular range of
	//  keys.  the reason we use the pointer in the calculation is that we want
	//  to minimize the number of times the user has to update the position of
	//  an entry due to a key change yet we need some property of the entry
	//  over which we can reproducibly hash

	const CBucket::ID	iBucketKey		= CBucket::ID( key >> m_shfKeyUncertainty );
	const CBucket::ID	iBucketPtr		= CBucket::ID( DWORD_PTR( pentry ) / sizeof( CEntry ) );

	return ( ( iBucketKey & m_maskBucketKey ) << m_shfBucketHash ) + ( iBucketPtr & m_maskBucketPtr );
	}

//  performs a wrap-around insensitive delta of a bucket ID by an offset

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CBucket::ID CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_DeltaId( const CBucket::ID id, const long did ) const
	{
	return ( id + CBucket::ID( did ) ) & m_maskBucketID;
	}

//  performs a wrap-around insensitive subtraction of two bucket IDs

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_SubId( const CBucket::ID id1, const CBucket::ID id2 ) const
	{
	//  munge bucket IDs to fill the Most Significant Bit of a long so that we
	//  can make a wrap-around aware subtraction
	
	const long lid1 = id1 << m_shfFillMSB;
	const long lid2 = id2 << m_shfFillMSB;

	//  munge the result back into the same scale as the bucket IDs
	
	return CBucket::ID( ( lid1 - lid2 ) >> m_shfFillMSB );
	}

//  performs a wrap-around insensitive comparison of two bucket IDs

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline long CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_CmpId( const CBucket::ID id1, const CBucket::ID id2 ) const
	{
	//  munge bucket IDs to fill the Most Significant Bit of a long so that we
	//  can make a wrap-around aware comparison
	
	const long lid1 = id1 << m_shfFillMSB;
	const long lid2 = id2 << m_shfFillMSB;
	
	return lid1 - lid2;
	}

//  converts a pointer to an entry to a pointer to the invasive context

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::CInvasiveContext* CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_PicFromPentry( CEntry* const pentry ) const
	{
	return (CInvasiveContext*)( (BYTE*)pentry + OffsetOfIC() );
	}

//  tries to expand the bucket ID range by adding the new bucket ID.  if this
//  cannot be done without violating the range constraints, fFalse will be
//  returned

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline BOOL CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_FExpandIdRange( const CBucket::ID idNew )
	{
	//  fetch the current ID range

	const long			cidRange	= m_cidRange;
	const CBucket::ID	idFirst		= m_idRangeFirst;
	const CBucket::ID	idLast		= m_idRangeLast;
	const long			didRange	= _SubId( idLast, idFirst );

	COLLAssert( didRange >= 0 );
	COLLAssert( didRange <= m_didRangeMost );
	COLLAssert( cidRange >= 0 );
	COLLAssert( cidRange <= m_didRangeMost + 1 );

	//  if there are no entries in the ID range then simply set the ID range to
	//  exactly contain this new bucket ID

	if ( !cidRange )
		{
		m_cidRange		= 1;
		m_idRangeFirst	= idNew;
		m_idRangeLast	= idNew;

		return fTrue;
		}

	//  compute the valid range for the new first ID and new last ID.  these
	//  points and the above points form four ranges in a circular number
	//  line containing all possible bucket IDs:
	//
	//    ( idFirstMic, idFirst )    Possible extension of the ID range
	//    [ idFirst, idLast ]        The current ID range
	//    ( idLast, idLastMax )      Possible extension of the ID range
	//    [ idLastMax, idFirstMic ]  Cannot be part of the ID range
	//
	//  these ranges will never overlap due to the restriction that the
	//  ID range cannot meet or exceed half the number of bucket IDs
	//
	//  NOTE:  due to a quirk in 2's complement arithmetic where the 2's
	//  complement negative of the smallest negative number is itself, the
	//  inclusive range tests fail when idFirst == idLast and idNew ==
	//  idFirstMic == idLastMax or when idFirstMic == idLastMax and idnew ==
	//  idFirst == idLast.  we have added special logic to handle these
	//  cases correctly

	const CBucket::ID	idFirstMic	= _DeltaId( idFirst, -( m_didRangeMost - didRange + 1 ) );
	const CBucket::ID	idLastMax	= _DeltaId( idLast, m_didRangeMost - didRange + 1 );

	//  if the new bucket ID is already part of this ID range, no change
	//  is needed

	if (	_CmpId( idFirstMic, idNew ) != 0 && _CmpId( idLastMax, idNew ) != 0 &&
			_CmpId( idFirst, idNew ) <= 0 && _CmpId( idNew, idLast ) <= 0 )
		{
		m_cidRange = cidRange + 1;
		
		return fTrue;
		}

	//  if the new bucket ID cannot be a part of this ID range, fail the
	//  expansion

	if (	_CmpId( idFirst, idNew ) != 0 && _CmpId( idLast, idNew ) != 0 &&
			_CmpId( idLastMax, idNew ) <= 0 && _CmpId( idNew, idFirstMic ) <= 0 )
		{
		return fFalse;
		}

	//  compute the new ID range including this new bucket ID

	CBucket::ID	idFirstNew	= idFirst;
	CBucket::ID	idLastNew	= idLast;

	if ( _CmpId( idFirstMic, idNew ) < 0 && _CmpId( idNew, idFirst ) < 0 )
		{
		idFirstNew = idNew;
		}
	else
		{
		COLLAssert( _CmpId( idLast, idNew ) < 0 && _CmpId( idNew, idLastMax ) < 0 );
		
		idLastNew = idNew;
		}

	//  the new ID range should be larger than the old ID range and should
	//  include the new bucket ID

	COLLAssert( _CmpId( idFirstNew, idFirst ) <= 0 );
	COLLAssert( _CmpId( idLast, idLastNew ) <= 0 );
	COLLAssert( _SubId( idLastNew, idFirstNew ) > 0 );
	COLLAssert( _SubId( idLastNew, idFirstNew ) <= m_didRangeMost );
	COLLAssert( _CmpId( idFirstNew, idNew ) <= 0 );
	COLLAssert( _CmpId( idNew, idLastNew ) <= 0 );

	//  update the key range to include the new bucket ID

	m_cidRange		= cidRange + 1;
	m_idRangeFirst	= idFirstNew;
	m_idRangeLast	= idLastNew;

	return fTrue;
	}

//  inserts a new bucket in the bucket table

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrInsertBucket( CLock* const plock )
	{
	//  try to update the bucket ID range and subrange of the index to include
	//  this new bucket ID

	m_critUpdateIdRange.Enter();
	const BOOL fRangeUpdated = _FExpandIdRange( plock->m_bucket.m_id );
	m_critUpdateIdRange.Leave();
	
	//  if the update failed, fail the bucket insertion

	if ( !fRangeUpdated )
		{
		return errKeyRangeExceeded;
		}

	//  the bucket does not yet exist, so try to insert it in the bucket table

	CBucketTable::ERR err;
	
	if ( ( err = m_bt.ErrInsertEntry( &plock->m_lock, plock->m_bucket ) ) != CBucketTable::errSuccess )
		{
		COLLAssert( err == CBucketTable::errOutOfMemory );
	
		//  we cannot do the insert so fail

		m_critUpdateIdRange.Enter();
		m_cidRange--;
		m_critUpdateIdRange.Leave();
		
		return errOutOfMemory;
		}

	return errSuccess;
	}

//  performs an entry insertion that must insert a new bucket in the bucket table

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrInsertEntry( CLock* const plock, CEntry* const pentry )
	{
	ERR err;
	
	//  insert this bucket in the bucket table

	if ( ( err = _ErrInsertBucket( plock ) ) != errSuccess )
		{
		COLLAssert( err == errOutOfMemory || err == errKeyRangeExceeded );

		//  we cannot insert the bucket so undo the list insertion and fail
		
		plock->m_bucket.m_il.Remove( pentry );
		plock->m_bucket.m_cPin--;
		return err;
		}

	//  set the current entry to the newly inserted entry

	plock->m_pentryPrev	= NULL;
	plock->m_pentry		= pentry;
	plock->m_pentryNext	= NULL;
	return errSuccess;
	}

//  performs a move next that possibly goes to the next bucket.  we won't go to
//  the next bucket if we are already at the last bucket ID

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrMoveNext( CLock* const plock )
	{
	//  set our currency to be after the last entry in this bucket

	plock->m_pentryPrev	= plock->m_bucket.m_il.NextMost();
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= NULL;

	//  scan forward until we have a current entry or we are at or beyond the
	//  last bucket ID

	while (	!plock->m_pentry && _CmpId( plock->m_bucket.m_id, m_idRangeLast ) < 0 )
		{
		//  we are currently at the first bucket ID and that bucket isn't pinned

		if ( !plock->m_bucket.m_cPin )
			{
			//  delete this empty bucket (if it exists)

			const CBucketTable::ERR err = m_bt.ErrDeleteEntry( &plock->m_lock );
			COLLAssert(	err == CBucketTable::errSuccess ||
					err == CBucketTable::errNoCurrentEntry );

			//  advance the first bucket ID by one so that subsequent searches
			//  do not scan through this empty bucket unnecessarily

			m_critUpdateIdRange.Enter();

			if ( m_idRangeFirst == plock->m_bucket.m_id )
				{
				m_idRangeFirst = _DeltaId( m_idRangeFirst, 1 );
				}

			if ( err == CBucketTable::errSuccess )
				{
				m_cidRange--;
				}

			m_critUpdateIdRange.Leave();
			}

		//  unlock the current bucket ID in the bucket table

		m_bt.WriteUnlockKey( &plock->m_lock );

		//  this bucket ID may not be in the valid bucket ID range

		if (	_CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
				_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
			{
			//  we can get the critical section protecting the bucket ID range

			if ( m_critUpdateIdRange.FTryEnter() )
				{
				//  this bucket ID is not in the valid bucket ID range

				if (	_CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
						_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
					{
					//  go to the first valid bucket ID

					plock->m_bucket.m_id = m_idRangeFirst;
					}

				//  this bucket ID is in the valid bucket ID range

				else
					{
					//  advance to the next bucket ID

					plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
					}

				m_critUpdateIdRange.Leave();
				}

			//  we cannot get the critical section protecting the bucket ID range

			else
				{
				//  advance to the next bucket ID

				plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
				}
			}

		//  this bucket may be in the valid bucket ID range

		else
			{
			//  advance to the next bucket ID

			plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, 1 );
			}

		//  write lock this bucket ID in the bucket table

		m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

		//  fetch this bucket from the bucket table if it exists.  if it doesn't
		//  exist, the bucket will start out empty and have the above bucket ID

		plock->m_bucket.m_cPin = 0;
		plock->m_bucket.m_il.Empty();
		(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

		//  set our currency to be the first entry in this bucket

		plock->m_pentryPrev	= NULL;
		plock->m_pentry		= plock->m_bucket.m_il.PrevMost();
		plock->m_pentryNext	= NULL;
		}

	//  return the status of our currency

	return plock->m_pentry ? errSuccess : errNoCurrentEntry;
	}

//  performs a move prev that goes possibly to the prev bucket.  we won't go to
//  the prev bucket if we are already at the first bucket ID

template< class CKey, class CEntry, PfnOffsetOf OffsetOfIC >
inline CApproximateIndex< CKey, CEntry, OffsetOfIC >::ERR CApproximateIndex< CKey, CEntry, OffsetOfIC >::
_ErrMovePrev( CLock* const plock )
	{
	//  set our currency to be before the first entry in this bucket

	plock->m_pentryPrev	= NULL;
	plock->m_pentry		= NULL;
	plock->m_pentryNext	= plock->m_bucket.m_il.PrevMost();

	//  scan backward until we have a current entry or we are at or before the
	//  first bucket ID

	while (	!plock->m_pentry && _CmpId( m_idRangeFirst, plock->m_bucket.m_id ) < 0 )
		{
		//  we are currently at the last bucket ID and that bucket isn't pinned

		if ( !plock->m_bucket.m_cPin )
			{
			//  delete this empty bucket (if it exists)

			const CBucketTable::ERR err = m_bt.ErrDeleteEntry( &plock->m_lock );
			COLLAssert(	err == CBucketTable::errSuccess ||
					err == CBucketTable::errNoCurrentEntry );

			//  retreat the last bucket ID by one so that subsequent searches
			//  do not scan through this empty bucket unnecessarily

			m_critUpdateIdRange.Enter();

			if ( m_idRangeLast == plock->m_bucket.m_id )
				{
				m_idRangeLast = _DeltaId( m_idRangeLast, -1 );
				}

			if ( err == CBucketTable::errSuccess )
				{
				m_cidRange--;
				}

			m_critUpdateIdRange.Leave();
			}

		//  unlock the current bucket ID in the bucket table

		m_bt.WriteUnlockKey( &plock->m_lock );

		//  this bucket ID may not be in the valid bucket ID range

		if (	_CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
				_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
			{
			//  we can get the critical section protecting the bucket ID range

			if ( m_critUpdateIdRange.FTryEnter() )
				{
				//  this bucket ID is not in the valid bucket ID range

				if (	_CmpId( m_idRangeFirst, plock->m_bucket.m_id ) > 0 ||
						_CmpId( plock->m_bucket.m_id, m_idRangeLast ) > 0 )
					{
					//  go to the last valid bucket ID

					plock->m_bucket.m_id = m_idRangeLast;
					}

				//  this bucket ID is in the valid bucket ID range

				else
					{
					//  retreat to the previous bucket ID

					plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
					}

				m_critUpdateIdRange.Leave();
				}

			//  we cannot get the critical section protecting the bucket ID range

			else
				{
				//  retreat to the previous bucket ID

				plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
				}
			}

		//  this bucket may be in the valid bucket ID range

		else
			{
			//  retreat to the previous bucket ID

			plock->m_bucket.m_id = _DeltaId( plock->m_bucket.m_id, -1 );
			}

		//  write lock this bucket ID in the bucket table

		m_bt.WriteLockKey( plock->m_bucket.m_id, &plock->m_lock );

		//  fetch this bucket from the bucket table if it exists.  if it doesn't
		//  exist, the bucket will start out empty and have the above bucket ID

		plock->m_bucket.m_cPin = 0;
		plock->m_bucket.m_il.Empty();
		(void)m_bt.ErrRetrieveEntry( &plock->m_lock, &plock->m_bucket );

		//  set our currency to be the last entry in this bucket

		plock->m_pentryPrev	= NULL;
		plock->m_pentry		= plock->m_bucket.m_il.NextMost();
		plock->m_pentryNext	= NULL;
		}

	//  return the status of our currency

	return plock->m_pentry ? errSuccess : errNoCurrentEntry;
	}

	
#define DECLARE_APPROXIMATE_INDEX( CKey, CEntry, OffsetOfIC, Typedef )	\
																		\
typedef CApproximateIndex< CKey, CEntry, OffsetOfIC > Typedef;			\
																		\
inline ULONG_PTR Typedef::CBucketTable::CKeyEntry::						\
Hash( const CBucket::ID& id )											\
	{																	\
	return id;															\
	}																	\
																		\
inline ULONG_PTR Typedef::CBucketTable::CKeyEntry::						\
Hash() const															\
	{																	\
	return m_entry.m_id;												\
	}																	\
																		\
inline BOOL Typedef::CBucketTable::CKeyEntry::							\
FEntryMatchesKey( const CBucket::ID& id ) const							\
	{																	\
	return m_entry.m_id == id;											\
	}																	\
																		\
inline void Typedef::CBucketTable::CKeyEntry::							\
SetEntry( const CBucket& bucket )										\
	{																	\
	m_entry = bucket;													\
	}																	\
																		\
inline void Typedef::CBucketTable::CKeyEntry::							\
GetEntry( CBucket* const pbucket ) const								\
	{																	\
	*pbucket = m_entry;													\
	}


//////////////////////////////////////////////////////////////////////////////////////////
//  CPool
//
//  Implements a pool of objects that can be inserted and deleted quickly in arbitrary
//  order.
//
//  CObject			= class representing objects in the pool.  each class must contain
//					  storage for a CInvasiveContext for embedded pool state
//  OffsetOfIC		= inline function returning the offset of the CInvasiveContext
//					  contained in the CObject

template< class CObject, PfnOffsetOf OffsetOfIC >
class CPool
	{
	public:

		//  class containing context needed per CObject

		class CInvasiveContext
			{
			public:

				CInvasiveContext() {}
				~CInvasiveContext() {}

				static SIZE_T OffsetOfILE() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_ile ); }

			private:

				CInvasiveList< CObject, OffsetOfILE >::CElement	m_ile;
			};

		//  API Error Codes

		enum ERR
			{
			errSuccess,
			errInvalidParameter,
			errOutOfMemory,
			errObjectNotFound,
			errOutOfObjects,
			errNoCurrentObject,
			};

		//  API Lock Context

		class CLock;

	public:

		//  ctor / dtor

		CPool();
		~CPool();

		//  API

		ERR ErrInit( const double dblSpeedSizeTradeoff );
		void Term();

		void Insert( CObject* const pobj );
		ERR ErrRemove( CObject** const ppobj, const BOOL fWait = fTrue );

		void BeginPoolScan( CLock* const plock );
		ERR ErrGetNextObject( CLock* const plock, CObject** const ppobj );
		ERR ErrRemoveCurrentObject( CLock* const plock );
		void EndPoolScan( CLock* const plock );

		DWORD Cobject();
		DWORD CWaiter();
		DWORD CRemove();
		DWORD CRemoveWait();

	private:

		//  bucket used for containing objects in the pool

		class CBucket
			{
			public:

				CBucket() : m_crit( CLockBasicInfo( CSyncBasicInfo( "CPool::CBucket::m_crit" ), 0, 0 ) ) {}
				~CBucket() {}

			public:

				CCriticalSection										m_crit;
				CInvasiveList< CObject, CInvasiveContext::OffsetOfILE >	m_il;
				BYTE													m_rgbReserved[20];
			};

	public:

		//  API Lock Context

		class CLock
			{
			public:

				CLock() {}
				~CLock() {}

			private:

				friend class CPool< CObject, OffsetOfIC >;

				CBucket*	m_pbucket;
				CObject*	m_pobj;
				CObject*	m_pobjNext;
			};

	private:

		void _GetNextObject( CLock* const plock );
		static void* _PvMEMIAlign( void* const pv, const size_t cbAlign );
		static void* _PvMEMIUnalign( void* const pv );
		static void* _PvMEMAlloc( const size_t cbSize, const size_t cbAlign = 1 );
		static void _MEMFree( void* const pv );

	private:

		//  never updated

		DWORD			m_cbucket;
		CBucket*		m_rgbucket;
		BYTE			m_rgbReserved1[24];

		//  commonly updated

		CSemaphore		m_semObjectCount;
		DWORD			m_cRemove;
		DWORD			m_cRemoveWait;
		BYTE			m_rgbReserved2[20];
	};

//  ctor

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::
CPool()
	:	m_semObjectCount( CSyncBasicInfo( "CPool::m_semObjectCount" ) )
	{
	}

//  dtor

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::
~CPool()
	{
	//  nop
	}

//  initializes the pool using the given parameters.  if the pool cannot be
//  initialized, errOutOfMemory is returned

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrInit( const double dblSpeedSizeTradeoff )
	{
	//  validate all parameters

	if ( dblSpeedSizeTradeoff < 0.0 || dblSpeedSizeTradeoff > 1.0 )
		{
		return errInvalidParameter;
		}
		
	//  allocate our bucket array, one per CPU, on a cache-line boundary

	m_cbucket = OSSyncGetProcessorCount();
	const SIZE_T cbrgbucket = sizeof( CBucket ) * m_cbucket;
	if ( !( m_rgbucket = (CBucket*)_PvMEMAlloc( cbrgbucket, cbCacheLine ) ) )
		{
		return errOutOfMemory;
		}

	//  setup our bucket array

	for ( DWORD ibucket = 0; ibucket < m_cbucket; ibucket++ )
		{
		new( m_rgbucket + ibucket ) CBucket;
		}

	//  init out stats

	m_cRemove		= 0;
	m_cRemoveWait	= 0;

	return errSuccess;
	}

//  terminates the pool.  this function can be called even if the pool has never
//  been initialized or is only partially initialized
//
//  NOTE:  any data stored in the pool at this time will be lost!

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
Term()
	{
	//  free our bucket array

	if ( m_rgbucket )
		{
		for ( DWORD ibucket = 0; ibucket < m_cbucket; ibucket++ )
			{
			m_rgbucket[ ibucket ].~CBucket();
			}
		_MEMFree( m_rgbucket );
		m_rgbucket = NULL;
		}

	//  remove any free counts on our semaphore

	while ( m_semObjectCount.FTryAcquire() )
		{
		}
	}

//  inserts the given object into the pool

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
Insert( CObject* const pobj )
	{
	//  add the given object to the bucket for this CPU.  we use one bucket per
	//  CPU to reduce cache sloshing.  if we cannot lock the bucket for this CPU,
	//  we will try another bucket instead of blocking

	DWORD ibucketBase;
	DWORD ibucket;

	ibucketBase	= OSSyncGetCurrentProcessor();
	ibucket		= 0;

	do	{
		CBucket* const pbucket = m_rgbucket + ( ibucketBase + ibucket++ ) % m_cbucket;

		if ( ibucket < m_cbucket )
			{
			if ( !pbucket->m_crit.FTryEnter() )
				{
				continue;
				}
			}
		else
			{
			pbucket->m_crit.Enter();
			}

		pbucket->m_il.InsertAsNextMost( pobj );
		pbucket->m_crit.Leave();
		break;
		}
	while ( fTrue );

	//  increment the object count

	m_semObjectCount.Release();
	}

//  removes an object from the pool, optionally waiting until an object can be
//  removed.  if an object can be removed, errSuccess is returned.  if an
//  object cannot be immediately removed and waiting is not desired,
//  errOutOfObjects will be returned

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrRemove( CObject** const ppobj, const BOOL fWait )
	{
	//  reserve an object for removal from the pool by acquiring a count on the
	//  object count semaphore.  if we get a count, we are allowed to remove an
	//  object from the pool.  acquire a count in the requested mode, i.e. wait
	//  or do not wait for a count

	if ( !m_semObjectCount.FTryAcquire() )
		{
		if ( !fWait )
			{
			return errOutOfObjects;
			}
		else
			{
			m_cRemoveWait++;
			m_semObjectCount.FAcquire( cmsecInfinite );
			}
		}

	//  we are now entitled to an object from the pool, so scan all buckets for
	//  an object to remove until we find one.  start with the bucket for the
	//  current CPU to reduce cache sloshing

	DWORD ibucketBase;
	DWORD ibucket;

	ibucketBase	= OSSyncGetCurrentProcessor();
	ibucket		= 0;
	*ppobj		= NULL;

	do	{
		CBucket* const pbucket = m_rgbucket + ( ibucketBase + ibucket++ ) % m_cbucket;

		if ( pbucket->m_il.FEmpty() )
			{
			continue;
			}

		if ( ibucket < m_cbucket )
			{
			if ( !pbucket->m_crit.FTryEnter() )
				{
				continue;
				}
			}
		else
			{
			pbucket->m_crit.Enter();
			}

		if ( !pbucket->m_il.FEmpty() )
			{
			*ppobj = pbucket->m_il.PrevMost();
			pbucket->m_il.Remove( *ppobj );
			}
		pbucket->m_crit.Leave();
		}
	while ( *ppobj == NULL );

	//  return the object

	m_cRemove++;
	return errSuccess;
	}

//  sets up the specified lock context in preparation for scanning all objects
//  in the pool
//
//  NOTE:  this function will acquire a lock that must eventually be released
//  via EndPoolScan()

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
BeginPoolScan( CLock* const plock )
	{
	//  we will start in the first bucket
	
	plock->m_pbucket = m_rgbucket;

	//  lock this bucket

	plock->m_pbucket->m_crit.Enter();

	//  set out currency to be before the first object in this bucket

	plock->m_pobj		= NULL;
	plock->m_pobjNext	= plock->m_pbucket->m_il.PrevMost();
	}

//  retrieves the next object in the pool locked by the specified lock context.
//  if there are no more objects to be scanned, errNoCurrentObject is returned

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrGetNextObject( CLock* const plock, CObject** const ppobj )
	{
	//  move to the next object in this bucket

	plock->m_pobj		=	plock->m_pobj ?
								plock->m_pbucket->m_il.Next( plock->m_pobj ) :
								plock->m_pobjNext;
	plock->m_pobjNext	= NULL;

	//  we still have no current object

	if ( !plock->m_pobj )
		{
		//  possibly advance to the next bucket

		_GetNextObject( plock );
		}

	//  return the current object, if any

	*ppobj = plock->m_pobj;
	return plock->m_pobj ? errSuccess : errNoCurrentObject;
	}

//  removes the current object in the pool locaked by the specified lock context
//  from the pool.  if there is no current object, errNoCurrentObject will be
//  returned

template< class CObject, PfnOffsetOf OffsetOfIC >
inline CPool< CObject, OffsetOfIC >::ERR CPool< CObject, OffsetOfIC >::
ErrRemoveCurrentObject( CLock* const plock )
	{
	//  there is a current object and we can remove that object from the pool
	//
	//  NOTE:  we must get a count from the semaphore to remove an object from
	//  the pool

	if ( plock->m_pobj && m_semObjectCount.FTryAcquire() )
		{
		//  save the current object's next pointer so that we can recover our
		//  currency when it is deleted

		plock->m_pobjNext	= plock->m_pbucket->m_il.Next( plock->m_pobj );
		
		//  delete the current object from this bucket

		plock->m_pbucket->m_il.Remove( plock->m_pobj );

		//  set our currency to no current object

		plock->m_pobj = NULL;
		return errSuccess;
		}

	//  there is no current object

	else
		{
		//  return no current object

		return errNoCurrentObject;
		}
	}

//  ends the scan of all objects in the pool associated with the specified lock
//  context and releases all locks held

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
EndPoolScan( CLock* const plock )
	{
	//  unlock the current bucket

	plock->m_pbucket->m_crit.Leave();
	}

//  returns the current count of objects in the pool

template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
Cobject()
	{
	//  the number of objects in the pool is equal to the available count on the
	//  object count semaphore
	
	return m_semObjectCount.CAvail();
	}

//  returns the number of waiters for objects in the pool

template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CWaiter()
	{
	//  the number of waiters on the pool is equal to the waiter count on the
	//  object count semaphore
	
	return m_semObjectCount.CWait();
	}

//  returns the number of times on object has been successfully removed from the
//  pool

template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CRemove()
	{
	return m_cRemove;
	}

//  returns the number of waits that occurred while removing objects from the
//  pool

template< class CObject, PfnOffsetOf OffsetOfIC >
inline DWORD CPool< CObject, OffsetOfIC >::
CRemoveWait()
	{
	return m_cRemoveWait;
	}

//  performs a move next that possibly goes to the next bucket.  we won't go to
//  the next bucket if we are already at the last bucket

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
_GetNextObject( CLock* const plock )
	{
	//  set our currency to be after the last object in this bucket

	plock->m_pobj		= NULL;
	plock->m_pobjNext	= NULL;

	//  scan forward until we have a current object or we are at or beyond the
	//  last bucket

	while ( !plock->m_pobj && plock->m_pbucket < m_rgbucket + m_cbucket - 1 )
		{
		//  unlock the current bucket

		plock->m_pbucket->m_crit.Leave();

		//  advance to the next bucket

		plock->m_pbucket++;

		//  lock this bucket

		plock->m_pbucket->m_crit.Enter();

		//  set our currency to be the first object in this bucket

		plock->m_pobj		= plock->m_pbucket->m_il.PrevMost();
		plock->m_pobjNext	= NULL;
		}
	}

//	calculate the address of the aligned block and store its offset (for free)

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMIAlign( void* const pv, const size_t cbAlign )
	{
	//	round up to the nearest cache line
	//	NOTE: this formula always forces an offset of at least 1 byte

	const ULONG_PTR ulp			= ULONG_PTR( pv );
	const ULONG_PTR ulpAligned	= ( ( ulp + cbAlign ) / cbAlign ) * cbAlign;
	const ULONG_PTR ulpOffset	= ulpAligned - ulp;

	COLLAssert( ulpOffset > 0 );
	COLLAssert( ulpOffset <= cbAlign );
	COLLAssert( ulpOffset == BYTE( ulpOffset ) );	//	must fit into a single BYTE

	//	store the offset

	BYTE *const pbAligned	= (BYTE*)ulpAligned;
	pbAligned[ -1 ]			= BYTE( ulpOffset );

	//	return the aligned block

	return (void*)pbAligned;
	}

//	retrieve the offset of the real block being freed

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMIUnalign( void* const pv )
	{
	//	read the offset of the real block

	BYTE *const pbAligned	= (BYTE*)pv;
	const BYTE bOffset		= pbAligned[ -1 ];

	COLLAssert( bOffset > 0 );

	//	return the real unaligned block

	return (void*)( pbAligned - bOffset );
	}

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void* CPool< CObject, OffsetOfIC >::
_PvMEMAlloc( const size_t cbSize, const size_t cbAlign )
	{
	void* const pv = new BYTE[ cbSize + cbAlign ];
	if ( pv )
		{
		return _PvMEMIAlign( pv, cbAlign );
		}
	return NULL;
	}

template< class CObject, PfnOffsetOf OffsetOfIC >
inline void CPool< CObject, OffsetOfIC >::
_MEMFree( void* const pv )
	{
	if ( pv )
		{
		delete [] _PvMEMIUnalign( pv );
		}
	}


////////////////////////////////////////////////////////////////////////////////
//  CArray
//
//  Implements a dynamically resized array of entries stored for efficient
//  iteration.
//
//  CEntry			= class representing entries stored in the array
//
//  NOTE:  the user must provide CEntry::CEntry() and CEntry::operator=()

template< class CEntry >
class CArray
	{
	public:

		//  API Error Codes

		enum ERR
			{
			errSuccess,
			errInvalidParameter,
			errOutOfMemory,
			};
		
	public:

		CArray();
		CArray( const size_t centry, CEntry* const rgentry );
		~CArray();

		ERR ErrClone( const CArray& array );

		ERR ErrSetSize( const size_t centry );
		
		ERR ErrSetEntry( const size_t ientry, const CEntry& entry );
		void SetEntry( const CEntry* const pentry, const CEntry& entry );

		size_t Size() const;
		const CEntry* Entry( const size_t ientry ) const;

	private:

		size_t		m_centry;
		CEntry*		m_rgentry;
		BOOL		m_fInPlace;
	};

template< class CEntry >
inline CArray< CEntry >::
CArray()
	:	m_centry( 0 ),
		m_rgentry( NULL ),
		m_fInPlace( fTrue )
	{
	}

template< class CEntry >
inline CArray< CEntry >::
CArray( const size_t centry, CEntry* const rgentry )
	:	m_centry( centry ),
		m_rgentry( rgentry ),
		m_fInPlace( fTrue )
	{
	}

template< class CEntry >
inline CArray< CEntry >::
~CArray()
	{
	ErrSetSize( 0 );
	}

//  clones an existing array

template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrClone( const CArray& array )
	{
	CEntry*		rgentryNew	= NULL;
	size_t		ientryCopy	= 0;

	if ( array.m_centry )
		{
		if ( !( rgentryNew = new CEntry[ array.m_centry ] ) )
			{
			return errOutOfMemory;
			}
		}

	for ( ientryCopy = 0; ientryCopy < array.m_centry; ientryCopy++ )
		{
		rgentryNew[ ientryCopy ] = array.m_rgentry[ ientryCopy ];
		}

	if ( !m_fInPlace )
		{
		delete [] m_rgentry;
		}
	m_centry	= array.m_centry;
	m_rgentry	= rgentryNew;
	m_fInPlace	= fFalse;
	rgentryNew	= NULL;

	delete [] rgentryNew;
	return errSuccess;
	}

//  sets the size of the array

template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrSetSize( const size_t centry )
	{
	CEntry*		rgentryNew	= NULL;
	size_t		ientryCopy	= 0;

	if ( Size() != centry )
		{
		if ( centry )
			{
			if ( !( rgentryNew = new CEntry[ centry ] ) )
				{
				return errOutOfMemory;
				}

			for ( ientryCopy = 0; ientryCopy < Size(); ientryCopy++ )
				{
				rgentryNew[ ientryCopy ] = *Entry( ientryCopy );
				}

			if ( !m_fInPlace )
				{
				delete [] m_rgentry;
				}
			m_centry	= centry;
			m_rgentry	= rgentryNew;
			m_fInPlace	= fFalse;
			rgentryNew	= NULL;
			}
		else
			{
			if ( !m_fInPlace )
				{
				delete [] m_rgentry;
				}

			m_centry	= 0;
			m_rgentry	= NULL;
			m_fInPlace	= fTrue;
			}
		}

	delete [] rgentryNew;
	return errSuccess;
	}

//  sets the Nth entry of the array, growing the array if necessary

template< class CEntry >
inline CArray< CEntry >::ERR CArray< CEntry >::
ErrSetEntry( const size_t ientry, const CEntry& entry )
	{
	ERR		err			= errSuccess;
	size_t	centryReq	= ientry + 1;

	if ( Size() < centryReq )
		{
		if ( ( err = ErrSetSize( centryReq ) ) != errSuccess )
			{
			return err;
			}
		}

	SetEntry( Entry( ientry ), entry );

	return errSuccess;
	}

//  sets an existing entry of the array

template< class CEntry >
inline void CArray< CEntry >::
SetEntry( const CEntry* const pentry, const CEntry& entry )
	{
	*const_cast< CEntry* >( pentry ) = entry;
	}

//  returns the current size of the array

template< class CEntry >
inline size_t CArray< CEntry >::
Size() const
	{
	return m_centry;
	}

//  returns a pointer to the Nth entry of the array or NULL if it is empty

template< class CEntry >
inline const CEntry* CArray< CEntry >::
Entry( const size_t ientry ) const
	{
	return ientry < m_centry ? m_rgentry + ientry : NULL;
	}


////////////////////////////////////////////////////////////////////////////////
//  CTable
//
//  Implements a table of entries identified by a key and stored for efficient
//  lookup and iteration.  The keys need not be unique.
//
//  CKey			= class representing keys used to identify entries
//  CEntry			= class representing entries stored in the table
//
//  NOTE:  the user must implement the CKeyEntry::Cmp() functions and provide
//  CEntry::CEntry() and CEntry::operator=()

template< class CKey, class CEntry >
class CTable
	{
	public:

		class CKeyEntry
			:	public CEntry
			{
			public:

				//  Cmp() return values:
				//
				//      < 0  this entry < specified entry / key
				//      = 0  this entry = specified entry / key
				//      > 0  this entry > specified entry / key

				int Cmp( const CKeyEntry& keyentry ) const;
				int Cmp( const CKey& key ) const;
			};

		//  API Error Codes

		enum ERR
			{
			errSuccess,
			errInvalidParameter,
			errOutOfMemory,
			errKeyChange,
			};
			
	public:

		CTable();
		CTable( const size_t centry, CEntry* const rgentry, const BOOL fInOrder = fFalse );

		ERR ErrLoad( const size_t centry, const CEntry* const rgentry );
		ERR ErrClone( const CTable& table );
		
		ERR ErrUpdateEntry( const CEntry* const pentry, const CEntry& entry );

		size_t Size() const;
		const CEntry* Entry( const size_t ientry ) const;

		const CEntry* SeekLT( const CKey& key ) const;
		const CEntry* SeekLE( const CKey& key ) const;
		const CEntry* SeekEQ( const CKey& key ) const;
		const CEntry* SeekHI( const CKey& key ) const;
		const CEntry* SeekGE( const CKey& key ) const;
		const CEntry* SeekGT( const CKey& key ) const;

	private:

		typedef size_t (CTable< CKey, CEntry >::*PfnSearch)( const CKey& key, const BOOL fHigh ) const;

	private:

		const CKeyEntry& _Entry( const size_t ikeyentry ) const;
		void _SetEntry( const size_t ikeyentry, const CKeyEntry& keyentry );
		void _SwapEntry( const size_t ikeyentry1, const size_t ikeyentry2 );

		size_t _LinearSearch( const CKey& key, const BOOL fHigh ) const;
		size_t _BinarySearch( const CKey& key, const BOOL fHigh ) const;
		void _InsertionSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn );
		void _QuickSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn );

	private:

		CArray< CKeyEntry >	m_arrayKeyEntry;
		PfnSearch				m_pfnSearch;
	};

template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::
CTable()
	:	m_pfnSearch( _LinearSearch )
	{
	}

//  loads the table over an existing array of entries.  if the entries are not
//  in order then they will be sorted in place

template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::
CTable( const size_t centry, CEntry* const rgentry, const BOOL fInOrder )
	:	m_arrayKeyEntry( centry, reinterpret_cast< CKeyEntry* >( rgentry ) )
	{
	size_t n;
	size_t log2n;
	for ( n = Size(), log2n = 0; n; n = n  / 2, log2n++ );

	if ( 2 * log2n < Size() )
		{
		if ( !fInOrder )
			{
			_QuickSort( 0, Size() );
			}
		m_pfnSearch = _BinarySearch;
		}
	else
		{
		if ( !fInOrder )
			{
			_InsertionSort( 0, Size() );
			}
		m_pfnSearch = _LinearSearch;
		}
	}

//  loads an array of entries into the table.  additional entries may also be
//  loaded into the table via this function

template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrLoad( const size_t centry, const CEntry* const rgentry )
	{
	CArray< CKeyEntry >::ERR	err			= CArray< CKeyEntry >::errSuccess;
	size_t						ientry		= 0;
	size_t						ientryMin	= Size();
	size_t						ientryMax	= Size() + centry;
	const CKeyEntry*			rgkeyentry	= reinterpret_cast< const CKeyEntry* >( rgentry );

	if ( ( err = m_arrayKeyEntry.ErrSetSize( Size() + centry ) ) != CArray< CKeyEntry >::errSuccess )
		{
		COLLAssert( err == CArray< CKeyEntry >::errOutOfMemory );
		return errOutOfMemory;
		}
	for ( ientry = ientryMin; ientry < ientryMax; ientry++ )
		{
		err = m_arrayKeyEntry.ErrSetEntry( ientry, rgkeyentry[ ientry - ientryMin ] );
		COLLAssert( err == CArray< CKeyEntry >::errSuccess );
		}

	size_t n;
	size_t log2n;
	for ( n = Size(), log2n = 0; n; n = n  / 2, log2n++ );

	if ( 2 * log2n < centry )
		{
		_QuickSort( 0, Size() );
		}
	else
		{
		_InsertionSort( 0, Size() );
		}

	if ( 2 * log2n < Size() )
		{
		m_pfnSearch = _BinarySearch;
		}
	else
		{
		m_pfnSearch = _LinearSearch;
		}

	return errSuccess;
	}

//  clones an existing table

template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrClone( const CTable& table )
	{
	CArray< CKeyEntry >::ERR err = CArray< CKeyEntry >::errSuccess;

	if ( ( err = m_arrayKeyEntry.ErrClone( table.m_arrayKeyEntry ) ) != CArray< CKeyEntry >::errSuccess )
		{
		COLLAssert( err == CArray< CKeyEntry >::errOutOfMemory );
		return errOutOfMemory;
		}
	m_pfnSearch = table.m_pfnSearch;

	return errSuccess;
	}

//  updates an existing entry in the table as long as it doesn't change
//  that entry's position in the table

template< class CKey, class CEntry >
inline CTable< CKey, CEntry >::ERR CTable< CKey, CEntry >::
ErrUpdateEntry( const CEntry* const pentry, const CEntry& entry )
	{
	ERR					err			= errSuccess;
	const CKeyEntry*	pkeyentry	= reinterpret_cast< const CKeyEntry* >( pentry );
	const CKeyEntry&	keyentry	= reinterpret_cast< const CKeyEntry& >( entry );

	if ( !pkeyentry->Cmp( keyentry ) )
		{
		m_arrayKeyEntry.SetEntry( pkeyentry, keyentry );
		err = errSuccess;
		}
	else
		{
		err = errKeyChange;
		}

	return err;
	}

//  returns the current size of the table

template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
Size() const
	{
	return m_arrayKeyEntry.Size();
	}

//  returns a pointer to the Nth entry of the table or NULL if it is empty

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
Entry( const size_t ientry ) const
	{
	return static_cast< const CEntry* >( m_arrayKeyEntry.Entry( ientry ) );
	}

//  the following group of functions return a pointer to an entry whose key
//  matches the specified key according to the given criteria:
//
//      Suffix    Description                 Positional bias
//
//      LT        less than                   high
//      LE        less than or equal to       low
//      EQ        equal to                    low
//		HI        equal to                    high
//      GE        greater than or equal to    high
//      GT        greater than                low
//
//  if no matching entry was found then NULL will be returned
//
//  "positional bias" means that the function will land on a matching entry
//  whose position is closest to the low / high end of the table

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekLT( const CKey& key ) const
	{
	const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );

	if (	ikeyentry < Size() &&
			_Entry( ikeyentry ).Cmp( key ) < 0 )
		{
		return Entry( ikeyentry );
		}
	else
		{
		return Entry( ikeyentry - 1 );
		}
	}

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekLE( const CKey& key ) const
	{
	const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );

	if (	ikeyentry < Size() &&
			_Entry( ikeyentry ).Cmp( key ) <= 0 )
		{
		return Entry( ikeyentry );
		}
	else
		{
		return Entry( ikeyentry - 1 );
		}
	}

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekEQ( const CKey& key ) const
	{
	const size_t ikeyentry = (this->*m_pfnSearch)( key, fFalse );

	if (	ikeyentry < Size() &&
			_Entry( ikeyentry ).Cmp( key ) == 0 )
		{
		return Entry( ikeyentry );
		}
	else
		{
		return NULL;
		}
	}

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekHI( const CKey& key ) const
	{
	const size_t ikeyentry = (this->*m_pfnSearch)( key, fTrue );

	if (	ikeyentry > 0 &&
			_Entry( ikeyentry - 1 ).Cmp( key ) == 0 )
		{
		return Entry( ikeyentry - 1 );
		}
	else
		{
		return NULL;
		}
	}

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekGE( const CKey& key ) const
	{
	const size_t ikeyentry = (this->*m_pfnSearch)( key, fTrue );

	if (	ikeyentry > 0 &&
			_Entry( ikeyentry - 1 ).Cmp( key ) == 0 )
		{
		return Entry( ikeyentry - 1 );
		}
	else
		{
		return Entry( ikeyentry );
		}
	}

template< class CKey, class CEntry >
inline const CEntry* CTable< CKey, CEntry >::
SeekGT( const CKey& key ) const
	{
	return Entry( (this->*m_pfnSearch)( key, fTrue ) );
	}

template< class CKey, class CEntry >
inline const CTable< CKey, CEntry >::CKeyEntry& CTable< CKey, CEntry >::
_Entry( const size_t ikeyentry ) const
	{
	return *( m_arrayKeyEntry.Entry( ikeyentry ) );
	}

template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_SetEntry( const size_t ikeyentry, const CKeyEntry& keyentry )
	{
	m_arrayKeyEntry.SetEntry( m_arrayKeyEntry.Entry( ikeyentry ), keyentry );
	}

template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_SwapEntry( const size_t ikeyentry1, const size_t ikeyentry2 )
	{
	CKeyEntry keyentryT;

	keyentryT = _Entry( ikeyentry1 );
	_SetEntry( ikeyentry1, _Entry( ikeyentry2 ) );
	_SetEntry( ikeyentry2, keyentryT );
	}

template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
_LinearSearch( const CKey& key, const BOOL fHigh ) const
	{
	for ( size_t ikeyentry = 0; ikeyentry < Size(); ikeyentry++ )
		{
		const int cmp = _Entry( ikeyentry ).Cmp( key );

		if ( !( cmp < 0 || cmp == 0 && fHigh ) )
			{
			break;
			}
		}
		
	return ikeyentry;
	}

template< class CKey, class CEntry >
inline size_t CTable< CKey, CEntry >::
_BinarySearch( const CKey& key, const BOOL fHigh ) const
	{
	size_t	ikeyentryMin	= 0;
	size_t	ikeyentryMax	= Size();

	while ( ikeyentryMin < ikeyentryMax )
		{
		const size_t ikeyentryMid = ikeyentryMin + ( ikeyentryMax - ikeyentryMin ) / 2;

		const int cmp = _Entry( ikeyentryMid ).Cmp( key );

		if ( cmp < 0 || cmp == 0 && fHigh )
			{
			ikeyentryMin = ikeyentryMid + 1;
			}
		else
			{
			ikeyentryMax = ikeyentryMid;
			}
		}

	return ikeyentryMax;
	}
	
template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_InsertionSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn )
	{
	size_t		ikeyentryLast;
	size_t		ikeyentryFirst;
	CKeyEntry	keyentryKey;

	for (	ikeyentryFirst = ikeyentryMinIn, ikeyentryLast = ikeyentryMinIn + 1;
			ikeyentryLast < ikeyentryMaxIn;
			ikeyentryFirst = ikeyentryLast++ )
		{
		if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
			{
			keyentryKey = _Entry( ikeyentryLast );

			_SetEntry( ikeyentryLast, _Entry( ikeyentryFirst ) );
			
			while (	ikeyentryFirst-- >= ikeyentryMinIn + 1 &&
					_Entry( ikeyentryFirst ).Cmp( keyentryKey ) > 0 )
				{
				_SetEntry( ikeyentryFirst + 1, _Entry( ikeyentryFirst ) );
				}

			_SetEntry( ikeyentryFirst + 1, keyentryKey );
			}
		}
	}

template< class CKey, class CEntry >
inline void CTable< CKey, CEntry >::
_QuickSort( const size_t ikeyentryMinIn, const size_t ikeyentryMaxIn )
	{
	//  quicksort cutoff

	const size_t	ckeyentryMin	= 32;
	
	//  partition stack (used to reduce levels of recursion)

	const size_t	cpartMax		= 16;
	size_t			cpart			= 0;
	struct
		{
		size_t	ikeyentryMin;
		size_t	ikeyentryMax;
		}			rgpart[ cpartMax ];

	//  current partition = partition passed in arguments

	size_t	ikeyentryMin	= ikeyentryMinIn;
	size_t	ikeyentryMax	= ikeyentryMaxIn;

	//  _QuickSort current partition
	
	for ( ; ; )
		{
		//  if this partition is small enough, insertion sort it

		if ( ikeyentryMax - ikeyentryMin < ckeyentryMin )
			{
			_InsertionSort( ikeyentryMin, ikeyentryMax );
			
			//  if there are no more partitions to sort, we're done

			if ( !cpart )
				{
				break;
				}

			//  pop a partition off the stack and make it the current partition

			ikeyentryMin	= rgpart[ --cpart ].ikeyentryMin;
			ikeyentryMax	= rgpart[ cpart ].ikeyentryMax;
			continue;
			}

		//  determine divisor by sorting the first, middle, and last entries and
		//  taking the resulting middle entry as the divisor

		size_t		ikeyentryFirst	= ikeyentryMin;
		size_t		ikeyentryMid	= ikeyentryMin + ( ikeyentryMax - ikeyentryMin ) / 2;
		size_t		ikeyentryLast	= ikeyentryMax - 1;

		if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryMid ) ) > 0 )
			{
			_SwapEntry( ikeyentryFirst, ikeyentryMid );
			}
		if ( _Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
			{
			_SwapEntry( ikeyentryFirst, ikeyentryLast );
			}
		if ( _Entry( ikeyentryMid ).Cmp( _Entry( ikeyentryLast ) ) > 0 )
			{
			_SwapEntry( ikeyentryMid, ikeyentryLast );
			}

		//  sort large partition into two smaller partitions (<=, >)
		
		do	{
			//  advance past all entries <= the divisor
			
			while (	ikeyentryFirst <= ikeyentryLast &&
					_Entry( ikeyentryFirst ).Cmp( _Entry( ikeyentryMin ) ) <= 0 )
				{
				ikeyentryFirst++;
				}

			//  advance past all entries > the divisor
			
			while (	ikeyentryFirst <= ikeyentryLast &&
					_Entry( ikeyentryLast ).Cmp( _Entry( ikeyentryMin ) ) > 0 )
				{
				ikeyentryLast--;
				}

			//  if we have found a pair to swap, swap them and continue
			
			if ( ikeyentryFirst < ikeyentryLast )
				{
				_SwapEntry( ikeyentryFirst++, ikeyentryLast-- );
				}
			}
		while ( ikeyentryFirst <= ikeyentryLast );

		//  move the divisor to the end of the <= partition

		_SwapEntry( ikeyentryMin, ikeyentryLast );

		//  determine the limits of the smaller and larger sub-partitions

		size_t	ikeyentrySmallMin;
		size_t	ikeyentrySmallMax;
		size_t	ikeyentryLargeMin;
		size_t	ikeyentryLargeMax;

		if ( ikeyentryMax - ikeyentryFirst == 0 )
			{
			ikeyentryLargeMin	= ikeyentryMin;
			ikeyentryLargeMax	= ikeyentryLast;
			ikeyentrySmallMin	= ikeyentryLast;
			ikeyentrySmallMax	= ikeyentryMax;
			}
		else if ( ikeyentryMax - ikeyentryFirst > ikeyentryFirst - ikeyentryMin )
			{
			ikeyentrySmallMin	= ikeyentryMin;
			ikeyentrySmallMax	= ikeyentryFirst;
			ikeyentryLargeMin	= ikeyentryFirst;
			ikeyentryLargeMax	= ikeyentryMax;
			}
		else
			{
			ikeyentryLargeMin	= ikeyentryMin;
			ikeyentryLargeMax	= ikeyentryFirst;
			ikeyentrySmallMin	= ikeyentryFirst;
			ikeyentrySmallMax	= ikeyentryMax;
			}

		//  push the larger sub-partition or recurse if the stack is full

		if ( cpart < cpartMax )
			{
			rgpart[ cpart ].ikeyentryMin	= ikeyentryLargeMin;
			rgpart[ cpart++ ].ikeyentryMax	= ikeyentryLargeMax;
			}
		else
			{
			_QuickSort( ikeyentryLargeMin, ikeyentryLargeMax );
			}

		//  set our current partition to be the smaller sub-partition

		ikeyentryMin	= ikeyentrySmallMin;
		ikeyentryMax	= ikeyentrySmallMax;
		}
	}


};  //  namespace COLL


using namespace COLL;


#endif  //  _COLLECTION_HXX_INCLUDED