#include "ShmClientInfo.h"

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


#include <string.h>

// 2018-02-22 CHG huibong FHS Disk busy 상황시 report 로깅을 위한 기준값 
#define REPORT_MAX_DISK_QUEUE	10		// disk queue 값이 해당 정의 값 이상인 경우
#define REPORT_MAX_DISK_BUSY	90.0	// disk busy 값이 해당 정의 값 이상인 경우 


// 생성자
CShmClientInfo::CShmClientInfo( struct ClientInfo * p )
	: m_pClient( p )
{
	// 서비스 상태 code 에 대한 문자열 정보를 저장하기 위한 멤버 변수 설정.
	// - may 객체이므로.. key - value 형식으로 저장 처리
	m_mapServiceStatString[SERVICE_STAT_NOT_DEFINE] = "Not Define";
	m_mapServiceStatString[SERVICE_STAT_OK] = "OK";
	m_mapServiceStatString[SERVICE_STAT_OUT] = "Out";
	m_mapServiceStatString[SERVICE_STAT_DISABLE] = "Disable";
	m_mapServiceStatString[SERVICE_STAT_RONLY] = "Read-only";

	// NEW 2018-01-11 huibong 메모리 사용량 초과시 report 기능 추가 (#31360, #31679)
	// check 여부 판단을 위한 변수 초기화
	if( CProcessConfig::GetInstance()->GetReportMemoryMaxUsage() > 0 )
		m_bReportMemoryMaxUsage = true;
	else
		m_bReportMemoryMaxUsage = false;

	m_iShmSystemUpdateCount = 0;

}

// 소멸자
CShmClientInfo::~CShmClientInfo()
{

}

// Service 상태 정보에 대한 문자열 반환.
const char * CShmClientInfo::GetServiceStatString( const int & stat )
{
	std::map< int, std::string >::iterator pos;

	pos = m_mapServiceStatString.find( stat );
	if( pos != m_mapServiceStatString.end() )
	{
		// 해당 항목 존재시
		return pos->second.c_str();
	}
	else
	{
		// 해당 항목이 존재하지 않는 경우.
		return m_mapServiceStatString[SERVICE_STAT_NOT_DEFINE].c_str();
	}
}

// 현재 Service 상태 정보에 대한 문자열 반환.
const char * CShmClientInfo::GetServiceStatString()
{
	std::map< int, std::string >::iterator pos;

	pos = m_mapServiceStatString.find( m_pClient->service.service_stat);
	if( pos != m_mapServiceStatString.end() )
	{
		// 해당 항목 존재시
		return pos->second.c_str();
	}
	else
	{
		// 해당 항목이 존재하지 않는 경우.
		return m_mapServiceStatString[SERVICE_STAT_NOT_DEFINE].c_str();
	}
}


// 공유 메모리 상태가 유효하지 여부 판단.
bool CShmClientInfo::IsValid( const char * pClientAddress ) const
{
	if( m_pClient == NULL )
		return false;
	else
	{
		// 해당 공유메모리가 미사용 중으로 마킹된 경우...
		if( m_pClient->use == 0 )
			return false;

		// 해당 공유메모리의 client IP 가 서로 다른 경우...
		if( strcmp( pClientAddress, m_pClient->client_ip ) != 0 )
			return false;
	}

	return true;
}


