/***************************************************************************
                   Service Class (Service.cpp)
                -----------------------------------------

	begin      : 2013/05/30
	copyright  : (C) 2013 Solbox Inc.
	author     : Development 1 Team
					- 2013/05/30 - 1st dadamin
	email      : dev1@solbox.com
	version    : 3.2.0

	CopyRight(C) 2005 Solbox Inc. All Rights reserved.
	Redistribution and use in source and binary forms, with or with out
	modification, are not permitted in outside of Solbox Inc.
***************************************************************************/
#include "cc_statd.h"
#include "Service.h"
#include "Configs.h"
#include "Logger.h"

#define FAIL_LOG_PREFIX				PROG_NAME"_" 
#define FAIL_LOG_TAIL				".xml"
#define FAIL_LOG_WAIT_TIME			60 // sec

#define DEFAULT_ACCEPT_WAIT_COUNT	30
#define ACCEPT_TIMEOUT				60

// class CService
bool CService::Create()
{
	// create work thread pool
	if(m_WokrPool.CreatePool(CMyConfig::GetInstance()->GetWorkThreadCnt(), &m_DBManager) == false)
		return false;

	// create DB connection pool
	int n = CMyConfig::GetInstance()->GetUsedDBTypeCnt();
	for(int i = 0; i < n; ++i)
	{
		string sDBType = CMyConfig::GetInstance()->GetUsedDBType(i);
		map<string, CDataBaseInfo> infos;
		CMyConfig::GetInstance()->GetDBInfo(sDBType, infos);

		for( map<string, CDataBaseInfo>::iterator iter2 = infos.begin(); iter2 != infos.end(); ++iter2)
		{
			if( m_DBManager.CreatePool(sDBType, iter2->first, iter2->second) == false)
				return false;
		}
	}

	return true;
}

bool CService::Start()
{
	return true;
}

bool CService::Stop()
{
	if(m_done) return true;

	m_done = true;
	m_WokrPool.Finalized();
	m_DBManager.Finalized();
	return true;
}


// class CFailLogMon
CFailLogMon::CFailLogMon()
{

}

CFailLogMon::~CFailLogMon()
{
	
}

bool CFailLogMon::Create()
{
	if(CService::Create() == false)
		return false;

	m_fiallogpath = CMyConfig::GetInstance()->GetFailLogPath();

	return true;
}

bool CFailLogMon::FindLog()
{
	ostringstream msg;
	while(!m_done)
	{
		msg << "Fail Log Working...";
		LOGACONSOLE(LDEV1, msg);

		// 특정 디렉토리 하위에 위치한 로그 탐색
		DIR *dp;
		struct dirent *dirp;
		struct stat sb;
		string sfull;
		if((dp  = opendir(m_fiallogpath.c_str())) != NULL) 
		{
			bool skip = true;
			while ((dirp = readdir(dp)) != NULL) 
			{
				skip = true;
				sfull = m_fiallogpath + "/" + dirp->d_name;	
				
				stat(sfull.c_str(), &sb);

				if (S_ISREG(sb.st_mode))
				{
					// 로그 파일명 규칙 : cc_statd_YYYYMMDDHHmmSS_RandomKey.xml
					if(sfull.find(FAIL_LOG_PREFIX) != string::npos && 
						sfull.find(FAIL_LOG_TAIL) != string::npos)
					{
						skip = false;
						msg << "Working File " << sfull;
						LOGACONSOLE(LDBG, msg);

						// work
						CWorkThread *work = m_WokrPool.GetWork();
						if(work)
						{
							work->RunWork(sfull);
						}
						else
						{
							msg << "Failed Work pool allocation. [FILE : " << sfull << "]";
							LOGACONSOLE(LERR, msg);
						}
					}
				}

				if(skip)
				{
					if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, ".."))
					{
						msg << "SIKP : Fail Log worker [Path : " << sfull << "]";
						LOGACONSOLE(LWAR, msg);
					}
				}
			}
			closedir(dp);
		}
		else
		{
			msg << "Error(" << errno << ") opening " << m_fiallogpath;
			LOGACONSOLE(LERR, msg);
		}

		// sleep 
		sleep(FAIL_LOG_WAIT_TIME);
	}

	return true;
}

bool CFailLogMon::Start()
{
	ostringstream msg;

	msg << "Fail Log Mon START.";
	LOGACONSOLE(LDEV1, msg);

	FindLog();
	
	msg << "Fail Log Mon END.";
	LOGACONSOLE(LDEV1, msg);
	return true;
}

bool CFailLogMon::Stop()
{
	CService::Stop();
	return true;
}

// class CTCPService
int CTCPService::m_port = 0;
int CTCPService::m_listenSocket = -1;
bool CTCPService::m_ipv6 = false;

void CTCPService::SetPort(int port)
{
	m_port = port;
}

