///////////////////////////////////////////////////////////////////////////////
/// @file THANDLE.h
///
/// @brief 
///
/// @author trajar
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jun 2002
/// @author trajar
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#ifndef _THANDLE_H
#define _THANDLE_H

#include	<assert.H>

#ifndef		_VECTOR_
 #include	<vector>
#endif

#pragma warning( disable : 4786 )  
#pragma warning( disable : 4285 )  

#define  SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }
#define  SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p);   (p)=NULL; } }
#define  SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }

typedef unsigned long DWORD;

enum EraseType
{
	ERASE_UNKNOWN,
	ERASE_NORMAL,
	ERASE_DELETE,
	ERASE_DELETE_ARRAY,
	ERASE_RELEASE
};

//--------------------------------------------------
// THandleMgr
//--------------------------------------------------
template <typename DATA, typename HANDLE>
class THandleMgr
{
public:

	// public types
    typedef std::vector <DATA>				DataVec;
    typedef std::vector <unsigned int>		MagicVec;
    typedef std::vector <unsigned int>		FreeVec;
	typedef	std::vector <int>				RefVec;

	// Lifetime.

    THandleMgr( void )   {	assert(!m_Singleton); 
							m_Singleton = this; 
							m_EraseType = ERASE_UNKNOWN; }

	~THandleMgr( void )  { EraseEntries(); m_Singleton = NULL; }

	DATA *		 GetDataAt(int Index)			   { return (m_UserData.begin() + Index); }	
	void		 SetEraseMethod( EraseType pType ) { assert(pType != ERASE_UNKNOWN); m_EraseType = pType; }	
    unsigned int GetUsedHandleCount( void ) const  {  return ( m_MagicNumbers.size() - m_FreeSlots.size() );  }    
	bool		 HasUsedHandles( void ) const	   {  return ( !!GetUsedHandleCount() );  }
	
	//-----------------------------------------------------------------
	// AddReference( HANDLE& )
	//
	// - adds a references to a handle
	// - returns the current reference count
	//-----------------------------------------------------------------
	int		AddReference( HANDLE& pHandle )
			{
				if( !pHandle.IsNull() )
				{						
					unsigned int index = pHandle.GetIndex();
				
					if(	(index < m_UserData.size()) ||
						(m_MagicNumbers[ index ] == pHandle.GetMagic()) )
					{
						int&	 count = m_References[index];
						return   count++;
					}
					else
						return -1;
				}
				else
					return -1;
			}

	//-----------------------------------------------------------------
	// ReleaseReference( HANDLE& )
	//
	// - releases a reference to a handle
	// - if no more references are found, the in release the handle
	// - returns the current reference count
	//-----------------------------------------------------------------
	int		ReleaseReference( HANDLE& pHandle )
			{
				if( !pHandle.IsNull() )
				{
					unsigned int index = pHandle.GetIndex();
							 int& count = m_References[index];
					
					 if(	(index < m_UserData.size()) ||
						 (m_MagicNumbers[ index ] == pHandle.GetMagic()) )
					 {
						count--;
						if( count == 0 )  // release it
						{
							Release(pHandle);
							pHandle = HANDLE();
							m_References[index] = -1;
							return 0;
						}
						else
							return count;
					 }
					 else
						 return -1;
				}
				else
					return -1;
			}

    //-----------------------------------------------------------------
	// DATA * Acquire( HANDLE& )
	//
	// Adds/returns handle AND address of data
	//
	// * NOTE * if data is a ptr, changing it will not 
	//			change the data in this mgr
	//-----------------------------------------------------------------
    DATA*	Acquire( HANDLE& handle )
			{
				assert( !handle.IsNull() );

				// if free list is empty, add a new one otherwise use first one found
				
				unsigned int index;
				if ( m_FreeSlots.empty() )
				{
					index = m_MagicNumbers.size();
					handle.Init( index );
					m_UserData.push_back( DATA() );
					m_References.push_back(1);
					m_MagicNumbers.push_back( handle.GetMagic() );
				}
				else
				{
					index = m_FreeSlots.back();
					handle.Init( index );
					m_References[ index ] = 1;
					m_FreeSlots.pop_back();
					m_MagicNumbers[ index ] = handle.GetMagic();
				}
				return ( m_UserData.begin() + index );
			}