// serviceinfo 정보를 공유메모리에 저장 처리.
// return false : 공유메모리가 유효하지 않은 경우...		
bool CShmClientInfo::UpdateServiceInfo( std::string & strHostname, int iServiceStat )
{
	// 공유메모리가 유효하지 않은 경우..
	if( m_pClient == NULL || m_pClient->use == 0 )
		return false;

	int iPreviousServiceStat = 0;

	// 현재 시간 정보 추출.
	time_t currentTime = time( 0 );

	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );

	// 정보 설정.
	if( m_pClient->valid == 1 )
	{
		// hostname 정보 설정.
		// - 기존 값과 비교하여 변경된 경우.. 관련 내역 로깅.
		if( strcmp( m_pClient->hostname, strHostname.c_str() ) != 0 )
		{
			LOG( LWAR, "client [%s] hostname changed [%s] -> [%s].", m_pClient->client_ip, m_pClient->hostname, strHostname.c_str() );

			// hostname 정보 복사
			strncpy( m_pClient->hostname, strHostname.c_str(), HOST_NAME_LENGTH );
		}

		// 이전 서비스 상태 정보 저장
		iPreviousServiceStat = m_pClient->service.service_stat;

		// 서비스 상태 정보 변경 여부 관련 내역 로깅
		if( iPreviousServiceStat != iServiceStat )
		{
			_LOG( LINF, "client[%s] hostname[%s] service stat changed. [%s] -> [%s]."
				  , m_pClient->client_ip, m_pClient->hostname, GetServiceStatString( iPreviousServiceStat ), GetServiceStatString( iServiceStat ) );

			// 서비스 상태 정보 업데이트
			m_pClient->service.service_stat = iServiceStat;
		}

		// update 시간 설정
		m_pClient->service.update_time           = currentTime;
		m_pClient->service.update_time_clock_sec = currentClock.tv_sec;

		// apllication 정보 설정.
		m_pClient->client_app = 0;

		// 2018-02-22 NEW huibong (#31703)
		// - 운영 편의성을 위해 다음 상황을 report 로깅 처리한다.
		// - 1. 서비스 상태가 OK , Read only 상태에서 Disable, OUT 상황으로 변경되는 경우 
		// - 2. 서비스 상태가 비정상 상황(disable, out) 상황에서 정상( ok, read only) 상황으로 변경되는 경우

		// 서비스 상태가 변경된 경우 - 대부분 상태 변경은 잘 일어나지 않으므로.. 빠른 로직 검사를 위해 불필요해도 넣어 둠
		if( iPreviousServiceStat != iServiceStat )
		{
			// 현재 상태가 비정상 상황인 경우
			if( iServiceStat == SERVICE_STAT_OUT || iServiceStat == SERVICE_STAT_DISABLE )
			{
				// 이전 상황은 정상인 경우 
				if( iPreviousServiceStat != SERVICE_STAT_OUT && iPreviousServiceStat != SERVICE_STAT_DISABLE )
				{
					// report 로깅 객체 생성
					CReportLog reportLog;
					std::string strErrorMessage;
					strErrorMessage.clear();

					if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
					{
						reportLog.GetLastError( strErrorMessage );
						LOG( LERR, "client[%s] service stat change log init failed.[%s]", strHostname.c_str(), strErrorMessage.c_str() );
					}
					else
					{
						reportLog.Write( "WAR", "[%s] [ %s, service stat changed. %s -> %s. check FHS status.]"
										 , PROG_NAME, strHostname.c_str()
										 , GetServiceStatString( iPreviousServiceStat ), GetServiceStatString( iServiceStat ) );
					}
				}
			}
			else
			{
				// 현재 상태가 OK, Read only 의 정상 상황인 경우
				// 이전 상태가 비정상 상황인 경우
				if( iPreviousServiceStat == SERVICE_STAT_OUT || iPreviousServiceStat == SERVICE_STAT_DISABLE )
				{
					// report 로깅 객체 생성
					CReportLog reportLog;
					std::string strErrorMessage;
					strErrorMessage.clear();

					if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
					{
						reportLog.GetLastError( strErrorMessage );
						LOG( LERR, "client[%s] service stat change log init failed.[%s]", strHostname.c_str(), strErrorMessage.c_str() );
					}
					else
					{
						reportLog.Write( "INF", "[%s] [ %s, service stat changed. %s -> %s. ]"
										 , PROG_NAME, strHostname.c_str()
										 , GetServiceStatString( iPreviousServiceStat ), GetServiceStatString( iServiceStat ) );
					}
				}
			}
		}

	}
	else
	{
		// hostname 정보 설정.
		strncpy( m_pClient->hostname, strHostname.c_str(), HOST_NAME_LENGTH );

		// 서비스 상태 정보 설정.
		m_pClient->service.service_stat = iServiceStat;

		// update 시간 설정
		m_pClient->service.update_time           = currentTime;
		m_pClient->service.update_time_clock_sec = currentClock.tv_sec;
		
		// apllication 정보 설정.
		m_pClient->client_app = 0;

		// Data 유효 여부 설정.
		m_pClient->valid = 1;
	}
	
	return true;
}

