#include "ProcessConfig.h"

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

#include "Logger.h"

// 2021-01-19 NEW huibong move 대상 FHS 선정시 disk busy 허용 max 값 추가 (#33395)
// - conf 처리 관련 default 값 추가
#define DEFAULT_TARGET_FHS_MAX_DISK_BUSY_LIMIT		50.0


// static 변수 초기화
CProcessConfig * CProcessConfig::m_pInstance = NULL;


// 생성자
CProcessConfig::CProcessConfig()
{
	// default 값 설정.

	// Max Queue Size ( default: 4096 )
	m_nMaxQueueSize = 4096;

	// FHS File Transfer Daemon (ftsd) TCP Listen Port = 14001 (default)
	m_nFhsTransferDaemonPort = 14001;

	// Rebalance target FHS reselect time limit( min ), default 10 min, use transfer total size calc.
	m_nFhsReselectionTimeLimit = 10;

	// Content 복제 및 Cache 관련 전송시 Hash Check Level
	// 0:Not check (default), 1:Partitial hash, 2:Full Hash
	m_nHashCheckLevel = 0;


	// 2021-01-05 NEW huibong 이동 대상 content 선택 관련 mode 기능 추가 (#33306)
	// - default 는 큰 파일 순서대로 이동 처리 시도...
	m_nContentSelectMode = SELECT_MODE_SIZE;
	memset( &m_timeContentSelectModeChange, 0x00, sizeof( struct timespec ) );


	// 2021-01-19 NEW huibong move 대상 FHS 선정시 disk busy 허용 max 값 추가 (#33395)
	// - conf 처리 관련 default 값 추가
	m_dTargetFhsMaxDiskBusyLimit = DEFAULT_TARGET_FHS_MAX_DISK_BUSY_LIMIT;

}

// 소멸자 
CProcessConfig::~CProcessConfig()
{
	// 객체 소멸시... 
	// 만약 static GetInstance() 함수가 가르키는 객체가 자기 자신이라면...
	// 소멸 처리에 의해 문제가 발생할 수 있으므로... 이에 대한 처리를 해 준다.
	if( CProcessConfig::GetInstance() == this )
	{
		CProcessConfig::m_pInstance = NULL;
	}
}

bool CProcessConfig::Init( const std::string strProgramName, const std::string & strConfFileName, std::string & strErrorMessage )
{

	// 1. 임시 처리용 Instance 를 생성한다.
	CProcessConfig * pTempInstance = new CProcessConfig();

	// 2. 전달 받은 Config 정보를 임시 Instance 로 로딩한다.
	//   - 만약 로딩이 실패할 경우... 임시 Instance 객체를 삭제 처리한다.
	if( pTempInstance->Load( strProgramName, strConfFileName, strErrorMessage ) == false )
	{
		delete pTempInstance;
		pTempInstance = NULL;

		return false;	// 생성 실패 사유는 Error String 관련 함수를 사용하여 확인.
	}

	// 3. 전달받은 Conf 정보 로딩에 성공한 경우...
	//   - 기존 Instance 객체가 존재하는 경우.. 교체 처리..
	//   - 기존 Instance 객체가 없는 경우는 신규 Instance 사용토록 처리.
	if( CProcessConfig::GetInstance() == NULL )
	{
		CProcessConfig::m_pInstance = pTempInstance;
	}
	else
	{
		// Instance replace...
		CProcessConfig * pPreviosInstance = CProcessConfig::m_pInstance;
		CProcessConfig::m_pInstance = pTempInstance;
		
		delete pPreviosInstance;
	}

	return true;
}


CProcessConfig * CProcessConfig::GetInstance()
{ 
	return CProcessConfig::m_pInstance;
}