	//-----------------------------------------------------------------
	// bool AddItem( HANDLE, const DATA )
	//
	// Adds a data item to the manager and returns the handle
	//		- returns true if successful
	//-----------------------------------------------------------------
	DATA *	AddItem( HANDLE& handle, const DATA& data )
			{
				bool bOK = false;
				// if free list is empty, add a new one otherwise use first one found

				unsigned int index;
				if ( m_FreeSlots.empty() )
				{
					index = m_MagicNumbers.size();
					handle.Init( index );
					m_UserData.push_back( data );
					m_References.push_back(1);
					m_MagicNumbers.push_back( handle.GetMagic() );
					bOK = true;
				}
				else
				{
					index = m_FreeSlots.back();
					handle.Init( index );
					m_FreeSlots.pop_back();
					m_References[ index ] = 1;
					m_MagicNumbers[ index ] = handle.GetMagic();
					m_UserData[ index ] = data;
					bOK = true;
				}
								
				m_References[index] = 1;

				return ( m_UserData.begin() + index );
			}

	//-----------------------------------------------------------------
	// Release( HANDLE  handle )
	//-----------------------------------------------------------------
    void	Release( HANDLE&  handle )
			{
				// which one?
				unsigned int index = handle.GetIndex();

				if( handle.IsNull() )
					return;

				// make sure it's valid
				if(	(index < m_UserData.size()) ||
					(m_MagicNumbers[ index ] == handle.GetMagic()) )
				{
					assert( m_EraseType != ERASE_UNKNOWN );

					// clear up the numbers before we delete/release
					m_MagicNumbers[ index ] = 0;
					m_References[ index ] = -1;
					m_FreeSlots.push_back( index );
					handle = HANDLE();

					// ok remove it - tag as unused and add to free list
					switch( m_EraseType )
					{
					case ERASE_DELETE:
						//SAFE_DELETE(m_UserData[index]);
						assert(false);
						break;

					case ERASE_DELETE_ARRAY:
						//SAFE_DELETE_ARRAY(m_UserData[index]);
						assert(false);
						break;

					case ERASE_RELEASE:
						assert(false);
						break;
					}
				}
				else
				{
					assert( false );
				}
			}

	//-----------------------------------------------------------------
	// DATA *	GetData( HANDLE handle )
	//
    // dereferencing
	//-----------------------------------------------------------------
	DATA *	GetData( HANDLE handle )
			{
				assert( !handle.IsNull() );

				if ( handle.IsNull() )  return ( 0 );

				unsigned int index = handle.GetIndex();

			#if	_DEBUG
				// check handle validity - $ this check can be removed for speed
				// if you can assume all handle references are always valid.
				if (   ( index >= m_UserData.size() )
					|| ( m_MagicNumbers[ index ] != handle.GetMagic() ) )
				{
					// no good! invalid handle == client programming error
					return ( 0 );
				}
			#endif
				
				return ( m_UserData.begin() + index );
			}

	//-----------------------------------------------------------------
	// const DATA *	GetData( HANDLE handle ) const
	//
    // dereferencing
	//-----------------------------------------------------------------
    const DATA* GetData( HANDLE handle ) const
			{
				// this lazy cast is ok - non-const version does not modify anything
				return ( const_cast<THandleMgr<DATA, HANDLE>*> ( this )->GetData( handle ) );
			}
	
	//-----------------------------------------------------------------
	// EraseEntries(void)
	//-----------------------------------------------------------------
	void	EraseEntries(void)
			{
				int i = 0;
				int cnt = m_UserData.size();

				assert( m_EraseType != ERASE_UNKNOWN );

				switch( m_EraseType )
				{
				case ERASE_DELETE:
					//for( i=0; i<cnt; i++ )
					//	SAFE_DELETE(m_UserData[i]);
					assert(false);
					break;

				case ERASE_DELETE_ARRAY:
					//for( i=0; i<cnt; i++ )
					//	SAFE_DELETE_ARRAY(m_UserData[i]);
					assert(false);
					break;

				case ERASE_RELEASE:
					//for( i=0; i<cnt; i++ )
					//	SAFE_RELEASE(m_UserData[i]);
					assert(false);
					break;
				}

				m_UserData.clear();
				m_MagicNumbers.clear();
				m_FreeSlots.clear();
				m_References.clear();
			}
	
