//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// $Header: $
// $NoKeywords: $
//
// A growable memory class. This memory class does *not* store all
// of its memory in a contiguous block, and but it does guarantee to not
// reallocate it. Note the strange interface... ints are used instead of void*s
// to make this look as similar as possible to CUtlMemory.
//=============================================================================

#ifndef UTLFIXEDMEMORY_H
#define UTLFIXEDMEMORY_H

#ifdef _WIN32
#pragma once
#endif

#include "tier0/dbg.h"
#include <string.h>
#include <new.h>
#include "tier0/platform.h"

#pragma warning (disable:4100)
#pragma warning (disable:4514)


//-----------------------------------------------------------------------------
// The CUtlFixedMemory class:
// A growable memory class which doubles in size by default.
//-----------------------------------------------------------------------------
template< class T >
class CUtlFixedMemory
{
public:
	// constructor, destructor
	CUtlFixedMemory( int nGrowSize = 0, int nInitSize = 0 );
	~CUtlFixedMemory();

	// element access
	T& operator[]( int i );
	T const& operator[]( int i ) const;
	T& Element( int i );
	T const& Element( int i ) const;

	// Can we use this index?
	bool IsIdxValid( int i ) const;

	// Size
	int AllocationCount() const;

	// Allocate, free a single element
	int Alloc();
	void Remove( int i );
	void RemoveAll( );

	// Makes sure we've got at least this much memory
	void EnsureCapacity( int num );

	// Memory deallocation
	void Purge();

	// is the memory externally allocated?
	bool IsExternallyAllocated() const;

	// Set the size by which the memory grows
	void SetGrowSize( int size );

	// Used to iterate over all elements
	int FirstElement();
	int NextElement( int i );

private:
	enum
	{
		EXTERNAL_BUFFER_MARKER = -1,
	};

	struct BlockHeader_t
	{
		BlockHeader_t *m_pNext;
		int m_nBlockCount;
	};

private:
	void SetupFreeList( BlockHeader_t *pHeader );
	void Grow( int num = 1 );


private:
	BlockHeader_t *m_pMemory;
	int m_nAllocationCount;
	int m_nGrowSize;
	T *m_pFirstFree;
};


//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
template< class T >
CUtlFixedMemory<T>::CUtlFixedMemory( int nGrowSize, int nInitAllocationCount ) : m_pMemory(0), 
	m_nAllocationCount( nInitAllocationCount ), m_nGrowSize( nGrowSize )
{
	// T must be at least as big as a pointer or the free list fails
	COMPILE_TIME_ASSERT( sizeof(T) >= 4 );

	Assert( (nGrowSize >= 0) && (nGrowSize != EXTERNAL_BUFFER_MARKER) );
	if (m_nAllocationCount)
	{
		m_pMemory = (BlockHeader_t*)Plat_Alloc( sizeof(BlockHeader_t) + m_nAllocationCount * sizeof(T) );
		m_pMemory->m_pNext = NULL;
		m_pMemory->m_nBlockCount = m_nAllocationCount;
		SetupFreeList( m_pMemory );
	}
}

template< class T >
CUtlFixedMemory<T>::~CUtlFixedMemory()
{
	Purge();
}


//-----------------------------------------------------------------------------
// Sets up the free list
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::SetupFreeList( BlockHeader_t *pHeader )
{
	T* pPrev = m_pFirstFree;
	m_pFirstFree = (T*)( pHeader + 1 );
	T* pCurr = m_pFirstFree + pHeader->m_nBlockCount;
	while ( --pCurr >= m_pFirstFree )
	{
		// Treat the first four
		*((T**)pCurr) = pPrev;
		pPrev = pCurr;
	}
}


//-----------------------------------------------------------------------------
// Used to iterate over all elements. Not particularly fast.
//-----------------------------------------------------------------------------
template< class T >
int CUtlFixedMemory<T>::FirstElement()
{
	return m_pMemory ? (int)(m_pMemory + 1) : 0;
}


template< class T >
int CUtlFixedMemory<T>::NextElement( int i )
{
	T* pTest = (T*)i;
	BlockHeader_t *pCheck = m_pMemory;
	while (pCheck)
	{
		T* pBase = (T*)(pCheck + 1);
		T* pLast = pBase + pCheck->m_nBlockCount;
		if ( (i >= (int)pBase) && ( i < (int)pLast ) )
		{
			++pTest;
			if ( pTest >= pLast )
			{
				pTest = (T*)(pCheck->m_pNext + 1);
				return (int)pTest;
			}
		}
		pCheck = pCheck->m_pNext;
	}

	return 0;
}