bool CProcessConfig::Load( const std::string & strProgramName, const std::string & strConfFileName, std::string & strErrorMessage )
{
	// 변수 유효성 확인
	if( strProgramName.size() <= 0 )
	{
		strErrorMessage = "Config Program Name[" + strProgramName + "] not valid.";
		return false;
	}

	// Config class (lib 하위) 를 이용하여 config 파일 상의 정보를 로딩 처리 
	Config conf;

	if( conf.Open( strConfFileName ) == false )
	{
		strErrorMessage = "Config file[" + strConfFileName + "] open failed.";
		return false;
	}

	// Config 모듈에 로딩된 정보 중 필요한 정보만 가져온다...
	// - 만약 오류가 발생할 경우... 해당 내역은 Error String 에 저장 처리...

	std::string strValue;
	//std::vector< std::string > vecValue;


	// DEFAULT_LOG_DIR
	if( GetConfigValue( conf, strProgramName, "DEFAULT_LOG_DIR", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strLogPath = strValue;
		strValue.clear();
	}

	// LOG_LEVEL
	if( GetConfigValue( conf, strProgramName, "LOG_LEVEL", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_nLogLevel = atoi(strValue.c_str());
		if( m_nLogLevel < 0 || m_nLogLevel > MAX_LOG_LEVEL )
		{
			strErrorMessage = "Config [LOG_LEVEL] value[" + strValue + "] is not valid";
			return false;
		}		
		strValue.clear();
	}
	
	// RCID
	if( GetConfigValue( conf, strProgramName, "RC_ID", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strRcId = strValue;
		strValue.clear();
	}

	// RCDB IP
	if( GetConfigValue( conf, strProgramName, "RCDB_IP", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strRcdbIp = strValue;
		strValue.clear();
	}

	// RCDB PORT
	if( GetConfigValue( conf, strProgramName, "RCDB_PORT", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_nRcdbPort = atoi( strValue.c_str() );

		if( m_nRcdbPort <= 0 || m_nRcdbPort > 65535 )
		{
			strErrorMessage = "Config [RCDB_PORT] value[" + strValue + "] is not valid";
			return false;
		}

		strValue.clear();
	}

	// RCDB DB NAME
	if( GetConfigValue( conf, strProgramName, "RCDB_DB_NAME", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strRcdbName = strValue;
		strValue.clear();
	}

	// RCDB ACCOUNT
	if( GetConfigValue( conf, strProgramName, "RCDB_ACCT", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strRcdbAcct = strValue;
		strValue.clear();
	}

	// RCDB ACCOUNT PASSWORD
	if( GetConfigValue( conf, strProgramName, "RCDB_ACCT_PW", strValue, strErrorMessage ) == false )
		return false;
	else
	{
		m_strRcdbAcctPw = strValue;
		strValue.clear();
	}


	// MAX_QUEUE_COUNT
	// - 설정이 없는 경우 default 값 사용
	if( GetConfigValue(conf, strProgramName, "MAX_QUEUE_COUNT", strValue, strErrorMessage) == true)
	{
		m_nMaxQueueSize = atoi(strValue.c_str());

		// Queue 역시 1~ 65535 까지만 설정할 수 있도록 한다. 
		if( m_nMaxQueueSize <= 0 || m_nMaxQueueSize > 65535 )
		{
			strErrorMessage = "Config [MAX_QUEUE_COUNT] value[" + strValue + "] is not valid";
			return false;
		}

		strValue.clear();
	}

	// FHS_TRANSFER_DAEMON_PORT
	// - 설정이 없는 경우 default 값 사용
	if( GetConfigValue(conf, strProgramName, "FHS_TRANSFER_DAEMON_PORT", strValue, strErrorMessage) == true )
	{
		m_nFhsTransferDaemonPort = atoi(strValue.c_str());

		if( m_nFhsTransferDaemonPort <= 0 || m_nFhsTransferDaemonPort > 65535 )
		{
			strErrorMessage = "Config [FHS_TRANSFER_DAEMON_PORT] value[" + strValue + "] is not valid";
			return false;
		}

		strValue.clear();
	}

	// FHS_RESELECTION_TIME_LIMIT
	// - 설정이 없는 경우 default 값 사용
	if( GetConfigValue( conf, strProgramName, "FHS_RESELECTION_TIME_LIMIT", strValue, strErrorMessage ) == true )
	{
		m_nFhsReselectionTimeLimit = atoi( strValue.c_str() );

		if( m_nFhsReselectionTimeLimit < 5 || m_nFhsReselectionTimeLimit > 1440 )
		{
			strErrorMessage = "Config [FHS_RESELECTION_TIME_LIMIT] value[" + strValue + "] is not valid";
			return false;
		}

		strValue.clear();
	}

	// FILE_HASH_CHECK_LEVEL
	// - 설정이 없는 경우 default 값 사용
	if( GetConfigValue(conf, strProgramName, "FILE_HASH_CHECK_LEVEL", strValue, strErrorMessage) == true )
	{
		m_nHashCheckLevel = atoi(strValue.c_str());

		if( m_nHashCheckLevel < 0 || m_nHashCheckLevel > 2 )
		{
			strErrorMessage = "Config [FILE_HASH_CHECK_LEVEL] value[" + strValue + "] is not valid";
			return false;
		}

		strValue.clear();
	}

	// TARGET_FHS_MAX_DISK_BUSY_LIMIT
	// - 설정이 없는 경우 default 값 사용
	// - conf 설정은 정수, 저장 변수는 double type 이므로.. 설정값 존재지 형 변환 처리 필요.
	if( GetConfigValue( conf, strProgramName, "TARGET_FHS_MAX_DISK_BUSY_LIMIT", strValue, strErrorMessage ) == true )
	{
		int value = atoi( strValue.c_str() );

		// 80% 이상값 설정시 오류로 표시 처리
		if( value < 0 || value > 80 )
		{
			strErrorMessage = "Config [TARGET_FHS_MAX_DISK_BUSY_LIMIT] value[" + strValue + "] is not valid";
			return false;
		}

		// double type 으로 변환 처리하여 저장.
		m_dTargetFhsMaxDiskBusyLimit = (double)( 1.0 * value );

		strValue.clear();
	}


	// conf 정보 로딩이 모두 정상적으로 완료된 경우... 마지막으로 전달받은 config 명, section 정보를 저장한다.
	m_strConfigFileName = strConfFileName;
	m_strProgramName    = strProgramName;

	return true;
}


bool CProcessConfig::GetConfigValue( Config & conf, const std::string & strProgramName, const std::string strKey, std::string & strValue, std::string & strErrorMessage )
{
	// conf get value
	if( conf.GetConfig( strProgramName, strKey, strValue) == false )
	{
		if( conf.GetConfig( "COMMON", strKey, strValue) == false )
		{
			strErrorMessage = "Config [" + strKey + "] value is not exist";

			return false;
		}
	}

	// value check.
	if( strValue.empty() == true )
	{
		strErrorMessage = "Config [" + strKey + "] value is not valid";
		return false;
	}


	return true;
}

void CProcessConfig::SetContentSelectMode( ContentSelectMode mode )
{
	// 기존 mode 와 비교하여 서로 다른 mode 이면...
	if( m_nContentSelectMode != mode )
	{
		m_nContentSelectMode = mode;

		// mode 변경 시간 정보 update.
		clock_gettime( CLOCK_MONOTONIC, &m_timeContentSelectModeChange );
	}

	return;
}

bool CProcessConfig::SetContentSelectModeToDefault()
{
	// mode 변경 시간이 ... 지정된 시간을 초과한 경우.. default mode 로 변경 처리한다.
	if( m_nContentSelectMode != SELECT_MODE_SIZE )
	{
		struct timespec timeNow;
		clock_gettime( CLOCK_MONOTONIC, &timeNow );

		long timeElapsed = (long)( timeNow.tv_sec ) - (long)( m_timeContentSelectModeChange.tv_sec );
		if( timeElapsed > CONTENT_SELECT_MODE_TO_DEFAULT_TIMEOUT )
		{
			m_nContentSelectMode = SELECT_MODE_SIZE;

			_LOG( LINF, "content select mode change to default: Big Size Order" );

			return true;
		}
	}

	return false;

}