	static THandleMgr<DATA, HANDLE> * GetHandleMgr(void) {  //assert(m_Singleton); 
															return m_Singleton; }

protected:
	// private data
	EraseType	m_EraseType;
    DataVec		m_UserData;     // data we're going to get to
    MagicVec	m_MagicNumbers; // corresponding magic numbers
    FreeVec		m_FreeSlots;    // keeps track of free slots in the db
	RefVec		m_References;
	
	static  THandleMgr<DATA, HANDLE> * m_Singleton;
};


//-----------------------------------------------------


//--------------------------------------------------
// THandle base
//--------------------------------------------------
template <typename TAG>
class THandle
{
private:
    union
    {
        enum
        {
            // sizes to use for bit fields
            MAX_BITS_INDEX = 16,
            MAX_BITS_MAGIC = 16,

            // sizes to compare against for asserting dereferences
            MAX_INDEX = ( 1 << MAX_BITS_INDEX) - 1,
            MAX_MAGIC = ( 1 << MAX_BITS_MAGIC) - 1,
        };

        struct
        {
            unsigned m_Index : MAX_BITS_INDEX;  // index into resource array
            unsigned m_Magic : MAX_BITS_MAGIC;  // magic number to check
        };
        unsigned long m_Handle;
    };

	typedef THandleMgr< TAG, THandle<TAG> > ThisMgrType;
	typedef THandle<TAG>					ThisType;

public:

    THandle (void) : m_Handle( 0 ) {  }
	~THandle(void)	
	{ 
		m_Handle = 0;
	}
	//--------------------------------------------------
	// Init( U16 )
	//
	// Inits the handle with given index
	//--------------------------------------------------
    void	Init( unsigned int index )
			{
				assert( IsNull() );             // don't allow reassignment
				assert( index <= MAX_INDEX );   // verify range

				static unsigned int s_AutoMagic = 0;
				if ( ++s_AutoMagic > MAX_MAGIC )
				{
					s_AutoMagic = 1;    // 0 is used for "null handle"
				}

				m_Index = index;
				m_Magic = s_AutoMagic;
			}

    unsigned int	GetIndex ( void ) const  { return (  m_Index  ); }
    unsigned int	GetMagic ( void ) const  { return (  m_Magic  ); }
    unsigned long	GetHandle( void ) const  { return (  m_Handle ); }
	TAG *			GetData  ( void ) const  { return (  (ThisMgrType::GetHandleMgr()->GetData( *this ))); }
    bool			IsNull   ( void ) const  { return ( !m_Handle ); }
	
    operator unsigned int ( void ) const  { return (  m_Handle );  }	
	
	THandle<TAG>& operator = ( const THandle<TAG>& r )
	{	
		m_Handle = r.m_Handle;
		return *this;
	}

	THandle<TAG>& operator = ( const DWORD& r )
	{	
		m_Handle = r;
		return *this;
	}

	
	friend bool operator != ( const THandle<TAG>& l, const THandle<TAG>& r )
					{  return ( l.m_Handle != r.m_Handle );  }

	friend bool operator == ( const THandle<TAG>& l, const THandle<TAG>& r )
					{  return ( l.m_Handle == r.m_Handle );  }

	friend bool operator != ( const DWORD& l, const THandle<TAG>& r )
					{  return ( l != r.m_Handle );  }

	friend bool operator == ( const DWORD& l, const THandle<TAG>& r )
					{  return ( l == r.m_Handle );  }

	friend bool operator != ( const THandle<TAG>& l, const DWORD& r )
					{  return ( l.m_Handle != r );  }

	friend bool operator == ( const THandle<TAG>& l, const DWORD& r )
					{  return ( l.m_Handle == r );  }
};

#endif