//-----------------------------------------------------------------------------
// element access
//-----------------------------------------------------------------------------
template< class T >
inline T& CUtlFixedMemory<T>::operator[]( int i )
{
	// Indices are actually pointers here
	return *(T*)i;
}

template< class T >
inline T const& CUtlFixedMemory<T>::operator[]( int i ) const
{
	return *(T*)i;
}

template< class T >
inline T& CUtlFixedMemory<T>::Element( int i )
{
	// Indices are actually pointers here
	return *(T*)i;
}

template< class T >
inline T const& CUtlFixedMemory<T>::Element( int i ) const
{
	// Indices are actually pointers here
	return *(T*)i;
}


//-----------------------------------------------------------------------------
// Sets the grow size
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::SetGrowSize( int nSize )
{
	Assert( nSize >= 0 );
	m_nGrowSize = nSize;
}


//-----------------------------------------------------------------------------
// Number allocated
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlFixedMemory<T>::AllocationCount() const
{
	return m_nAllocationCount;
}


//-----------------------------------------------------------------------------
// Is element index valid?
//-----------------------------------------------------------------------------
template< class T >
inline bool CUtlFixedMemory<T>::IsIdxValid( int i ) const
{
#ifdef _DEBUG
	BlockHeader_t *pCheck = m_pMemory;
	while (pCheck)
	{
		T* pBase = &pCheck->m_Data;
		T* pLast = pBase + pCheck->m_nBlockCount;
		if ( (i >= (int)pBase) && ( i < (int)pLast ) )
			return true;

		pCheck = pCheck->m_pNext;
	}
#endif

	return ( i != 0 );
}
 

//-----------------------------------------------------------------------------
// Allocate, free a single element
//-----------------------------------------------------------------------------
template< class T >
int CUtlFixedMemory<T>::Alloc( )
{
	if (!m_pFirstFree)
	{
		Grow();
	}

	int nRetVal = (int)m_pFirstFree;
	m_pFirstFree = *((T**)m_pFirstFree);
	return nRetVal;
}

template< class T >
void CUtlFixedMemory<T>::Remove( int i )
{
	Assert( IsIdxValid(i) );
	*((T**)i) = m_pFirstFree;
	m_pFirstFree = (T*)i;
}

template< class T >
void CUtlFixedMemory<T>::RemoveAll( )
{
	m_pFirstFree = 0;

	BlockHeader_t *pHeader;
	for ( pHeader = m_pMemory; pHeader; pHeader = pHeader->m_pNext )
	{
		SetupFreeList( pHeader );
	}
}


//-----------------------------------------------------------------------------
// Grows the memory
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::Grow( int num )
{
	int nOldAllocation = m_nAllocationCount;
	int nAllocationRequested = m_nAllocationCount + num;
	while (m_nAllocationCount < nAllocationRequested)
	{
		if ( m_nAllocationCount != 0 )
		{
			if (m_nGrowSize)
			{
				m_nAllocationCount += m_nGrowSize;
			}
			else
			{
				m_nAllocationCount += m_nAllocationCount;
			}
		}
		else
		{
			// Compute an allocation which is at least as big as a cache line...
			m_nAllocationCount = (31 + sizeof(T)) / sizeof(T);
			Assert(m_nAllocationCount != 0);
		}
	}

	// Make sure we have at least numallocated + num allocations.
	// Use the grow rules specified for this memory (in m_nGrowSize)
	int nNewAllocationCount = m_nAllocationCount - nOldAllocation;

	BlockHeader_t *pHeader = (BlockHeader_t*)Plat_Alloc( sizeof(BlockHeader_t) + nNewAllocationCount * sizeof(T) );
	pHeader->m_pNext = NULL;
	pHeader->m_nBlockCount = nNewAllocationCount;
	SetupFreeList( pHeader );

	pHeader->m_pNext = m_pMemory;
	m_pMemory = pHeader;
}


//-----------------------------------------------------------------------------
// Makes sure we've got at least this much memory
//-----------------------------------------------------------------------------
template< class T >
inline void CUtlFixedMemory<T>::EnsureCapacity( int num )
{
	if (m_nAllocationCount < num)
	{
		Grow( num - m_nAllocationCount );
	}
}


//-----------------------------------------------------------------------------
// Memory deallocation
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::Purge()
{
	BlockHeader_t *pHeader, *pNext;
	for ( pHeader = m_pMemory; pHeader; pHeader = pNext )
	{
		pNext = pHeader->m_pNext;
		Plat_Free( pHeader );
	}

	m_pMemory = NULL;
	m_pFirstFree = NULL;
	m_nAllocationCount = 0;
}


#endif // UTLFIXEDMEMORY_H
