#include "ProperFhs.h"

#include <errno.h>
#include <string.h>
#include <algorithm>

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


// 처리 loop 간의 sleep time 정의
#define DEFAULT_SLEEP_TIME  30
// RCDB 접속 실패 또는 Query 실패 등의 상황 발생시 
#define MAX_SLEEP_TIME     180

// Query 문장 저장을 위한 Buffer 크기
#define DEFAULT_QUERY_BUFFER_SIZE 512


// 생성자
CProperFhs::CProperFhs()	
{
	// 멤버 변수 초기화
	pthread_mutex_init( &m_mutexLock, NULL );
	m_indexRoundRobin = 0;

	m_threadHandle = 0;

	m_uSleep = DEFAULT_SLEEP_TIME;
}

// 소멸자
CProperFhs::~CProperFhs()
{
	pthread_mutex_destroy( &m_mutexLock );

	if(m_threadHandle != 0 )
		pthread_cancel( m_threadHandle );
	    
}

// 객체 초기화
bool CProperFhs::Init( )
{
	// RCDB 접속 정보 Load.
	m_rcdbInfo.Load();

	// 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 )
	{
		LOG( LERR, "RCDB connection failed." );
		return false;
	}
	else
	{
		// 접속 성공한 경우...
		LOG( LINF, "RCDB connection test ok..." );

		rcdb.PgCloseDB();

		return true;		
	}
}

// thread를 생성하여 업무 flow 를 시작.
bool CProperFhs::Start()
{
	int nRet = ::pthread_create( &m_threadHandle, NULL, CProperFhs::threadFunc, this );
	if( nRet )
	{
		// Thread 생성 실패시
		int errorNum = errno;
		LOG( LERR, "Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

// 스레드 함수
void* CProperFhs::threadFunc( void* arg )
{
	CProperFhs* pObject = reinterpret_cast<CProperFhs *>( arg );
	pthread_detach( pthread_self() );

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

		// 1회 수행 후 지정된 시간 만큼 대기
		sleep( pObject->m_uSleep );
	}

	// NEW 2014-06-19 huibong
	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;

	return NULL;
}

// 스레드 내에서 각 loop 단위 처리 호출 함수.
bool CProperFhs::Execute()
{
	if( UpdateFromDB() == false )
	{
		// RCDB 연결 또는 query 관련 문제 발생시...
		// sleep time 을 높인다.
		m_uSleep = MAX_SLEEP_TIME;
	}
	else
	{
		m_uSleep = DEFAULT_SLEEP_TIME;
	}

	return true;
}


// Proper fhs의 목록을 RCDB t_fhs_top_status 에서 조회.
// CHG 2014-06-24 huibong
// #19042 의 팀회의 결과에 따라
// - RCDB 연결 실패 및 Query 오류 발생시 이전 FHS 목록 유지
// - RCDB 에서 조회된 Proper FHS 가 없는 경우.. Proper FHS 목록 Clear 처리
// - 그외 정상 처리.
bool CProperFhs::UpdateFromDB()
{
	// 1. RCDB 와 연결처리.
	DataBase 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 )
	{
		// RCDB 접속이 실패
		// - 기존 proper fhs 항목 유지 처리
		LOG( LERR, "RCDB connection failed." );
		return false;
	}

	// 2. RCDB t_fhs_top_status 테이블 조회
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];
	// Query 수행 결과값 저장을 위한 변수..	
	int nResult = 0;

	// t_fhs_top_status 테이블에 업데이트 된 FHS 목록 중...
	// 최근 1분내에 갱신된 정보만 사용한다.
	// - 신형 rc_statd 에서는 29 sec 마다 갱신 처리
	snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
		"SELECT fhs_hostname FROM t_fhs_top_status "
		" WHERE lastupdate >= (NOW() - interval'01 minute') " );

	// Query 수행 및 결과 확인
	rcdb.PgDoExec( szQuery );
	nResult = rcdb.PgResult( DataBase::NOT_CLEAR );
	if( nResult < 0 )
	{
		// Query 수행 실패시
		// - RCDB 연결 오류 발생의 경우.
		// - 기존 proper fhs 항목 유지 처리
		LOG( LERR, "RCDB t_fhs_top_status select failed.[%s][%s]"
			, rcdb.GetErrorMessage().c_str(), szQuery );

		rcdb.PgClear();
		rcdb.PgCloseDB();
		return false;
	}

	// 결과 Row 수 확인 
	nResult = rcdb.GetNoTuples();
	if( nResult < 0 )
	{
		// 오류 발생시
		// - 기존 proper fhs 항목 유지 처리
		LOG( LERR, "RCDB t_fhs_top_status GetNoTuples() error[%s][%s]"
			, rcdb.GetErrorMessage().c_str(), szQuery );

		rcdb.PgClear();
		rcdb.PgCloseDB();
		
		return false;
	}
	else if( nResult == 0 )
	{
		// 조회 결과가 존재하지 않는 경우
		// - proper fhs 목록 초기화.
		_LOG( LWAR, "CProperFhs: Proper fhs not exist. Check RCDB t_fhs_top_status table.");
		
		rcdb.PgClear();
		rcdb.PgCloseDB();

		pthread_mutex_lock( &m_mutexLock );
		m_vecFHS.clear();
		m_indexRoundRobin = 0;
		pthread_mutex_unlock( &m_mutexLock );
		
		return true;
	}

	// 3. 조회 결과가 존재하는 경우.
	//  - 내부 변수에 저장
	pthread_mutex_lock( &m_mutexLock );
	m_vecFHS.clear();
	m_indexRoundRobin = 0;

	std::string strTemp;
	for( int i = 0; i < nResult; i++ )
	{
		strTemp = rcdb.GetValue( i, 0 );

		if( strTemp.size() > 0 )
		{
			_LOG( LDBG, "CProperFhs: add host [%s]", strTemp.c_str() );
			m_vecFHS.push_back( strTemp );
		}
	}
	pthread_mutex_unlock( &m_mutexLock );
	
	// RCDB 관련 정리 처리.
	rcdb.PgClear();
	rcdb.PgCloseDB();

	return true;
}

