#include "WorkerSend.h"

#include "ProcessConfig.h"
#include "Logger.h"
#include "ShmDefine.h"
#include "FhsShm.h"
#include "RctsSocket.h"

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <time.h>



// 수집된 시스템 정보 및 서비스 상태 정보의 RCTS 전송 주기.
#define SEND_INTERVAL_SYSTEM_INFO       10
#define SEND_INTERVAL_SERVICE_STATUS     4


// 공유메모리에 Data Set, Get 을 담당할 객체에 대한 포인터.
// Main 프로세스에서 생성 처리하며... 전역변수로 설정되어 있다.
extern CFhsShm * g_pFhsShm;


// System 수집 정보의 RCTS 로 전송을 처리할 Thread 함수 선언. ( .h 에 선언시.. 다른 process 모듈에 포함되므로.. 이를 방지하기 위함)
void * WorkerSendSystemThread( void * arg );
// service 상태 정보의 RCTS 로 전송을 처리할 Thread 함수 선언. ( .h 에 선언시.. 다른 process 모듈에 포함되므로.. 이를 방지하기 위함)
void * WorkerSendServiceThread( void * arg );

// 생성된 각 Thread 의 초기화 실패 여부 flag
// - 초기화 실패시... 프로세스를 종료처리 하기 위함.
bool g_bThreadInitError = false;

