#include "WorkerSendInfo.h"

#include "Logger.h"
#include "ProcessRename.h"
#include "ShmManager.h"
#include "ProcessConfig.h"
#include "AliveFhsCacheThread.h"
#include "SocketSendInfo.h"

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>



// 요청 수신 관련 최대 대기 시간. (sec)
#define MAX_REQUEST_TIMEOUT      60


// 공유메모리에 Data Set, Get 을 담당할 객체에 대한 포인터.
// Main.cpp 에 전역변수로 정의 되어 있다.
extern CShmManager * g_pShmManager;

// FHS 의 vmasd 가 Broadcasting 하는 각 FHS 의 상태 정보를 수신받기 위한 UDP Socket....
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockVmasdRecv;

// FHS csagentd 가 전송하는 시스템 상태 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockSystemRecv;

// FHS csagentd 가 전송하는 서비스 상태 체크 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockCheckRecv;

// fimngd 등과 같은 외부 모듈과 통신을 통해 정보를 전달하기 위한 TCP Socket 생성.
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockInfoSend;


// Alive FHS Cache Thread 객체 대한 포인터.
CAliveFhsCacheThread * g_pThreadAliveFhs = NULL;


// 각 Client 를 처리할 Thread 에 전달할 구조체 
struct stSendInfoThreadArg {

	// client socket descriptor
	int clientSocketDesc;

	// Client 의 IP 주소 정보를 저장한 변수
	// - 공유메모리 client_ip 항목 크기와 동일한 크기로 설정.
	char szClientAddress[IP_ADDR_LENGTH];
};


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

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

	return;
}

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

	ReadyToExitWorkerSendInfo();

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

