// Include
#include "logdemon.h"

// Local definitions

/* macro for SQL Class checking */
#define SQL_LOG_CHECK(sqlLog)               \
	sqlLog = g_logDemon->GetSQLLog( );      \
	if ( sqlLog == NULL )                   \
		return false;

// Global data

// prototype
void ErrorLog( LPCTSTR format, ... )
{
	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 )
	{
		FILE*      stream = NULL;
		char       filename[ MAX_PATH ];
		SYSTEMTIME systemtime;
		char       buffer[ 1024 ];

		GetLocalTime( &systemtime );

		sprintf( filename,
				 "%s_%04d_%02d_%02d.log",
				 g_logDemon->GetServiceName( ),
				 systemtime.wYear,
				 systemtime.wMonth,
				 systemtime.wDay );

		stream = fopen( filename, "at" );
		if ( stream != NULL )
		{
			sprintf( buffer,
					 "%04d %02d %02d::%02d %02d %02d : %s\n",
					 systemtime.wYear,
					 systemtime.wMonth,
					 systemtime.wDay,
					 systemtime.wHour,
					 systemtime.wMinute,
					 systemtime.wSecond,
					 (char*)msgBuf );

			fputs( buffer, stream );
			fclose( stream );
		}

		free( msgBuf );
	}
}

// cLogRecver Constructor
cLogRecver::cLogRecver(void)
{
	mRecvUdpCount = 0;
	mRecvUdpSize  = 0;
}

// ~cLogRecver Destructor
cLogRecver::~cLogRecver(void)
{
}

// Initialize Method
bool cLogRecver::Initialize(char* ipAddr, unsigned short port, unsigned short numWorkerThreads)
{
	return IocpUdpRecv::Initialize( ipAddr, port, numWorkerThreads );
}

// Shutdown Method
void cLogRecver::Shutdown(DWORD maxWait)
{
	Sleep( 50 );
	IocpUdpRecv::Shutdown( maxWait );
}

