//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
// A growable memory class.
//===========================================================================//

#ifndef UTLBLOCKMEMORY_H
#define UTLBLOCKMEMORY_H

#ifdef _WIN32
#pragma once
#endif

#include "tier0/dbg.h"
#include "tier0/platform.h"
#include "mathlib/mathlib.h"

#include "tier0/memalloc.h"
#include "tier0/memdbgon.h"

#ifdef _WIN32
#pragma warning (disable:4100)
#pragma warning (disable:4514)
#endif

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

#ifdef UTBLOCKLMEMORY_TRACK
#define UTLBLOCKMEMORY_TRACK_ALLOC()		MemAlloc_RegisterAllocation( "Sum of all UtlBlockMemory", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 )
#define UTLBLOCKMEMORY_TRACK_FREE()		if ( !m_pMemory ) ; else MemAlloc_RegisterDeallocation( "Sum of all UtlBlockMemory", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 )
#else
#define UTLBLOCKMEMORY_TRACK_ALLOC()		((void)0)
#define UTLBLOCKMEMORY_TRACK_FREE()		((void)0)
#endif


//-----------------------------------------------------------------------------
// The CUtlBlockMemory class:
// A growable memory class that allocates non-sequential blocks, but is indexed sequentially
//-----------------------------------------------------------------------------
template< class T, class I >
class CUtlBlockMemory
{
public:
	// constructor, destructor
	CUtlBlockMemory( int nGrowSize = 0, int nInitSize = 0 );
	~CUtlBlockMemory();

	// Set the size by which the memory grows - round up to the next power of 2
	void Init( int nGrowSize = 0, int nInitSize = 0 );

	// here to match CUtlMemory, but only used by ResetDbgInfo, so it can just return NULL
	T* Base() { return NULL; }
	const T* Base() const { return NULL; }

	class Iterator_t
	{
	public:
		Iterator_t( I i ) : index( i ) {}
		I index;

		bool operator==( const Iterator_t it ) const	{ return index == it.index; }
		bool operator!=( const Iterator_t it ) const	{ return index != it.index; }
	};
	Iterator_t First() const							{ return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); }
	Iterator_t Next( const Iterator_t &it ) const		{ return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); }
	I GetIndex( const Iterator_t &it ) const			{ return it.index; }
	bool IsIdxAfter( I i, const Iterator_t &it ) const	{ return i > it.index; }
	bool IsValidIterator( const Iterator_t &it ) const	{ return IsIdxValid( it.index ); }
	Iterator_t InvalidIterator() const					{ return Iterator_t( InvalidIndex() ); }

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

	// Can we use this index?
	bool IsIdxValid( I i ) const;
	static I InvalidIndex() { return ( I )-1; }

	void Swap( CUtlBlockMemory< T, I > &mem );

	// Size
	int NumAllocated() const;
	int Count() const { return NumAllocated(); }

	// Grows memory by max(num,growsize) rounded up to the next power of 2, and returns the allocation index/ptr
	void Grow( int num = 1 );

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

	// Memory deallocation
	void Purge();

	// Purge all but the given number of elements
	void Purge( int numElements );

protected:
	int Index( int major, int minor ) const { return ( major << m_nIndexShift ) | minor; }
	int MajorIndex( int i ) const { return i >> m_nIndexShift; }
	int MinorIndex( int i ) const { return i & m_nIndexMask; }
	void ChangeSize( int nBlocks );
	int NumElementsInBlock() const { return m_nIndexMask + 1; }

	T** m_pMemory;
	int m_nBlocks;
	int m_nIndexMask : 27;
	int m_nIndexShift : 5;
};

//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------

template< class T, class I >
CUtlBlockMemory<T,I>::CUtlBlockMemory( int nGrowSize, int nInitAllocationCount )
: m_pMemory( 0 ), m_nBlocks( 0 ), m_nIndexMask( 0 ), m_nIndexShift( 0 )
{
	Init( nGrowSize, nInitAllocationCount );
}

template< class T, class I >
CUtlBlockMemory<T,I>::~CUtlBlockMemory()
{
	Purge();
}


//-----------------------------------------------------------------------------
// Fast swap
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlBlockMemory<T,I>::Swap( CUtlBlockMemory< T, I > &mem )
{
	this->swap( m_pMemory, mem.m_pMemory );
	this->swap( m_nBlocks, mem.m_nBlocks );
	this->swap( m_nIndexMask, mem.m_nIndexMask );
	this->swap( m_nIndexShift, mem.m_nIndexShift );
}


//-----------------------------------------------------------------------------
// Set the size by which the memory grows - round up to the next power of 2
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlBlockMemory<T,I>::Init( int nGrowSize /* = 0 */, int nInitSize /* = 0 */ )
{
	Purge();

	if ( nGrowSize == 0)
	{
		// default grow size is smallest size s.t. c++ allocation overhead is ~6% of block size
		nGrowSize = ( 127 + sizeof( T ) ) / sizeof( T );
	}
	nGrowSize = SmallestPowerOfTwoGreaterOrEqual( nGrowSize );
	m_nIndexMask = nGrowSize - 1;

	m_nIndexShift = 0;
	while ( nGrowSize > 1 )
	{
		nGrowSize >>= 1;
		++m_nIndexShift;
	}
	Assert( m_nIndexMask + 1 == ( 1 << m_nIndexShift ) );

	Grow( nInitSize );
}


