// Include
#include "gamesrv.h"

// Local definitions
// memory를 안전하게 해제
#define SafeDelete(P) if (P!=NULL) { delete(P); (P)=NULL; }

// Global data

// cSQLAccount Constructor.
cSQLAccount::cSQLAccount(void) : mIoContextPool(NULL)
{
}

// ~cSQLAccount Destructor.
cSQLAccount::~cSQLAccount(void)
{
}

// Initialize Method
bool cSQLAccount::Initialize(char* dsn, char* uid, char* pwd, unsigned int numWorkerThreads, unsigned int bufferLength)
{
	mIoContextPool = new cIoContextPool( bufferLength );
	return cSQLPool::Initialize( dsn, uid, pwd, numWorkerThreads );
}

// Shutdown Method
void cSQLAccount::Shutdown(void)
{
	cSQLPool::Shutdown( );

	if ( mIoContextPool != NULL )
	{
		delete mIoContextPool;
		mIoContextPool = NULL;
	}
}

// AllocSQLConnection Method
PerSQLConnection* cSQLAccount::AllocSQLConnection(void)
{
	PerSQLConnection* perSQLConnection = (PerSQLConnection*)GlobalAlloc( GPTR, sizeof(PerSQLConnection) );

	if ( perSQLConnection != NULL )
	{
		perSQLConnection->sqlConnection = new cSQLConnection( );  // 1. SQLConnection  생성.
		perSQLConnection->sqlStatement  = new cSQLAccountStmt( ); // 2. cSQLAccountStmt 생성.
		perSQLConnection->prev          = NULL;                   // 선형리스트의 포인터 - 이전.
		perSQLConnection->next          = NULL;                   // 선형리스트의 포인터 - 다음.

		// 클래스 생성 확인.
		if ( perSQLConnection->sqlConnection != NULL && perSQLConnection->sqlStatement != NULL )
		{
			// SQLEnvironment 클래스를 참조하여 핸들 생성.
			if ( perSQLConnection->sqlConnection->AllocDbc( mSqlEnv ) == true )
			{
				// DBC 연결.
				if ( perSQLConnection->sqlConnection->Connect( (SQLCHAR*)mDsn, (SQLCHAR*)mUid, (SQLCHAR*)mPwd ) == true )
				{
					// SQLConnection클래스를 참조하여 핸들 생성.
					if ( perSQLConnection->sqlStatement->AllocStmt( perSQLConnection->sqlConnection ) == true )
					{
						// mWorkingSetSize를 증가.
						mWorkingSetSize++;
						return perSQLConnection;
					}
				}
			}
		}
	}

	FreeSQLConnection( &perSQLConnection );
	return perSQLConnection;
}

// PostServerEvent Method
bool cSQLAccount::PostServerEvent(LPCTSTR format, ...)
{
	bool retvalue = false;
	if ( g_gameSrv != NULL )
	{
		LPVOID  msgBuf = NULL;
		DWORD   bufferLength;

		va_list args;

		va_start( args, format );

		bufferLength = _vscprintf( format, args ) + 1;
		msgBuf       = malloc( bufferLength );

		vsprintf( (char*)msgBuf, format, args );

		va_end( args );

		if ( msgBuf != NULL )
		{
			retvalue = g_gameSrv->PostServerEvent( (char*)msgBuf );
			free( msgBuf );
		}
	}
	return retvalue;
}

// GetIoContextPool Method
void cSQLAccount::GetIoContextPool(SIZE_T& quotaPagedPoolUsage, SIZE_T& quotaNonPagedPoolUsage, SIZE_T& workingSetSize)
{
	mIoContextPool->GetProcessMemoryInfo( quotaPagedPoolUsage, quotaNonPagedPoolUsage, workingSetSize );
}

// Get Method
PerIoContext* cSQLAccount::Get( )
{
	return mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
}

// Release Method
void cSQLAccount::Release(PerIoContext* perIoContext)
{
	DWORD error = 0;
	mIoContextPool->ReleaseIoContext( perIoContext /*false, &error*/ );
	if ( error != 0 )
		PostServerEvent( "WARNING - cSQLAccount::Release:Error(=0x%08xh)", error );
}

