#include "ThreadDbUpdate.h"


#include "Logger.h"
#include "ProcessConfig.h"

#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>


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


CThreadDbUpdate::CThreadDbUpdate( CDataQueue<CDataAccess> * const pQueue, unsigned int uTargetService ) 
	: m_pQueue( pQueue ), m_uTargetService( uTargetService )
{
	// 멤버변수 초기화

	m_threadHandle = 0;
	m_uSleep = SLEEP_TIME_DEFAULT;

}

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

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


	// 혹시나 해서 정석대로 내부 변수 NLLL 처리
	if( m_pQueue != NULL )
		m_pQueue = NULL;

}

bool CThreadDbUpdate::Start( void )
{
	// 멤버 변수 유효성 check
	if( m_pQueue == NULL )
	{
		LOG( LERR, "CThreadDbUpdate: content queue pointer is null." );
		return false;
	}

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


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

		return false;
	}

	return true;

}


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


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

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

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

	// thread 종료시
	pObject->m_threadHandle = 0;

	return NULL;
}

void CThreadDbUpdate::Execute( void )
{
	// sleep time 조정 처리
	m_uSleep = SLEEP_TIME_DEFAULT;

	// 조회 queue 상에 처리할 data 가 존재하는지 확인

	unsigned int uStandbyCount = m_pQueue->GetSize();

	if( uStandbyCount == 0)
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[UPDATE ] completed queue empty [%u / %u].  max sleep %u sec."
			, uStandbyCount, m_pQueue->GetMaxSize(), m_uSleep );
		return;
	}

	_LOG( LINF, "[UPDATE ] start with target service: %u, queue_count: %u"
		, m_uTargetService, uStandbyCount );


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

	// DB 와 연결을 시도한다.
	if( db.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 ] db connection failed. sleep %u sec.", m_uSleep );
		return;
	}

	// DB 상에 access time 업데이트 작업을 수행한다.
	UpdateContentAccessTime( db );


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


	return;
}

bool CThreadDbUpdate::UpdateContentAccessTime( DataBase & db )
{
	// Query 수행 시간 check  를 위한 변수
	struct timespec timeStart, timeNow;
	double timeElapsed = 0.0;
	memset( &timeStart, 0x00, sizeof( struct timespec ) );
	memset( &timeNow, 0x00, sizeof( struct timespec ) );

	CDataAccess data;
	char szQuery[4096];
	int nResult = 0;
	int nPopCount = 0;

	LOG( LINF, "[UPDATE ] access time db update start. standby queue count: %u", m_pQueue->GetSize() );

	// start 시간 check.
	clock_gettime( CLOCK_MONOTONIC, &timeStart );

	// queue 에 존재하면 무조건 처리한다.
	while( m_pQueue->GetSize() > 0 )
	{
		// queue pop
		data = m_pQueue->Front();
		m_pQueue->Pop();
		++nPopCount;


		// 대상 content에 대해 access time 정보를 우선 조회
		// - max 조회시 대상이 없는 경우 1 row 의 NULL 값을 반환하기 때문에.
		// - having count 항목을 추가하여.. max 조회시 대상이 없는 경우.. 0 row 를 반환하도록 처리.
		snprintf( szQuery, 4096 - 1
			, "SELECT MAX( file_lastaccess ) FROM t_access_%u "
			"WHERE uri = '%s' AND resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' "
			"HAVING COUNT(*) > 0 ;"
			, m_uTargetService
			, data.GetQueryURI().c_str()
		);

		// Query 실행
		db.PgDoExec( szQuery );
		nResult = db.PgResult( DataBase::NOT_CLEAR );
		if( nResult < 0 )
		{
			// query 수행 실패시
			LOG( LERR, "[UPDATE ] content select exec error. [%d][%s][%s]", nResult, db.GetErrorMessage().c_str(), szQuery );

			// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
			db.PgClear();

			// 재시도 시간 최대 설정
			m_uSleep = SLEEP_TIME_MAX;

			// db error 시 loop 종료
			break;
		}

		// 결과 Row 수 확인
		nResult = db.GetNoTuples();

		if( nResult < 0 )
		{
			// query 수행 실패시
			LOG( LERR, "[UPDATE ] content select tuple error. [%d][%s][%s]", nResult, db.GetErrorMessage().c_str(), szQuery );

			// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
			db.PgClear();

			// 재시도 시간 최대 설정
			m_uSleep = SLEEP_TIME_MAX;

			// db error 시 loop 종료
			break;
		}
		else if( nResult == 0 )
		{
			// 해당 Content 가  없는 경우.. 
			LOG( LINF, "[UPDATE ] content not found in db. update skip. [%llu %s]", data.m_uResourceId, data.m_strUri.c_str() );

			// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
			db.PgClear();

			// db 조회 결과가 없는 경우... 해당 content 는 버린다.
			continue;
		}

		long lPreAccessTime = atol( db.GetValue( 0, 0 ) );

		if( lPreAccessTime < data.m_lAccessTime || (lPreAccessTime == 0  && data.m_lAccessTime == -1 ) )
		{
			// DB access time 시간이 추출된 시간보다 작은 경우 .. DB update 작업을 수행한다.

			snprintf( szQuery, 4096 - 1
				, "UPDATE t_access_%u "
				"SET file_lastaccess = %ld "
				"WHERE uri = '%s' AND resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' ;"
				, m_uTargetService
				, data.m_lAccessTime
				, data.GetQueryURI().c_str()
			);

			// update query 수행
			db.PgDoExec( szQuery );
			nResult = db.GetCmdTuples();

			//_LOG( LDBG, "[UPDATE ] access time update [%ld -> %ld] [%s]", lPreAccessTime, data.m_lAccessTime, data.m_strUri.c_str() );
		}

		db.PgClear();

	} // while()


	// 작업 소요 시간 check.
	clock_gettime( CLOCK_MONOTONIC, &timeNow );
	timeElapsed = (double)( ( timeNow.tv_sec - timeStart.tv_sec ) * 1.0e9 + ( timeNow.tv_nsec - timeStart.tv_nsec ) ) / 1.0e9;

	LOG( LINF, "[UPDATE ] access time db update end %.2lf sec. queue pop count: %d", timeElapsed, nPopCount );

	return true;
}