//-----------------------------------------------------------------------------
// element access
//-----------------------------------------------------------------------------
template< class T, class I >
inline T& CUtlBlockMemory<T,I>::operator[]( I i )
{
	Assert( IsIdxValid(i) );
	T *pBlock = m_pMemory[ MajorIndex( i ) ];
	return pBlock[ MinorIndex( i ) ];
}

template< class T, class I >
inline const T& CUtlBlockMemory<T,I>::operator[]( I i ) const
{
	Assert( IsIdxValid(i) );
	const T *pBlock = m_pMemory[ MajorIndex( i ) ];
	return pBlock[ MinorIndex( i ) ];
}

template< class T, class I >
inline T& CUtlBlockMemory<T,I>::Element( I i )
{
	Assert( IsIdxValid(i) );
	T *pBlock = m_pMemory[ MajorIndex( i ) ];
	return pBlock[ MinorIndex( i ) ];
}

template< class T, class I >
inline const T& CUtlBlockMemory<T,I>::Element( I i ) const
{
	Assert( IsIdxValid(i) );
	const T *pBlock = m_pMemory[ MajorIndex( i ) ];
	return pBlock[ MinorIndex( i ) ];
}


//-----------------------------------------------------------------------------
// Size
//-----------------------------------------------------------------------------
template< class T, class I >
inline int CUtlBlockMemory<T,I>::NumAllocated() const
{
	return m_nBlocks * NumElementsInBlock();
}


//-----------------------------------------------------------------------------
// Is element index valid?
//-----------------------------------------------------------------------------
template< class T, class I >
inline bool CUtlBlockMemory<T,I>::IsIdxValid( I i ) const
{
	return ( i >= 0 ) && ( MajorIndex( i ) < m_nBlocks );
}

template< class T, class I >
void CUtlBlockMemory<T,I>::Grow( int num )
{
	if ( num <= 0 )
		return;

	int nBlockSize = NumElementsInBlock();
	int nBlocks = ( num + nBlockSize - 1 ) / nBlockSize;

	ChangeSize( m_nBlocks + nBlocks );
}

template< class T, class I >
void CUtlBlockMemory<T,I>::ChangeSize( int nBlocks )
{
	UTLBLOCKMEMORY_TRACK_FREE(); // this must stay before the recalculation of m_nBlocks, since it implicitly uses the old value

	int nBlocksOld = m_nBlocks;
	m_nBlocks = nBlocks;

	UTLBLOCKMEMORY_TRACK_ALLOC(); // this must stay after the recalculation of m_nBlocks, since it implicitly uses the new value

	if ( m_pMemory )
	{
		// free old blocks if shrinking
		// Only possible if m_pMemory is non-NULL (and avoids PVS-Studio warning)
		for ( int i = m_nBlocks; i < nBlocksOld; ++i )
		{
			UTLBLOCKMEMORY_TRACK_FREE();
			free( (void*)m_pMemory[ i ] );
		}

		MEM_ALLOC_CREDIT_CLASS();
		m_pMemory = (T**)realloc( m_pMemory, m_nBlocks * sizeof(T*) );
		Assert( m_pMemory );
	}
	else
	{
		MEM_ALLOC_CREDIT_CLASS();
		m_pMemory = (T**)malloc( m_nBlocks * sizeof(T*) );
		Assert( m_pMemory );
	}

	if ( !m_pMemory )
	{
		Error( "CUtlBlockMemory overflow!\n" );
	}

	// allocate new blocks if growing
	int nBlockSize = NumElementsInBlock();
	for ( int i = nBlocksOld; i < m_nBlocks; ++i )
	{
		MEM_ALLOC_CREDIT_CLASS();
		m_pMemory[ i ] = (T*)malloc( nBlockSize * sizeof( T ) );
		Assert( m_pMemory[ i ] );
	}
}


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


//-----------------------------------------------------------------------------
// Memory deallocation
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlBlockMemory<T,I>::Purge()
{
	if ( !m_pMemory )
		return;

	for ( int i = 0; i < m_nBlocks; ++i )
	{
		UTLBLOCKMEMORY_TRACK_FREE();
		free( (void*)m_pMemory[ i ] );
	}
	m_nBlocks = 0;

	UTLBLOCKMEMORY_TRACK_FREE();
	free( (void*)m_pMemory );
	m_pMemory = 0;
}

template< class T, class I >
void CUtlBlockMemory<T,I>::Purge( int numElements )
{
	Assert( numElements >= 0 );

	int nAllocated = NumAllocated();
	if ( numElements > nAllocated )
	{
		// Ensure this isn't a grow request in disguise.
		Assert( numElements <= nAllocated );
		return;
	}

	if ( numElements <= 0 )
	{
		Purge();
		return;
	}

	int nBlockSize = NumElementsInBlock();
	int nBlocksOld = m_nBlocks;
	int nBlocks = ( numElements + nBlockSize - 1 ) / nBlockSize;

	// If the number of blocks is the same as the allocated number of blocks, we are done.
	if ( nBlocks == m_nBlocks )
		return;

	ChangeSize( nBlocks );
}

#include "tier0/memdbgoff.h"

#endif // UTLBLOCKMEMORY_H