// WorkerThread Method
DWORD cSQLAccount::WorkerThread(void)
{
	PerSQLConnection* perSQLConnection = NULL;
	cSQLAccountStmt*  sqlAccountStmt   = NULL;
	SQLRETURN         sqlReturn;

	PerIoContext*     perIoContext     = NULL;
	BOOL              retValue;

	DWORD             bytesTransfered;
	ULONG_PTR         completionKey;
	OVERLAPPED*       overlapped;

	// [warning C4127 조건식이 상수].
	bool boolean = true;
	while ( boolean )
	{
		// 대기중 I/O 가져오기.
		retValue = GetQueuedCompletionStatus( mRequestQueue, &bytesTransfered, &completionKey, &overlapped, INFINITE );

		// Shutdown.
		if ( overlapped == POOL_SHUTDOWN )
			break;

		// Error - 오류.
		if ( retValue == FALSE || bytesTransfered == 0 ) 
			continue;

		// SQLConnection - 가져오기.
		perSQLConnection = GetPool( );

		// SQLConnection - 연결확인.
		if ( perSQLConnection == NULL )
		{
			// 1(순서에 주위), 지연시간 10/1000(s) - CPU 과부화 방지에 사용.
			Sleep( 10 );

			// 2(순서에 주위), 연결이 안 되었을 경우, 다시 시도.
			QueueRequest( completionKey, overlapped, bytesTransfered );
			continue;
		}

		// SQLStatement를 cSQLAccountStmt로 캐스팅.
		sqlAccountStmt = (cSQLAccountStmt*)perSQLConnection->sqlStatement;

		// OVERLAPPED를 PerIoContext로 케스팅.
		perIoContext = (PerIoContext*)overlapped;

		// 1. SQL를 실행
		switch ( perIoContext->iParam )
		{
		case SQL_REQUEST_ACCOUNT_SHUTDOWN:
			sqlReturn = sqlAccountStmt->Shutdown( (SHUTDOWN_ACCOUNT*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_CHANNEL_CHECK:
			sqlReturn = sqlAccountStmt->ChannelCheck( (CHANNEL_CHECK*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_MEMBER_CHECK:
			sqlReturn = sqlAccountStmt->MemberCheck( (MEMBER_CHECK*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_MEMBER_LOGOUT:
			sqlReturn = sqlAccountStmt->MemberLogout( (MEMBER_LOGOUT*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_COMEIN_GAME:
			sqlReturn = sqlAccountStmt->ComeinGame( (COMEIN_GAME*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_GAMEIN_COMPLETE:
			sqlReturn = sqlAccountStmt->GameinComplete( (GAMEIN_COMPLETE*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_GAMEOUT_COMPLETE:
			sqlReturn = sqlAccountStmt->GameoutComplete( (GAMEOUT_COMPLETE*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_GOTO_INSTANT_DUNGEON:
			sqlReturn = sqlAccountStmt->GotoInstantDungeon( (GOTO_INSTANT_DUNGEON*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_RETURN_TO_MAP:
			sqlReturn = sqlAccountStmt->ReturnToMap( (RETURN_TO_MAP*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_WHISPER_MEMBER_SELECT:
			sqlReturn = sqlAccountStmt->WhsiperMemberSelect( (WHISPERMEMBER_SELECT*)perIoContext->buffer );
			break;
		case SQL_GAME_PROCESS_NOTE_MEMBER_SELECT:
			sqlReturn = sqlAccountStmt->NoteMemberSelect( (NOTEMEMBER_SELECT*)perIoContext->buffer );
			break;
		default:
			sqlReturn = SQL_ERROR;
			break;
		}

		// 2. 이벤트 - 오류만 기록.
		if ( !SQL_SUCCEEDED( sqlReturn ) )
		{
			SQLHSTMT    hstmt = sqlAccountStmt->GetSqlStmt( );
			SQLCHAR     sqlstate[ SQL_SQLSTATE_SIZE + 1 ]     = "\0";
			SQLINTEGER  nativeErrorPtr                        = 0;;
			SQLCHAR     messageText[ SQL_MAX_MESSAGE_LENGTH ] = "\0";
			SQLSMALLINT bufferLength                          = SQL_MAX_MESSAGE_LENGTH;
			SQLSMALLINT textLength                            = 0;
			SQLRETURN   result;

			result = SQLGetDiagRec( SQL_HANDLE_STMT,
									hstmt,
									1,					// [IN ] Status records are numbered from 1.
									sqlstate,			// [OUT] SQL Error State string
									&nativeErrorPtr,	// [OUT] Native Error code
									messageText,		// [OUT] SQL Error Text string
									bufferLength,		// [IN ] Length of the *MessageText buffer in characters.
									&textLength );

			// 저장프로시저 쿼리 마무리.
			while ( SQLMoreResults( hstmt ) == SQL_SUCCESS );

			// 열려있는 결과 닫기. (Close the open result set.)
			SQLCloseCursor( hstmt );

			// 로그전송.
			PostServerEvent( "cSQLAccount::IParam[%d]:Returns[%d] - SQL Error State string[%s] Native Error code[%d] SQL Error Text string[%s]",
							 perIoContext->iParam,
							 sqlReturn,
							 sqlstate,
							 nativeErrorPtr,
							 messageText );
		}

		// 3. PerIoContext 사용완료 - 결과는 콜백으로 되돌려준다.
		// perIoContext->requestType   = IOCP_REQUEST_CALLBACK;
		// perIoContext->requestResult = IOCP_REQUEST_SUCCESS;
		cGameProcess* gameProcess = g_gameSrv->GetGameProcess( );
		if ( gameProcess != NULL )
		{
			perIoContext->requestResult = (sqlReturn == SQL_SUCCESS || sqlReturn == SQL_SUCCESS_WITH_INFO) ? IOCP_REQUEST_SUCCESS : IOCP_REQUEST_ERROR;
			gameProcess->QueueRequest( completionKey, (OVERLAPPED*)perIoContext, perIoContext->offset );
		}

		// SQLConnection - 내보내기.
//		ReleasePool( perSQLConnection, true );
		ReleasePool( perSQLConnection, !(sqlReturn == SQL_SUCCESS || sqlReturn == SQL_SUCCESS_WITH_INFO) );
	}
	return 0L;
}
