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

#ifndef UTLFIXEDMEMORY_H
#define UTLFIXEDMEMORY_H

#ifdef _WIN32
#pragma once
#endif

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

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

#include "mathlib/mathlib.h"

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

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

#ifdef UTLFIXEDMEMORY_TRACK
#define UTLFIXEDMEMORY_TRACK_ALLOC()		MemAlloc_RegisterAllocation( "||Sum of all UtlFixedMemory||", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 )
#define UTLFIXEDMEMORY_TRACK_FREE()		if ( !m_pMemory ) ; else MemAlloc_RegisterDeallocation( "||Sum of all UtlFixedMemory||", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 )
#else
#define UTLFIXEDMEMORY_TRACK_ALLOC()		((void)0)
#define UTLFIXEDMEMORY_TRACK_FREE()		((void)0)
#endif


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

	// Set the size by which the memory grows
	void Init(ssize_t nGrowSize = 0, ssize_t 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; }

protected:
	struct BlockHeader_t;

public:
	class Iterator_t
	{
	public:
		Iterator_t(BlockHeader_t* p, ssize_t i) : m_pBlockHeader(p), m_nIndex(i) {}
		BlockHeader_t* m_pBlockHeader;
		ssize_t m_nIndex;

		bool operator==(const Iterator_t it) const { return m_pBlockHeader == it.m_pBlockHeader && m_nIndex == it.m_nIndex; }
		bool operator!=(const Iterator_t it) const { return m_pBlockHeader != it.m_pBlockHeader || m_nIndex != it.m_nIndex; }
	};
	Iterator_t First() const { return m_pBlocks ? Iterator_t(m_pBlocks, 0) : InvalidIterator(); }
	Iterator_t Next(const Iterator_t& it) const
	{
		Assert(IsValidIterator(it));
		if (!IsValidIterator(it))
			return InvalidIterator();

		BlockHeader_t* RESTRICT pHeader = it.m_pBlockHeader;
		if (it.m_nIndex + 1 < pHeader->m_nBlockSize)
			return Iterator_t(pHeader, it.m_nIndex + 1);

		return pHeader->m_pNext ? Iterator_t(pHeader->m_pNext, 0) : InvalidIterator();
	}
	ssize_t GetIndex(const Iterator_t& it) const
	{
		Assert(IsValidIterator(it));
		if (!IsValidIterator(it))
			return InvalidIndex();

		return (ssize_t)(HeaderToBlock(it.m_pBlockHeader) + it.m_nIndex);
	}
	bool IsIdxAfter(ssize_t i, const Iterator_t& it) const
	{
		Assert(IsValidIterator(it));
		if (!IsValidIterator(it))
			return false;

		if (IsInBlock(i, it.m_pBlockHeader))
			return i > GetIndex(it);

		for (BlockHeader_t* RESTRICT pbh = it.m_pBlockHeader->m_pNext; pbh; pbh = pbh->m_pNext)
		{
			if (IsInBlock(i, pbh))
				return true;
		}
		return false;
	}
	bool IsValidIterator(const Iterator_t& it) const { return it.m_pBlockHeader && it.m_nIndex >= 0 && it.m_nIndex < it.m_pBlockHeader->m_nBlockSize; }
	Iterator_t InvalidIterator() const { return Iterator_t(NULL, -1); }

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

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

	// Specify the invalid ('null') index that we'll only return on failure
	static const ssize_t INVALID_INDEX = 0; // For use with COMPILE_TIME_ASSERT
	static ssize_t InvalidIndex() { return INVALID_INDEX; }

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

	// Grows memory by max(num,growsize), and returns the allocation index/ptr
	void Grow(ssize_t num = 1);

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

	// Memory deallocation
	void Purge();

protected:
	// Fast swap - WARNING: Swap invalidates all ptr-based indices!!!
	void Swap(CUtlFixedMemory< T >& mem);

	bool IsInBlock(ssize_t i, BlockHeader_t* pBlockHeader) const
	{
		T* p = (T*)i;
		const T* p0 = HeaderToBlock(pBlockHeader);
		return p >= p0 && p < p0 + pBlockHeader->m_nBlockSize;
	}

	struct BlockHeader_t
	{
		BlockHeader_t* m_pNext;
		ssize_t m_nBlockSize;
	};

	const T* HeaderToBlock(const BlockHeader_t* pHeader) const { return (T*)(pHeader + 1); }
	const BlockHeader_t* BlockToHeader(const T* pBlock) const { return (BlockHeader_t*)(pBlock)-1; }

	BlockHeader_t* m_pBlocks;
	ssize_t m_nAllocationCount;
	ssize_t m_nGrowSize;
};

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