// Worker::SendInfo Process 의 signal 처리기...
void SetWorkerSendInfoSignalHandler()
{
	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 = SignalWorkerSendInfoTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

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

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

// Worker::SendInfo 에서 각 Client 에 대한 처리를 수행하기 위한 Thread.
void * WorkerSendInfoClientThread( void * arg )
{
	// 전달받은 변수를 내부 변수에 저장 처리.

	// client socket 관련 처리 변수 생성	
	CSocketSendInfo clientSocket( ( ( struct stSendInfoThreadArg * ) arg )->clientSocketDesc );

	// client 주소 처리 관련 
	char szClientAddress[IP_ADDR_LENGTH];
	memcpy( szClientAddress, ( ( struct stSendInfoThreadArg * ) arg )->szClientAddress, IP_ADDR_LENGTH );
		
	// malloc 로 생성된 변수 해제 처리
	free( arg );


	// 본 thead 는 client 연결 상태에 따라 종료되므로.. process 에서 분리 처리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[SendInfo:%d] client thread start. [%s]", getpid(), szClientAddress );

	// 송수신 처리 관련 변수 
	int  nResult;
	char command;

	// hostname 수신 정보를 저장하기 위한 변수.
	std::string strHostname;

	// Alive FHS 목록정보 저장 처리를 위한 vector 
	std::vector<struct FhsServiceStatInfo> vecAliveFhs;
		
	// socket 수신 대기
	while( 1 )
	{
		// 요청 수신 대기
		nResult = clientSocket.GetPacketHeader( command, MAX_REQUEST_TIMEOUT );

		if( nResult == 0 )
		{
			// timeout 발생시...
			// - server 모드로 동작하므로... 지정된 시간동안 요청이 없는 경우 세션을 종료 처리한다.
			_LOG( LINF, "[SendInfo:%d] client[%s] request timeout[%d]. session close.", getpid(), szClientAddress, MAX_REQUEST_TIMEOUT );

			break;
		}
		else if( nResult < 0 )
		{
			// Socket 종료, 오류 또는 정의되지 않은 형식의 패킷 header 수신시...
			// 오류 내역은 socket 모듈에서 로깅하므로... 그냥 종료 처리.
			break;
		}
		else
		{
			// 정상 header 수신시.
			// - 수신된 command 에 따른 처리.
			
			if( command == COMMAND_ALIVE_FHS )
			{
				// Alive FHS 목록 요청인 경우..
								

				// Body 부분에 대한 처리.
				// - 각 protocol version 별 처리.				
				if( clientSocket.GetPacketBodyAliveFhs( strHostname ) == false )				
				{				
					// Data 수신 실패, 지원하지 않는 protocol version 또는 RC ID 정보가 다른 경우....
					// - 세션 종료 처리.
					// - 오류 내역은 위의 해당 함수에서 client 로 전송 처리함.
					LOG( LERR, "[SendInfo:%d] client[%s] alive fhs request body fail. session close.", getpid(), szClientAddress );				
					break;
				}
				
				// Alive FHS 목록 조회.
				vecAliveFhs.clear();
				if( g_pThreadAliveFhs->GetAliveFhs( vecAliveFhs ) == -1 )
				{
					// 공유메모리의 FHS 정보가 아직 불완전하여 SKIP 처리해야 하는 경우.
					if( clientSocket.SendResultAliveFhsSkip() == false )
					{
						// 전송 실패 발생시.
						LOG( LERR, "[SendInfo:%d] client[%s][%s] alive fhs skip send error. session close."
							 , getpid(), szClientAddress, strHostname.c_str() );
						break;
					}				
				}
				else
				{
					// Alive FHS 목록 전송 처리.
					if( clientSocket.SendResultAliveFhs( vecAliveFhs ) == false )
					{
						// 전송 실패 발생시.
						LOG( LERR, "[SendInfo:%d] client[%s][%s] alive fhs send error. session close."
							 , getpid(), szClientAddress, strHostname.c_str() );
						break;
					}
					else
					{
						// 전송 성공시
						_LOG( LDBG, "[SendInfo:%d] client[%s][%s] alive fhs send ok. fhs count[%zu]"
							  , getpid(), szClientAddress, strHostname.c_str(), vecAliveFhs.size() );
					}
				}
			}
			else
			{
				// 정의되지 않은 command 인 경우..
				// - 오류 로깅 후 세션 종료 처리.
				LOG( LERR, "[SendInfo:%d] client[%s] request type not defined. session close.", getpid(), szClientAddress );
				clientSocket.PrintHeaderToLog();
				break;
			}
		}
	}

	// client 와 연결 종료 처리.
	clientSocket.Close();
	_LOG( LINF, "[SendInfo:%d] client connection closed. [%s]", getpid(), szClientAddress );
		
	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::SendInfo 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerSendInfoMain()
{
	// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
	if( g_sockVmasdRecv != -1 )
	{
		close( g_sockVmasdRecv );
		g_sockVmasdRecv = -1;
	}
	if( g_sockSystemRecv != -1 )
	{
		close( g_sockSystemRecv );
		g_sockSystemRecv = -1;
	}
	if( g_sockCheckRecv != -1 )
	{
		close( g_sockCheckRecv );
		g_sockCheckRecv = -1;
	}
	

	// Process Rename..
	set_ps_display( PROG_NAME": SendInfo", false );
	
	// Set Signal Handler
	SetWorkerSendInfoSignalHandler();


	// 사용할 객체 초기화 처리

	// Alive FHS 목록을 주기적으로 추출할 Thread 모듈 초기화 및 기동 처리.
	CAliveFhsCacheThread threadAliveFhsCache( g_pShmManager );
	if( threadAliveFhsCache.Start() == false )
	{
		_LOG( LWAR, "[SendInfo:%d] alive fhs cache thread create failed. => Process Exit", getpid() );

		ReadyToExitWorkerSendInfo();
		return EXIT_FAILURE;	// EXIT_FAILURE 를 반환하여 재생성 처리 방지...
	}
	// 각 Client thread 에서 해당 객체 접근이 가능토록 전역 포인터에 저장 처리.
	g_pThreadAliveFhs = &threadAliveFhsCache;
	

	
	// 접속 Client 에 대한 처리.
	int clientSock;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof( clientSockAddr );

	pthread_t clientThreadId;
	struct stSendInfoThreadArg * pThreadArg = NULL;
		
	while( 1 )
	{
		clientSock = accept( g_sockInfoSend, ( struct sockaddr * ) &clientSockAddr, &clientSockLen );

		if( clientSock == -1 )
		{
			// 오류 발생시.... 해당 내역 로깅 처리.
			int errorNum = errno;
			LOG( LERR, "[SendInfo:%d] client accept failed [%d][%s]", getpid(), errorNum, strerror( errorNum ) );
		}
		else
		{
			// 정상적인 Client 인 경우...
			// 처리 Thread 로 전달할 변수를 malloc 로 생성 처리
			pThreadArg = ( struct stSendInfoThreadArg * ) malloc( sizeof( struct stSendInfoThreadArg ) );
			if( pThreadArg == NULL )
			{
				int errorNum = errno;
				close( clientSock );    // client 접속 종료 처리.

				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					LOG( LERR, "[SendInfo:%d] client[%s] thread malloc failed [%d][%s]", getpid(), tempBuffer, errorNum, strerror( errorNum ) );
				}
				else
				{
					LOG( LERR, "[SendInfo:%d] client thread malloc failed [%d][%s] and inet_ntop error[%d][%s]"
						 , getpid(), errorNum, strerror( errorNum ), errno, strerror( errno ) );
				}

				continue;
			}
			else
			{
				// 인자로 전달할 변수정보 설정.
				pThreadArg->clientSocketDesc = clientSock;

				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					memcpy( pThreadArg->szClientAddress, tempBuffer, sizeof( tempBuffer ) );
				}
				else
				{
					// inet_ntop 오류 발생시
					// - IP 정보는 그냥 로깅 목적등으로 전달되는 정보이므로.. 0.0.0.0 으로 설정 처리한다.
					int errorNum = errno;
					LOG( LERR, "[SendInfo:%d] client ip not valid. inet_ntop error[%d][%s]", getpid(), errorNum, strerror( errorNum ) );
					snprintf( pThreadArg->szClientAddress, IP_ADDR_LENGTH - 1, "0.0.0.0" );					
				}

				// Client 처리를 담당할 Thread 생성 처리
				if( pthread_create( &clientThreadId, NULL, WorkerSendInfoClientThread, (void *)pThreadArg ) != 0 )
				{
					// 생성 실패시...
					// - 로깅 
					// - client socket close 
					// - malloc 로 생성된 전달 변수 free 처리.

					int errorNum = errno;
					LOG( LERR, "[SendInfo:%d] client[%s] thread create failed [%d][%s]"
						 , getpid(), pThreadArg->szClientAddress, errorNum, strerror( errorNum ) );

					close( clientSock );
					free( pThreadArg );
				}
				else
				{
					_LOG( LDBG, "[SendInfo:%d] client[%s] accept & client thread create ok.", getpid(), pThreadArg->szClientAddress );
				}
			}
		}

	} // while(1)
	

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