
// CHG 2020-11-09 huibong FreeBSD 12.2 컴파일시 오류 발생 관련 header 추가 (#33297)
//                 - unlink(), sleep() 사용 컴파일 오류 발생 관련 #include <unistd.h> 구문 추가
#include <unistd.h>

#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

#include "LogFileProcessThread.h"
#include "HostInfo.h"

/// 테스트를 위해 값을 4,7에서 0, 23로 변경했음.
#define		TIME_SCOPE_FIRST_BEGIN	0	
#define		TIME_SCOPE_FIRST_END	23
#define		TIME_SCOPE_BEGIN	4	
#define		TIME_SCOPE_END		7

using namespace std;

CLogFileProcessThread::CLogFileProcessThread(string myname)
{
	// 멤버 변수 초기화
	m_threadHandle = 0;

	m_szMyName = myname;
	m_nToday = GetToday();
	m_TimeScope.begin = TIME_SCOPE_FIRST_BEGIN;
	m_TimeScope.end = TIME_SCOPE_FIRST_END;
	SetStatus( LFP_READY );
	bFirst = true;
}

CLogFileProcessThread::~CLogFileProcessThread()
{

}

bool CLogFileProcessThread::ThreadInit(const sig_atomic_t *sighandle) 
{
	m_sighandle = sighandle;

	return true;
}

int CLogFileProcessThread::Rand( uint32_t max, uint32_t key )
{
	srand( time(NULL) + key );
	return ( rand() % max );
}

bool CLogFileProcessThread::IsBeginWork( struct tm& now )
{
	

	int status = GetStatus();

	switch( status )
	{
		/// READY 상태는 현재 시간이 작업 구간에 있는지를 확인 할 수 있는 상태이다.
		case LFP_READY:
			return IsInWorkTimeScope( now.tm_hour );

		/// DONE 상태는 오늘 이미 작업을 마쳤기 때문에 날자가 변경 되었는지를 확인해서
		/// 날자가 변경 되었으면 상태를 READY로 변경해서 작업 시간에 작업을 다시 할 수 있도록 한다.
		case LFP_DONE:
			if( IsChangedDay( now.tm_mday ) == true )
			{
				LOG( LDEV1, "call SetStatus( LFP_READY )" );
				SetStatus( LFP_READY );
				m_nToday = now.tm_mday;
			}
			break;
	
		/// SLEEP, WORKING 상태를 이미 작업이 시작된 상태로 IsBeginWork() 함수가 외부에 노출 되지 않았기 때문에
		/// 정상적인 경우에는 SLEEP, WORKING 상태에서 IsBeginWork() 함수가 호출 될 수 없다.
		case LFP_SLEEP:
		case LFP_WORKING:
		default:
			LOG( LWAR, "%s is invalid status.", GetStrStatus( GetStatus() ).c_str() );
			break;
	}

	

	return false;
}

bool CLogFileProcessThread::IsInWorkTimeScope( int hour )
{
	

	if( hour >= m_TimeScope.begin && hour < m_TimeScope.end )
	{
		return true;
	}

	

	return false;
}

void CLogFileProcessThread::SetStatus( LogFileProcessStatus status )
{
	switch( status )
	{
		case LFP_READY:
		case LFP_SLEEP:
		case LFP_WORKING:
		case LFP_DONE:
			_LOG( LINF, "change status of %s %s to %s.", m_szMyName.c_str(), GetStrStatus( m_nStatus ).c_str(), GetStrStatus( status ).c_str() );
			m_nStatus = status;
			break;

		default:
			_LOG( LERR, "can't set status of CLogFileProcess because of invalid status(%d)", status );
	}
}

string CLogFileProcessThread::GetStrStatus( LogFileProcessStatus status )
{
	string res = "";

	switch( status )
	{
		case LFP_READY:
			res = "READY";
			break;
	
		case LFP_SLEEP:
			res = "SLEEP";
			break;

		case LFP_WORKING:
			res = "WORKING";
			break;

		case LFP_DONE:
			res = "DONE";

		default:
			res = "UNKNOWN";
			break;
	}

	return res;
}

bool CLogFileProcessThread::IsChangedDay( int day )
{
	

	bool res = ( m_nToday != day ? true : false  );

	

	return res;
}

bool CLogFileProcessThread::IsDirectory( string path )
{
	

	struct stat dirStat;

	if( lstat ( path.c_str(), &dirStat ) != 0 )
	{
		LOG( LERR, "lstat failed. check path(%s).", path.c_str() );
		return false;
	}

	// 해당 정보가 Directory 가 아닌 경우
	if( S_ISDIR( dirStat.st_mode ) == false )
	{
		LOG( LDBG, "%s is not directory.", path.c_str() );
		
		return false;
	}

	

	return true;
}

bool CLogFileProcessThread::Unlink( string filename )
{
	

	if( IsDirectory( filename ) == true )
	{
		LOG( LWAR, "can't unlink file(%s) because %s is direcotry.", filename.c_str(), filename.c_str() );
		
		return false;
	}

	int error = unlink( filename.c_str() );

	if( error == -1 )
	{
		LOG( LWAR, "can't unlink file(%s). errno=%d, error=%s", 
					filename.c_str(), error, strerror( error ) );
		
		return false;
	}

	_LOG( LINF, "unlinke file: %s", filename.c_str() );

	

	return true;
}

