//==========================================================================//
// Copyright (C) 2000 Microsoft Corp.										//
// All Rights reserved worldwide											//
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL				//
//
// Author: mdm 05/00
//===========================================================================//

#include "pch.hpp"
#include "GUNGameList.h"
#include "Net_Header.hpp"
#include "zping.h"

// mlyons convenience functions for _mb funcs
__forceinline int _mbscmp(const char *s1, const char *s2)
{
	return _mbscmp((const unsigned char *)s1, (const unsigned char *)s2);
}

// utility macro -- sorry I know macros are bad but what's a guy to do
// when speed is of the essence and inline functions are unweildy?

#define GAMELISTFILTER_CHECK_RETURN( passed ) \
{ \
	if ( passed ) \
	{\
		if ( m_FilterType == Browse::TableItemFilter::FT_INCLUDE_ANY ) \
		{\
			return true;\
		}\
		else if (m_FilterType == FT_EXCLUDE_ANY ) \
		{\
			return false; \
		}\
	}\
	else\
	{ \
		if ( m_FilterType == FT_INCLUDE_ALL ) \
		{\
			return false; \
		}\
		else if ( m_FilterType == FT_EXCLUDE_ALL )\
		{\
			return true; \
		}\
	}\
}

//#define GAMELISTFILTER_CHECK_RETURN( passed ) { if ( passed ) { if ( m_FilterType == Browse::TableItemFilter::FT_INCLUDE_ANY ) { return true; } else if (m_FilterType == FT_EXCLUDE_ANY ) { return false; }} else { if ( m_FilterType == FT_INCLUDE_ALL ) { return false; } else if ( m_FilterType == FT_EXCLUDE_ALL ) { return true; }}}

const char GAMELIST_KEY_INSTANCE[]				= "Instance";
const char GAMELIST_KEY_SERVER_NAME[]			= "Server Name";
const char GAMELIST_KEY_SERVER_ADDRESS[]		= "Server Address";
const char GAMELIST_KEY_SERVER_PORT[]			= "Server Port";
const char GAMELIST_KEY_ID[]					= "ID";
const char GAMELIST_KEY_NUM_PLAYERS[]			= "Num Players";
const char GAMELIST_KEY_NUM_BOTS[]				= "Num Bots";
const char GAMELIST_KEY_MAX_PLAYERS[]			= "Max Players";
const char GAMELIST_KEY_PRODUCT_NAME[]			= "Product Name";
const char GAMELIST_KEY_PRODUCT_VERSION[]		= "Game Version";
const char GAMELIST_KEY_MOD_NAME[]				= "Mod Name";
const char GAMELIST_KEY_MAP_TYPE[]				= "Map Type";
const char GAMELIST_KEY_PASSWORD_PROTECTED[]	= "Password Protected";
const char GAMELIST_KEY_PASSWORD[]				= "Password";
const char GAMELIST_KEY_BROWSER_HANDLE[]		= "Handle";
const char GAMELIST_KEY_PING[]					= "Ping"; // local field
const char GAMELIST_KEY_GAME_TYPE[]				= "Game Type";
const char GAMELIST_KEY_UPDATE_TIME[]			= "Update Time";
const char GAMELIST_KEY_PROTOCOL[]				= "Protocol";
const char GAMELIST_KEY_FLAGS[]					= "Flags";
const char GAMELIST_KEY_SERVER_INFO_STATUS[]    = "ServerInfoStatus";
const char GAMELIST_KEY_REFRESH_PENDING[]       = "ExpectRefresh";

const char GAMELIST_VALUE_PROTOCOL_GUN[]		= "0";
const char GAMELIST_VALUE_PROTOCOL_TCPIP_LAN[]	= "1";
const char GAMELIST_VALUE_PROTOCOL_INTERNET[]	= "2";
const char GAMELIST_VALUE_PROTOCOL_IPX[]		= "3";
const char GAMELIST_VALUE_PROTOCOL_GAMESPY[]	= "4";

const char GAMELIST_VALUE_REFRESH_PENDING_TRUE[]= "true";
const char GAMELIST_VALUE_REFRESH_PENDING_FALSE[]="false";


const char GAMELIST_VALUE_SERVER_INFO_STATUS_PENDING[]  = "0";
const char GAMELIST_VALUE_SERVER_INFO_STATUS_SUCCESS[]  = "1";
const char GAMELIST_VALUE_SERVER_INFO_STATUS_ERROR[]    = "2";
const char GAMELIST_VALUE_SERVER_INFO_STATUS_NOSERVER[] = "3";

const char PLAYERLIST_KEY_SERVER_ROWID[]		= "Server Row ID";
const char PLAYERLIST_KEY_PLAYER_NAME[]			= "Player Name";
const char PLAYERLIST_KEY_CLAN_NAME[]			= "Clan Name"; // not currently supported by MW4 8/1/2000
const char PLAYERLIST_KEY_UPDATE_TIME[]			= "Update Time"; // whether the player was updated or not in the last EnumPlayers
const char ZONE_SERVER_KEY[]					= "ZoneServer";
const char ZONE_ADVERTISE_PORT_KEY[]            = "ZoneAdvertisePort";
const char ZONE_SERVER_INTERNET_ADDRESS[]		= "mwmatch.zone.com";
const char ZONE_SERVER_LAN_ADDRESS[]			= "mwmatch.zone.com";
//const char ZONE_SERVER_INTERNET_ADDRESS[]		= "zonematch.zone.com";
//const char ZONE_SERVER_LAN_ADDRESS[]			= "zonematch.zone.com";


const char REFERENCE_GAME_IP[]                  = "127.0.0.1";

const char INVALID_PING_STR[]                   = "-1";

extern u_long (WINAPI * _ntohl)(u_long netlong);
extern unsigned long (WINAPI * _inet_addr)(const char FAR * cp);

const int STARTING_SERVER_COUNT = 50;
const int GROW_INCREMENT = 100;
const int MAX_FILTER_STRING = 255;
const double DEFAULT_RESORT_FREQUENCY = .5f;


using namespace Browse;

TableList::FieldItem AscendingSentinal = { "unused", "ZZZZZZZZZZ", 999999 };
TableList::FieldItem DescendingSentinal = { "unused", "", -999999 };


/////////////////////////////////////////////////////////
// TableList methods
/////////////////////////////////////////////////////////


TableList::TableList() :
	m_SearchKey( 0 ),
	m_SearchVal( 0 ),
	m_SearchResult( 0 ),
	m_Items(TableList::HashLPSTR,
			TableList::HashCompare, NULL, 500 ),
	m_IndexedView( NULL )
{
	// mdm - for now, indexed view is managed internally
	m_IndexedView = new IndexedTableView(this);
}

TableList::~TableList()
{
  	RemoveAll();


	// mdm - for now, indexed view is managed internally
	if ( m_IndexedView )
	{
		delete m_IndexedView;
		m_IndexedView = NULL;
	}

}

void * TableList::operator new(size_t size)
{
	// 0xffffffff specifies use of the heap set by CurrentHeapLevel
	return malloc( size );
}

void TableList::operator delete( void* ptr )
{
	free( ptr );
}

void TableList::operator delete[] ( void* ptr )
{
	free( ptr );
}

template<class T, class K>
void * CHash<T,K>::operator new(size_t size) {
	return malloc(size);
}

template<class T, class K>
void CHash<T,K>::operator delete( void* ptr )
{
	free(ptr);
}

template<class T, class K>
void CHash<T,K>::operator delete[] ( void* ptr )
{
	free(ptr);
}

void * CMTListNode::operator new(size_t size)
{
	return malloc(size);
}

void * CMTListNode::operator new[](size_t size)
{
	return malloc(size);
}

void CMTListNode::operator delete( void* ptr )
{
	free(ptr);
}
void CMTListNode::operator delete[] ( void* ptr )
{
	free(ptr);
}


void * TableList::ExtraData::operator new( size_t size )
{
	return malloc( size );
}

void TableList::ExtraData::operator delete( void* ptr )
{
	free( ptr );
}

void TableList::ExtraData::operator delete[] ( void * ptr )
{
	free( ptr );
}

void * TableList::TableItem::operator new( size_t size )
{
	return malloc( size );
}

void TableList::TableItem::operator delete( void* ptr )
{
	free( ptr );
}

void TableList::TableItem::operator delete[] ( void * ptr )
{
	free( ptr );
}

void * TableList::FieldItem::operator new( size_t size )
{
	return malloc( size );
}

void TableList::FieldItem::operator delete( void * ptr )
{
	free( ptr );
}

void TableList::FieldItem::operator delete[]( void * ptr )
{
	free( ptr );
}

////////////////////////////////////
// TableList Hash Table functions //
////////////////////////////////////

DWORD TableList::HashLPSTR( const char * Key )
{
	DWORD hash = 0;

	if ( Key )
	{
		int i = 0;
		while( *Key && ( i++ < 16 ) )
		{
			hash = (hash<<4)+hash+*Key++;	// multiple by 17 to get a good bit distribution
		}
	}
	return hash;
}

bool TableList::HashCompare(TableItem *obj,const char * key)
{

	return ( lstrcmpi(obj->id, key ) == 0 );
}


/*
	Create a new entry in the TableList, using internal ID
	generator to provide a unique ID for the entry, which is
	returned as the value of the function. On failure, the
	function will return INVALID_ID.

	The CreateEntry() method must be called before SetField can
	be used to set the various fields for a specific item.
*/
bool TableList::CreateEntry( const char * id )
{
	// first, make sure not already in the list
	TableItem * pItem = m_Items.Get( id );
	if ( pItem )
	{
		return false; // already in the hash table
	}

	pItem = new TableItem;
	pItem->id = (char *) malloc(strlen(id) + 1);
	strcpy(pItem->id, id);
	pItem->extraData = NULL;

	if ( !m_Items.Add( id, pItem ) )
	{
		return false;
	}

	// mdm - new indexed view
	if ( m_IndexedView )
	{
		m_IndexedView->CreateEntryNotify(*pItem);
	}


	return true;
}

/*
	Set a field (key-value pair) for the TableItem that
	has const char * 'id'.
*/
bool TableList::SetField( const char * id, const char * key,
							const char * val )
{
	gosASSERT( key ); gosASSERT( val );

	// find item with const char * 'id'
	TableItem * pItem = m_Items.Get( id );
	if ( !pItem )
	{
		return false;
	}

	// update or create field item
	int keyLen = strlen(key);
	int valLen = strlen(val);
	FieldItem * pFieldItem = FindField( pItem->fieldList, key );
	if ( pFieldItem )
	{
		int fieldLen = pFieldItem->value ? strlen(pFieldItem->value) : 0;

		if ( fieldLen <= valLen )
		{
			if ( pFieldItem->value )
			{
				free(pFieldItem->value);
			}
			pFieldItem->value = (char *) malloc(valLen+1);
		}
		strcpy(pFieldItem->value, val);
	}
	else
	{
		pFieldItem = new FieldItem;

		pFieldItem->key = (char *) malloc(keyLen + 1);
		pFieldItem->value = (char *) malloc(valLen + 1);

		strcpy(pFieldItem->key, key);
		strcpy(pFieldItem->value, val);
		pItem->fieldList.AddTail( pFieldItem );
	}

	pFieldItem->iValue = atoi(pFieldItem->value);

	// mdm - new indexed view support
	if ( m_IndexedView )
	{
		m_IndexedView->SetFieldNotify( *pItem, *pFieldItem );
	}

	return true;
}

