// Include
#include "loginsrv.h"
//#include <time.h>

// Local definitions
#define SetLeft(P,L)    { (P)->left  = L; if(L) (L)->parent = P; }
#define SetRight(P,R)   { (P)->right = R; if(R) (R)->parent = P; }

// Global data

// cServerManager Constructor
cServerManager::cServerManager( )
{
	memset( mServerTable, 0, sizeof(mServerTable) );
	// Pool Usage Pointer
	mNonPagedPoolUsage      = NULL;

	// 메모리 카운터.
	mQuotaPagedPoolUsage    = 0;
	mQuotaNonPagedPoolUsage = 0;
	mWorkingSetSize         = 0;
}

// ~cServerManager Destructor.
cServerManager::~cServerManager(void)
{
	// cServerManager를 해제.
	Shutdown();
}

// Shutdown Method
void cServerManager::Shutdown()
{
	PerChannel* temp;

	// Pool Usage Shutdown - NonPagedPoolUsage
	while ( mNonPagedPoolUsage != NULL )
	{
		temp               = mNonPagedPoolUsage;
		mNonPagedPoolUsage = mNonPagedPoolUsage->next;
		FreeChannel( &temp );
	}
}

// AllocChannel Method
PerChannel* cServerManager::AllocChannel()
{
	PerChannel* pch = (PerChannel*)GlobalAlloc( GPTR, sizeof(PerChannel) );

	if ( pch != NULL )
	{
		mWorkingSetSize++;	// mWorkingSetSize를 증가.

		pch->cid        = 0;					// Connection Index
		pch->status     = _E_STATUS_CLOSED_;	// Status
		pch->timeToLive = STATUS_SYNC_INIT;		// Time To Live

		pch->prev       = NULL;	// 선형리스트의 포인터 - 이전
		pch->next       = NULL;	// 선형리스트의 포인터 - 다음
		pch->parent     = NULL;	// BST 포인터 - 상위
		pch->left       = NULL;	// BST 포인터 - 왼쪽
		pch->right      = NULL;	// BST 포인터 - 오른쪽
	}

	return pch;
}

// FreeChannel Method.
void cServerManager::FreeChannel(PerChannel** pch)
{
	if ( (*pch) != NULL )
	{
		GlobalFree( (*pch) );
		(*pch) = NULL;

		// mWorkingSetSize를 감소.
		mWorkingSetSize--;
	}
}

// CompareCID Method
int cServerManager::CompareCID(PerChannel* pch1, PerChannel* pch2)
{
	return (pch1->cid - pch2->cid);
}
int cServerManager::CompareCID(long cid1, long cid2)
{
	return (cid1 - cid2);
}

// AttachPool Method
void cServerManager::AttachPool(PerChannel** pool, PerChannel* pch)
{
	if ( (*pool) != NULL )
	{
		(*pool)->prev = pch;

		pch->prev = NULL;
		pch->next = (*pool);
	}
	(*pool) = pch;
}

// DetachPool Method
void cServerManager::DetachPool(PerChannel** pool, PerChannel* pch)
{
	PerChannel* prev = pch->prev;
	PerChannel* next = pch->next;

	if ( prev == NULL && next == NULL )
	{
		(*pool) = NULL;
	}
	else if ( prev == NULL && next != NULL )
	{
		next->prev = NULL;
		(*pool) = next;
	}
	else if ( prev != NULL && next == NULL )
	{
		prev->next = NULL;
	}
	else if ( prev != NULL && next != NULL )
	{
		prev->next = next;
		next->prev = prev;
	}

	pch->prev = NULL;
	pch->next = NULL;
}

// AttachBst Method
bool cServerManager::AttachBst(PerChannel** root, PerChannel* pch)
{
	PerChannel* parent = NULL;
	PerChannel* child  = (*root);
	int          result;

	while ( child != NULL )
	{
		parent = child;
		result = CompareCID( child->cid, pch->cid );

		if ( result == 0 ) return false;
		else if ( result > 0 ) child = child->left;
		else if ( result < 0 ) child = child->right;
	}

	if ( parent == NULL )
	{
		(*root) = pch;
	}
	else if ( CompareCID( parent, pch ) > 0 )
	{
		SetLeft( parent, pch );
	}
	else // if ( CompareCID( parent, pch ) < 0 )
	{
		SetRight( parent, pch );
	}
	return true;
}