bool CTCPService::MakeListenSocket()
{
	ostringstream msg;

	if(m_listenSocket > 0)
		return true;

#ifdef AF_INET6
	m_listenSocket = ::socket( AF_INET6, SOCK_STREAM, 0 );
	if(m_listenSocket > 0)
	{
		msg << "Enable IPv6 Socket.";
		LOGACONSOLE(LINF, msg);
		m_ipv6 = true;
	}
#endif // AF_INET6
	if(m_ipv6 == false)
		m_listenSocket = ::socket( AF_INET, SOCK_STREAM, 0 );
	
	if( m_listenSocket == -1 )
	{
		msg << "Listen socket create failed.[" << errno << "][" 
			<< strerror(errno)  << "] [Port : " << m_port << "]";
		LOGACONSOLE(LERR, msg);
		return false;
	}

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

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


	// Socket Bind
#ifdef AF_INET6
	if(m_ipv6)
	{
		struct sockaddr_in6 listenSockAddrv6;
		socklen_t listenSockLen = 0;
		memset(&listenSockAddrv6, 0x00, sizeof(listenSockAddrv6));

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

		listenSockLen = sizeof(listenSockAddrv6);

		
		result = ::bind( m_listenSocket, (struct sockaddr *)&listenSockAddrv6, listenSockLen);
	}
	else 
#endif  //AF_INET6
	{		
		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_listenSocket, (struct sockaddr *)&listenSockAddr, listenSockLen);
	}

	if( result != 0 )
	{
		msg << "Listen socket bind failed. [" << errno << "][" << strerror(errno)  
			<< "][Port : " << m_port << "]";
		LOGACONSOLE(LERR, msg);
		return false;
	}

	// Socket Listen
	result = ::listen( m_listenSocket, DEFAULT_ACCEPT_WAIT_COUNT );
	if( result != 0 )
	{
		msg << "Listen socket listen failed. [" << errno << "][" << strerror(errno)  
			<< "][Port : " << m_port << "]";
		LOGACONSOLE(LERR, msg);
		return false;
	}

	return true;
}

CTCPService::CTCPService()
{

}

CTCPService::~CTCPService()
{

}

bool CTCPService::Create()
{
	if(CService::Create() == false)
		return false;
	return true;
}

bool CTCPService::Accpet()
{
	ostringstream msg;
	// 접속 요청을 변수 생성 및 초기화.
	int nClientfd;
	socklen_t clientSockLen;
#ifdef AF_INET6
	struct sockaddr_in6 clientSockAddrv6;
#endif //AF_INET6
	struct sockaddr_in clientSockAddr;
	
	if(m_ipv6)
		clientSockLen = sizeof(clientSockAddrv6);
	else 
		clientSockLen = sizeof(clientSockAddr);

	while(!m_done)
	{
		pid_t pid = getpid();

#ifdef AF_INET6
		if(m_ipv6)
		{
			msg << "Client(v6) Waiting... [Port:" << m_port << ",PID:" << pid << "]";
			LOGACONSOLE(LDEV1, msg);
			nClientfd = ::accept( CTCPService::m_listenSocket, (struct sockaddr *) &clientSockAddrv6, &clientSockLen );
		}
		else 
#endif //AF_INET6
		{
			msg << "Client Waiting... [Port:" << m_port << ",PID:" << pid << "]";
			LOGACONSOLE(LDEV1, msg);
			nClientfd = ::accept( CTCPService::m_listenSocket, (struct sockaddr *) &clientSockAddr, &clientSockLen );
		}
		
		if( nClientfd == -1 )
		{
			if( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
			{
				msg << "Service : client accept Warning. [" << errno << "][" 
					<< strerror(errno) << "][Port : " << m_port << ",PID:" << pid << "]";

				LOGACONSOLE(LDBG, msg);
				continue;
			}

			// 오류발생시 해당 내역 로깅처리.
			msg << "Service : client accept failed. [" << errno << "][" 
				<< strerror(errno) << "][Port : " << m_port << ",PID:" << pid << "]";

			LOGACONSOLE(LERR, msg);
		}
		else
		{
			// 정상적인 Client 인 경우 
			msg << "Client..... [PID :" << pid << "]";
			LOGACONSOLE(LDBG, msg);

#ifdef AF_INET6
			char tempBuffer[INET6_ADDRSTRLEN] = {0};
#else //AF_INET6
			char tempBuffer[INET_ADDRSTRLEN] = {0};
#endif // AF_INET6
#ifdef AF_INET6
			if(m_ipv6)
			{
				if( inet_ntop( AF_INET6, (void *)&clientSockAddrv6.sin6_addr, tempBuffer, sizeof(tempBuffer)) != NULL )
				{
					LOG( LDBG, "Client Info(v6) : %s",tempBuffer);
				}
				else
				{
					LOG( LERR, "inet_ntop(v6) error[%d][%s]", errno, strerror(errno) );
				}
			}
			else
#endif //AF_INET6
			{
				if( inet_ntop( AF_INET, (void *)&clientSockAddr.sin_addr, tempBuffer, sizeof(tempBuffer)) != NULL )
				{
					LOG( LDBG, "Client Info : %s",tempBuffer);
				}
				else
				{
					LOG( LERR, "inet_ntop error[%d][%s]", errno, strerror(errno) );
				}
			}

			// work
			CWorkThread *work = m_WokrPool.GetWork();
			if(work)
			{
				work->RunWork(nClientfd, tempBuffer);
			}
			else
			{
				msg << "Failed Work pool allocation.[IP :" << tempBuffer << "]";
				LOGACONSOLE(LERR, msg);
				::close(nClientfd);
			}
		}
	}

	return true;
}

bool CTCPService::Start()
{
	ostringstream msg;

	msg << "TCP Service START.";
	LOGACONSOLE(LDEV1, msg);

	Accpet();

	msg << "TCP Service END.";
	LOGACONSOLE(LDEV1, msg);
	return true;
}

bool CTCPService::Stop()
{
	CService::Stop();
	return true;
}

// Abstract Factory returning a Service

CService* CServiceFactory::CreateSerivce(SERVICE_TYPE::sType t)
{
	CService * r = NULL;
	
	switch(t)
	{
	case SERVICE_TYPE::FAIL_MON:
		r = new CFailLogMon;
		break;
	case SERVICE_TYPE::TCP_SERVICE:
		r = new CTCPService;
		break;
	case SERVICE_TYPE::UNSET:
	default:
		LOG(LERR, "Service Type is unknown.");
		break;
	}

	_LOG( LDEV, "Service Factory Type = [%d]", t);

	return r;
}
