#include "Main.h"

#include "ProcessRename.h"
#include "ServiceConfig.h"
#include "Logger.h"
#include "Worker.h"

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>


// Accept 대기 Count 수
#define DEFAULT_ACCEPT_WAIT_COUNT	50

// 전역 객체 정보
// Hot Content 생성 관련 fimngd로 부터 정보 수신을 위한 TCP Listen Socket
int g_listenHotContentSocket = -1;


// 사용방법 표시
void PrintUsage()
{
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: " PROG_NAME " [-h] [-v] [-c {file}]            \n");
	fprintf(stderr, "Options:                                              \n");
	fprintf(stderr, "  -h                       Display help information   \n");
	fprintf(stderr, "  -v                       Display version            \n");
	fprintf(stderr, "  -c {file}                Use {file} as config file  \n");

	fprintf(stderr, "\n");
	fprintf(stderr, PROG_NAME " is Solbox Cloud Storage module that control content replication, hot content cache flow.\n\n");

	return;
}

// 버전 정보 표시
void PrintVersion()
{
	fprintf(stderr, "\n");
	fprintf(stderr, PROG_NAME " version: " PROG_VERSION "\n\n");

	return;
}


int main( int argc, char * argv[] )
{
	// 설정 파일을 저장할 변수.
	std::string strConfigFileName = DEFAULT_CONFIG_FILE;
	
	// linux 에서 프로세스를 Titile 변경을 지원을 위해서 argv 메모리에 저장
	argv = save_ps_display_args(argc, argv);
	
	// 전달 받은 옵션 여부 확인 및 처리
	if( argc >= 2)
	{
		int opt;
		while((opt = getopt(argc, argv, "hvc:")) != -1)	// 옵션 끝까지 파싱처리
		{
			switch(opt)
			{
			case 'h':
			case '?':	// 정의되지 않은 문자가 나타날 경우 getopt 에 자동반환. 도움말 표시 처리. 
				PrintUsage();
				return EXIT_SUCCESS;

			case 'v':
				PrintVersion();
				return EXIT_SUCCESS;

			case 'c':
				strConfigFileName = optarg;
				fprintf(stderr, PROG_NAME " start with user define conf[%s]\n", strConfigFileName.c_str());
				break;

			default:	// 본 조건절은 getopt 특성상 동작하지 않지만...업무 Flow 이해(?)를 위해 유지한다.
				fprintf(stderr, "Unkonwn options[%c] used. program terminated.\n", opt);
				return EXIT_FAILURE;
			}
		}
	}
		
	// 프로그램 중복 실행 체크
	if( IsCurrentProcessRun() == true )
	{
		fprintf(stderr, "[warning] Process[" PROG_NAME "] is already running...\n");		
		return EXIT_FAILURE;
	}

	// conf 로딩 및 검사	
	std::string szErrorMessage;
	if( CServiceConfig::Init( PROG_NAME, strConfigFileName, szErrorMessage ) == false)
	{
		fprintf(stderr, "[ERR] %s\n\n", szErrorMessage.c_str());
		return EXIT_FAILURE;
	}

	// Log 객체 생성 및 초기화
	if( CLogger::Init( PROG_NAME, CServiceConfig::GetInstance()->GetLogPath(), LINF ) == false)
	{
		fprintf(stderr, "[ERR] Log module initialize failed.[%s]\n\n", CServiceConfig::GetInstance()->GetLogPath());
		return EXIT_FAILURE;
	}

	// Daemonize...
	// - nochdir: true(작업 디렉토리 변경 안함)
	// - noclose: false (표준 입출력, 에러를 /dev/null 로 리디렉트 처리)
	if( daemon(1, 0) == -1) 
	{
		fprintf(stderr, "[ERR] Process daemonize failed.[%s]\n\n", strerror(errno) );

		ReadyToExit();
		return EXIT_FAILURE;
	}

	// Signal 처리기 설정
	SetSignalMain();

		
	// Process 기동 관련 정보 로깅 
	std::vector<std::string> vecTemp;
	std::vector< std::string >::const_iterator it;	
	_LOG( LINF, "***********************************************************");
	_LOG( LINF, " %s Start. Version: %s", PROG_NAME, PROG_VERSION);
	_LOG( LINF, "***********************************************************");
	_LOG( LINF, "Config                  : %s", strConfigFileName.c_str());
	_LOG( LINF, "Log                     : %s/%s", CServiceConfig::GetInstance()->GetLogPath(), PROG_NAME);
	_LOG( LINF, "Log level               : %d", CServiceConfig::GetInstance()->GetLogLevel());
	_LOG( LINF, "RC ID                   : %s", CServiceConfig::GetInstance()->GetRcId());
	_LOG( LINF, "RCDB                    : %s %u %s %s", CServiceConfig::GetInstance()->GetRcdbIp()
                                                      , CServiceConfig::GetInstance()->GetRcdbPort()
													  , CServiceConfig::GetInstance()->GetRcdbName()
													  , CServiceConfig::GetInstance()->GetRcdbAcct());
	_LOG( LINF, "FHS deamon tcp port     : %u", CServiceConfig::GetInstance()->GetFhsTransferDaemonPort());
	_LOG( LINF, "Hash check level        : %u", CServiceConfig::GetInstance()->GetHashCheckLevel());
	_LOG( LINF, "Max queue size          : %u", CServiceConfig::GetInstance()->GetMaxQueueSize() );
	_LOG( LINF, "Replication");
	_LOG( LINF, "  max thread            : %u", CServiceConfig::GetInstance()->GetMaxReplicationThread() );
	_LOG( LINF, "  max retry             : %u", CServiceConfig::GetInstance()->GetMaxRetryCount());
	_LOG( LINF, "  select delay (sec)    : %u", CServiceConfig::GetInstance()->GetTargetSelectInterval());
	_LOG( LINF, "  select on start (hour): %u", CServiceConfig::GetInstance()->GetTargetSelectIntervalOnStarting());
	_LOG( LINF, "  skip keyword" );
	vecTemp = CServiceConfig::GetInstance()->GetSkipWord();
	for( it = vecTemp.begin(); it != vecTemp.end(); it++ )
	{
		_LOG( LINF, "            : %s", it->c_str());
	}
	_LOG( LINF, "Hot Content");
	_LOG( LINF, "  tcp listen  port      : %u", CServiceConfig::GetInstance()->GetFhsHotContentListenPort() );
	_LOG( LINF, "  minimum fd count      : %u", CServiceConfig::GetInstance()->GetHotContentCreateMinimumFd() );
	_LOG( LINF, "  max thread            : %u", CServiceConfig::GetInstance()->GetMaxCacheThread());
	_LOG( LINF, "  expire time(sec)      : %u", CServiceConfig::GetInstance()->GetCacheFileTimeout());	

	_LOG( LINF, "***********************************************************");

	// Log Level 재설정. -> conf 설정대로 변경 처리.
#ifdef _DEBUG_
	CLogger::GetInstance()->SetLogLevel(LDBG);
#else
	CLogger::GetInstance()->SetLogLevel(CServiceConfig::GetInstance()->GetLogLevel());
#endif
	
	// NEW 2014-07-02 huibong
	// fimngd 와의 통신을 UDP -> TCP 변경함에 따라
	// Main Process 에서 이와 관련된 Socket 을 생성토록 기능 추가
	if( MakeHotContentListenSocket( g_listenHotContentSocket ) == false )
	{
		LOG( LERR, "Hot content listen socket create failed. => process exit." );
		return EXIT_FAILURE;
	}
	
	// Worker Process 생성
	if(	MakeProcessWorker( g_listenHotContentSocket ) == false )
	{
		LOG( LERR, "Worker process make failed. => process exit.");

		ReadyToExit();
		return EXIT_FAILURE;
	}
	
	// Process Rename
	set_ps_display(PROG_NAME": Main [monitor worker process]", false);
	
	// Main Process : 그냥 대기
	while( 1 )
	{
		// Main Process 는 할 일이 없다. 
		// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
		// => 따라서 그냥 시그널 대기
		pause();
	}

	// 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.	
	ReadyToExit();
	return EXIT_SUCCESS;
}