// DetachBst Method
bool cServerManager::DetachBst(PerChannel** root, PerChannel* pch)
{
	PerChannel* parent = pch->parent;
	PerChannel* left   = pch->left;
	PerChannel* right  = pch->right;
	PerChannel* child  = NULL;

	if ( left == NULL )
	{
		child = right;
	}
	else if ( right == NULL )
	{
		child = left;
	}
	else
	{
		child = right;
		while ( child->left != NULL ) child = child->left;

		if ( child->parent != pch )
		{
			SetLeft( child->parent, child->right );
			SetRight( child, right );
		}
		child->parent = parent;
		SetLeft( child, left );
	}

	if ( (*root) == pch )
	{
		(*root) = child;
		if ( (*root) != NULL ) (*root)->parent = NULL;
	}
	else if ( pch == parent->left )
	{
		SetLeft( parent, child );
	}
	else
	{
		SetRight( parent, child );
	}

	pch->parent = NULL;
	pch->left   = NULL;
	pch->right  = NULL;

	return true;
}

// GetPool Method
PerChannel* cServerManager::GetPool(ServerTable* root, long cid)
{
	PerChannel* pch = mNonPagedPoolUsage;

	if ( pch != NULL )
	{
		// 페이지 안된 풀 사용량를 차감.
		DetachPool( &mNonPagedPoolUsage, pch );
		mQuotaNonPagedPoolUsage--;
	}
	else
	{
		// 페이지가 없을 경우 새로 생성한다.
		pch = AllocChannel( );
	}

	if ( pch != NULL )
	{
		pch->cid = cid;
		// BST - 연결시도 / 페이지 된 풀 사용량을 증감.
		if ( AttachBst( &root->root, pch ) == true )
		{
			AttachPool( &root->pool, pch );
			mQuotaPagedPoolUsage++;
		}
		// BST - 연결실패 / 페이지 안된 풀 사용량을 증감.
		else
		{
			AttachPool( &mNonPagedPoolUsage, pch );
			mQuotaNonPagedPoolUsage++;
			return NULL;
		}
	}

	return pch;
}

// ReleasePool Method
void cServerManager::ReleasePool(ServerTable* root, PerChannel* ch)
{
	// BST - 연결종료.
	DetachBst( &root->root, ch );

	// 페이지 된 풀 사용량을 차감.
	DetachPool( &root->pool, ch );
	mQuotaPagedPoolUsage--;

	// 페이지 안된 풀 사용량을 증감.
	AttachPool( &mNonPagedPoolUsage, ch );
	mQuotaNonPagedPoolUsage++;
}

// SearchChannel Method
PerChannel* cServerManager::SearchChannel(ServerTable* serverTable, long cid)
{
	PerChannel* ch = serverTable->root;
	int         result;

	while ( ch != NULL )
	{
		result = CompareCID( ch->cid, cid );
		if ( result == 0 )
			return ch;
		else if ( result > 0 )
			ch = ch->left;
		else if ( result < 0 )
			ch = ch->right;
	}

	return NULL;
}

// GetServerTable Method
ServerTable* cServerManager::GetServerTable(long cid)
{
	WORD idx = (HIWORD(cid) - 1);
	return (idx < MAX_SERVER) ? (mServerTable + idx) : NULL;
}

// FindChannel Method
long cServerManager::FindChannel(long cid)
{
	ServerTable* serverTable = GetServerTable( cid );
	PerChannel*  ch;
	int          result;

	if ( serverTable != NULL )
	{
		ch = serverTable->root;

		while ( ch != NULL )
		{
			result = CompareCID( ch->cid, cid );
			if ( result == 0 )
				return ch->cid;
			else if ( result > 0 )
				ch = ch->left;
			else if ( result < 0 )
				ch = ch->right;
		}

		ch = serverTable->pool;
		if ( ch != NULL )
			return ch->cid;
	}

	return 0;
}

// CompareChannel Method
bool cServerManager::CompareChannel(long cid, BYTE status)
{
	ServerTable* serverTable = GetServerTable( cid );
	if ( serverTable != NULL )
	{
		PerChannel* ch = SearchChannel( serverTable, cid );
		if ( ch != NULL )
		{
			g_loginSrv->GetSender( )->PostChStatus( cid, status );

			if (ch->status == status)
			{
				ch->TimeToLive( GetTickCount( ) + STATUS_SYNC_WAIT );
				return true;
			}
		}
	}
	return false;
}

