#include "TopFhsUpdateThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include "ReportLog.h"
#include "Database.h"

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

#include <algorithm>    // vector sort 사용 목적


// Top FHS 정보를 RCDB 에 업데이트 하는 주기 ( sec )
// - 2015-07-29 현재 다른 모듈들은 30 sec 단위로 해당 정보를 조회하는 중.
#define TOP_FHS_UPDATE_INTERVAL           28   

// RCDB 에 저장할 Top FHS 의 최대 갯수.
#define MAX_TOP_FHS_COUNT                  8   


// Top FHS 추출을 위한 score 계산시... disk_max_busy 항목에 부여할 가중치 정보.
// - 파트 협의에 따라.. storage_max_usage : disk_max_busy = 1 : 4 로 계산하여 처리하기로 함.
#define DISK_MAX_BUSY_WEIGHT             4.0



// 생성자..
CTopFhsUpdateThread::CTopFhsUpdateThread( CShmManager * pShmManager )
	: m_pShmManager( pShmManager)
{
	// Thread Handle 초기화
	m_threadHandle = 0;
}

// 소멸자..
CTopFhsUpdateThread::~CTopFhsUpdateThread()
{
	// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
	if( m_threadHandle != 0 )
		pthread_cancel(m_threadHandle);
}


// Thread 를 생성하여 작업을 시작한다...
bool CTopFhsUpdateThread::Start()
{
	// RCDB 연결을 위한 접속 정보 load
	m_rcdbInfo.Load();

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

		return false;
	}

	return true;
}


// Thread 함수...
void * CTopFhsUpdateThread::threadFunc( void * arg )
{
	CTopFhsUpdateThread * pObject = reinterpret_cast<CTopFhsUpdateThread *>( arg );
	pthread_detach( pthread_self() );

	// 초기 기동시...FHS 상태 정보가 아직 수집되지 않은 상태이므로...
	// 잠시 sleep 했다가.. 작업을 시작한다.
	sleep( 10 );

	// 주기적으로 RCDB 에 업데이트 처리 수행.
	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		sleep( TOP_FHS_UPDATE_INTERVAL );
	}

	// Thread 종료시 Handle 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

bool FhsScoreSort( const struct WritableFhsInfo & fhs1, const struct WritableFhsInfo & fhs2 )
{
	return ( fhs1.score < fhs2.score );
}