template< class T >
CUtlFixedMemory<T>::CUtlFixedMemory(ssize_t nGrowSize, ssize_t nInitAllocationCount)
	: m_pBlocks(0), m_nAllocationCount(0), m_nGrowSize(0)
{
	Init(nGrowSize, nInitAllocationCount);
}

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


//-----------------------------------------------------------------------------
// Fast swap - WARNING: Swap invalidates all ptr-based indices!!!
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::Swap(CUtlFixedMemory< T >& mem)
{
	V_swap(m_pBlocks, mem.m_pBlocks);
	V_swap(m_nAllocationCount, mem.m_nAllocationCount);
	V_swap(m_nGrowSize, mem.m_nGrowSize);
}


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

	m_nGrowSize = nGrowSize;

	Grow(nInitSize);
}

//-----------------------------------------------------------------------------
// element access
//-----------------------------------------------------------------------------
template< class T >
inline T& CUtlFixedMemory<T>::operator[](ssize_t i)
{
	Assert(IsIdxValid(i));
	return *(T*)i;
}

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

template< class T >
inline T& CUtlFixedMemory<T>::Element(ssize_t i)
{
	Assert(IsIdxValid(i));
	return *(T*)i;
}

template< class T >
inline const T& CUtlFixedMemory<T>::Element(ssize_t i) const
{
	Assert(IsIdxValid(i));
	return *(T*)i;
}


//-----------------------------------------------------------------------------
// Size
//-----------------------------------------------------------------------------
template< class T >
inline ssize_t CUtlFixedMemory<T>::NumAllocated() const
{
	return m_nAllocationCount;
}


//-----------------------------------------------------------------------------
// Is element index valid?
//-----------------------------------------------------------------------------
template< class T >
inline bool CUtlFixedMemory<T>::IsIdxValid(ssize_t i) const
{
#ifdef _DEBUG
	for (BlockHeader_t* pbh = m_pBlocks; pbh; pbh = pbh->m_pNext)
	{
		if (IsInBlock(i, pbh))
			return true;
	}
	return false;
#else
	return i != InvalidIndex();
#endif
}

template< class T >
void CUtlFixedMemory<T>::Grow(ssize_t num)
{
	if (num <= 0)
		return;

	ssize_t nBlockSize = m_nGrowSize;
	if (nBlockSize == 0)
	{
		if (m_nAllocationCount)
		{
			nBlockSize = m_nAllocationCount;
		}
		else
		{
			// Compute an allocation which is at least as big as a cache line...
			nBlockSize = (31 + sizeof(T)) / sizeof(T);
			Assert(nBlockSize);
		}
	}
	if (nBlockSize < num)
	{
		ssize_t n = (num + nBlockSize - 1) / nBlockSize;
		Assert(n * nBlockSize >= num);
		Assert((n - 1) * nBlockSize < num);
		nBlockSize *= n;
	}
	m_nAllocationCount += nBlockSize;

	MEM_ALLOC_CREDIT_CLASS();
	BlockHeader_t* RESTRICT pBlockHeader = (BlockHeader_t*)malloc(sizeof(BlockHeader_t) + nBlockSize * sizeof(T));
	if (!pBlockHeader)
	{
		Error(eDLL_T::COMMON, EXIT_FAILURE, "CUtlFixedMemory overflow!\n");
	}
	pBlockHeader->m_pNext = NULL;
	pBlockHeader->m_nBlockSize = nBlockSize;

	if (!m_pBlocks)
	{
		m_pBlocks = pBlockHeader;
	}
	else
	{
#if 1	// IsIdxAfter assumes that newly allocated blocks are at the end
		BlockHeader_t* RESTRICT  pbh = m_pBlocks;
		while (pbh->m_pNext)
		{
			pbh = pbh->m_pNext;
		}
		pbh->m_pNext = pBlockHeader;
#else
		pBlockHeader = m_pBlocks;
		pBlockHeader->m_pNext = m_pBlocks;
#endif
	}
}


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


//-----------------------------------------------------------------------------
// Memory deallocation
//-----------------------------------------------------------------------------
template< class T >
void CUtlFixedMemory<T>::Purge()
{
	if (!m_pBlocks)
		return;

	for (BlockHeader_t* pbh = m_pBlocks; pbh; )
	{
		BlockHeader_t* pFree = pbh;
		pbh = pbh->m_pNext;
		MemAllocSingleton()->Free(pFree);
	}
	m_pBlocks = NULL;
	m_nAllocationCount = 0;
}

//#include "tier0/memdbgoff.h"

#endif // UTLFIXEDMEMORY_H