// Hot Content 정보 수신을 위한 TCP Listen socket 을 생성한다.
bool MakeHotContentListenSocket( int & listenSocket )
{
	listenSocket = socket( AF_INET, SOCK_STREAM, 0 );
	if( listenSocket == -1 )
	{
		int errorNum = errno;
		LOG( LERR, "Hot content listen socket create failed.[%d][%s]", errorNum, strerror( errorNum ) );		
		return false;
	}

	struct sockaddr_in listenSockAddr;
	socklen_t listenSockLen = 0;
	memset( &listenSockAddr, 0x00, sizeof( listenSockAddr ) );

	listenSockAddr.sin_family      = AF_INET;
	listenSockAddr.sin_port        = htons( CServiceConfig::GetInstance()->GetFhsHotContentListenPort() );
	listenSockAddr.sin_addr.s_addr = htonl( INADDR_ANY );

	listenSockLen = sizeof( listenSockAddr );

	int result = 0;

	// Socket Port Reuse Option Set
	int opt = 1;
	result = setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof( opt ) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Hot content listen socket option[SO_REUSEADDR] set failed. [%d][%s]", errorNum, strerror( errorNum ) );		
		return false;
	}

	// Keep Alive Option set
	opt = 1;
	result = setsockopt( listenSocket, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Hot content listen socket option[SO_KEEPALIVE] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
		return false;
	}
		
	// Socket Bind
	result = bind( listenSocket, ( struct sockaddr * )&listenSockAddr, listenSockLen );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Hot content listen socket bind failed.[%d][%s]", errorNum, strerror( errorNum ) );		
		return false;
	}

	// Socket Listen
	result = listen( listenSocket, DEFAULT_ACCEPT_WAIT_COUNT );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Hot content listen socket listen failed.[%d][%s]", errorNum, strerror( errorNum ) );		
		return false;
	}

	return true;
}


