#include "RcdbUpdateThread.h"

#include "Logger.h"
#include "Database.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>


// 매 Loop 마다 Sleep 시간 정보 정의 ( sec 단위)
#define SLEEP_TIME_DEFAULT		15 
#define SLEEP_TIME_MAX			30


CRcdbUpdateThread::CRcdbUpdateThread( CDataQueue<CRebalanceData> * const pQueueUpdateStandby
	, CDataQueue<CRebalanceData> * const pQueueDeleteStandby )
	: m_pQueueUpdateStandby( pQueueUpdateStandby ), m_pQueueDeleteStandby( pQueueDeleteStandby )
{
	// 멤버변수 초기화

	m_threadHandle = 0;		// thread handle 초기화

	m_uSleep = SLEEP_TIME_DEFAULT;
}

CRcdbUpdateThread::~CRcdbUpdateThread()
{
	// thread 가 동작 중인 경우.. thread 중지 처리
	if( m_threadHandle != 0 )
	{
		pthread_cancel( m_threadHandle );
	}

	sleep( 1 );	// thread 종료 잠시 대기

	// 혹시나 해서 정석대로 NULL 처리 추가함.
	if( m_pQueueUpdateStandby != NULL )
		m_pQueueUpdateStandby = NULL;
	if( m_pQueueDeleteStandby != NULL )
		m_pQueueDeleteStandby = NULL;
}


///////////////////////////////////////////////////