int CLogFileProcessThread::GetMaxSleepSecond( struct tm& now )
{
	if( IsInWorkTimeScope( now.tm_hour ) == false )
	{
		LOG( LWAR, "can't get sleep time because of invalid hour(%d).", now.tm_hour );
		return -1;
	}

	/// (현재 시간 + 1분)에서 (m_TimeScope.end - 1 )시 59분까지의 남은 분을 계산한다. 초 단위 생략.
	/// (남은 분 * 60)으로 초 단위로 환산한 범위에서 random 한 값을 반환 한다.
		
	// 우선 시간을 빼고 분만 가지고 계산.
	// 58분 경우 59로 계산 되므로 해당 값이 '0'되는 것을 방지 하기 위해서 59 => 60으로 변경 한다.
	int min = 60 - ( now.tm_min + 1 );
	if( min < 0 )
	{
		LOG( LERR, "can't get sleep time because invalid time[%02d:%02d:%02d].",
					now.tm_hour, now.tm_min, now.tm_sec );
		return -1;

	}

	/// 시간을 계산해서 분에 더한다.
	int hour = ( m_TimeScope.end - 1 ) - now.tm_hour;
	if( hour < 0 )
	{
		LOG( LERR, "can't get sleep time because invalid time[%02d:%02d:%02d].",
					now.tm_hour, now.tm_min, now.tm_sec );
		return -1;
	}

	min += (hour * 60);
	
	/// 분을 초로 환산한다.
	int sec = min * 60;

	return sec;
}


int CLogFileProcessThread::GetToday()
{
	time_t timestamp = time( NULL );
	struct tm now;
	localtime_r( &timestamp, &now );

	return now.tm_mday;
}


///@brief 이 함수에서 컨트롤 로직이 표현된다.
void CLogFileProcessThread::Execute()
{	
	time_t timestamp;
	struct tm now;

	while (*m_sighandle == 0)
	{
		/// 현재 시각 확인
		timestamp = time( NULL );
		localtime_r( &timestamp, &now );

		/// 작업 시작 여부 확인.
		if( IsBeginWork( now ) == false )
		{
			LOG( LDEV1, "I'm sleeping now for 1 minute." );

			/// 작업을 하지 않아야 하면 1분 대기.
			sleep( 60 );	/// 60초
			continue;
		}
		
		/// 여기서 부터 작업 시작.

		/// 현재 시간부터 TIME_SCOPE_END까지 최대 대기 할 수 있는 시간을 초 단위로 가지고 온다.
		int maxSleepSecond = GetMaxSleepSecond( now );
		if( maxSleepSecond < 0 )
		{
			LOG( LERR, "invalid max sleep time. current time = [%02d:%02d:%02d].",
						now.tm_hour, now.tm_min, now.tm_sec );
			continue;
		}

		/// 0 ~ maxSleepSecond 사이의 랜덤한 값을 가지고 온다.
		CHostInfo hostInfo;
		int sleeptime = Rand( maxSleepSecond, hostInfo.GetHostNumber() );

		_LOG( LINF, "CLogFileProcess will begin work after %d seconds.", sleeptime );

		if ( bFirst == true )
			sleeptime = 5;

		/// 위에서 계산한 sleeptime 만큼 대기한 후에 실제 작업 시작.
		LOG( LDEV1, "call SetStatus( LFP_SLEEP )" );
		SetStatus( LFP_SLEEP );
		sleep( sleeptime );

		/// 여기서 부터 실제 작업 시작.
		/// 실제 작업 구현은 CLogFileProcess를 상속받은 자식 클래스에서 구현한다.
		LOG( LDEV1, "call SetStatus( LFP_WORKING )" );
		SetStatus( LFP_WORKING );
		if( DoWork() == false )
		{
			LOG( LWAR, "log file process failed." );
		}

		// DoWork가 한번 이상 수행 되면 아래이 시간 되어야 한다.
		bFirst = false;

		// 새벽 4~7시 사이...
		m_TimeScope.begin = TIME_SCOPE_BEGIN;
		m_TimeScope.end = TIME_SCOPE_END;
		/// 작업이 끝나면 작업의 성공 여부에 상관 없이 상태를 LFP_DONE로 변경한다.
		LOG( LDEV1, "call SetStatus( LFP_DONE )" );
		SetStatus( LFP_DONE );
	}
	
	LOG(LERR, "Thread Terminated...");
}

void* CLogFileProcessThread::EntryPoint(void* arg)
{
    CLogFileProcessThread* pObject = reinterpret_cast<CLogFileProcessThread *>(arg);
	pthread_detach( pthread_self() );
    
	pObject->Execute();
	
	pObject->m_threadHandle = 0;

	return 0;
}

bool CLogFileProcessThread::Start()
{
	int nRet = ::pthread_create(&m_threadHandle, 0, CLogFileProcessThread::EntryPoint, this);

	if( nRet )
	{
		LOG(LERR, "Thread create failed.: errno:%d errmsg:%s", errno, strerror(errno));
		return false;
	}
	LOG(LDBG, "Thread create succeed");
	sleep(0);
	return true;
}