/// @brief Worker Process 생성(fork) 처리 함수
/// @return 생성 성공시 true, 실패시에는 false 를 반환.
bool MakeProcessWorker( int listenHotContentSocket )
{
	// Worker 프로세스 fork
	pid_t processId = fork();
	if( processId < 0 ) // fork fail
	{
		int errorNum = errno;
		LOG( LERR, "Worker process create failed. [%d][%s]", errorNum, strerror(errorNum) );
		return false;
	}
	else if( processId == 0 ) // Child Process => Worker Process
	{
		// Worker Process Main 함수 호출 및 종료처리.
		int iResult;
		iResult = WorkerMain( listenHotContentSocket );

		ReadyToExit();

		// WorkerMain() 함수 return 값으로 종료처리되도록 한다.
		exit( iResult );
	}
	else
	{
		// Parent Process => Logging
		_LOG(LINF, "Worker[%d] create success." , processId);
		
		// 잠시 대기

		// CHG 2015-09-14 huibong
		// - #24369 usleep -> nanosleep 으로 변경 처리.
		struct timespec sleep;
		sleep.tv_sec = 0;
		sleep.tv_nsec = 200000000;  // 0.2 sec
		nanosleep( &sleep, NULL );

	}
	
	return true;
}

/// @brief std 상에 trim 함수가 없어서 직접 구현 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
/// @return void
void Trim( std::string & str)
{
	if( str.length() == 0 )
		return;

	// 문자열 뒤의 공백, TAB, CR 등의 문자 제거처리.
	std::string::size_type pos = str.find_last_not_of(" \a\b\f\n\r\t\v");
	if( pos != std::string::npos )
		str.erase(pos + 1);

	// 문자열 앞의 공백, TAB, CR 등의 문자 제거처리.
	pos = str.find_first_not_of(" \a\b\f\n\r\t\v");
	if( pos != std::string::npos )
		str.erase(0, pos);
}

/// @brief 현재 Process가 기동중인지 여부를 판단하기 위한 함수( 프로세스 중복 실행 체크)
/// @return 이미 해당 프로세스가 기동 중인 경우 true 반환, 그렇지 않으면 false 반환.
bool IsCurrentProcessRun()
{
	char tempBuffer[512];
	FILE * fd = NULL;
	bool bRun = false;

	snprintf( tempBuffer, sizeof(tempBuffer), "pgrep -x %s | sort", PROG_NAME );

	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		// 오류 발생시 true 반환하여 프로세스 실행 방지처리
		fprintf(stderr, "[ERR] Process duplication check failed.[popen error][%s]\n", strerror(errno));
		return true;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof(tempBuffer) );
		while( fgets( tempBuffer, sizeof(tempBuffer)-1, fd) != NULL )
		{
			string tempPid( tempBuffer );
			Trim(tempPid);

			if( atoi( tempPid.c_str() ) != getpid() )
			{
				fprintf(stderr, "[info] Process duplication found. pid[%d]\n", atoi(tempPid.c_str()));
				bRun = true;
				break;
			}
		}
		
		pclose( fd );
		return bRun;
	}
}


// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExit()
{
	if( g_listenHotContentSocket != -1 )
	{
		close( g_listenHotContentSocket );
		g_listenHotContentSocket = -1;
	}

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

	return;
}

/// @brief Main Process 가 종료 Signal 을 전달받은 경우 이를 처리하기 위한 함수.
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SignalMainTerminate(int nSignalNumber)
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LINF, "Main[%d] process exit by user signal[SIGTERM]. Good Bye..", getpid() );
	}
	else
	{
		_LOG( LWAR, "Main[%d] process exit by abnormal signal[%d]. Good Bye..", getpid(), nSignalNumber );
	}
	
	ReadyToExit();

	// CHG 2015-09-14 huibong
	// - #24369 usleep -> nanosleep 으로 변경 처리.
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
		
	exit( EXIT_SUCCESS );
}

/// @brief Main Process 에서 자식 프로세스인 Worker Process 가 종료된 경우 시그널 처리를 위한 함수
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SignalWorkerDead(int nSignalNumber)
{
	pid_t deadPid;
	int nDeadStatus;
	
	while( (deadPid = waitpid(-1, &nDeadStatus, WNOHANG)) > 0 )
	{
		// Worker 프로세스가 Signal 에 의해 종료되었는가?
		if( WIFSIGNALED( nDeadStatus ) )
		{
			_LOG( LWAR, "Worker[%d] killed by signal[%d].", deadPid, WTERMSIG(nDeadStatus));
		}
		else
		{
			_LOG( LWAR, "Worker[%d] killed. Not signal.", deadPid);
			
			// Worker 프로세스가 EXIT_FAILURE 반환 ( 초기화 실패시 )
			// 해당 내역을 화면 및 로그 상에 출력하고 
			// Worker 프로세스를 재생성 처리하지 않는다.
			if( WIFEXITED( nDeadStatus ) )
			{
				if( WEXITSTATUS( nDeadStatus ) == EXIT_FAILURE )
				{
					_LOG( LERR, "Worker[%d] initilaize failed.", deadPid);
					_LOG( LERR, "Main[%d] abnormally exit by worker initialize failed. Good Bye..", getpid());
					
					ReadyToExit();

					// CHG 2015-09-14 huibong
					// - #24369 usleep -> nanosleep 으로 변경 처리.
					struct timespec sleep;
					sleep.tv_sec = 0;
					sleep.tv_nsec = 500000000;  // 0.5 sec
					nanosleep( &sleep, NULL );
										
					exit( EXIT_SUCCESS );
				}
			}			
		}

		// Worker Process 재생성 처리.
		if( MakeProcessWorker( g_listenHotContentSocket ) == false )
		{
			// Worker Process 재성성 실패시
			// 로깅 처리 후 Main 프로세스 종료 시그널 발생			
			_LOG( LERR, "Worker process recreate failed. => Main process exit.");
			raise( SIGTERM );
		}
		else
		{
			_LOG( LWAR, "Worker process recreate success.");
		}
	}

	// 오류 발생시 해당 내역 로깅
	if( deadPid < 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Main process error: SIG_CHLD receive but waitpid return error[%d][%s]", errorNum, strerror(errorNum));
	}

	return;
}

/// @brief Main 프로세스 signal 처리 설정을 위한 함수	
/// @return void	
void SetSignalMain()
{
	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 신호 처리 => ? */
	
	// Child Process 인 Worker 프로세스 종료에 대한 처리기 설정.
	act.sa_handler = SignalWorkerDead;
	sigaction( SIGCHLD, &act, NULL);
	
	// 사용자 종료 또는 에러 관련 신호 처리.
	act.sa_handler = SignalMainTerminate;
	sigaction( SIGTERM, &act, NULL);	/* kill -TERM 에 의한 프로세스 종료시 */

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