// 현재 저장된 FHS 의 수를 반환.
int CProperFhs::GetFhsCount()
{
    return (int)m_vecFHS.size();
}

// 적절한 FHS들의 정보를 전달한다.
// @param [in] nCount FHS정보의 요청 개수.
// @param [out] vecRet 적절한 FHS의 host정보를 vector 타입으로 전달 한다.
// @param [in] listIgnoreFhs 무시할 host정보를 입력한다.
// @return 성공은 return true, 그렇지 않으면 false. 
// @note return false를 해도 nCount만큼은 못보내지만 일부 리스트가 전달 될 수 있다.
bool CProperFhs::GetProperFhs( unsigned int nCount, std::vector<std::string> &vecRet, std::list<std::string> &listIgnoreFhs )
{
	// CHG 2014-06-24 huibong
	// FHS 목록 전달시 m_indexRoundRobin 변수 사용으로 인해 중복된 FHS 가 선택되어 전달될 수 있음.
	// 따라서 이에 대한 수정 처리함.

	// 결과값을 전달할 vector 참조 객체 초기화.	
	// - 요청한 수만큼 FHS 가 존재하지 않을 경우 false 를 반환하므로...
	// - 호출 모듈에서 문제가 생기지 않도록 미리 Clear 처리
	vecRet.clear();
		
	// 요청한 FHS 수가 저장된 FHS 수보다 많은 경우..
	// - 동일 FHS 장비에 Content 복제 또는 cache 가 동일하게 발생하여....
	// - 동일 파일 Write 에 의한 content 이상 문제를 발생시킬수 있으므로.. 방어 처리.
	if( nCount > m_vecFHS.size() )
	{
		LOG( LERR, "proper fhs count[%zu], but request count[%u]. check flow."
			, m_vecFHS.size(), nCount );
		return false;
	}
	
	// 변수 생성.	
	std::list<std::string>::iterator itIgnore;

	// 중복된 FHS 정보가 push 되지 않기 위한 while loop 체크 횟수 저장 변수
	unsigned int iCheckCount = 0;
	
	// Lock 처리
	pthread_mutex_lock( &m_mutexLock );

	// 내부 Proper FHS 목록 정보가 존재하지 않는 경우.
	// - m_vecFHS 의 크기값이 Thread 모듈에 의해 변경될 수 있으므로...
	// - Lock 처리 후 판단하는 것으로 위치 수정.
	if( m_vecFHS.size() <= 0 )
	{
		// Lock 해제
		pthread_mutex_unlock( &m_mutexLock );

		LOG( LWAR, "Proper FHS not exist." );
		return false;
	}
	
	// 맴버변수인 index 지시자의 오버플로우를 막기 위해 계산 처리.
	m_indexRoundRobin %= m_vecFHS.size();
				
	// 요청한 개수 만큼 저장할때까지 반복.
	// - 중복 방지를 위해 루프 횟수는 최대 저장된 FHS 횟수 만큼으로 제한
	while( vecRet.size() != nCount && iCheckCount < m_vecFHS.size())
	{
		string strTemp = m_vecFHS.at(m_indexRoundRobin);
		// 인덱스 오버플로우를 막기 위함.
		m_indexRoundRobin++;
		m_indexRoundRobin %= m_vecFHS.size();

		++iCheckCount;

		// ignore list에 속해 있는 것은 skip 해야한다.
		// - 추출된 FHS 가 Ignore 목록에 포함되지 않은 경우에만 결과 vector 에 add 처리
		itIgnore = std::find( listIgnoreFhs.begin(), listIgnoreFhs.end(), strTemp );
		if( itIgnore == listIgnoreFhs.end() )
		{
			// 해당 결과 값이 ignore FHS 항목에 포함되지 않은 경우..
			// 결과 변수에 Push 처리.
			vecRet.push_back( strTemp );
		}
	}
	
	// Lock 해제
	pthread_mutex_unlock( &m_mutexLock );

	// 요청한 nCount 만큼 결과 변수에 저장했는지 확인..
	// - 요청 수 만큼 저장한 경우.. true 반환.
	// - 요청 수 만큼은 아니지만.. 일부 Data 가 저장된 경우에는 false 반환.
	if( nCount == vecRet.size() )
		return true;
	else
		return false;
}
