#include "ThreadTargetSelect.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


// DB 조회시 SELECT limit count
// - queue 의 최소 크기가 2048 이므르.. DB 조회시 2000 씩만 조회하도록 한다.
#define DB_SELECT_LIMIT			2000


// Content select query 수행 시간이 지정된 시간 이상일 경우 로깅 처리 (sec)
#define LOGGING_MAX_TIME_QUERY_EXEC			30


CThreadTargetSelect::CThreadTargetSelect( CDataQueue<CDataAccess> * const pQueue, unsigned int uTargetService )
	: m_pQueue(pQueue), m_uTargetService(uTargetService)
{
	// 멤버변수 초기화
	
	m_threadHandle	= 0;
	m_uSleep		= SLEEP_TIME_DEFAULT;

	m_ullResourceIdMin = 0;
	m_ullResourceIdMax = 0;
	m_ullResourceIdStart = 0;
}

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

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


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

}


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

bool CThreadTargetSelect::Start( void )
{

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

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


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

		return false;
	}

	return true;
}


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


void * CThreadTargetSelect::threadFunc( void * arg )
{
	CThreadTargetSelect * pObject = reinterpret_cast<CThreadTargetSelect *> ( 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 CThreadTargetSelect::Execute( void )
{
	// sleep time 조정 처리
	m_uSleep = SLEEP_TIME_DEFAULT;


	// 전체 구간 scan 이 완료된 경우...
	if( m_ullResourceIdStart > m_ullResourceIdMax )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[SELECT ] content select completed. max sleep[%u]. resource_id min:%llu max:%llu next:%llu"
			, m_uSleep, m_ullResourceIdMin, m_ullResourceIdMax, m_ullResourceIdStart );

		return;
	}

	// 조회 queue 상에 저장 가능 공간이 있는지 확인
	if( m_pQueue->GetFreeSize() < DB_SELECT_LIMIT )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[SELECT ] queue free size[%u / %u] too lower. select job skip and max sleep %u sec."
			, m_pQueue->GetFreeSize(), m_pQueue->GetMaxSize(), m_uSleep );
		return;
	}

	_LOG( LINF, "[SELECT ] start with target svc[%u], queue_free[%u]"
		, m_uTargetService, m_pQueue->GetFreeSize() );


	// 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, "[SELECT ] db connection failed. sleep %u sec.", m_uSleep );
		return;
	}

	// resource_id 정보를 추출한다.
	if( m_ullResourceIdMin == 0 && m_ullResourceIdMax == 0 )
	{
		char szQuery[4096];
		int nResult = 0;

		// resource_id 정보 조회
		snprintf( szQuery, 4096 - 1
			, "SELECT MIN(resource_id), MAX(resource_id) FROM t_access_%u "
			"WHERE resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' ; "
			, m_uTargetService
		);

		// Query 실행
		db.PgDoExec( szQuery );
		nResult = db.PgResult( DataBase::NOT_CLEAR );

		if( nResult < 0 )
		{
			// query 수행 실패시
			LOG( LERR, "[SELECT ] service[%u] resource_id min/max select error. [%d][%s][%s]"
				, m_uTargetService, nResult, db.GetErrorMessage().c_str(), szQuery );


			// DB 연결 해제
			db.PgCloseDB();

			// 대부분 t_access_xxx table 이 존재하지 않는 경우 이므로... 
			// 재시도 시간을 최대한 지연시킨다.
			m_uSleep = SLEEP_TIME_MAX;

			return;
		}

		// 추출된 정보 저장
		m_ullResourceIdMin = atoll( db.GetValue( 0, 0 ) );
		m_ullResourceIdMax = atoll( db.GetValue( 0, 1 ) );

		// resource_id 정보 최초 추출시.. start 시점은  최소값으로 설정 처리
		m_ullResourceIdStart = m_ullResourceIdMin;

		// db resultset clear.
		db.PgClear();
	
		_LOG( LINF, "[SELECT ] service[%u] resource_id min: %llu max: %llu"
			, m_uTargetService, m_ullResourceIdMin, m_ullResourceIdMax );
	}


	// DB 에서 access time 추출 대상 content 목록을 조회하여 queue 에 저장 처리한다.
	SelectContent( db );


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

	return;
}

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

	clock_gettime( CLOCK_MONOTONIC, &timeStart );


	_LOG( LINF, "[SELECT ] service[%u] content select start with start resource_id: %llu"
		, m_uTargetService, m_ullResourceIdStart );


	// DB 연결은 이미 되어 있는 상태
	char szQuery[4096];
	int nResult = 0;


	// 대상 content 조회
	snprintf( szQuery, 4096 - 1
		, "SELECT resource_id, uri, host_name, filename_hash "
		"FROM t_access_%u "
		"WHERE resource_id >= %llu AND resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' AND file_lastaccess = 0 "
		"ORDER BY resource_id ASC LIMIT %d"
		, m_uTargetService
		, m_ullResourceIdStart
		, DB_SELECT_LIMIT
	);


	// Query 실행
	db.PgDoExec( szQuery );
	nResult = db.PgResult( DataBase::NOT_CLEAR );

	// Query 수행시간이 지정된 시간 이상 걸리면 로깅....	   
	clock_gettime( CLOCK_MONOTONIC, &timeNow );
	timeElapsed = (double)( ( timeNow.tv_sec - timeStart.tv_sec ) * 1.0e9 + ( timeNow.tv_nsec - timeStart.tv_nsec ) ) / 1.0e9;

	if( timeElapsed >= (double)LOGGING_MAX_TIME_QUERY_EXEC )
	{
		_LOG( LWAR, "[SELECT ] query execute time too long [%.2lf sec]. result[%d] [%s]"
			, timeElapsed, nResult, szQuery );
	}
	else
	{
		_LOG( LDBG, "[SELECT ] query execute [%.2lf sec]. result[%d] [%s]"
			, timeElapsed, nResult, szQuery );
	}


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

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

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

		return false;
	}

	unsigned int uPushTotalCount = 0;

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

	_LOG( LINF, "[SELECT ] content selected. [svc:%u select_count:%d ]"
		, m_uTargetService, nResult );

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

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		db.PgClear();
		
		// 재시도 시간 최대 설정
		m_uSleep = SLEEP_TIME_MAX;
	
		return false;
	}
	else if( nResult == 0 )
	{
		// 적합한 Content 가  없는 경우.. 
		LOG( LWAR, "[SELECT ] content select not found. [%d][svc:%u][%s]"
			, nResult, m_uTargetService, szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		db.PgClear();
		
		// 재시도 시간 최대 설정
		m_uSleep = SLEEP_TIME_MAX;

		// 더 이상 작업할 contnet 가 없으므로.. 더 이상 DB 조회 발생하지 않도록 처리
		m_ullResourceIdStart = m_ullResourceIdMax + 1;
	
		return false;
	}
	else
	{
		// N 개의 조회 결과가 존재하는 경우
		// - 정해진 용량까지 Queue 에 push 처리한다.
		CDataAccess data;

		for( int i = 0; i < nResult; i++ )
		{
			// 0: resouce_id
			// 1: uri
			// 2: host_name
			// 3: filename_hash

			// 확인 결과 resouce_id 의 최대값은 9223372036854775807
			// long long int 의 최대값은 9223372036854775807 으로 resouce_id 값과 동일
			// 실제 변환 테스트해 보니 최대값까지 정상 변환 확인함.

			data.m_uResourceId = atoll( db.GetValue( i, 0 ) );
			data.m_strUri = db.GetValue( i, 1 );
			data.m_strHostname = db.GetValue( i, 2 );
			data.m_strFilenamehash = db.GetValue( i, 3 );

			// push 처리
			if( m_pQueue->Push( data ) == false )
			{
				// push 처리 실패시...
				// 2 sec 후 재시도해서 실패하면.. queue full 가능성이 있으므로...
				// 버리고.. loop 중지

				sleep( 2 );
				if( m_pQueue->Push( data ) == false )
				{
					LOG( LWAR, "[SELECT ] content select ok. but queue push fail. [svc:%u resource_id:%llu]"
						, m_uTargetService,  data.m_uResourceId );
					break;
				}
			}

			// total push count 1 증가
			uPushTotalCount++;

			// 로깅 for debug
			//_LOG( LINF, "[SELECT ]  content select, push ok. [svc:%u resource_id:%llu]", m_uTargetService, data.m_uResourceId);
		}

		// 다음 조회 start resouce_id 정보 설정
		m_ullResourceIdStart = data.m_uResourceId + 1;

	}

	db.PgClear();

	// Content 선택 결과 로깅
	_LOG( LINF, "[SELECT ] content push end. service[%u] select[%d][%.2lf sec] push[%u]  netxt start resource_id[%llu]"
		, m_uTargetService, nResult, timeElapsed, uPushTotalCount, m_ullResourceIdStart );

	return true;
}