bool TableList::RemoveField( const char * id, const char * key )
{
	TableItem * item = m_Items.Get( id );
	if ( !item )
	{
		return false;
	}

	if ( m_IndexedView )
	{
		m_IndexedView->RemoveFieldNotify( item, key );
	}

	POSITION pos = item->fieldList.GetHeadPosition();
	while ( pos )
	{
		POSITION oldPos = pos;
		FieldItem * fieldItem = (FieldItem *)
			item->fieldList.GetNext( pos );
		if ( stricmp(fieldItem->key, key) == 0 )
		{
			FreeFieldItem(fieldItem);
			item->fieldList.RemoveIt( oldPos, fieldItem );
			return true;
		}
	}
	return false;
}

/*
	Remove the indicated item from the list.
*/
bool TableList::Remove( const char * id )
{
	TableItem * item = m_Items.Get( id );
	if ( !item )
	{
		return false;
	}

	// remove this item
	if ( m_Items.Delete( id ) )
	{
		FreeTableItem( item );
		return true;
	}
	return false;
}

void TableList::RemoveAll( void )
{
	m_Items.RemoveAll( FreeTableItemCallback, this );
}


void TableList::FreeFieldList( FieldList * pList )
{
	while (pList->GetSize())
	{
		FieldItem * fieldItem = *(pList->GetHead());
		FreeFieldItem( fieldItem );
		pList->RemoveHead();
	}
}

bool TableList::Exists( const char * id )
{
	return ( m_Items.Get( id ) != NULL );
}


////////////////////////////////////////////////////////////////////////////////////////
//  TableList::GetTotalCount()
//  Purpose:
//       Returns the total number of items in the table list.  Note this is 
//       potentially larger than the GetCount() method of the attached IndexedView 
//       might return, depending on how many items are filtered out in the indexed 
//       view.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          092900  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int TableList::GetTotalCount( void )
{
    return m_Items.Count();
}


const char * TableList::GetDataField( const char * id,
										const char * key )
{
	TableItem * pItem = m_Items.Get( id );
	if ( !pItem )
	{
//		PAUSE(( "Invalid const char * %d for GetDataField.", id ));
		return NULL;
	}

	return FindFieldVal( pItem->fieldList, key );

}

/*
	This is needed for the hash table RemoveAll() method, which
	requires a callback delete function.
*/
void TableList::FreeTableItemCallback( TableItem * pItem, void * pThis )
{
	TableList * pTableListObj = (TableList *) pThis;

	pTableListObj->FreeTableItem( pItem );
}


/*
	Free the indicated item and all associated
	data (including field list)
*/
void TableList::FreeTableItem( TableItem * pItem )
{
	// inform the indexed view of the remove
	if ( m_IndexedView )
	{
		m_IndexedView->RemoveNotify( *pItem );
	}

	FreeFieldList(&(pItem->fieldList));

	if ( pItem->extraData )
	{
		delete pItem->extraData;
		pItem->extraData = NULL;
	}
	free( pItem->id );
	free( pItem );
}

/*
	Free the indicated field item.
*/
void TableList::FreeFieldItem( FieldItem * pItem )
{
	if (pItem->key)
	{
		free(pItem->key);
	}
	if (pItem->value)
	{
		free(pItem->value);
	}
	free(pItem);
}


/*
	Returns a pointer to the requested item if
	it exists. Otherwise, it returns NULL;
*/
TableList::TableItem *
TableList::FindItem( const char * id )
{
	return m_Items.Get( id );
}

/*
	Returns the value of the field indicated, or NULL if the
	value is not found.
*/
const char *
TableList::FindFieldVal( FieldList & pList,
						const char * key )
{
	POSITION pos = pList.GetHeadPosition();
	while ( pos )
	{
		FieldItem * fieldItem = (FieldItem *) *(pList.GetNext(pos));
		if ( !strcmp( fieldItem->key, key ) )
		{
			// match!
			return fieldItem->value;
		}
	}
	return NULL;
}

/*
	Returns a pointer to the field item with the indicated
	name, or NULL if no matching field is found.
*/
TableList::FieldItem *
TableList::FindField( FieldList & pList,
						const char * key )
{
	POSITION pos = pList.GetHeadPosition();
	while ( pos )
	{
		FieldItem * fieldItem = (FieldItem *) *(pList.GetNext(pos));
		if ( !strcmp( fieldItem->key, key ) )
		{
			// match!
			return fieldItem;
		}
	}
	return NULL;
}

