//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "entitylist_base.h"
#include "ihandleentity.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

enum
{
	SERIAL_MASK = 0x7fff // the max value of a serial number, rolls back to 0 when it hits this limit
};

void CEntInfo::ClearLinks()
{
	m_pPrev = m_pNext = this;
}

CBaseEntityList::CEntInfoList::CEntInfoList()
{
	m_pHead = NULL;
	m_pTail = NULL;
}

// NOTE: Cut from UtlFixedLinkedList<>, UNDONE: Find a way to share this code
void CBaseEntityList::CEntInfoList::LinkBefore( CEntInfo *pBefore, CEntInfo *pElement )
{
	Assert( pElement );
	
	// Unlink it if it's in the list at the moment
	Unlink(pElement);
	
	// The element *after* our newly linked one is the one we linked before.
	pElement->m_pNext = pBefore;
	
	if (pBefore == NULL)
	{
		// In this case, we're linking to the end of the list, so reset the tail
		pElement->m_pPrev = m_pTail;
		m_pTail = pElement;
	}
	else
	{
		// Here, we're not linking to the end. Set the prev pointer to point to
		// the element we're linking.
		Assert( IsInList(pBefore) );
		pElement->m_pPrev = pBefore->m_pPrev;
		pBefore->m_pPrev = pElement;
	}
	
	// Reset the head if we linked to the head of the list
	if (pElement->m_pPrev == NULL)
	{
		m_pHead = pElement;
	}
	else
	{
		pElement->m_pPrev->m_pNext = pElement;
	}
}

void CBaseEntityList::CEntInfoList::LinkAfter( CEntInfo *pAfter, CEntInfo *pElement )
{
	Assert( pElement );
	
	// Unlink it if it's in the list at the moment
	if ( IsInList(pElement) )
		Unlink(pElement);
	
	// The element *before* our newly linked one is the one we linked after
	pElement->m_pPrev = pAfter;
	if (pAfter == NULL)
	{
		// In this case, we're linking to the head of the list, reset the head
		pElement->m_pNext = m_pHead;
		m_pHead = pElement;
	}
	else
	{
		// Here, we're not linking to the end. Set the next pointer to point to
		// the element we're linking.
		Assert( IsInList(pAfter) );
		pElement->m_pNext = pAfter->m_pNext;
		pAfter->m_pNext = pElement;
	}
	
	// Reset the tail if we linked to the tail of the list
	if (pElement->m_pNext == NULL )
	{
		m_pTail = pElement;
	}
	else
	{
		pElement->m_pNext->m_pPrev = pElement;
	}
}

void CBaseEntityList::CEntInfoList::Unlink( CEntInfo *pElement )
{
	if (IsInList(pElement))
	{
		// If we're the first guy, reset the head
		// otherwise, make our previous node's next pointer = our next
		if ( pElement->m_pPrev )
		{
			pElement->m_pPrev->m_pNext = pElement->m_pNext;
		}
		else
		{
			m_pHead = pElement->m_pNext;
		}
		
		// If we're the last guy, reset the tail
		// otherwise, make our next node's prev pointer = our prev
		if ( pElement->m_pNext )
		{
			pElement->m_pNext->m_pPrev = pElement->m_pPrev;
		}
		else
		{
			m_pTail = pElement->m_pPrev;
		}
		
		// This marks this node as not in the list, 
		// but not in the free list either
		pElement->ClearLinks();
	}
}

bool CBaseEntityList::CEntInfoList::IsInList( CEntInfo *pElement )
{
	return pElement->m_pPrev != pElement;
}

CBaseEntityList::CBaseEntityList()
{
	// These are not in any list (yet)
	int i;
	for ( i = 0; i < NUM_ENT_ENTRIES; i++ )
	{
		m_EntPtrArray[i].ClearLinks();
		m_EntPtrArray[i].m_SerialNumber = (rand()& SERIAL_MASK); // generate random starting serial number
		m_EntPtrArray[i].m_pEntity = NULL;
	}

	// make a free list of the non-networkable entities
	// Initially, all the slots are free.
	for ( i=MAX_EDICTS+1; i < NUM_ENT_ENTRIES; i++ )
	{
		CEntInfo *pList = &m_EntPtrArray[i];
		m_freeNonNetworkableList.AddToTail( pList );
	}
}