// UpdateChannel Method
bool cServerManager::UpdateChannel(long cid, BYTE status)
{
	ServerTable* serverTable = GetServerTable( cid );
	if ( serverTable != NULL )
	{
		PerChannel* ch = SearchChannel( serverTable, cid );
		if ( ch != NULL )
		{
			g_loginSrv->GetSender( )->PostChStatus( cid, status );

			serverTable->status = status;

			ch->Status( status );
			ch->TimeToLive( GetTickCount( ) + STATUS_SYNC_WAIT );
			return true;
		}
	}
	return false;
}

// GetChannel Method - 선형리스트의 HEAD를 넘겨준다.
// ReleaseChannel와 함께 LIFO를 이룬다. LIFO는 Context Switching를 최소화 하기 위해 사용
PerChannel* cServerManager::GetChannel(long cid, BYTE status)
{
	ServerTable* serverTable = GetServerTable( cid );
	PerChannel*  ch          = NULL;

	if ( serverTable != NULL )
	{
		ch = GetPool( serverTable, cid );
		if ( ch != NULL )
		{
			if ( serverTable->channelCounter == 0 )
			{
				serverTable->status = status;
			}
			serverTable->channelCounter++;

			ch->Status( status );
			ch->TimeToLive( GetTickCount( ) + STATUS_SYNC_WAIT );

			g_loginSrv->GetSender( )->PostChList( serverTable );
		}
	}

	return ch;
}

// ReleaseChannel Method - 선형리스트의 HEAD로 회수한다.
// GetChannel와 함께 LIFO를 이룬다. LIFO는 Context Switching를 최소화 하기 위해 사용
bool cServerManager::ReleaseChannel(long cid)
{
	ServerTable* serverTable = GetServerTable( cid );
	PerChannel*  ch          = NULL;

	if ( serverTable != NULL )
	{
		ch = SearchChannel( serverTable, cid );
		if ( ch != NULL )
		{
			g_loginSrv->GetSender( )->PostChStatus( cid, _E_STATUS_CLOSING_ );
			g_loginSrv->GetLoginProcess( )->ServerDown( HIWORD(cid), LOWORD(cid) );

			if ( serverTable->channelCounter == 1 )
			{
				serverTable->status = _E_STATUS_CLOSED_;
			}
			serverTable->channelCounter--;

			ch->Status( _E_STATUS_CLOSED_ );
			ch->TimeToLive( STATUS_SYNC_INIT );

			ReleasePool( serverTable, ch );
		}
		return true;
	}
	return false;
}

// UpdateProcess Method
void cServerManager::UpdateProcess(DWORD currentTick)
{
	ServerTable* serverTable = mServerTable;
	PerChannel*  ch          = NULL;
	PerChannel*  next        = NULL;
	for ( int i = 0; i < MAX_SERVER; i++, serverTable++ )
	{
		if ( serverTable->channelCounter > 0 )
		{
			ch = serverTable->pool;
			while ( ch != NULL )
			{
				next = ch->next;

				if ( ch->timeToLive < currentTick )
				{
					switch ( ch->status )
					{
					case _E_STATUS_RUNABLE_:
					case _E_STATUS_RUNNING_S1_:
					case _E_STATUS_RUNNING_S2_:
					case _E_STATUS_RUNNING_S3_:
						g_loginSrv->GetSender( )->PostChStatus( ch->cid, _E_STATUS_FIN_WAIT1_ );
						ch->Status( _E_STATUS_FIN_WAIT1_ );
						ch->TimeToLive( currentTick + STATUS_SYNC_WAIT );
						break;
					case _E_STATUS_FIN_WAIT1_:
						g_loginSrv->GetSender( )->PostChStatus( ch->cid, _E_STATUS_FIN_WAIT2_ );
						ch->Status( _E_STATUS_FIN_WAIT2_ );
						ch->TimeToLive( currentTick + STATUS_SYNC_WAIT );
						break;
					case _E_STATUS_FIN_WAIT2_:
						g_loginSrv->GetSender( )->PostChStatus( ch->cid, _E_STATUS_LAST_ACK_ );
						ch->Status( _E_STATUS_LAST_ACK_ );
						ch->TimeToLive( currentTick + STATUS_SYNC_WAIT );
						break;
					case _E_STATUS_LAST_ACK_:
						ReleaseChannel( ch->cid );
						break;
					}
				}

				ch = next;
			}
		}
	}
}