// systeminfo 정보를 공유메모리에 저장 처리.
// return false : 공유메모리가 유효하지 않거나... valid 가 유효하지 않거나.. hostname 이 동일하지 않은 경우. 
bool CShmClientInfo::UpdateSystemInfo( std::string & strHostname, struct SystemInfo * pData )
{
	// 공유메모리가 유효하지 않은 경우..
	if( m_pClient == NULL || m_pClient->use == 0 )
		return false;

	// 현재 시간 정보 추출.
	time_t currentTime = time( 0 );

	// valid 설정값이 유효하지 않은 경우..
	// - service info 정보가 먼저 기록되지 않은 상태인 경우....
	// - 정보를 기록하지 않는다.
	if( m_pClient->valid == 0 )
	{
		LOG( LWAR, "client [%s] hostname[%s] info not valid. [%d].", m_pClient->client_ip, m_pClient->hostname, m_pClient->valid );
		return false;
	}

	// hostname 정보가 동일한지 검사.	
	if( strcmp( m_pClient->hostname, strHostname.c_str() ) != 0 )
	{
		LOG( LWAR, "client [%s] hostname[%s] not match. [%s].", m_pClient->client_ip, m_pClient->hostname, strHostname.c_str() );
		return false;
	}

	// 정보가 동일한 경우.... copy 처리
	memcpy( &(m_pClient->system), pData, sizeof( struct SystemInfo ) );
	m_pClient->system.update_time = currentTime;

	// 2018-02-22 CHG huibong (#31703)
	// - 2018-01-12 #31544, #31679 에 의해 추가된 다음 기능 제거
	// --  FHS Disk hang 또는 이상 상황으로 disk busy 비정상 증가시 자동 FHS out 처리
	// - 사유
	// -- Disk Hang 이 아닌 상황에서도 기준값(queue 10 & busy 90%)을 초과하는 Disk Busy 상황 발생으로 잘못된 서비스 OUT 발생 가능
	// - 기타
	// -- Disk busy 상황시 Report 로깅 처리 기능은 유지
	// -- Disk busy 판단 기준값은 그대로 유지 queue 10, busy 90%
	// -- 참고로 FHS 는 queue 7, busy 50%  이상인 경우 자동으로 read only 로 변경 처리됨
	// --- FHS 와 같은 기준값을 사용하지 않은 이유는 FHS 기준값은 순간적인 응답지연을 막기 위한 최소한의 기준값이기 때문.

	// 현재 상태가 out, disable 상태가 아닌 정상 상황인 경우
	if( m_pClient->service.service_stat != SERVICE_STAT_OUT && m_pClient->service.service_stat != SERVICE_STAT_DISABLE )
	{
		// system 상태가 정보가 disk busy 상황인 경우
		if( pData->disk_max_queue >= REPORT_MAX_DISK_QUEUE && pData->disk_max_busy >= REPORT_MAX_DISK_BUSY )
		{
			// report 로깅 객체 생성
			CReportLog reportLog;
			std::string strErrorMessage;
			strErrorMessage.clear();

			if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
			{
				reportLog.GetLastError( strErrorMessage );
				LOG( LERR, "client[%s] disk busy report log init failed.[%s]", strHostname.c_str(), strErrorMessage.c_str() );
			}
			else
			{
				reportLog.Write( "WAR", "[%s] [ %s, disk busy too high. busy %4.1lf%% , queue %ju. check FHS status.]"
								 , PROG_NAME, strHostname.c_str(), pData->disk_max_busy, pData->disk_max_queue );

				_LOG( LWAR, "client[%s] disk busy[%4.1lf %%] too high. queue[%ju]. report logging."
					  , strHostname.c_str(), pData->disk_max_busy, pData->disk_max_queue );
			}
		}
	}

	// NEW 2018-01-11 huibong 메모리 사용량 초과시 report 기능 추가 (#31360, #31679)
	// - FHS memory 사용량이 지정된 값 이상인 경우 report 로깅 처리
	// - FHS system 정보가 10 sec 마다 전송되므로.. 잦은 로깅 발생 가능
	// - 따라서 분당 1회만 check 하도록 한다 (최초 요청은 기동시 초기화 이슈 있으므로.. 버리고.. 3번째 요청부터 체크)
	if( m_bReportMemoryMaxUsage == true && m_iShmSystemUpdateCount % 6 == 2)
	{
		// FHS memory 사용률이 conf 설정값 이상인 경우.
		if( pData->memory_usage >= CProcessConfig::GetInstance()->GetReportMemoryMaxUsage() )
		{
			CReportLog reportLog;
			std::string strErrorMessage;
			strErrorMessage.clear();

			if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
			{
				reportLog.GetLastError( strErrorMessage );
				LOG( LERR, "client[%s] memory usage report log init failed.[%s]", strHostname.c_str(), strErrorMessage.c_str() );
			}
			else
			{
				reportLog.Write( "WAR", "[%s] [FHS (%s) memory usage too high = %d%%. total=%ju byte. free=%ju byte. check FHS status.]"
								 , PROG_NAME, strHostname.c_str(), pData->memory_usage, pData->memory_total, pData->memory_free );

				_LOG( LWAR, "client[%s] memory usage[%d %%] too high. total[%ju] free[%ju]. report logging."
					  , strHostname.c_str(), pData->memory_usage, pData->memory_total, pData->memory_free );

			}
		}
	}

	// system 관련 shared memory update count 증가 처리
	// - 10 sec 마다 업데이트 되므로.. 일 단위 count reset
	m_iShmSystemUpdateCount++;
	if( m_iShmSystemUpdateCount > 8640 )
		m_iShmSystemUpdateCount = 0;

	return true;
}