CBaseEntityList::~CBaseEntityList()
{
	CEntInfo *pList = m_activeList.Head();

	while ( pList )
	{
		CEntInfo *pNext = pList->m_pNext;
		RemoveEntityAtSlot( GetEntInfoIndex( pList ) );
		pList = pNext;
	}
}


CBaseHandle CBaseEntityList::AddNetworkableEntity( IHandleEntity *pEnt, int index, int iForcedSerialNum )
{
	Assert( index >= 0 && index < MAX_EDICTS );
	return AddEntityAtSlot( pEnt, index, iForcedSerialNum );
}


CBaseHandle CBaseEntityList::AddNonNetworkableEntity( IHandleEntity *pEnt )
{
	// Find a slot for it.
	CEntInfo *pSlot = m_freeNonNetworkableList.Head();
	if ( !pSlot )
	{
		Warning( "CBaseEntityList::AddNonNetworkableEntity: no free slots!\n" );
		AssertMsg( 0, ( "CBaseEntityList::AddNonNetworkableEntity: no free slots!\n" ) );
		return CBaseHandle();
	}

	// Move from the free list into the allocated list.
	m_freeNonNetworkableList.Unlink( pSlot );
	int iSlot = GetEntInfoIndex( pSlot );
	
	return AddEntityAtSlot( pEnt, iSlot, -1 );
}


void CBaseEntityList::RemoveEntity( CBaseHandle handle )
{
	RemoveEntityAtSlot( handle.GetEntryIndex() );
}


CBaseHandle CBaseEntityList::AddEntityAtSlot( IHandleEntity *pEnt, int iSlot, int iForcedSerialNum )
{
	// Init the CSerialEntity.
	CEntInfo *pSlot = &m_EntPtrArray[iSlot];
	Assert( pSlot->m_pEntity == NULL );
	pSlot->m_pEntity = pEnt;

	// Force the serial number (client-only)?
	if ( iForcedSerialNum != -1 )
	{
		pSlot->m_SerialNumber = iForcedSerialNum;
		
		#if !defined( CLIENT_DLL )
			// Only the client should force the serial numbers.
			Assert( false );
		#endif
	}
	
	// Update our list of active entities.
	m_activeList.AddToTail( pSlot );
	CBaseHandle retVal( iSlot, pSlot->m_SerialNumber );

	// Tell the entity to store its handle.
	pEnt->SetRefEHandle( retVal );

	// Notify any derived class.
	OnAddEntity( pEnt, retVal );
	return retVal;
}


void CBaseEntityList::RemoveEntityAtSlot( int iSlot )
{
	Assert( iSlot >= 0 && iSlot < NUM_ENT_ENTRIES );

	CEntInfo *pInfo = &m_EntPtrArray[iSlot];

	if ( pInfo->m_pEntity )
	{
		pInfo->m_pEntity->SetRefEHandle( INVALID_EHANDLE_INDEX );

		// Notify the derived class that we're about to remove this entity.
		OnRemoveEntity( pInfo->m_pEntity, CBaseHandle( iSlot, pInfo->m_SerialNumber ) );

		// Increment the serial # so ehandles go invalid.
		pInfo->m_pEntity = NULL;
		pInfo->m_SerialNumber = ( pInfo->m_SerialNumber+1)& SERIAL_MASK;

		m_activeList.Unlink( pInfo );

		// Add the slot back to the free list if it's a non-networkable entity.
		if ( iSlot >= MAX_EDICTS )
		{
			m_freeNonNetworkableList.AddToTail( pInfo );
		}
	}
}


void CBaseEntityList::OnAddEntity( IHandleEntity *pEnt, CBaseHandle handle )
{
}



void CBaseEntityList::OnRemoveEntity( IHandleEntity *pEnt, CBaseHandle handle )
{
}