// 공유메모리에 저장된 정보 중 Top FHS 정보를 추출하여 RCDB 에 저장 처리한다.
void CTopFhsUpdateThread::Execute()
{
	// 공유메모리로 부터 write 가능한 FHS 정보를 받아온다.
	m_vecTopFhs.clear();
	m_pShmManager->GetWritableFhsList( m_vecTopFhs, DISK_MAX_BUSY_WEIGHT );

	// Top FHS 정보가 존재하지 않는 경우...
	if( m_vecTopFhs.empty() == true )
	{
		_LOG( LWAR, "TopFhsUpdateThread: Top FHS is empty. rcdb update skip.." );


		// 관련 내역을 report 로그에 기록하여 운영자가 확인할 수 있도록 처리.(#21020)
		CReportLog reportLog;
		std::string strErrorMessage;
		strErrorMessage.clear();

		if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
		{
			reportLog.GetLastError( strErrorMessage );
			LOG( LERR, "TopFhsUpdateThread: report log init failed.[%s]", strErrorMessage.c_str() );
		}
		else
		{
			reportLog.Write( "WAR", "[%s] [Top FHS not exist. RCDB update skip.]", PROG_NAME );
		}

		return;
	}

	// 추출된 writable FHS 가 RCDB 에 업데이트할 MAX FHS 수 보다 많은 경우...
	if( m_vecTopFhs.size() > MAX_TOP_FHS_COUNT )
	{
		// idle 한 순으로 정렬 처리
		// - score 점수가 낮은 FHS 가 idle 한 FHS 이므로...
		// - score 점수가 낮은 순서대로 정렬 처리한다.
		std::sort( m_vecTopFhs.begin(), m_vecTopFhs.end(), FhsScoreSort );
		
		// debug
		/*
		std::vector<struct WritableFhsInfo>::iterator it;
		for( it = m_vecTopFhs.begin(); it != m_vecTopFhs.end(); ++it )
		{
			_LOG( LDBG, "TopFhsUpdateThread: top fhs sort [%s] [%.1lf]" , it->hostname, it->score );
		}
		*/
		
		// 정렬 작업 후... RCDB 에 업데이트 할 MAX FHS 수 만큼만 저장하도록 처리.
		m_vecTopFhs.resize( MAX_TOP_FHS_COUNT );
	}
	else
	{
		// Top FHS 수가 RCDB 에 업데이트할 MAX FHS 수 이하인 경우...
		// - 최근 RC 운영 특성상 FHS 용량은 커지고.. FHS 수는 줄고 있는 상황.
		// - 이로 인해 실제 부하를 받는 FHS 까지 Top FHS 에 포함될 가능성이 높아짐.
		// - 따라서 이를 최소화하기 위해 다음과 같이 처리한다.
		// -- FHS 수가 MAX FHS 수의 50% 이하인 경우... 기존과 같이 그대로 업데이트 처리.
		// -- FHS 수가 MAX FHS 수의 50% 를 초과하는 경우...
		// --- idle 순서대로 정렬한 후
		// --- 초과된 FHS 의 절반만 등록...
		// ---- 5 개인 경우... MAX 50% 인 4개 기본 + 나머지 1대의 50% 인 0 개 = 최종 4대 FHS
		// ---- 6 개인 경우... MAX 50% 인 4개 기본 + 나머지 2대의 50% 인 1 개 = 최종 5대 FHS
		// ---- 7 개인 경우... MAX 50% 인 4개 기본 + 나머지 3대의 50% 인 1 개 = 최종 5대 FHS
		// ---- 8 개인 경우... MAX 50% 인 4개 기본 + 나머지 4대의 50% 인 2 개 = 최종 6대 FHS

		// FHS 수가 MAX FHS 수의 50% 를 초과하는 경우...
		if( m_vecTopFhs.size() > MAX_TOP_FHS_COUNT / 2 )
		{
			unsigned int uSelectCount = 0;

			// idle 한 순으로 정렬 처리
			// - score 점수가 낮은 FHS 가 idle 한 FHS 이므로...
			// - score 점수가 낮은 순서대로 정렬 처리한다.
			std::sort( m_vecTopFhs.begin(), m_vecTopFhs.end(), FhsScoreSort );

			// MAX 값의 절반만큼 기본 등록한 후
			// 나머지 FHS 의 50% 만큼만 등록 처리.			
			uSelectCount = ( MAX_TOP_FHS_COUNT / 2 ) + (( m_vecTopFhs.size() - ( MAX_TOP_FHS_COUNT / 2) ) / 2 );

			m_vecTopFhs.resize( uSelectCount );
		}
	}
	

	// RCDB 업데이트 작업 수행.

	// 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 )
	{
		// 연결 실패시...
		LOG( LERR, "TopFhsUpdateThread: RCDB connection failed. Top FHS update fail." );
	}
	else
	{

		// RCDB 연결 성공시...
		char szQuery[1024];
		int index = 0;
		std::vector<struct WritableFhsInfo>::iterator it;		

		// Top FHS 정보를 RCDB 에 업데이트 처리한다.
		for( it = m_vecTopFhs.begin(); it != m_vecTopFhs.end(); ++it )
		{
			// index 정보는 1부터 시작..
			++index;			

			// 업데이트 Query 조립.
			snprintf( szQuery, 1023,
					  "UPDATE t_fhs_top_status SET fhs_hostname='%s', lastupdate=now() "
					  "WHERE fhs_top = %d"
					  , it->hostname, index );

			//_LOG( LDBG, "TopFhsUpdateThread: Query[%s]", szQuery);

			// update query 수행.
			rcdb.PgDoExec( szQuery );
			
			// 적용된 Row 수가 1 row 가 아닌 경우 인서트 처리를 해야한다.
			if( rcdb.GetCmdTuples() != 1 )
			{
				_LOG( LDBG, "TopFhsUpdateThread: RCDB update nothing.... try insert [%d][%s]", index, it->hostname );

				// 기존 Update 관련 RCDB 결과 Set 을 Clear 처리한 후
				rcdb.PgClear();

				// RCDB update 작업이 실패한 경우.... insert 를 시도한다.
				snprintf( szQuery, 1023,
						  "INSERT INTO t_fhs_top_status (fhs_top, fhs_hostname, lastupdate) "
						  "VALUES ( %d, '%s', now())"
						  , index, it->hostname );

				//_LOG( LDBG, "TopFhsUpdateThread: Query[%s]", szQuery);

				// insert query 수행.
				rcdb.PgDoExec( szQuery );
				if( rcdb.PgResult( DataBase::CLEAR ) < 0 )
				{
					// RCDB insert query 실패시... 
					LOG( LERR, "TopFhsUpdateThread: RCDB insert query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

					// Top FHS 업데이트 관련 작업을 우선 중지하고 다음 interval 후에 다시 시도한다.
					break;
				}
			}
			else
			{
				// Update 처리 정상 수행시... RCDB 결과 Set 을 Clear 처리한다.
				rcdb.PgClear();
			}

			_LOG( LDBG, "TopFhsUpdateThread: RCDB update/insert OK. [%d][%s] score [%.1lf]", index, it->hostname, it->score );


		} // Top FHS 의 RCDB update loop

		_LOG( LINF, "TopFhsThread: RCDB Top FHS update/insert [%d] rows.", index );

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