#include "ProcessDummy.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sstream>

#include "Logger.h"

#define DEFAULT_ACCEPT_WAIT_COUNT	30

#define ACCEPT_TIMEOUT				60


CProcessDummy::CProcessDummy()
	: m_port(-1), m_ipv6(false), m_listenfd(-1)
{

}

CProcessDummy::~CProcessDummy()
{

}

pid_t CProcessDummy::Launcher(const sig_atomic_t *sighandle)
{
	Fork();

	if (m_pid == 0)
	{
		if (m_port > 0)
		{
			if (TCPListen() == -1)
			{
				m_launched = false;
				exit(EXIT_FAILURE);
				return -1;
			}
		}

		ostringstream msg;
		bool running = false;
		while (*sighandle == 0)
		{
			// work			
			if (m_port > 0)
			{
				if (!running)
				{

					msg.str("");
					msg << "Worker [dummy TCP : " << m_port << ", PID : " << getpid() << "]";
					set_ps_display(msg.str().c_str(), false);			
					_LOG(LINF, "%s", msg.str().c_str());
					running = true;
				}
				
				int n = TCPAccept();
				if (n > 0)
				{
					sleep(1);
					::close(n);
				}
			}
			else
			{
				if (!running)
				{

					msg.str("");					
					msg << "Worker [dummy, PID : " << getpid() << "]";
					set_ps_display(msg.str().c_str(), false);					
					_LOG(LINF, "%s", msg.str().c_str());
					running = true;
				}
				
				pause();
			}
		}

		if (m_port > 0)
			Close();

		m_launched = false;
	}

	return m_pid;
}