bool CRcdbUpdateThread::Start()
{
	// 멤버 변수 유효성 check
	if( m_pQueueUpdateStandby == NULL || m_pQueueDeleteStandby == NULL )
	{
		LOG( LERR, "CRcdbUpdateThread: queue object pointer not valid." );
		return false;
	}

	// RCDB 접속 정보 load
	m_rcdbInfo.Load();

	// 작업 thread 생성
	int nResult = pthread_create( &m_threadHandle, NULL, CRcdbUpdateThread::threadFunc, this );
	if( nResult != 0 )
	{
		// thread 생성 실패
		int errorNum = errno;
		LOG( LERR, "CRcdbUpdateThread: Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

///////////////////////////////////////////////////

void * CRcdbUpdateThread::threadFunc( void * arg )
{
	CRcdbUpdateThread * pObject = reinterpret_cast<CRcdbUpdateThread *>(arg);
	pthread_detach( pthread_self() );

	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		// 지정된 주기로 실행
		sleep( pObject->m_uSleep );
	}

	// thread 종료시 handle 정보 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

void CRcdbUpdateThread::Execute()
{
	// RCDB update 대상 content 가 없다면.. MAX sleep 처리한다.
	if( m_pQueueUpdateStandby->GetSize() == 0 )
	{
		m_uSleep = SLEEP_TIME_MAX;
	
		_LOG( LDBG, "[UPDATE ] standby queue empty. max sleep %u sec.", m_uSleep );
		return;
	}
	else
	{
		// update 대기 count 가  delete 대기 count 보다 많은 경우
		// delete 대기 queue 에 push 실패 발생할 가능성 있으므로.. push 실패할 상황이면.. 차라리 sleep 한다.

		unsigned int uUpdateStandbyCount = m_pQueueUpdateStandby->GetSize();
		unsigned int uDeletePushEnableCount = m_pQueueDeleteStandby->GetMaxSize() - m_pQueueDeleteStandby->GetSize();
		if( uUpdateStandbyCount > uDeletePushEnableCount )
		{
			m_uSleep = SLEEP_TIME_MAX;

			_LOG( LDBG, "[UPDATE ] delete queue not enough (%u %u). max sleep %u sec."
				, uUpdateStandbyCount, uDeletePushEnableCount, m_uSleep );
			return;
		}
	}

	// RCDB 처리를 수행할 객체 생성
	// RCDB 와 연결은 필요시마다 연결 후 해제 처리한다. (세션 유지 않도록...)		
	DataBase rcdb;

	// RCDB 와 연결을 시도한다.
	if( rcdb.PgOpenDB( m_rcdbInfo.m_strRcdbIp, m_rcdbInfo.m_nRcdbPort, m_rcdbInfo.m_strRcdbName
		, m_rcdbInfo.m_strRcdbAcct, m_rcdbInfo.m_strRcdbAcctPw ) == NULL )
	{
		// 연결 실패시...
		m_uSleep = SLEEP_TIME_DEFAULT;
		LOG( LERR, "[UPDATE ] RCDB connection failed. sleep %u sec.", m_uSleep );
		return;
	}
	
	_LOG( LINF, "[UPDATE ] start." );

	CRebalanceData data;
	char szQuery[4096];
	int nResult = 0;

	// RCDB 연결 성공시
	// - queue pop 처리하여... RCDB 업데이트 처리.
	while( m_pQueueUpdateStandby->GetSize() > 0 )
	{
		data = m_pQueueUpdateStandby->Front();
		m_pQueueUpdateStandby->Pop();

		// COPY 처리가 성공한 content 만 RCDB 업데이트 수행
		// - 그 외에는 실패 상황이므로.. RCDB 업데이트 하면 안됨.
		if( data.m_uMoveResult == MOVE_OK )
		{
			// Query 조립
			// - deleted_yn = 'N' : 이미 삭제된 경우... COPY 처리한 파일을 삭제하기 위함
			// - get_content_length = %llu : 원본이 변경된 경우.. copy 처리한 파일을 삭제처리하기 위함
			// - uri 미사용 :  uri 는 특수문자 이슈 때문에 사용하지 않고 대신 resource_id 정보를 사용함.
			snprintf( szQuery, 4096 - 1
				, "UPDATE t_meta_%s "
 				  "SET host_name = '%s', filename_hash = '%s' "
				  "WHERE resource_id = %llu AND host_name = '%s' AND filename_hash = '%s' "
				  " AND get_content_length = %llu AND deleted_yn = 'N' AND resource_type = 0 ;"
				, data.m_szServiceSeq.c_str()
				, data.m_szMoveHost.c_str(), data.m_szMoveFileNameHash.c_str()
				, data.m_uResourceId, data.m_szOriginHost.c_str(), data.m_szOriginFileNameHash.c_str()
				, data.m_uOriginContentLength );

			// update query 수행
			rcdb.PgDoExec( szQuery );
			nResult = rcdb.GetCmdTuples();
		
			_LOG( LDBG, "[UPDATE ] query result. [%d] [%s]", nResult, szQuery );
			
			if( nResult == 1 )			// 적용된 row count = 1 : 정상
			{
				// RCDB 결과 Set 을 Clear 처리한다.
				rcdb.PgClear();

				_LOG( LINF, "[UPDATE ] content meta update ok.[%s id:%llu size:%llu %s]->[%s size:%llu %s]"
					, data.m_szOriginHost.c_str(), data.m_uResourceId
					, data.m_uOriginContentLength, data.m_szOriginFileNameHash.c_str()
					, data.m_szMoveHost.c_str(), data.m_uMoveContentLength, data.m_szMoveFileNameHash.c_str() );
			}
			else if( nResult == 0 )		// 적용된 row count = 0 : 이미 삭제 또는 원본 변경된 경우... copy content 삭제하도록 설정
			{
				// RCDB 결과 Set 을 Clear 처리한다.
				rcdb.PgClear();

				// copy content 삭제하도록 data 객체 정보 변경 처리
				data.m_uMoveResult = MOVE_ERROR_UPDATE;

				LOG( LERR, "[UPDATE ] content meta update failed. origin modified. [%s id:%llu size:%llu %s][%s]"
					, data.m_szOriginHost.c_str(), data.m_uResourceId
					, data.m_uOriginContentLength, data.m_szOriginFileNameHash.c_str(), data.m_szURI.c_str() );
			}
			else	// 그외 : resource_id 조건으로 query 실패 가능성은 없음.. DB 장애로 인지해야 할 듯.
			{
				// RCDB 결과 Set 을 Clear 처리한다.
				rcdb.PgClear();

				// 어떤 상황인지 정확히 알 수 없으므로..  원본, copy 본 모두 유지시킨다.
				data.m_uMoveResult = MOVE_ERROR_CREATE;

				// 운영자가 판단하도록 로깅 처리한다.
				LOG( LERR, "[UPDATE ] content meta update result error. op check.[%d][id:%llu %s]"
					, nResult
					, data.m_uResourceId, data.m_szURI.c_str() );

				// 더 이상 작업을 수행하지 않고 MAX sleep 처리한다.
				m_uSleep = SLEEP_TIME_MAX;
				break;
			}
		}

		// delete standby queue 에 content 를 push 처리한다.
		// - 앞에서 delete queue 에 충분한 여유공간 존재하는지 check 한 후 push 하므로...
		// - push 실패는 .. 아마도... 공간 부족이 아닌 다른 문제일 가능성 존재..
		// - 우선 1번 재시도해서도 실패하면 버린다.
		if( m_pQueueDeleteStandby->Push( data ) == false )
		{
			LOG( LWAR, "[UPDATE ] delete standby queue 1st push fail.");
			sleep( 1 );
			
			if( m_pQueueDeleteStandby->Push( data ) == false )
			{
				LOG( LERR, "[UPDATE ] delete standby queue push all fail. throw.");

				// 다음번 push 작업도 실패 가능성 존재하므로....
				// 더 이상 작업하지 않고.. loop 종료하고.. 다음에 작업 진행한다.
				m_uSleep = SLEEP_TIME_DEFAULT;
				break;
			}
		}

	} // while

	// RCDB 와의 연결을 명시적으로 해제 처리
	rcdb.PgCloseDB();

	return;
}