// Worker::Send 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerSendMain()
{
	setproctitle( "Send [initialize]" );

	// Set Signal Handler
	SetWorkerSendSignalHandler();




	// Main Process 에서 생성한 공유메모리 접근 객체 유효성 확인
	// NULL 인 경우... 초기화 실패로 처리한다.
	if( g_pFhsShm == NULL )
	{
		// 객체 생성 실패시
		LOG( LERR, "[Send:%d] Shared memory object not valid. process exit", getpid() );

		ReadyToExitWorkerSend();
		return EXIT_FAILURE;
	}


	// 전체 프로세스의 생성이 완료될때까지 대기 후 작업 시작
	// - 시스템 정보 수집 및 서비스 체크가 1회 완료된 후 동작하도록 3 sec 간 대기 처리.
	sleep( 3 );
	setproctitle( "Send" );

	// System 수집 정보의 RCTS 로 전송을 처리할 Thread 생성.
	pthread_t threadWorkerSendSystem;
	if( pthread_create( &threadWorkerSendSystem, NULL, WorkerSendSystemThread, NULL ) != 0 )
	{
		int errorNum = errno;

		LOG( LERR, "[Send:%d] system info send thread create fail[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );

		ReadyToExitWorkerSend();
		return EXIT_FAILURE;
	}

	// service 상태 정보의 RCTS 로 전송을 처리할 Thread 생성.
	pthread_t threadWorkerSendService;
	if( pthread_create( &threadWorkerSendService, NULL, WorkerSendServiceThread, NULL ) != 0 )
	{
		int errorNum = errno;

		LOG( LERR, "[Send:%d] service status send thread create fail[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );

		ReadyToExitWorkerSend();
		return EXIT_FAILURE;
	}

	// Thread 생성 및 초기화시 오류 발생 여부 확인.
	// - Thread 생성 및 초기화가 완료될때까지 잠시 대기 후...
	// - Thread 오류 발생시 프로세스 종료 처리.

	for( int i = 0; i < 5; i++ )
	{
		sleep( 1 );
		if( g_bThreadInitError == true )
		{
			LOG( LERR, "[Send:%d] send thread init fail. process exit", getpid());

			ReadyToExitWorkerSend();
			return EXIT_FAILURE;
		}
	}

	
	// 대기
	while( 1 )
	{
		// 할일 없어서.. 시그널 수신까지 대기 한다.
		pause();
	}
	

	// 종료시 정리 작업 수행
	ReadyToExitWorkerSend();
	return EXIT_SUCCESS;
}

// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerSend()
{

	// Logger 객체 종료 처리.
	CLogger::Exit();

	return;
}

// Worker Send 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerSendTerminate( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LINF, "[Send:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
	}
	else
	{
		_LOG( LWAR, "[Send:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
	}

	ReadyToExitWorkerSend();

	// 종료전 잠시 대기
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
	
	exit( EXIT_SUCCESS );
}

// Worker::Send Process 의 signal 처리기...
void SetWorkerSendSignalHandler()
{
	sigset_t set;
	struct sigaction act;

	memset( &act, 0x00, sizeof( act ) );
	sigfillset( &set );
	sigprocmask( SIG_SETMASK, &set, NULL );
	sigfillset( &act.sa_mask );

	// 무시 처리 signal
	act.sa_handler = SIG_IGN;
	sigaction( SIGPIPE, &act, NULL );	/* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP,  &act, NULL );	/* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
	sigaction( SIGINT,  &act, NULL );	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL );	/* 키보드에 의한  Abort 신호 처리 => ? */

	// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
	act.sa_handler = SIG_IGN;
	sigaction( SIGCHLD, &act, NULL );

	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SignalWorkerSendTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

	// 나머지 신호는 default 처리.

	sigemptyset( &set );                  /* 신호 처리기 처리 설정 위한 블록 해제 */
	sigprocmask( SIG_SETMASK, &set, NULL );
}


// System 수집 정보의 RCTS 로 전송을 처리할 Thread
void * WorkerSendSystemThread( void * arg )
{
	// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[Send:%d] system info send thread start.", getpid() );


	// RCTS 와 통신을 담당할 객체 생성 및 초기화.
	CRctsSocket rcts;
	if( rcts.Init( CProcessConfig::GetInstance()->GetRctsServer(),
		           CProcessConfig::GetInstance()->GetRctsSystemInfoPort(),
		           CProcessConfig::GetInstance()->GetRcId() ) == false )
	{
		// RCTS 소켓 초기화 실패시..
		// - 로깅 후.. 
		// - 전역 flag 에 오류 설정.
		// - thread 종료 처리.
		LOG( LERR, "[Send:%d] system info send thread error. rcts socket init fail", getpid());
		g_bThreadInitError = true;

		// Thread 종료 관련 처리.		
		return NULL;
	}
	
	// 시스템 수집 정보를 저장할 변수.
	struct SystemInfo stSystem;

	// 마지막 시스템 정보 수집 시간 정보를 저장할 변수
	time_t timePrevSystemCheck = 0;
	time_t timeCurrentSystemCheck = 0;


	// 초기 기동시... Service 상태 check 정보 전송 후... System 정보가 전송되도록 
	// 약 2 sec 대기 후 전송 시작
	sleep( 2 );
		
	// 공유메모리에 저장된 System 수집 정보를 RCTS 에 주기적으로 전송.
	while( 1 )
	{
		// 2018-02-02 CHG huibong  
		// - system 정보 수집시 hang 등이 발생한 경우 RCTS 로 과거 정보 전송하지 않는다. (#31701)
		// -- 즉 공유메모리 상의 last check 시간 정보를 기록했다가... 해당 정보가 바뀐 경우에만 RCTS 로 전송 처리
		timeCurrentSystemCheck = g_pFhsShm->GetTimeSystemCheck();

		if( timeCurrentSystemCheck == 0 )
		{
			// 아직 초기화 중인 상태
			// - 전송하지 말고.. 1 sec 정도 대기 후 다시 시도
			sleep( 1 );
			continue;
		}

		// 현재 시간 정보와 이전 시간 정보가 동일한 경우.. 전송하지 말고 
		if( timeCurrentSystemCheck == timePrevSystemCheck )
		{
			_LOG( LERR, "[RCTS send system] system collect time not changed. prev[%ld] current[%ld]. RCTS not send."
				  , timePrevSystemCheck, timeCurrentSystemCheck );

			// 2 sec 대기 후 재시도
			sleep( 2 );
			continue;
		}
		else
		{
			timePrevSystemCheck = timeCurrentSystemCheck;
		}

		// 임시변수 초기화.
		memset( &stSystem, 0x00, sizeof( struct SystemInfo ) );

		// 공유메모리에 저장된 시스템 수집 정보 복사.
		memcpy( &stSystem, g_pFhsShm->GetSystemInfo(), sizeof( struct SystemInfo ) );
		
		//_LOG( LINF, "[Send:%d] system info send:  mem_usage[%d] storage_avail[%ju] tcp80_est[%d]"
		//	  , getpid(), stSystem.memory_usage, stSystem.storage_avail, stSystem.tcp_80_establish );
		
		// System 수집 정보를 RCTS 로 전송.
		// - 실패 관련 로깅은 전송 모듈에서 처리.
		// - 실패했다고 해서 딱히 할일 없으므로.. 반환값 처리 안함.
		rcts.SendSystemInfo( &stSystem);

		// 지정된 주기마다 전송 처리.
		sleep( SEND_INTERVAL_SYSTEM_INFO );
	}


	// Thread 종료 관련 처리.
	rcts.Close();
	return NULL;
}


// service 상태 정보의 RCTS 로 전송을 처리할 Thread
void * WorkerSendServiceThread( void * arg )
{
	// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[Send:%d] service status send thread start.", getpid() );


	// RCTS 와 통신을 담당할 객체 생성 및 초기화.
	CRctsSocket rcts;
	if( rcts.Init( CProcessConfig::GetInstance()->GetRctsServer(),
		CProcessConfig::GetInstance()->GetRctsServiceInfoPort(),
		CProcessConfig::GetInstance()->GetRcId() ) == false )
	{
		// RCTS 소켓 초기화 실패시..
		// - 로깅 후.. 
		// - 전역 flag 에 오류 설정.
		// - thread 종료 처리.
		LOG( LERR, "[Send:%d] service status send thread error. rcts socket init fail", getpid() );
		g_bThreadInitError = true;

		// Thread 종료 관련 처리.		
		return NULL;
	}
	
	// Hostname 정보를 저장할 변수.
	std::string strHostname;

	// 서비스 상태 정보를 저장할 변수.
	struct ServiceStatus stService;

	// Disk 최대 사용률 정보를 저장할 변수.
	int iStorageMaxUsage;

	// 공유메모리에 저장된 Service Check start time 정보를 저장할 변수 
	time_t timeServiceCheck = 0;
	struct timespec currentClock;
		
	// 공유메모리에 저장된 Service 상태 정보를 RCTS 에 주기적으로 전송.
	while( 1 )
	{
		// 2018-02-05 NEW huibong service check 관련 last check 시작 시간 timeout 기능 추가 (#31701)
		// - service check 시작시 공유메모리에 시간 정보 기록
		// - service check Hang 발생으로 인한 timeout 발생시 서비스 상태를 Disable 상태로 변경하여 전송
		timeServiceCheck = g_pFhsShm->GetTimeServiceCheck();

		if( timeServiceCheck == 0 )
		{
			// 아직 초기화 중인 상태
			// - 전송하지 말고.. 1 sec 정도 대기 후 다시 시도
			sleep( 1 );
			continue;
		}
		else
		{
			// 그 외의 경우는 현재 시간을 추출하여 timeout 발생 여부 check 한다.
			memset( &currentClock, 0x00, sizeof( struct timespec ) );
			clock_gettime( CLOCK_MONOTONIC, &currentClock );

			if( timeServiceCheck < currentClock.tv_sec - TIMEOUT_SERVICE_CHECK )
			{
				// timeout 발생시 서비스 Disable 상태로 변경 처리
				g_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

				_LOG( LERR, "[RCTS send service] service check timeout[%d] last[%ld], current[%ld]. service stat set: %d (%s)"
					  , TIMEOUT_SERVICE_CHECK, timeServiceCheck, currentClock.tv_sec
					  , g_pFhsShm->GetServiceStat(), g_pFhsShm->GetServiceStatString() );
			}
		}


		// 임시변수 초기화.
		memset( &stService, 0x00, sizeof( struct ServiceStatus ) );
		iStorageMaxUsage = 0;
		strHostname.clear();
		
		// hostname 정보 저장.
		strHostname = g_pFhsShm->GetHostName();
		
		// 공유메모리에 저장된 서비스 상태 관련 정보 복사.
		memcpy( &stService, g_pFhsShm->GetServiceStatus(), sizeof( struct ServiceStatus ) );

		// 공유메모리에 저장된 Storage 최대 사용률 정보 복사.
		iStorageMaxUsage = g_pFhsShm->GetStorageMaxUsage();
		
		//_LOG( LINF, "[Send:%d] service status send: hostname[%s] service[%d] max_usage[%d]"
		//	  , getpid(), strHostname.c_str(), stService.stat, iStorageMaxUsage );

		// Service 상태 정보를 RCTS 로 전송.
		// - 실패 관련 로깅은 전송 모듈에서 처리.
		// - 실패했다고 해서 딱히 할일 없으므로.. 반환값 처리 안함.
		rcts.SendServiceStatus( strHostname, &stService, iStorageMaxUsage );

		// 지정된 주기마다 전송 처리.
		sleep( SEND_INTERVAL_SERVICE_STATUS );
	}
	
	// Thread 종료 관련 처리.
	rcts.Close();
	return NULL;
}