int CProcessDummy::SetOption()
{
	ostringstream msg;

	int result = 0;
	// Socket Port Reuse Option Set
	int opt = 1;
	result = ::setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	if (result != 0)
	{
		msg << "Listen socket option[SO_REUSEADDR] set failed. [" << errno
			<< "][" << strerror(errno) << "][Port : " << m_port << "]";
		LOG(LERR, msg.str().c_str());
		return -1;
	}

	// Keep Alive Option set
	opt = 1;
	result = ::setsockopt(m_listenfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
	if (result != 0)
	{
		msg << "Listen socket option[SO_KEEPALIVE] set failed. [" << errno
			<< "][" << strerror(errno) << "]";
		LOG(LERR, msg.str().c_str());
		return -1;
	}

	// Recv Timeout 설정
	//  만일 리스너 소켓에 SO_RCVTIMEO를 지정하면, accept(2) 호출시에 지정된 타임아웃 동안 새로운 접속이 없으면 
	//  recv(2)처럼 EAGAIN에러로 리턴된다.
	struct timeval tv_timeo = { ACCEPT_TIMEOUT, 0 };
	result = ::setsockopt(m_listenfd, SOL_SOCKET, SO_RCVTIMEO, &tv_timeo, sizeof(tv_timeo));
	if (result != 0)
	{
		int errorNum = errno;
		LOG(LERR, "SO_RCVTIMEO set error.[%d][%s]", errorNum, strerror(errorNum));
		return -1;
	}

	// IPv6로 IPv4 지원을 위한 설정
#ifdef IPV6_V6ONLY
	int v6only = 0;
	if (m_ipv6 && setsockopt(m_listenfd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) < 0) {
		int errorNum = errno;
		LOG(LERR, "IPV6_V6ONLY set error.[%d][%s]", errorNum, strerror(errorNum));
		return -1;
	}
#endif // IPV6_V6ONLY

	return 0;
}

int CProcessDummy::TCPListen()
{
	int result = 0;
	Close();
#ifdef AF_INET6
	result = Listenv6();
	if (m_ipv6 == false)
#endif // AF_INET6
		result = Listen();
	return result;
}

int CProcessDummy::Listen()
{
	int result = 0;
	ostringstream msg;
	
	m_listenfd = ::socket(AF_INET, SOCK_STREAM, 0);

	if (m_listenfd == -1)
	{
		msg << "Listen socket create failed.[" << errno << "]["
			<< strerror(errno) << "] [Port : " << m_port << "]";
		LOG(LERR, "%s", msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

	if (SetOption() != 0)
		return -1;

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

	listenSockAddr.sin_family = AF_INET;
	listenSockAddr.sin_port = htons(m_port);
	listenSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	listenSockLen = sizeof(listenSockAddr);

	// Socket Bind
	result = ::bind(m_listenfd, (struct sockaddr *)&listenSockAddr, listenSockLen);
	if (result != 0)
	{
		msg << "Listen socket bind failed. [" << errno << "][" << strerror(errno)
			<< "][Port : " << m_port << "]";
		LOG(LERR, msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

	// Socket Listen
	result = ::listen(m_listenfd, DEFAULT_ACCEPT_WAIT_COUNT);
	if (result != 0)
	{
		msg << "Listen socket listen failed. [" << errno << "][" << strerror(errno)
			<< "][Port : " << m_port << "]";
		LOG(LERR, msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}
	
	return 0;
}

int CProcessDummy::Listenv6()
{
	int result = 0;
	ostringstream msg;
	
#ifdef AF_INET6
	m_listenfd = ::socket(AF_INET6, SOCK_STREAM, 0);
	if (m_listenfd > 0)
	{
		msg << "Enable IPv6 Socket.[Port :" << m_port << "]";
		_LOG(LINF, msg.str().c_str());
		msg.str("");
		m_ipv6 = true;
	}

	if (m_listenfd == -1)
	{
		msg << "Listen socket create failed.[" << errno << "]["
			<< strerror(errno) << "] [Port : " << m_port << "]";
		LOG(LERR, "%s", msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

	if (SetOption() != 0)
		return -1;

	struct sockaddr_in6 listenSockAddrv6;
	socklen_t listenSockLen = 0;
	memset(&listenSockAddrv6, 0x00, sizeof(listenSockAddrv6));

	listenSockAddrv6.sin6_family = AF_INET6;
	listenSockAddrv6.sin6_flowinfo = 0;
	listenSockAddrv6.sin6_port = htons(m_port);
	listenSockAddrv6.sin6_addr = in6addr_any;

	listenSockLen = sizeof(listenSockAddrv6);

	// Socket Bind
	result = ::bind(m_listenfd, (struct sockaddr *)&listenSockAddrv6, listenSockLen);
	if (result != 0)
	{
		msg << "Listen socket bind failed. [" << errno << "][" << strerror(errno)
			<< "][Port : " << m_port << "]";
		LOG(LERR, msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

	// Socket Listen
	result = ::listen(m_listenfd, DEFAULT_ACCEPT_WAIT_COUNT);
	if (result != 0)
	{
		msg << "Listen socket listen failed. [" << errno << "][" << strerror(errno)
			<< "][Port : " << m_port << "]";
		LOG(LERR, msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

#else 
	return -1;
#endif // AF_INET6

	return 0;
}

int CProcessDummy::TCPAccept()
{
#ifdef AF_INET6
	if(m_ipv6)
		return Acceptv6();
	else 
#endif // AF_INET6
		return Accept();
}

int CProcessDummy::Accept()
{
	// 접속 요청을 변수 생성 및 초기화.
	int nClientfd;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof(clientSockAddr);

	nClientfd = ::accept(m_listenfd, (struct sockaddr *) &clientSockAddr, &clientSockLen);

	return nClientfd;

}

int CProcessDummy::Acceptv6()
{
#ifdef AF_INET6
	// 접속 요청을 변수 생성 및 초기화.
	int nClientfd;
	struct sockaddr_in6 clientSockAddr;
	socklen_t clientSockLen = sizeof(clientSockAddr);

	nClientfd = ::accept(m_listenfd, (struct sockaddr *) &clientSockAddr, &clientSockLen);
#else
	return -1;
#endif  //AF_INET6

	return nClientfd;
}

void CProcessDummy::Close()
{
	if (m_listenfd > 0)
	{
		::close(m_listenfd);
		m_listenfd = -1;
	}
}