// SendSQL Method
bool cLogRecver::SendSQL(MSGBUF* msgBuf, int msgLen, SQL_REQUEST_LOG_TYPE logType)
{
	cSQLLog* sqlLog = NULL;
	SQL_LOG_CHECK( sqlLog );

	PerIoContext* cbIoContext = m_ioContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	BOOL          retvalue;

	memcpy( cbIoContext->buffer, msgBuf, msgLen );

	cbIoContext->offset = msgLen;
	cbIoContext->iParam = logType;

	// sqlLog를 통해 SQLServer로 요청.
	retvalue = sqlLog->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// BatchComplete Method
bool cLogRecver::BatchComplete(char* ptr)
{
	cCSLock lock( &m_cs );
	Packet* packet = (Packet*)ptr;

	if ( g_packet == true )
	{
		// 패킷해더(Packet Header) 정보 출력 - 버전/헤더길이/서비스 종류/전체길이.
		printf( "LOGRECVER::RECV COMPLETE::VER:%d/HLEN:%d/TOS:%d/TLEN:%d\n",
				packet->ver,
				packet->hlen,
				packet->tos,
				packet->tlen );
	}

	// 서비스 타입이 [TOS_LOGIN] 또는 [TOS_GAME] 아닐경우 오류 처리.
	// 데이터의 길이가 최소 단위보다 작을 경우 오류 처리.
	if ( !(packet->tos == TOS_LOGIN_LOG || packet->tos == TOS_GAME_LOG) || packet->hlen != PHLen )
		return false;

	MSGBUF* msgBuf = (MSGBUF*)(ptr + packet->hlen);
	int     msgLen = packet->tlen - packet->hlen;

	switch ( msgBuf->protocol )
	{
	case MB_SERVER_EVENT:     return SendSQL( msgBuf, msgLen, SQL_LOG_SERVER_EVENT     );
	case MB_MEMBER_EVENT:     return SendSQL( msgBuf, msgLen, SQL_LOG_MEMBER_EVENT     );
	case MB_CHARACTER_EVENT:  return SendSQL( msgBuf, msgLen, SQL_LOG_CHARACTER_EVENT  );
	case MB_MONEY_EVENT:      return SendSQL( msgBuf, msgLen, SQL_LOG_MONEY_EVENT      );
	case MB_DEPOSIT_EVENT:    return SendSQL( msgBuf, msgLen, SQL_LOG_DEPOSIT_EVENT    );
	case MB_INVENTORY_EVENT:  return SendSQL( msgBuf, msgLen, SQL_LOG_INVENTORY_EVENT  );
	case MB_CONCURRENT_EVENT: return SendSQL( msgBuf, msgLen, SQL_LOG_CONCURRENT_EVENT );
	case MB_QUEST_EVENT:      return SendSQL( msgBuf, msgLen, SQL_LOG_QUEST_EVENT      );
	case MB_GUILD_EVENT:	  return SendSQL( msgBuf, msgLen, SQL_LOG_GUILD_EVENT      );
	default:
		ErrorLog( "Warning - Unknown Protocol. Throwing 'cLogRecver::BatchComplete' exception." );
		return false;
	}
}

// RecvComplete Method
//
// UDP:.......User Datagram Protocol.
// Datagram:..패킷 교환에서, 데이터 단말 장치와 망과의 사전 접속 절차에 의하지 않고,
//            하나하나의 패킷이 발신 데이터 단말 장치와 수신처 데이터 단말 장치 간의
//            경로 지정을 위한 충분한 정보를 가지고 있는 패킷.
bool cLogRecver::RecvComplete(ULONG_PTR completionKey, PerIoContext* perIoContext, DWORD bytesTransferred)
{
	cCSLock lock( &m_cs );

	// Recv Packet Size 누적.
	mRecvUdpCount += 1;
	mRecvUdpSize  += bytesTransferred;

	// 수신된 데이터 처리.
	perIoContext->offset = bytesTransferred;

	// 더블버퍼링 처리.
	PerIoContext** buffer = m_ioContextBackBuffer->buffer;
	long&          offset = m_ioContextBackBuffer->offset;

	if ( offset < MAX_IO_CONTEXT_BUFFER_LEN )
	{
		buffer[ offset ] = perIoContext;
		offset++;
	}
	else
	{
		m_ioContextPool->ReleaseIoContext( perIoContext );
		ErrorLog( "Warning - Overflow I/O Context Buffer. Throwing 'cLogRecver::RecvComplete' exception." );
	}

	// 수신을 위해 I/O Context를 준비한다.
	perIoContext = m_ioContextPool->GetIoContext( m_socket, IOCP_REQUEST_READ );
	if ( RecvPost( completionKey, perIoContext ) == false )
	{
		ErrorLog( "Error - RecvPost Method - Return value is false. Throwing 'cLogRecver::RecvComplete' exception." );
		return false;
	}
	return true;
}

// CallbackComplete Method
bool cLogRecver::CallbackComplete(ULONG_PTR completionKey, PerIoContext* perIoContext, DWORD bytesTransferred)
{
	try {
		// 사용이 완료된 I/O Context는 회수한다.
		m_ioContextPool->ReleaseIoContext( perIoContext );
	} catch ( char* str ) {
		ErrorLog( "Caught 'IoContextPool::ReleaseIoContext' exception type: %s. Throwing 'cLogRecver::CallbackComplete' exception.", str );
	} catch ( ... ) {
		ErrorLog( "In IoContextPool::ReleaseIoContext( PerIoContext::iParam(=%d) ) Method. Throwing 'cLogRecver::CallbackComplete' exception.", perIoContext->iParam );
	}
	return true;
}

// BackendThread Method
DWORD cLogRecver::BackendThread( )
{
	DWORD      currentTick;
	DWORD      elapsedTick;
	SYSTEMTIME systemtime;
	WORD       minute = 0;

	try {
		while ( true )
		{
			currentTick = GetTickCount( );

			// 서버종료, Thread를 나간다.
			if ( m_endServer == true )
				break;

			// Double Buffering 처리.
			EnterCriticalSection( &m_cs );

				IoContextBuffer* temp  = m_ioContextBackBuffer;
				m_ioContextBackBuffer  = m_ioContextFrontBuffer;
				m_ioContextFrontBuffer = temp;

			LeaveCriticalSection( &m_cs );

			// 수신된 데이터 처리.
			PerIoContext** buffer = m_ioContextFrontBuffer->buffer;
			long&          offset = m_ioContextFrontBuffer->offset;

			while ( offset > 0 )
			{
				try {
					BatchComplete( (*buffer)->buffer );
				} catch ( ... ) {
					ErrorLog( "In BatchComplete Method. Throwing 'cLogRecver::BackendThread' exception." );
				}

				try {
					m_ioContextPool->ReleaseIoContext( (*buffer) );
				} catch ( ... ) {
					ErrorLog( "In PerIoContext::ReleaseIoContext Method. Throwing 'cLogRecver::BackendThread' exception." );
				}

				buffer++;
				offset--;
			}

			// 간격당 로그.
			GetLocalTime( &systemtime );
			if ( systemtime.wMinute != minute )
			{
				ErrorLog( "RecvInfo::PerCount(=%u):PerSize(=%u).", mRecvUdpCount, mRecvUdpSize );

				mRecvUdpCount = 0;
				mRecvUdpSize  = 0;

				minute = systemtime.wMinute;
			}

			// 지연시간 약16(ms) - CPU 과부화 방지 & 내부 동기화.
			elapsedTick = GetTickCount( ) - currentTick;
			if ( elapsedTick < 10 )
			{
				Sleep( (10 - elapsedTick) );
			}
		}
	} catch ( char* str ) {
		ErrorLog( "Caught 'while ( true ) { ... }.' exception type: %s. Throwing 'cLogRecver::BackendThread' exception.", str );
	} catch ( ... ) {
		ErrorLog( "In while ( true ) { ... }. Throwing 'cLogRecver::BackendThread' exception." );
	}
	return 0;
}