bool TableList::ForEachFindField( TableItem * tableItem,
									MTListNodeHandle handle,
									void * pThis )
{
	TableList * pObj = (TableList *) pThis;

	if (!pObj->m_SearchKey ||
		!pObj->m_SearchVal ) return false;

	FieldItem * fieldItem =
		pObj->FindField( tableItem->fieldList,
			pObj->m_SearchKey );

	if ( fieldItem )
	{
		if ( !stricmp(fieldItem->value, pObj->m_SearchVal) )
		{
			pObj->m_SearchResult = tableItem->id;
			return false;
		}
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  TableList::GetIDFromField()
//  Purpose:
//       Returns the id of the first matching tableitem that has the specified key and 
//       value.  If no match is found, NULL is returned.
//  
//  Parameters:
//       [key] :
//       [val] :
//  
//  Returns:  const char *
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
const char * TableList::GetIDFromField( const char * key,
										const char * val )
{
	m_SearchKey = key;
	m_SearchVal = val;
	m_SearchResult = NULL;
	m_Items.ForEach( ForEachFindField, this );
	return m_SearchResult;
}


void TableList::Lock( void )
{
	EnterCriticalSection( &EnumCriticalSection );
}

void TableList::Unlock( void )
{
	LeaveCriticalSection( &EnumCriticalSection );
}

void TableList::EnumerateAll( CHash<TableItem, const char *>::PFITERCALLBACK pfnCB, void * cookie )
{
    m_Items.ForEach( pfnCB, cookie );
}




/////////////////////////////////////////////////////////
// PlayerList methods
/////////////////////////////////////////////////////////

// none currently defined because player list is typedef
// of tablelist.

/////////////////////////////////////////////////////////
// GameList methods
/////////////////////////////////////////////////////////

GameList::GameList()
{
	// attach a filter to the indexed view
	GameListFilter * filter = new GameListFilter();
//	filter->AddStringFilter(GAMELIST_KEY_PRODUCT_VERSION, Environment.version);
	m_IndexedView->SetFilter( *filter );
    m_ReferenceGameID[0] = 0;// make ure this is the empty string

}

GameList::~GameList()
{
	// clean up filter
	GameListFilter * filter = reinterpret_cast<GameListFilter *>(
		m_IndexedView->GetFilter() );
	if ( filter )
	{
		delete filter;
	}

	m_IndexedView->ClearFilter();

	// shutdown ping if currently running
	if ( s_ZonePingServerStarted || s_ZonePingClientStarted )
	{
		s_ZonePing->Shutdown();
	}
	s_ZonePingClientStarted = false;
	s_ZonePingServerStarted = false;
}

bool GameList::CreateEntry( const char * id )
{
	if ( TableList::CreateEntry( id ) == false )
	{
		return false;
	}

	TableItem * pItem = m_Items.Get( id );
	if ( !pItem )
	{
		return false;
	}

	pItem->extraData = (ExtraData *) new GameExtraData;
	((GameExtraData *)pItem->extraData)->name = 0;

	return true;
}

bool GameList::SetField(const char * id,
						const char * key,
						const char * val )
{
	// call on base class to handle the basic mechanics of
	// adding the game to the table list.
	if ( !TableList::SetField( id, key, val ) )
	{
		return false; // setting field for invalid item id most likely
	}

	// find our game
	TableItem * pItem= m_Items.Get( id );
	if ( !pItem )
	{
		PAUSE(( "Unable to SetField() for item %s, key=%s, val=%s.",id,key,val ));
		return false; // should already be in the list.
	}

	GameExtraData * extraData = NULL;
	if ( pItem->extraData == NULL )
	{
		extraData = new GameExtraData;
		pItem->extraData = extraData;
		extraData->name = 0;
	}
	else
	{
		extraData = (GameExtraData *) pItem->extraData;
	}

	gosASSERT(extraData != NULL);

	if ( !stricmp(key, GAMELIST_KEY_INSTANCE) )
	{
		//
		// construct the GUID from the GUID string (val).
		//
		const cchBuf = 50;
		WCHAR wszGuid[cchBuf];
		MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,val,-1,wszGuid,cchBuf);
		CLSIDFromString(wszGuid,&(extraData->gameGUID) );
	}
	else if ( !stricmp( key, GAMELIST_KEY_SERVER_NAME ) )
	{
		if ( !extraData->name || strlen(extraData->name) <= strlen(val) )
		{
			if ( extraData->name )
			{
				free(extraData->name);
			}

			extraData->name = (char *) malloc(strlen(val)+1);
		}
		strcpy(extraData->name, val);
	}
    else if ( !stricmp( key, GAMELIST_KEY_SERVER_ADDRESS ) )
    {
        if ( !stricmp(val, REFERENCE_GAME_IP) )
        {
            // this is a reference game that tells us the current version of the game.
            strcpy(m_ReferenceGameID, id );
        }
    }

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GetReferenceGameID()
//  Purpose:
//       Returns the gameid of the reference game in the list, or NULL if there isn't 
//       one.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  const char *
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          102000  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
const char * GameList::GetReferenceGameID( void )
{
    if ( m_ReferenceGameID[0] != 0 && Exists(m_ReferenceGameID) ) // mdm - first check bypasses atoi(empty-string) = 0 in Exists
    {
        return m_ReferenceGameID;
    }
    else
    {
        return NULL;
    }
}


bool GameList::GetGUID( const char * id,
						GUID & gameGUID )
{
	TableItem * pItem = m_Items.Get( id );
	if ( !pItem ) return false;

	GameExtraData * extraData = (GameExtraData *)(
		pItem->extraData);

	gosASSERT(extraData);

	gameGUID = extraData->gameGUID;
	return true;
}

PlayerList * GameList::GetPlayerList( const char * id )
{
	TableItem * pItem = m_Items.Get( id );
	if ( !pItem ) return NULL;

	GameExtraData * extraData = (GameExtraData *)(
		pItem->extraData);

	gosASSERT(extraData);

	return &(extraData->players);
}


void * GameList::operator new( size_t size )
{
	return malloc( size );
}

void * GameList::operator new[]( size_t size )
{
	return malloc( size );
}

void GameList::operator delete( void* ptr )
{
	free( ptr );
}

void GameList::operator delete[] ( void * ptr )
{
	free( ptr );
}


GameList::GameExtraData::~GameExtraData()
{
	if ( name ) free(name);
}

void * GameList::GameExtraData::operator new( size_t size )
{
	return malloc( size );
}

void GameList::GameExtraData::operator delete( void* ptr )
{
	free( ptr );
}

void GameList::GameExtraData::operator delete[] ( void * ptr )
{
	free( ptr );
}

/*
	Enhance FreeTableItem to remove item from ping list
	so that we don't get callbacks using ID strings that
	are not valid.
*/
void GameList::FreeTableItem( TableItem * pItem )
{
	// find out inet address so we can remove from ping service.
	if ( s_ZonePingServerStarted || s_ZonePingClientStarted )
	{
		const char * address = GetDataField( pItem->id, GAMELIST_KEY_SERVER_ADDRESS );
		if ( address )
		{
			DWORD inet = _inet_addr( address );
			if ( inet != INADDR_NONE )
			{
				inet = _ntohl(inet);
				s_ZonePing->Remove( inet );
			}
		}
	}
	TableList::FreeTableItem( pItem );
}

///////////
// control functions for zone ping
///////////

bool __stdcall GameList::StartPingServer( void )
{
	if ( s_ZonePingServerStarted == true )
	{
		return false;
	}

	s_ZonePingServerStarted = (s_ZonePing->StartupServer() == 0);

	return s_ZonePingServerStarted;
}


bool __stdcall GameList::StartPingClient( DWORD ping_interval_sec )
{
	if ( s_ZonePingClientStarted == true )
	{
		return false;
	}

	if ( s_ZonePing->StartupClient( ping_interval_sec ) != S_OK )
	{
		return false;
	}

	Lock();

	s_ZonePingClientStarted = true;

	if ( ServerBrowser::GetGameList() )
	{
		Browse::IndexedTableView * indexView = ServerBrowser::GetGameList()->GetIndexedView();

		for (int i = 0; i < indexView->GetCount(); i++)
		{
			char * id = 0;

			if ( indexView->GetIndex( i, &id ) == false )
			{
				PAUSE(( "Invalid game index %d found in StartPingClient iteration.", i ));
				continue;
			}

			const char * address = ServerBrowser::GetGameList()->GetDataField( id, GAMELIST_KEY_SERVER_ADDRESS );
			if ( address == NULL )
			{
				// no address yet -- this is ok since the creation of an entry doesn't
				// immediately fill it with data
				continue;
			}

            const char * pszFlags = ServerBrowser::GetGameList()->GetDataField( id, GAMELIST_KEY_FLAGS);
            DWORD dwFlags = 0;
            if ( pszFlags )
            {
                dwFlags = atoi(pszFlags);
            }

			DWORD inet = _inet_addr( address );

            // mdm - noping flag enabled means they don't want us pinging them
            bool doPing = true;
            if ( dwFlags & NETFLAGS_NOPING_FLAG )
            {
                doPing = false;
            }

			if ( doPing && ( inet != INADDR_NONE ) )
			{
				inet = _ntohl(inet);
				if ( s_ZonePing->Add( inet ) != S_OK )
				{
					continue;
				}

				s_ZonePing->RegisterCallback( inet, Browse::PingCallback, id );
			}
            else
            {
                // mdm - only get here if game is hosted on isdn or modem, or bad address
                // indicate pings are invalid for this guy
                ServerBrowser::GetGameList()->SetField(id, GAMELIST_KEY_PING, INVALID_PING_STR);
            }

		}
	}
	Unlock();
	return true;
}

/*
	Shut down the zone ping service. THis is safe to call
	multiple times, since it tracks whether the server
	is actually up or not. (ZPing also does this, so its
	doubly-safe :-) )
*/
void __stdcall GameList::ShutdownPing( void )
{
	if (s_ZonePingServerStarted == true ||
		s_ZonePingClientStarted == true )
	{
		s_ZonePing->Shutdown();
		s_ZonePingServerStarted = false;
		s_ZonePingClientStarted = false;
	}
}


int GameList::GetPing( int index )
{
	if ( s_ZonePingClientStarted == false )
		return -1;

	char * id = 0;

	if ( GetIndexedView()->GetIndex( index, &id ) == false )
	{
		return -1;
	}

	return GetPing( id );
}

int GameList::GetPing( const char * id )
{
	const char * ping = GetDataField(id, GAMELIST_KEY_PING);
	if ( ping )
	{
		int latency = atoi(ping);
		return latency;
	}
	else
	{
		const char * address = GetDataField( id, GAMELIST_KEY_SERVER_ADDRESS );
		if ( address )
		{
            const char * pszFlags = GetDataField( id, GAMELIST_KEY_FLAGS );
            DWORD dwFlags = 0;
            if ( pszFlags )
            {
                dwFlags = atoi(pszFlags);
            }

			DWORD inet = _inet_addr( address );

            // mdm - noping flag enabled means they don't want us pinging them
            bool doPing = true;
            if ( dwFlags & NETFLAGS_NOPING_FLAG )
            {
                doPing = false;
            }

			if ( doPing && ( inet != INADDR_NONE ) )
			{
				inet = _ntohl(inet);
				DWORD latency;

				if (s_ZonePing->Lookup(inet, &latency) != S_OK )
				{
					// add it to the list of clients managed
					s_ZonePing->Add(inet);
					s_ZonePing->RegisterCallback( inet, Browse::PingCallback, (void *) id );
					return -1;
				}

				return latency;
			}
            else
            {
                // mdm - only get here if game is hosted on isdn or modem, or bad address
                // indicate pings are invalid for this guy
                SetField(id, GAMELIST_KEY_PING, INVALID_PING_STR);
            }
		}
	}
	return -1;
}




/////////////////////////////
// TableList Filter methods
/////////////////////////////

TableItemFilter::TableItemFilter() :
	m_FilterType(FT_INCLUDE_ALL)
{

}

TableItemFilter::~TableItemFilter()
{
	ClearAllFilters();
}


bool TableItemFilter::Filter( TableList::TableItem & tableItem )
{
	// if filters are off, return true
	if ( m_FilterType == FT_NONE )
	{
		return true;
	}

	POSITION pos = m_Filters.GetHeadPosition();
	while ( pos )
	{
		bool match = false;

		FilterItem * fi = m_Filters.GetNext(pos);

		TableList::FieldItem * fieldItem = TableList::FindField(tableItem.fieldList, fi->key);

		if ( fieldItem )
		{
			switch ( fi->type )
			{
			case FIT_Numeric:
				if (fieldItem->iValue >= fi->min &&
					fieldItem->iValue <= fi->max )
				{
					match = true;
				}
				break;
			case FIT_String:
				if ( !stricmp(fieldItem->value, fi->strVal) )
				{
					match = true;
				}
				break;
			default:
				PAUSE( ("Invalid fi->type in Filter() method") );
				break;
			}
		}

		switch ( m_FilterType )
		{
		case FT_INCLUDE_ALL:
			if ( !match )
			{
				return false;
			}
			break;
		case FT_INCLUDE_ANY:
			if ( match )
			{
				return true;
			}
			break;
		case FT_EXCLUDE_ALL:
			if ( !match )
			{
				return true;
			}
			break;
		case FT_EXCLUDE_ANY:
			if ( match )
			{
				return false;
			}
		}
	}

	switch ( m_FilterType )
	{
	case FT_INCLUDE_ALL:
		// they all matched, so true
		return true;
	case FT_INCLUDE_ANY:
		// they all didn't match, so false
		return false;
	case FT_EXCLUDE_ALL:
		// they all matched, so return false
		return false;
	case FT_EXCLUDE_ANY:
		// none matched, so true
		return true;
	default:
		PAUSE( (" Unexpected filter type in Filter() ") );
		break;
	}

	return false;
}


/*
	Set the kind of filtering policy the filtering rules specify.
*/
void TableItemFilter::SetFilterType( FilterType filterType )
{
	m_FilterType = filterType;
}


TableItemFilter::FilterType TableItemFilter::GetFilterType( void ) const
{
	return m_FilterType;
}

void TableItemFilter::AddNumericFilter( const char * key, int minVal, int maxVal )
{
	FilterItem fi;

	fi.key = (char *) malloc(strlen(key) + 1);
	strcpy(fi.key, key);
	fi.min = minVal;
	fi.max = maxVal;
	fi.strVal = NULL;
	fi.type = FIT_Numeric;

	m_Filters.AddTail(fi);
}

void TableItemFilter::AddStringFilter( const char * key, const char * val )
{
	FilterItem fi;

	fi.key = (char *) malloc(strlen(key) + 1);
	strcpy(fi.key, key);
	fi.strVal = (char *) malloc(strlen(val) + 1);
	strcpy(fi.strVal, val);
	fi.type = FIT_String;

	m_Filters.AddTail(fi);
}

bool TableItemFilter::ClearFilter(const char * key )
{
	POSITION pos = m_Filters.GetHeadPosition();
	while ( pos )
	{
		FilterItem * fi = m_Filters.GetNext(pos);

		if ( !strcmp(fi->key, key) )
		{
			FreeFilterItem(fi);
			FilterItem fi;
			m_Filters.RemoveIt(pos, fi);
			return true;
		}
	}
	return false;
}

void TableItemFilter::ClearAllFilters( void )
{
	POSITION pos = m_Filters.GetHeadPosition();

	while ( pos )
	{
		FilterItem * fi = m_Filters.GetNext(pos);

		FreeFilterItem( fi );
	}
	m_Filters.RemoveAll();
}

/////////
// protected TableItemFilter methods
/////////

void TableItemFilter::FreeFilterItem( FilterItem * item )
{
	free( item->key );
	if ( item->type == FIT_String )
	{
		free( item->strVal );
	}
}


//////////////////////////////
// GameListFilter Methods
//////////////////////////////
GameListFilter::GameListFilter() :
	m_pingOn(false),
	m_minPing(0),
	m_maxPing(9999),
	m_availableOn( false ),
	m_activeOn( false ),
	m_curVerOff( false ),
	m_newVerOff( false ),
	m_oldVerOff( false ),
	m_playerCountOn(false),
	m_minPlayers(0),
	m_maxPlayers(9999),
	m_gameTypeOn(false),
	m_gameType(NULL),
	m_playerNameOn(false),
	m_playerName(NULL),
	m_clanNameOn(false),
	m_clanName(NULL)
{
	// allocate strings
	m_gameType = (char *) malloc(MAX_FILTER_STRING);
	m_gameType[0] = 0;
	m_playerName = (char *)malloc(MAX_FILTER_STRING);
	m_playerName[0] = 0;
	m_clanName = (char *) malloc(MAX_FILTER_STRING);
	m_clanName[0] = 0;
}

GameListFilter::~GameListFilter()
{
	if ( m_gameType )
	{
		free(m_gameType);
		m_gameType = NULL;
	}
	if ( m_playerName )
	{
		free(m_playerName);
		m_playerName = NULL;
	}
	if ( m_clanName )
	{
		free(m_clanName);
		m_clanName = NULL;
	}

}


/*
	Returns true if the item should remain in the list, or
	false if it should be removed from the list.
*/
bool GameListFilter::Filter( TableList::TableItem & tableItem )
{
	// if filters are off, return true
	if ( m_FilterType == FT_NONE )
	{
		return true;
	}

	////////////// PING FILTER //////////////
	if ( m_pingOn )
	{
		bool bPassed = true;
		// find ping field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_PING );
		if ( pFieldItem )
		{
			if (pFieldItem->iValue < m_minPing ||
				pFieldItem->iValue > m_maxPing )
			{
				bPassed = false;
			}
		}

		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	/////////// AVAILABLE FILTER ///////////////
	if ( m_availableOn )
	{
		// todo - figure out if the game is locked!
		bool bPassed = false;

		// find num players field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_NUM_PLAYERS );

		if ( pFieldItem )
		{
			int numPlayers = pFieldItem->iValue;

			pFieldItem = TableList::FindField( tableItem.fieldList,
				GAMELIST_KEY_MAX_PLAYERS );
			if ( pFieldItem )
			{
				int maxPlayers = pFieldItem->iValue;
				if ( numPlayers < maxPlayers )
				{
					bPassed = true;
				}
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed )
	}

	///////////// ACTIVE FILTER //////////////////////
	if ( m_activeOn )
	{
		bool bPassed = false;

		// find num players field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_NUM_PLAYERS );

		if ( pFieldItem )
		{
			if ( pFieldItem->iValue > 0 )
			{
				bPassed = true;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	///////////// CURRENT VERSION FILTER //////////////////////
	if ( m_curVerOff )
	{
		bool bPassed = true;

		// find num players field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_PRODUCT_VERSION );

		if ( pFieldItem )
		{
			if ( !stricmp(pFieldItem->value, Environment.version) )
			{
				bPassed = false;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}
	
	///////////// NEWER VERSION FILTER //////////////////////
	if ( m_newVerOff )
	{
		bool bPassed = true;

		// find num players field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_PRODUCT_VERSION );

		if ( pFieldItem )
		{
			if ( stricmp(pFieldItem->value, Environment.version) > 0 )
			{
				bPassed = false;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	///////////// OLDER VERSION FILTER //////////////////////
	if ( m_oldVerOff )
	{
		bool bPassed = true;

		// find num players field
		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_PRODUCT_VERSION );

		if ( pFieldItem )
		{
			if ( stricmp(pFieldItem->value, Environment.version) < 0 )
			{
				bPassed = false;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}


	///////// PLAYER COUNT FILTER
	if ( m_playerCountOn )
	{
		bool bPassed = true;

		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_NUM_PLAYERS );

		if ( pFieldItem )
		{
			if (pFieldItem->iValue < m_minPlayers ||
				pFieldItem->iValue > m_maxPlayers )
			{
				bPassed = false;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	///////// PLAYER NAME FILTER
	if ( m_playerNameOn )
	{
		bool bPassed = false;

		// get the gamelist-specific extra data
		GameList::GameExtraData * pExtraData = (GameList::GameExtraData *)tableItem.extraData;
		if ( pExtraData )
		{
			// get the player list
			if ( pExtraData->players.GetIDFromField( PLAYERLIST_KEY_PLAYER_NAME, m_playerName ) )
			{
				// player exits
				bPassed = true;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	///////// Game Type filter
	if ( m_gameTypeOn )
	{
		bool bPassed = false;

		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_GAME_TYPE );

		if ( pFieldItem )
		{
			if ( !stricmp(pFieldItem->value, m_gameType) )
			{
				bPassed = true;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	if ( m_playerCountOn )
	{
		bool bPassed = true;

		TableList::FieldItem * pFieldItem =
			TableList::FindField( tableItem.fieldList,
									GAMELIST_KEY_NUM_PLAYERS );

		if ( pFieldItem )
		{
			if (pFieldItem->iValue < m_minPlayers ||
				pFieldItem->iValue > m_maxPlayers )
			{
				bPassed = false;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}

	///////// CLAN FILTER //////////
	if ( m_clanNameOn )
	{
		// todo -no clan field defined right now!
		bool bPassed = false;

		// get the gamelist-specific extra data
		GameList::GameExtraData * pExtraData = (GameList::GameExtraData *)tableItem.extraData;
		if ( pExtraData )
		{
			// get the player list
			if ( pExtraData->players.GetIDFromField( PLAYERLIST_KEY_CLAN_NAME, m_clanName ) )
			{
				// player exits
				bPassed = true;
			}
		}

		// macro check
		GAMELISTFILTER_CHECK_RETURN( bPassed );
	}



	// if we get here we have not yet been able to determine
	// the result of the filter. this is either because:
	// (1) FT_INCLUDE_ANY or FT_EXCLUDE_ANY and none of
	// the filters have matched yet.
	// (2) FT_EXCLUDE_ALL or FT_INCLUDE_ALL and all of
	// the filters have matched so far.

	// at this point, we call the parent function's filter item
	// and let it go through its own logic, which is a mirror
	// of our own:
	// For case (1), it will return if any of its own filters
	// pass, otherwise it will return the appropriate value.
	// For case (2), it will return if any of its own filters
	// don't pass, otherwise it will return the appropriate value.

	return TableItemFilter::Filter( tableItem );
}


/*
	Set the ping filter to the range indicated. Minping and maxping are
	specified in milliseconds.
*/
void GameListFilter::SetPingFilter(unsigned int minPing, unsigned int maxPing)
{
	gosASSERT(minPing <= maxPing);
	m_minPing = minPing;
	m_maxPing = maxPing;
}


/*
	Get the max ping time set for the ping filter. The result is
	undefined if SetPingFilter has not yet been called.
*/
unsigned int GameListFilter::GetMaxPing( void ) const
{
	return m_maxPing;
}

/*
	Get the min ping time set for the ping filter. The result is
	undefined if SetPingFilter has not yet been called.
*/
unsigned int GameListFilter::GetMinPing( void ) const
{
	return m_minPing;
}

/*
	Turn the ping filter on or off. True == turn it on,
	false == turn it off.
*/
void GameListFilter::EnablePingFilter( bool val )
{
	m_pingOn = val;
}

/*
	Returns true if the ping filter is enabled, false otherwise.
*/
bool GameListFilter::GetPingFilterEnabled( void ) const
{
	return m_pingOn;
}

/*
	Set to true to filter on active games - i.e. games that
	have at least one player in them. Set to false to turn
	this filter off.
*/
void GameListFilter::EnableActiveFilter( bool val )
{
	m_activeOn = val;
}

/*
	Returns true if the active filter is currently enabled,
	false otherwise.
*/
bool GameListFilter::GetActiveFilterEnabled( void ) const
{
	return m_activeOn;
}

/*
	Set to true to enable available filtering - i.e. games
	that have at least one available slot and are not
	locked.
*/
void GameListFilter::EnableAvailableFilter( bool val )
{
	m_availableOn = val;
}

/*
	Returns true if the available filter is currently enabled,
	false otherwise.
*/
bool GameListFilter::GetAvailableFilterEnabled( void ) const
{
	return m_availableOn;
}

/*
	Set to true to enable current version off filtering - i.e. games
	that have current version number
*/
void GameListFilter::EnableCurVerOffFilter( bool val )
{
	m_curVerOff = val;
}

/*
	Returns true if the current version off filter is currently enabled,
	false otherwise.
*/
bool GameListFilter::GetCurVerOffFilterEnabled( void ) const
{
	return m_curVerOff;
}

/*
	Set to true to enable newer version off filtering - i.e. games
	that have newer version number
*/
void GameListFilter::EnableNewVerOffFilter( bool val )
{
	m_newVerOff = val;
}

/*
	Returns true if the newer version off filter is currently enabled,
	false otherwise.
*/
bool GameListFilter::GetNewVerOffFilterEnabled( void ) const
{
	return m_newVerOff;
}

/*
	Set to true to enable older version off filtering - i.e. games
	that have older version number
*/
void GameListFilter::EnableOldVerOffFilter( bool val )
{
	m_oldVerOff = val;
}

/*
	Returns true if the old version off filter is currently enabled,
	false otherwise.
*/
bool GameListFilter::GetOldVerOffFilterEnabled( void ) const
{
	return m_oldVerOff;
}

/*
	Sets the player count filter.
*/
bool GameListFilter::SetPlayerCountFilter(unsigned int minPlayers,
											unsigned int maxPlayers )
{
	if ( minPlayers > maxPlayers )
	{
		gosASSERT(0);
		return false;
	}

	m_minPlayers = minPlayers;
	m_maxPlayers = maxPlayers;
	return true;
}

/*
	Returns the current setting for minimum players in the
	player count filter. The result is undefined if the
	SetPlayerCountFilter() method has not yet been invoked.
*/
unsigned int GameListFilter::GetMinPlayers( void ) const
{
	return m_minPlayers;
}

/*
	Returns the current setting for maximum players in the
	player count filter. The result is undefined if the
	SetPlayerCountFilter() method has not yet been invoked.
*/
unsigned int GameListFilter::GetMaxPlayers( void ) const
{
	return m_maxPlayers;
}

/*
	Turn the player count filter on (true) or off (false).
*/
void GameListFilter::EnablePlayerCountFilter( bool val )
{
	m_playerCountOn = val;
}

/*
	Returns true if the player count filter is currently
	enabled, false otherwise.
*/
bool GameListFilter::GetPlayerCountFilterEnabled( void ) const
{
	return m_playerCountOn;
}

/*
	Set the string representing the game type for the
	game type filter. The gameType must be less than
	MAX_FILTER_STRING characters in length.
*/
bool GameListFilter::SetGameTypeFilter( const char * gameType )
{
	if ( strlen(gameType) >= MAX_FILTER_STRING )
	{
		return false;
	}

	strcpy(m_gameType, gameType);
	return true;
}

/*
	Returns the current string setting of the game type filter.
	The return value is undefined if SetGameTypeFilter has not
	yet been called.
*/
const char * GameListFilter::GetGameTypeFilter( void ) const
{
	return m_gameType;
}

/*
	Turn the game type filter on (true) or off (false).
*/
void GameListFilter::EnableGameTypeFilter( bool val )
{
	m_gameTypeOn = val;
}

/*
	Returns true if the game type filter is currently
	enabled, false otherwise.
*/
bool GameListFilter::GetGameTypeFilterEnabled( void ) const
{
	return m_gameTypeOn;
}

/*
	Set the string for use as the player filter. This
	string must be less than MAX_FILTER_STRING characters
	in length.
*/
bool GameListFilter::SetPlayerFilter( const char * playerName )
{
	if ( strlen(playerName) >= MAX_FILTER_STRING )
	{
		return false;
	}
	strcpy(m_playerName, playerName);
	return true;
}

const char * GameListFilter::GetPlayerFilter( void ) const
{
	return m_playerName;
}

void GameListFilter::EnablePlayerFilter( bool val )
{
	m_playerNameOn = val;
}

bool GameListFilter::GetPlayerFilterEnabled( void ) const
{
	return m_playerNameOn;
}

bool GameListFilter::SetClanFilter( const char * clanName )
{
	if ( strlen(clanName) >= MAX_FILTER_STRING )
	{
		return false;
	}

	strcpy(m_clanName, clanName);
	return true;
}

const char * GameListFilter::GetClanFilter( void ) const
{
	return m_clanName;
}

void GameListFilter::EnableClanFilter( bool val )
{
	m_clanNameOn = val;
}

bool GameListFilter::GetClanFilterEnabled( void ) const
{
	return m_clanNameOn;
}



//////////////////////////////
// IndexedTableList public methods
//////////////////////////////

IndexedTableView::IndexedTableView( TableList * list ) :
	m_TableList( list ),
	m_Filter( NULL ),
	m_SortVec( NULL ),
	m_SortVecSize( 0 ),
	m_SortVecCapacity( -1 ),
	m_SortKey( NULL ),
    m_Selection( NULL ),
	m_SortOrder( ASCENDING_NUMERIC ),
	m_VersionOrder( CUR_OLD_NEW_VERSION ),
	m_DynamicSorting( true ),
	m_DynamicFiltering( true ),
    m_bSortDirty( false ),
    m_LastSortTime( 0 ),
    m_ResortFrequency( DEFAULT_RESORT_FREQUENCY )
{
	m_SortVec = (SortStruct *) malloc(sizeof(SortStruct) * STARTING_SERVER_COUNT );
	m_SortVecCapacity = STARTING_SERVER_COUNT;
	m_SortVecSize = 0;
}

IndexedTableView::~IndexedTableView( )
{
	if ( m_SortVec )
	{
		free(m_SortVec);
		m_SortVec = NULL;
	}
    if ( m_Selection )
    {
        free( m_Selection );
        m_Selection = NULL;
    }

	m_SortVecSize = -1;
	m_SortVecCapacity = -1;


}


const char * IndexedTableView::GetSelection( void ) const
{
    return m_Selection;
}

bool IndexedTableView::SetSelection( const char * gameID )
{
    if ( GetIndexFromID( gameID ) == -1 )
    {
        return false;
    }

    if ( m_Selection )
    {
        free(m_Selection);
        m_Selection = NULL;
    }

    m_Selection = _strdup(gameID);
    return true;
}

/*
	This function is invoked whenever the table the
	view is attached to has a new item added to it.
*/
void IndexedTableView::CreateEntryNotify( TableList::TableItem & item )
{
	// item indicated has just been added to gamelist as a new game
	Add(item);
}

/*
	This function is invoked whenever the table the
	view is attached to has had an item removed.
*/
void IndexedTableView::RemoveNotify( TableList::TableItem & item )
{
	// item indicated has just been removed from the gamelist

	/*
		Remove item from sortedvec
	*/

	int index = GetSortedIndex( &item );
	if ( index >= 0 )
	{
		DeleteSorted( index );
	}
	// else not in the list -- filtered out so nothing to do

}

/*
	This callback is invoked whenever an item in the table the
	view is attached to has a field change value.
*/
void IndexedTableView::SetFieldNotify( TableList::TableItem & item,
										TableList::FieldItem & fieldItem )
{
	// 'item' has had the indicated data field changed
	// todo - add dynamic filtering here
	if ( m_DynamicFiltering && m_Filter )
	{
		int index = GetSortedIndex( &item );

		bool doShow = m_Filter->Filter( item );
		if ( doShow && index == -1)
		{
			// add it to the list
			Add(item);
			// return early so we don't sort twice.
			return;
		}
		else if ( !doShow && index >= 0 )
		{
			// remove it from the list
			DeleteSorted(index);
		}
	}

	if ( m_DynamicSorting && m_SortKey)
	{
		if ( !strcmp(fieldItem.key, m_SortKey) )
		{
			int index = GetSortedIndex( &item );
			if ( index >= 0 )
			{
				// item was in the list
				// todo - do dynamic sort of single item instead of sorting the whole list
                SetSortDirty(true);
                //Sort(m_SortKey, m_SortOrder);
			}
		}
	}

}

////////////////////////////////////////////////////////////////////////////////////////
//  IndexedTableView::GetSortDirty()
//  Purpose:
//       Returns true if the indexed table list has had updates that require resorting 
//       since the last sort, or false otherwise.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  bool
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100700  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool IndexedTableView::GetSortDirty( void ) const
{
    return m_bSortDirty;
}

////////////////////////////////////////////////////////////////////////////////////////
//  IndexedTableView::SetSortDirty()
//  Purpose:
//       Allows you to manually indicate if the list is dirty or not.  Use with 
//       caution.
//  
//  Parameters:
//       [val] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100700  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void IndexedTableView::SetSortDirty( bool val ) 
{
    m_bSortDirty = val;
}


/*
	This callback is invoked whenever a field is removed from the
	table the view is attached to. The callback happens before
	the field being remove is actually freed.
*/
void IndexedTableView::RemoveFieldNotify( TableList::TableItem * item,
											const char * key )
{
	if ( m_SortKey && !strcmp(key, m_SortKey) )
	{
		// Tend to the sorted vec
		int index = GetSortedIndex( item );
		if ( index >= 0 )
		{
			if ( !strcmp(m_SortVec[index].sortField->key, key) )
			{
				gosASSERT(index >= 0);
				m_SortVec[index].sortField = NULL;
			}
		}
	}
}

// policy settings

/*
	True if the list is sorted as changes occur. If false,
	sorting only occurs when the Sort() method is invoked.
*/
void IndexedTableView::SetDynamicSorting( bool val )
{
	m_DynamicSorting = val;
}

bool IndexedTableView::GetDynamicSorting( void ) const
{
	return m_DynamicSorting;
}

/*
	True if the list is filtered as changes occur. If false,
	filtering only occurs when the Filter() method is invoked.
*/
void IndexedTableView::SetDynamicFiltering( bool val )
{
	m_DynamicFiltering = val;
}

bool IndexedTableView::GetDynamicFiltering( void ) const
{
	return m_DynamicFiltering;
}


void IndexedTableView::SetFilter( TableItemFilter & filter )
{
	m_Filter = &filter;
}

TableItemFilter * IndexedTableView::GetFilter( void ) const
{
	return m_Filter;
}

void IndexedTableView::ClearFilter( void )
{
	m_Filter = NULL;
}

bool IndexedTableView::GetIndex( int index, char ** id )
{
	if ( index < 0 || index >= m_SortVecSize )
	{
		return false; // index out of range
	}

	gosASSERT(index >= 0);
	*id = m_SortVec[index].tableItem->id;
	return true;
}


/*
	This callback is used to fill in the sort vec with all the
	entries found in the table list. Items are added to the
	Sort vec only if they pass the filter.
*/
bool IndexedTableView::HashForEachCB( TableList::TableItem * item,
										MTListNodeHandle h,
										void * context )
{
	IndexedTableView * pThis = (IndexedTableView *) context;

	if ( pThis->m_Filter && pThis->m_Filter->Filter(*item) )
	{
		int index = pThis->m_SortVecSize;

		gosASSERT(index >= 0);
		pThis->m_SortVec[index].tableItem = item;
		pThis->m_SortVec[index].sortField = NULL;
		pThis->m_SortVecSize++;
	}
	return true;
}




/*
	Performs a single pass filtering of all items in the
	sorted view. Return value is the number of items that
	were filtered out by the pass.
*/
bool IndexedTableView::Filter( VersionOrderType versionOrder )
{
	if ( !m_Filter )
	{
		return true;
	}

	m_TableList->Lock();

	// 1. rebuild sort vec to include all filtered items
	// 2. resort filter vec.
	while ( m_SortVecCapacity < m_TableList->m_Items.Count() )
	{
		GrowSorted();
	}

	m_SortVecSize = 0;
	m_TableList->m_Items.ForEach( IndexedTableView::HashForEachCB, this );

	m_TableList->Unlock();

	// resort if sorting has been applied to the list
	if ( m_SortKey )
	{
		if ( stricmp(m_SortKey, "Game Version") == 0)
		{	
			Sort( m_SortKey, m_SortOrder , versionOrder);
		}
		else
		Sort( m_SortKey, m_SortOrder );
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  IndexedTableView::SortIfDirty()
//  Purpose:
//       Sorts the table if the dirty bit is set.  Only resorts at most every
//       m_SortFrequency seconds.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100700  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool IndexedTableView::SortIfDirty( void ) 
{
    if ( m_DynamicSorting && m_SortKey && 
        (gos_GetHiResTime() - m_LastSortTime > m_ResortFrequency) )
    {
        if ( GetSortDirty() )
        {
			if ( stricmp(m_SortKey, "Game Version") == 0)
			{	
				Sort( m_SortKey, m_SortOrder , m_VersionOrder);
			}
			else
            Sort( m_SortKey, m_SortOrder );
            return true;
        }
    }
    return false;
}


////////////////////////////////////////////////////////////////////////////////////////
//  IndexedTableView::GetResortFrequency()
//  Purpose:
//       Returns the maximum frequency for automatic list resorts, in seconds.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  double
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100700  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
double IndexedTableView::GetResortFrequency( void ) const
{
    return m_ResortFrequency;
}

////////////////////////////////////////////////////////////////////////////////////////
//  IndexedTableView::SetResortFrequency()
//  Purpose:
//       Allows you to set the maximum frequency for automatic list resorts, in 
//       seconds.
//  
//  Parameters:
//       [seconds] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100700  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void IndexedTableView::SetResortFrequency( double seconds )
{
    m_ResortFrequency = seconds;
}


// sorting operations
bool IndexedTableView::Sort(const char * sortKey,
							SortOrderType sortOrder,
							VersionOrderType versionOrder)
{
	gosASSERT(sortKey);
	m_TableList->Lock();

	// avoid doing bad things if user passes us the m_SortKey itself
	if ( sortKey != m_SortKey )
	{
		if ( m_SortKey )
		{
			free( m_SortKey );
			m_SortKey = NULL;
		}

		m_SortKey = (char *) malloc( strlen(sortKey) + 1 );
		strcpy(m_SortKey, sortKey);
	}
	m_SortOrder = sortOrder;
	m_VersionOrder = versionOrder;

	for ( int i = 0; i < m_SortVecSize; i++)
	{
		m_SortVec[i].sortField = m_TableList->FindField(
			m_SortVec[i].tableItem->fieldList, m_SortKey );

		if ( !m_SortVec[i].sortField )
		{
			if (m_SortOrder == ASCENDING_NUMERIC ||
				m_SortOrder == ASCENDING_ALPHABETICAL )
			{
				m_SortVec[i].sortField = &AscendingSentinal;
			}
			else
			{
				m_SortVec[i].sortField = &DescendingSentinal;
			}
		}

	}

	QSort( m_SortVec, 0, m_SortVecSize - 1 );

	if ((m_VersionOrder != NO_VERSION) && (m_SortVecSize > 0))
	{
		int i = 0;
		int start = 0;
		int stop = 0;
		int interval = 0;

		// indices for secondary sort
		int sort_first_index = 0;
		int sort_second_index = 0;
	
		// Let's get the cutoff between old and cur and also cur and new
		while ((start < m_SortVecSize) && 
			(_mbscmp(m_SortVec[start].sortField->value, Environment.version) < 0))
			start++;
		
		stop = start;
		while ((stop < m_SortVecSize) && 
			(_mbscmp(m_SortVec[stop].sortField->value, Environment.version) == 0))
			stop++;

		// sort the versions now
		switch (m_VersionOrder)
		{
		case CUR_OLD_NEW_VERSION:
			{				
				// populate the secondary sort indices
				sort_first_index = stop - start;
				sort_second_index = sort_first_index + start;

				// No current version exists.  We can exit now
				if (start == 0)
					break;

				// No current version exists.  We can exit now
				if (start == m_SortVecSize)
					break;

				interval = stop - start;

				SortStruct* newSortVec = (SortStruct *) malloc(sizeof(SortStruct) * m_SortVecSize );
				for (i = start; i < stop; i++)
					newSortVec[i - start] = m_SortVec[i];
				for (i = 0; i < start; i++)
					newSortVec[i + interval] = m_SortVec[i];
				for (i = stop; i < m_SortVecSize; i++)
					newSortVec[i] = m_SortVec[i];

				memcpy(m_SortVec, newSortVec, sizeof(SortStruct) * m_SortVecSize);
				free(newSortVec);
			}
			break;
		case CUR_NEW_OLD_VERSION:
			{
				// populate the secondary sort indices
				sort_first_index = stop - start;
				sort_second_index = sort_first_index + (m_SortVecSize - stop);

				// No old version exists.  We can exit now
				if (_mbscmp(m_SortVec[0].sortField->value, Environment.version) >= 0)
					break;
				
				// No current and new version exists.  We can exit now
				if (start == m_SortVecSize)
					break;
				
				SortStruct* newSortVec = (SortStruct *) malloc(sizeof(SortStruct) * m_SortVecSize );
				for (i = 0; i < start; i++)
					newSortVec[m_SortVecSize - start + i] = m_SortVec[i];
				for (i = start; i < m_SortVecSize; i++)
					newSortVec[i - start] = m_SortVec[i];

				memcpy(m_SortVec, newSortVec, sizeof(SortStruct) * m_SortVecSize);
				free(newSortVec);
			}
			break;
		case OLD_CUR_NEW_VERSION:
			{
				// This is the default.  No need to do any rearranging

				// populate the secondary sort indices
				sort_first_index = start;
				sort_second_index = stop;
			}
			break;
		case OLD_NEW_CUR_VERSION:
			{				
				// populate the secondary sort indices
				sort_first_index = start;
				sort_second_index = sort_first_index + (m_SortVecSize - stop);

				// No current version exists.  We can exit now
				if (start == stop)
					break;

				// No new version exists.  We can exit now
				if (start == m_SortVecSize)
					break;

				interval = stop - start;

				SortStruct* newSortVec = (SortStruct *) malloc(sizeof(SortStruct) * m_SortVecSize );
				for (i = start; i < stop; i++)
					newSortVec[m_SortVecSize - stop + i] = m_SortVec[i];
				for (i = stop; i < m_SortVecSize; i++)
					newSortVec[i - interval] = m_SortVec[i];
				for (i = 0; i < start; i++)
					newSortVec[i] = m_SortVec[i];

				memcpy(m_SortVec, newSortVec, sizeof(SortStruct) * m_SortVecSize);
				free(newSortVec);
			}
			break;
		case NEW_OLD_CUR_VERSION:
			{
				// populate the secondary sort indices
				sort_first_index = m_SortVecSize - stop;
				sort_second_index = sort_first_index + start;

				// No new version exists.  We can exit now
				if (_mbscmp(m_SortVec[m_SortVecSize - 1].sortField->value, Environment.version) <= 0)
					break;
				
				// No old and current version exists.  We can exit now
				if (stop == 0)
					break;

				SortStruct* newSortVec = (SortStruct *) malloc(sizeof(SortStruct) * m_SortVecSize );
				for (i = 0; i < stop; i++)
					newSortVec[m_SortVecSize - stop + i] = m_SortVec[i];
				for (i = stop; i < m_SortVecSize; i++)
					newSortVec[i - stop] = m_SortVec[i];

				memcpy(m_SortVec, newSortVec, sizeof(SortStruct) * m_SortVecSize);
				free(newSortVec);	
			}
			break;
		case NEW_CUR_OLD_VERSION:
			{	
				// populate the secondary sort indices
				sort_first_index = m_SortVecSize - stop;
				sort_second_index = sort_first_index + (stop - start);

				// Only old version exists.  We can exit now
				if (start == m_SortVecSize)
					break;

				// Only new version exists.  We can exit now
				if (stop == 0)
					break;

				interval = stop - start;

				SortStruct* newSortVec = (SortStruct *) malloc(sizeof(SortStruct) * m_SortVecSize );

				// Switch new and old with current version in the middle
				for (i = stop; i < m_SortVecSize; i++)
					newSortVec[i - stop] = m_SortVec[i];
				for (i = start; i < stop; i++)
					newSortVec[i - start + m_SortVecSize - stop] = m_SortVec[i];
				for (i = 0; i < start; i++)
					newSortVec[i + m_SortVecSize - start] = m_SortVec[i];

				memcpy(m_SortVec, newSortVec, sizeof(SortStruct) * m_SortVecSize);
				free(newSortVec);
			}
			break;
		}
		
		// Now sort alphabetically within versions
		start = 0;
		stop = 0;
		for (i = 0; i < m_SortVecSize; i++)
		{
			m_SortVec[i].sortField = m_TableList->FindField(
				m_SortVec[i].tableItem->fieldList, "Server Name" );
			
			if ( !m_SortVec[i].sortField )
			{
				if (m_SortOrder == ASCENDING_NUMERIC ||
					m_SortOrder == ASCENDING_ALPHABETICAL )
				{
					m_SortVec[i].sortField = &AscendingSentinal;
				}
				else
				{
					m_SortVec[i].sortField = &DescendingSentinal;
				}
			}
		}
		QSort(m_SortVec, 0, sort_first_index - 1);
		QSort(m_SortVec, sort_first_index, sort_second_index - 1);
		QSort(m_SortVec, sort_second_index, m_SortVecSize - 1);
	}

    SetSortDirty(false);
    m_LastSortTime = gos_GetHiResTime();
	m_TableList->Unlock();

	return true;
}

// misc operations
int IndexedTableView::GetIndexFromID( const char * id )
{
    if ( id == NULL )
    {
        return -1;
    }

	for (int i = 0; i < m_SortVecSize; i++)
	{
		if ( !strcmp(m_SortVec[i].tableItem->id, id) )
		{
			return i;
		}
	}
	return -1;
}

int IndexedTableView::GetCount( void ) const
{
	return m_SortVecSize;
}

/*
	Protected method.

	Add a new item to the sortvec array.
*/
void IndexedTableView::Add( TableList::TableItem & item )
{
	SortStruct s;
	s.tableItem = &item;
	s.sortField = NULL;

	if ( m_SortVecSize >= m_SortVecCapacity )
	{
		GrowSorted();
	}

	gosASSERT(m_SortVecSize >= 0);
	m_SortVec[m_SortVecSize++] = s;

    SetSortDirty(true);
/*
	if ( m_DynamicSorting && m_SortKey)
	{
		// TODO: smart insert instead of full resort
		Sort(m_SortKey, m_SortOrder);
	}
    */
}



void * IndexedTableView::operator new(size_t size)
{
	return malloc(size);
}

void * IndexedTableView::operator new[](size_t size)
{
	return malloc(size);
}

void IndexedTableView::operator delete( void* ptr )
{
	free(ptr);
}
void IndexedTableView::operator delete[] ( void* ptr )
{
	free(ptr);
}
//////////////////////////////////////
// IndexedTableView: protected methods
///////////////////////////////////////

/*
	Grow the sorted vec array
*/
void IndexedTableView::GrowSorted( void )
{
	m_SortVecCapacity += GROW_INCREMENT;
	m_SortVec = (SortVec) realloc(m_SortVec, sizeof(SortStruct) * m_SortVecCapacity);
	if ( m_SortVec == NULL )
	{
		PAUSE(( "Out of memory in GrowSorted." ));
	}
}

/*
	Remove the item at the indicated index
*/
void IndexedTableView::DeleteSorted( int index )
{
	if ( index >= m_SortVecSize )
	{
		return; // invalid delete
	}

	for (int i = index; i < m_SortVecSize-1; i++)
	{
		m_SortVec[i] = m_SortVec[i+1];
	}
	if ( m_SortVecSize )
	{
		m_SortVecSize--;
	}

}

/*
	Find the sorted index for the indicated item.
*/
int IndexedTableView::GetSortedIndex( TableList::TableItem * item )
{
	for (int i = 0; i < m_SortVecSize; i++)
	{
		if ( m_SortVec[i].tableItem == item )
		{
			return i;
		}
	}
	return -1;
}


/*

	Algorithm description pulled from Douglas D. Dankel II's lecture on quicksort:
	http://www.cis.ufl.edu/~ddd/cis3020/summer-97/lectures/lec17/sld001.htm

	Quicksort Algorithm:
	1. If First < Last then begin
	2. Partition the elements in the subarray First..Last so that the pivot value is in place (in position PivIndex)
	3. Apply QuickSort to the first subarray First..PivIndex-1
	4. Apply QuickSort to the second subarray PivIndex+1..Last
	end
	The two stopping cases are:
	1. (First = Last) -- only one value in subarray so sorted!
	2. (First > Last) -- no values in subarray so sorted!
*/
void IndexedTableView::QSort( SortVec & sortVec, int first, int last )
{
	if ( first < last )
	{
		// partition the elements in the subarray first..last so that the
		// pivot value is in place
		int pivIndex = first;
		// 2. Initialize Up to First and Down to Last
		int up = first;
		int down = last;
		/*
		3. Repeat
		4. Increment Up until Up selects the first element greater than the pivot value
		5. Decrement Down until it selects the first element less than or equal to the pivot value
		6. if Up < Down exchange their values
		until Up meets or passes Down
		*/
		if ((m_SortOrder == ASCENDING_NUMERIC) ||
			(m_SortOrder == DESCENDING_NUMERIC) )
		{
			do
			{
				if ( m_SortOrder == ASCENDING_NUMERIC )
				{
					while ( (up < last) &&
							(sortVec[up].sortField->iValue <= sortVec[pivIndex].sortField->iValue ) )
					{
						up++;
					}
					while ( (down > first) &&
							(sortVec[down].sortField->iValue > sortVec[pivIndex].sortField->iValue ) )
					{
						down--;
					}
				}
				else
				{
					while ( (up < last) &&
							(sortVec[up].sortField->iValue > sortVec[pivIndex].sortField->iValue ) )
					{
						up++;
					}
					while ( (down > first) &&
							(sortVec[down].sortField->iValue <= sortVec[pivIndex].sortField->iValue ) )
					{
						down--;
					}
				}
				if ( up < down &&
                     (sortVec[down].sortField->iValue != sortVec[up].sortField->iValue) ) // don't swap same items
				{
					SortStruct tmp = sortVec[up];

					sortVec[up] = sortVec[down];
					sortVec[down] = tmp;
				}
			} while ( up < down );
		}
		else
		{
			do
			{
				if ( m_SortOrder == ASCENDING_ALPHABETICAL )
				{
					while ( (up < last) &&
							(_mbscmp(sortVec[up].sortField->value, sortVec[pivIndex].sortField->value) <= 0) )
					{
						up++;
					}
					while ( (down > first) &&
							(_mbscmp(sortVec[down].sortField->value, sortVec[pivIndex].sortField->value) > 0) )
					{
						down--;
					}
				}
				else
				{
					while ( (up < last) &&
							(_mbscmp(sortVec[up].sortField->value, sortVec[pivIndex].sortField->value) > 0) )
					{
						up++;
					}
					while ( (down > first) &&
							(_mbscmp(sortVec[down].sortField->value, sortVec[pivIndex].sortField->value) <= 0) )
					{
						down--;
					}
				}

				if ( up < down &&
                     ( _mbscmp(sortVec[down].sortField->value, sortVec[up].sortField->value) != 0) ) // don't swap same
				{
					SortStruct tmp = sortVec[up];

					sortVec[up] = sortVec[down];
					sortVec[down] = tmp;
				}
			} while ( up < down );
		}

		/* Exchange Table[First] and Table[Down] 
           unless they are the same comparison value */
		if ((m_SortOrder == ASCENDING_NUMERIC) ||
			(m_SortOrder == DESCENDING_NUMERIC) )
		{
            if ( sortVec[first].sortField->iValue != 
                 sortVec[down].sortField->iValue )
            {

		        SortStruct tmp = sortVec[first];
		        sortVec[first] = sortVec[down];
		        sortVec[down] = tmp;
            }
        }
        else
        {
            if ( _mbscmp(sortVec[first].sortField->value,
                        sortVec[down].sortField->value) != 0 )
            {
		        SortStruct tmp = sortVec[first];
		        sortVec[first] = sortVec[down];
		        sortVec[down] = tmp;
            }
        }

		/* Define PivIndex as Down */
		pivIndex = down;

		QSort(sortVec, first, pivIndex-1);
		QSort(sortVec, pivIndex+1, last );
	}
}

/////////////////////////////
// CList Methods
/////////////////////////////

template<class CItem>
CList<CItem>::CList()
{
	m_pTail = m_pHead = NULL;
}

template<class CItem>
CList<CItem>::~CList()
{
	RemoveAll();
}

template<class CItem>
void CList<CItem>::RemoveAll(void)
{
	while (!IsEmpty())
		RemoveHead();
}

template<class CItem>
bool CList<CItem>::IsEmpty(void)
{
	if ( m_pHead == NULL )
		return true;
	return false;
}

template<class CItem>
void CList<CItem>::AddHead(CItem& itemInput)
{
	CListNode* pNode;
	pNode = new CListNode();
	pNode->m_Item = itemInput;
	pNode->m_pNext = m_pHead;
	if ( m_pHead == NULL )
		m_pTail = m_pHead = pNode;
	else {
		m_pHead->m_pPrev = pNode;
		m_pHead = pNode;
	}
}

template<class CItem>
void CList<CItem>::AddTail(CItem& itemInput)
{
	CListNode* pNode;
	pNode = new CListNode();
	pNode->m_Item = itemInput;
	pNode->m_pPrev = m_pTail;
	if ( m_pHead == NULL )
		m_pTail = m_pHead = pNode;
	else {
		m_pTail->m_pNext = pNode;
		m_pTail = pNode;
	}
}


template<class CItem>
bool CList<CItem>::RemoveHead(void)
{
	CListNode* pNode;
	pNode = m_pHead;
	if ( pNode == NULL )
		return false;
	m_pHead = pNode->m_pNext;
	if ( m_pHead == NULL )
		m_pTail = NULL;
	else
		m_pHead->m_pPrev = NULL;
	delete pNode;
	return true;
}

template<class CItem>
bool CList<CItem>::Find(CItem& src)
{
	CListNode* pNode;
	pNode = m_pHead;
	while ( pNode ) {
		if (pNode->m_Item == src) {
			src = pNode->m_pItem;
			return true;
		}
		pNode = pNode->m_pNext;
	}
	return false;
}

template<class CItem>
bool CList<CItem>::AddNext(POSITION pos,CItem& ItemInput )
{
	CListNode *pNode,*pNewNode;
	if ( pos == NULL )
		return false;
	pNode = (CListNode*)pos;
	pNewNode = new CListNode();
	pNewNode->m_Item = ItemInput;

	if ( pNode == m_pTail ) {
		m_pTail = pNode->m_pNext = pNewNode;
		pNewNode->m_pPrev = pNode;
	}
	else {
		pNewNode->m_pNext = pNode->m_pNext;
		pNode->m_pNext->m_pPrev = pNewNode;

		pNode->m_pNext = pNewNode;
		pNewNode->m_pPrev = pNode;
	}
	return true;
}

template<class CItem>
bool CList<CItem>::AddPrev(POSITION pos,CItem& ItemInput )
{
	CListNode *pNode,*pNewNode;
	if ( pos == NULL )
		return false;
	pNode = (CListNode*)pos;
	pNewNode = new CListNode();
	pNewNode->m_Item = ItemInput;

	if ( pNode == m_pHead ) {
		m_pHead = pNode->m_pPrev = pNewNode;
		pNewNode->m_pNext = pNode;
	}
	else {
		pNewNode->m_pPrev = pNode->m_pPrev;
		pNode->m_pPrev->m_pNext = pNewNode;

		pNode->m_pPrev = pNewNode;
		pNewNode->m_pNext = pNode;
	}
	return true;
}

template<class CItem>
bool CList<CItem>::RemoveIt(POSITION pos, CItem& ItemRet)
{
	CListNode *pNode;
	if ( pos == NULL )
		return false;
	pNode = (CListNode*)pos;
	ItemRet = pNode->m_Item;
	if ( pos == m_pHead ) {
		m_pHead = pNode->m_pNext;
		if ( m_pHead )
			m_pHead->m_pPrev = NULL;
		else
			m_pTail = NULL;
	}
	else if ( pos == m_pTail ) {
		m_pTail = pNode->m_pPrev;
		m_pTail->m_pNext = NULL;
	}
	else {
		pNode->m_pPrev->m_pNext = pNode->m_pNext;
		pNode->m_pNext->m_pPrev = pNode->m_pPrev;
	}
	delete pNode;
	return true;
}

template<class CItem>
CItem* CList<CItem>::GetHead(void)
{
	if ( m_pHead == NULL )
		return NULL;
	return &(m_pHead->m_Item);
}


template<class CItem>
POSITION CList<CItem>::GetHeadPosition(void)
{
	return (POSITION)m_pHead;
}

template<class CItem>
POSITION CList<CItem>::GetTailPosition(void)
{
	return (POSITION)m_pTail;
}

template<class CItem>
CItem* CList<CItem>::GetNext(POSITION& pos)
{
	CItem* pItemRet;
	if ( pos == NULL ) return NULL;
	pItemRet = &((CListNode*)pos)->m_Item;
	pos = ((CListNode*)pos)->m_pNext;
	return pItemRet;
}

template<class CItem>
CItem* CList<CItem>::GetPrev(POSITION& pos)
{
	CItem* pItemRet;
	if ( pos == NULL ) return NULL;
	pItemRet = &((CListNode*)pos)->m_Item;
	pos = ((CListNode*)pos)->m_pPrev;
	return pItemRet;
}


template<class CItem>
UINT CList<CItem>::GetSize(void)
{
	UINT nCount=0;
	CListNode *pCurr;

	for (pCurr = m_pHead; pCurr; pCurr = pCurr->m_pNext )
		nCount++;
	return nCount;
}





template<class CItem>
void * CList<CItem>::operator new(size_t size)
{
	// 0xffffffff specifies use of the heap set by CurrentHeapLevel
	return malloc( size );
}

template<class CItem>
void CList<CItem>::operator delete( void* ptr )
{
	free( ptr );
}

template<class CItem>
void CList<CItem>::operator delete[] ( void* ptr )
{
	free( ptr );
}








//========================================================================
// ServerBrowser static members
//========================================================================
ServerBrowser * ServerBrowser::m_pHead = NULL;
ServerAdvertiser * ServerAdvertiser::m_pHead = NULL;
bool ServerAdvertiser::s_bAdvertiseOK = true;
DWORD ServerAdvertiser::m_dwFlags = 0;
Browse::GameList * ServerBrowser::m_pGameList = NULL;
bool ServerBrowser::IPX					        = false;
bool ServerBrowser::s_bCancelActivityPending	= false;




//========================================================================
// ServerBrowser::RegisterServerBrowser
//========================================================================
bool __stdcall ServerBrowser::Register(void)
{
	//
	// first, find an unused handle
	//
	m_handle = 1;

	while (true)
	{
		//
		// see if the current handle is used
		//
		ServerBrowser *p = m_pHead;

		//
		// loop until we find a ServerBrowser that has the same
		// handle as the one we're currently checking
		//
		while (p)
		{
			if (p->m_handle == m_handle)
				break;

			p = p->m_pNext;
		}

		//
		// did we go through the entire linked list (p==NULL)?
		// if so, the handle we're checking isn't being used,
		// so we can use it
		//
		if (p == NULL)
			break;

		//
		// increment the handle, and try again
		//
		m_handle++;
	}

	//
	// now, make the handle name
	//
	int hx = (int)m_handle;
	m_szHandle[2] = (char)('0' + hx % 10);
	hx /= 10;
	m_szHandle[1] = (char)('0' + hx % 10);
	hx /= 10;
	m_szHandle[0] = (char)('0' + hx % 10);
	hx /= 10;
	m_szHandle[3] = 0;


	//
	// if we don't yet have any server browsers, we must first
	// create a game list
	//
	if (m_pHead == NULL)
	{
        // mdm - won't always need to create -- only if its null do we call new
        if ( m_pGameList == NULL )
        {
		    m_pGameList = new Browse::GameList;
		    if (!m_pGameList)
			    return false;
        }
	}

	//
	// add the current serverbrowser to the linked list
	//
	m_pNext = m_pHead;
	m_pPrev = NULL;
	if (m_pHead)
		m_pHead->m_pPrev = this;
	m_pHead = this;

	Disconnect();
	return Initialize();
}

//========================================================================
// ServerBrowser::UnregisterServerBrowser
//========================================================================
bool __stdcall ServerBrowser::Unregister(void)
{
	//
	// are we the head of the list?
	//
	if (m_pPrev == NULL)
	{
		m_pHead = m_pNext;
	}
	else
	{
		m_pPrev->m_pNext = m_pNext;
	}

	// if we're not the last, update the prev of the next one
	if (m_pNext)
	{
		m_pNext->m_pPrev = m_pPrev;
	}

	return Disconnect();
}

//========================================================================
// ServerBrowser::GetGameList
//========================================================================
Browse::GameList * __stdcall ServerBrowser::GetGameList(void)
{
	return m_pGameList;
}



//========================================================================
//========================================================================
bool __stdcall ServerBrowser::DisconnectAll(void)
{
	bool bSuccess = true;
	ServerBrowser * p = m_pHead;

	while (p)
	{
		if (!p->Disconnect())
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerBrowser::SynchronizeAll(void)
{
	//
	// make sure we have a frickin' game list, silly!
	//
	if (m_pGameList == NULL)
		return false;

	bool bSuccess = true;
	ServerBrowser * p = m_pHead;

    GameList::Lock();
	while (p)
	{
		if (!p->Synchronize())
			bSuccess = false;
		p = p->m_pNext;
	}

	if ( s_bCancelActivityPending )
	{
		// check to see if all browsing is finished, in which case we
		// call cancelUpdate and then set the static bool s_bCancelUpdatePending
		// to false.
		if ( StillBusy() == false )
		{
			CancelAllActivity();
		}
	}

    // mdm - sort list if its necessary and enough time has passed since last sort
    m_pGameList->GetIndexedView()->SortIfDirty();


    GameList::Unlock();


	p = m_pHead;
	while (p)
	{
		if (p->m_bDelete)
		{
			p->Unregister();
			p->Release();
			p = m_pHead;
		}
		p = p->m_pNext;
	}

	return bSuccess;
}

////========================================================================
////========================================================================
//bool __stdcall ServerBrowser::AddPlayerAll(DWORD dwItemID, const char * szPlayerName )
//{
//	bool bSuccess = true;
//	ServerBrowser * p = m_pHead;
//
//	while (p)
//	{
//		if (!p->CreatePlayer(dwItemID, szPlayerName))
//			bSuccess = false;
//		p = p->m_pNext;
//	}
//
//	return bSuccess;
//}
//
////========================================================================
////========================================================================
//bool __stdcall ServerBrowser::RemovePlayerAll(DWORD dwItemID, const char * szPlayerName )
//{
//	bool bSuccess = true;
//	ServerBrowser * p = m_pHead;
//
//	while (p)
//	{
//		if (!p->RemovePlayer(dwItemID, szPlayerName))
//			bSuccess = false;
//		p = p->m_pNext;
//	}
//
//	return bSuccess;
//}


//========================================================================
//========================================================================
bool __stdcall ServerBrowser::InitializeAll(void)
{
	bool bSuccess = true;
	ServerBrowser * p = m_pHead;

	while (p)
	{
		if (!p->Initialize())
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}


////////////////////////////////////////////////////////////////////////////////////////
//  ServerBrowser::DestroyGameList()
//  Purpose:
//       Destroy the game list once and for all... weee
//  
//  Parameters:
//       [void] :
//  
//  Returns:  void __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void __stdcall ServerBrowser::DestroyGameList( void )
{
    if ( m_pGameList )
    {
	    Browse::GameList::Lock();
	    delete m_pGameList;
	    m_pGameList = NULL;
	    Browse::GameList::Unlock();
    }
}


//========================================================================
//========================================================================
bool __stdcall ServerBrowser::ReleaseAll(void)
{
	while (m_pHead)
		m_pHead->Release();

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  MarkRefreshPendingCB
//  Purpose:
//       Marks the table item passed in as being due for a refresh.
//  
//  Parameters:
//       [item] :
//       [handle] :
//       [cookie] :
//  
//  Returns:  bool
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool MarkRefreshPendingCB( TableList::TableItem * item, MTListNodeHandle handle, void * cookie )
{
    if ( ServerBrowser::GetGameList() == NULL )
    {
        return false;
    }

    ServerBrowser::GetGameList()->SetField( item->id, GAMELIST_KEY_REFRESH_PENDING, 
        GAMELIST_VALUE_REFRESH_PENDING_TRUE);
    return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  ServerBrowser::CullStaleGames()
//  Purpose:
//       This will go through the entire list of games and remove any games which have 
//       their GAMELIST_KEY_REFRESH_PENDING field set to true.  THis function should 
//       be called after the full update has completed.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void ServerBrowser::CullStaleGames( void )
{
    if ( m_pGameList == NULL )
    {
        return;
    }

    const char * pGameID = m_pGameList->GetIDFromField( GAMELIST_KEY_REFRESH_PENDING, GAMELIST_VALUE_REFRESH_PENDING_TRUE );
    while ( pGameID )
    {
        m_pGameList->Remove(pGameID);
        pGameID = m_pGameList->GetIDFromField( GAMELIST_KEY_REFRESH_PENDING, GAMELIST_VALUE_REFRESH_PENDING_TRUE );
    }
}

//========================================================================
//========================================================================
bool __stdcall ServerBrowser::Update( int browserHandle )
{
	// mdm - note that cancelactivity has not yet been called for this new update cycle.
	s_bCancelActivityPending = true;
	bool bSuccess = true;
	ServerBrowser * p = m_pHead;

	if (!m_pGameList)
		return false;

    m_pGameList->Lock();

    // mdm - erase all games unless the update request is only for a specific browser
    if ( browserHandle <= 0 )
    {
        m_pGameList->RemoveAll(); // mdm - remove all games when updates requested.
    }
//    m_pGameList->EnumerateAll( MarkRefreshPendingCB, NULL ); // mdm - 2 uncomment this if you want a non-nuking update

	while (p)
	{
        if ( browserHandle <= 0 || browserHandle == p->m_handle )
        {
		    if (!p->StartUpdate())
			    bSuccess = false;
        }
		p = p->m_pNext;
	}

    m_pGameList->Unlock();
	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerBrowser::CancelAllActivity(void)
{
	bool bSuccess = true;
	ServerBrowser * p = m_pHead;

    m_pGameList->Lock();

	while (p)
	{
		if (!p->CancelActivity())
			bSuccess = false;
		p = p->m_pNext;
	}

    // mdm - cull the game list of games that didn't get an update but should have
    CullStaleGames(); 

    m_pGameList->Unlock();
	// mdm - note that cancelactivity has occurred for this update cycle.
	s_bCancelActivityPending = false;
	return bSuccess;
}


//========================================================================
//========================================================================
bool __stdcall ServerBrowser::RefreshList(void)
{

 	s_bCancelActivityPending = true;
	bool bSuccess = true;
    ServerBrowser * p = m_pHead;

    m_pGameList->Lock();

    while (p) 
    {
        if ( !p->Refresh( NULL ) )
        {
            bSuccess = false;
        }
        p = p->m_pNext;
    }

    m_pGameList->Unlock();

	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerBrowser::ServerInfo(const char * gameID)
{
	bool bSuccess = false;
	ServerBrowser * p = m_pHead;

    if ( m_pGameList == NULL )
    {
        PAUSE(( "No game list when ServerBrowser::ServerInfo() was called.\n" ));
        return false;
    }

    m_pGameList->Lock();

    // clear the status for this game
    m_pGameList->SetField( gameID, GAMELIST_KEY_SERVER_INFO_STATUS, GAMELIST_VALUE_SERVER_INFO_STATUS_PENDING );

	while (p)
	{
        if ( p->RefreshServerInfo( (char *) gameID ) )
        {
            // success - we're done
            bSuccess = true;
        }
		p = p->m_pNext;
	}

    if ( !bSuccess )
    {
        // no one to handle our query!
        // set error status for the query
        m_pGameList->SetField( gameID, GAMELIST_KEY_SERVER_INFO_STATUS, GAMELIST_VALUE_SERVER_INFO_STATUS_ERROR );
    }

    m_pGameList->Unlock();
	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerBrowser::StillBusy(void)
{
	bool bStillUpdating = false;
	ServerBrowser * p = m_pHead;

	while (p)
	{
		if (p->GetNetStatus())
			bStillUpdating = true;
		p = p->m_pNext;
	}

	return bStillUpdating;
}


////========================================================================
////========================================================================
//bool __stdcall ServerBrowser::CreateGameAll(const char *szGameName, const char *szPlayerName, int MaxPlayers, const char *szPassword, const GUID &guidInstance)
//{
//	bool bSuccess = true;
//	ServerBrowser * p = m_pHead;
//
//	GetGameList();
//
//	while (p)
//	{
//		if (!p->CreateGame(szGameName, szPlayerName, MaxPlayers, szPassword, guidInstance))
//			bSuccess = false;
//		p = p->m_pNext;
//	}
//
//	return bSuccess;
//}


//========================================================================
//========================================================================
bool __stdcall ServerBrowser::JoinGame(const char *szGameName, void **ppDplay)
{
	//
	// find the game
	//
	const char * pID = m_pGameList->GetIDFromField( GAMELIST_KEY_SERVER_NAME, szGameName );
	//const char * pID;
	//m_pGameList->GetID( szGameName, pID );

	gosASSERT(pID);
	if (!pID)
		return false;

	//
	// now, find which server browser it's associated with
	//
	ServerBrowser *pBrowser = m_pHead;

	while (pBrowser)
	{
		//
		// we must have set the handle to the item
		//
		gosASSERT(m_pGameList->GetDataField(pID, GAMELIST_KEY_BROWSER_HANDLE) != NULL);

		//
		// also, we must have a valid handle
		//
		gosASSERT(pBrowser->GetBrowserHandle() != NULL);

		if (!strcmp(m_pGameList->GetDataField(pID, GAMELIST_KEY_BROWSER_HANDLE), pBrowser->GetBrowserHandle()))
			break;

		pBrowser = pBrowser->m_pNext;
	}

	//
	// make sure that the handle associated with the game we want actually has a browser
	// associated with it
	//
	gosASSERT(pBrowser);
	if (!pBrowser)
		return false;

	pBrowser->PrepareJoinGame(szGameName, ppDplay);


	return true;
}


////////////////////////////////////////////////////////////////////////////////////////
//  ServerBorwser::GetBrowserCount()
//  Purpose:
//       Returns the number of server browsers that are currently created.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          101300  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int __stdcall ServerBrowser::GetBrowserCount( void )
{
    int count = 0;

	ServerBrowser *pBrowser = m_pHead;

	while (pBrowser)
	{
        count++;
        pBrowser = pBrowser->m_pNext;
    }
    return count;
}














//========================================================================
// ServerAdvertiser::GetAdvertiseOK
//========================================================================
bool __stdcall ServerAdvertiser::GetAdvertiseOK(void)
{
	return s_bAdvertiseOK;
}



//========================================================================
// ServerAdvertiser::RegisterServerAdvertiser
//========================================================================
bool __stdcall ServerAdvertiser::Register(void)
{
	//
	// add the current ServerAdvertiser to the linked list
	//
	m_pNext = m_pHead;
	m_pPrev = NULL;
	if (m_pHead)
		m_pHead->m_pPrev = this;
	m_pHead = this;

	return true;
}

//========================================================================
// ServerAdvertiser::UnregisterServerAdvertiser
//========================================================================
bool __stdcall ServerAdvertiser::Unregister(void)
{
	//
	// are we the head of the list?
	//
	if (m_pPrev == NULL)
	{
		m_pHead = m_pNext;
	}
	else
	{
		m_pPrev->m_pNext = m_pNext;
	}

	// if we're not the last, update the prev of the next one
	if (m_pNext)
	{
		m_pNext->m_pPrev = m_pPrev;
	}

	return true;
}

//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::DisconnectAll(void)
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->Disconnect())
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::SynchronizeAll(void)
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->Synchronize())
			bSuccess = false;
		p = p->m_pNext;
	}

	p = m_pHead;
	while (p)
	{
		if (p->m_bDelete)
		{
			p->Unregister();
			p->Release();
			p = m_pHead;
		}
		p = p->m_pNext;
	}

	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::AddPlayerAll(DWORD dwItemID, const char * szPlayerName, bool bBot )
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->CreatePlayer(dwItemID, szPlayerName, bBot))
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}

//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::RemovePlayerAll(DWORD dwItemID, const char * szPlayerName, bool bBot )
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->RemovePlayer(dwItemID, szPlayerName, bBot))
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}


//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::InitializeAll(void)
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->Initialize())
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}


//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::AdvertiseItem(DWORD dwPlayer, const char * szName , const char * szValue)
{
	bool bSuccess = true;
	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		if (!p->SetItemValue(dwPlayer, szName, szValue))
			bSuccess = false;
		p = p->m_pNext;
	}

	return bSuccess;
}


//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::ReleaseAll(void)
{
	while (m_pHead)
		m_pHead->Release();

	return true;
}


//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::CreateGameAll(const char *szGameName, const char *szPlayerName, int MaxPlayers, const char *szPassword, const GUID &guidInstance, DWORD dwFlags)
{
	ServerAdvertiser * p = m_pHead;
	s_bAdvertiseOK = true; // advertisers set this to false if an error occurs.  

	while (p)
	{
		if (!p->CreateGame(szGameName, szPlayerName, MaxPlayers, szPassword, guidInstance, dwFlags))
			s_bAdvertiseOK = false;
		p = p->m_pNext;
	}

	SetFlagsAllAdvertisers(dwFlags);
	return s_bAdvertiseOK;
}


//========================================================================
//========================================================================
DWORD __stdcall ServerAdvertiser::GetFlagsAllAdvertisers(void)
{
	return m_dwFlags;
}


//========================================================================
//========================================================================
bool __stdcall ServerAdvertiser::SetFlagsAllAdvertisers(DWORD dwFlags)
{
	m_dwFlags=dwFlags;

	ServerAdvertiser * p = m_pHead;

	while (p)
	{
		p->SetFlags(dwFlags);
		p = p->m_pNext;
	}

	return true;

}




//========================================================================
// ServerBrowserMonitor
//
// All this class does is make sure that every registered server browser
// gets freed before we exit, or it returns a warning
//========================================================================
class ServerBrowserMonitor
{
public:
	~ServerBrowserMonitor()
	{
		gosASSERT(ServerBrowser::m_pHead == NULL);
		gosASSERT(ServerAdvertiser::m_pHead == NULL);
	}
};

// for ping management
void Browse::PingCallback(DWORD inet, DWORD latency, void * context )
{
	const char * id = (const char *) context;

	char s[50];

	sprintf(s, "%d", latency);
	ServerBrowser::GetGameList()->SetField(id, GAMELIST_KEY_PING, s);
}

static ServerBrowserMonitor sbm;


////////// static vars ///////////

namespace Browse
{
	CZonePing g_ZonePing;
}

CZonePing * GameList::s_ZonePing = &Browse::g_ZonePing;
bool GameList::s_ZonePingClientStarted = false;
bool GameList::s_ZonePingServerStarted = false;








