#include "NetworkStatSendThread.h"
#include "DaemonConfigs.h"

#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/time.h>

using namespace std;

#define STAT_STAMP(a)          (((a) + 299) / 300)         // 5분 단위 stamp를 얻기 위한 정의
#define STAT_LOC(a)            ((a) - (((a) >> 1) << 1))   // 입력값이 짝수인지 홀수인지 판단하기 위함 --> 실제로 
#define RESP_TIMEOUT			2	// 응답 수신 타임 아웃 (sec)

CNetworkStatSendThread::CNetworkStatSendThread()
{
	// 멤버 변수 초기화
	m_threadHandle = 0;
}

CNetworkStatSendThread::~CNetworkStatSendThread()
{

}

bool CNetworkStatSendThread::ThreadInit(const sig_atomic_t *sighandle, CSharedMem* pshmemNetworStat) 
{
	m_sighandle = sighandle;
	m_pshmemNetworStat = pshmemNetworStat;

	m_szHost = CDeamonConfig::GetInstance()->GetRctsServer();
	m_nPort = CDeamonConfig::GetInstance()->GetTransferStatPort();


	// 연결이 안되더라도... rc_mond가 구동 되면 소켓연결 하도록 처리되어있으므로...
	// warning 메시지만 로깅하고 넘어간다.
	if( m_objSocket.ConnectTarget( m_szHost, m_nPort ) == false )
	{
		LOG( LWAR, "CFhsStatUpdateThread: rc_sscd connect fail.[%s][%d]", m_szHost.c_str(), m_nPort );
	}

	return true;
}

void CNetworkStatSendThread::printNetworkStat(struct service_network_stat* pobjNetstat)
{
	LOG(LDBG, "xxxxx printNetworkStatxxxxx");
	_LOG(LDBG, "	nServiceSeq: %d", pobjNetstat->nServiceSeq);
	_LOG(LDBG, "	nUserSeq: %d", pobjNetstat->nUserSeq);
	_LOG(LDBG, "	timestamp: %d", pobjNetstat->timestamp);
	_LOG(LDBG, "	down_content_size: %ld", pobjNetstat->down_content_size);
	_LOG(LDBG, "	up_content_size: %ld", pobjNetstat->up_content_size);
	_LOG(LDBG, "	down_traffic: %ld", pobjNetstat->down_traffic);
	_LOG(LDBG, "	up_traffic: %ld", pobjNetstat->up_traffic);
}

void CNetworkStatSendThread::GetNetworkStat()
{
	struct service_network_stat* pSharedHeader = (struct service_network_stat*)m_pshmemNetworStat->GetData();

	int nMaxVolume = CDeamonConfig::GetInstance()->GetMaxAccount();

	time_t t = time(0);
	// 현재 시간대의 직전에 해당하는 값을 가져오기 위해 -1을 해준다.
	unsigned int stamp = STAT_STAMP(t) -1;

	for(int i = 0; i < nMaxVolume; i++ )
	{
		LOG(LDEV, "xxxxxxxxxxxxstamp: %d, index : %d]", stamp, (i << 1) + STAT_LOC(stamp) );
		
		struct service_network_stat* pCurNetworkStat = (pSharedHeader + (i << 1) + STAT_LOC(stamp));

		// nServiceSeq 값이 없는 경우는 shared 메모리의 마지막이라고 판단 함
		if( pCurNetworkStat->nServiceSeq == 0 )
		{
			LOG(LDBG, "Shared Memory End. [index: %d]", i );
			// 맵에 데이터가 남을 경우 해당 인덱스부터 추가해 주기 위해 저장한다.
			break;
		}
		printNetworkStat(pCurNetworkStat);
		
		struct service_network_stat objNetworkStat = *pCurNetworkStat;
		if( stamp != objNetworkStat.timestamp )
		{
			LOG(LDEV, "[cur stamp: %d, shared stamp: %d ]",  stamp, objNetworkStat.timestamp);
			continue;
		}

		if( objNetworkStat.down_content_size == 0 && objNetworkStat.up_content_size == 0 )
		{
			continue;
		}
		m_vecNetworkStat.push_back(objNetworkStat);
	}
}

bool CNetworkStatSendThread::NetworkStatSend()
{
	// 1. socket이 유효한지 판단한다.
	// 1-1. 유효하지 않다면 connect를 다시한다.
	// 1-2. 연결 실패 시 return false;
	if ( m_objSocket.IsValidSocket() == false )
	{
		if( m_objSocket.ConnectTarget( m_szHost, m_nPort ) == false )
		{
			LOG( LWAR, "CFhsStatUpdateThread: rc_sscd connect fail.[%s][%d]", m_szHost.c_str(), m_nPort );
			return false;
		}
	}

	// 2. vector에 있는 data를 rc_sscd로 보낸다.
	char szHostname[HOSTNAME_LEN];
    if( gethostname( szHostname, HOSTNAME_LEN ) != 0 )
    {
        LOG(LWAR, "gethostname() failed." );
        return false;
    }
	
	if( m_objSocket.SendNetworkStat(CDeamonConfig::GetInstance()->GetRCID(), szHostname, m_vecNetworkStat) == false )
	{
		// SendNetworkStat() 함수 내부에서 로깅처리 됨.
		LOG(LINF, "The number of data to send the network state is [%d].", (int)m_vecNetworkStat.size() );
		m_objSocket.Close();
		return false;
	}

	if (m_objSocket.GetNetworkStatResult(RESP_TIMEOUT) == false)
	{
		// SendNetworkStat() 함수 내부에서 로깅처리 됨.
		LOG(LINF, "The number of data to send the network state is [%d].", (int)m_vecNetworkStat.size());
		m_objSocket.Close();
		return false;
	}

	m_vecNetworkStat.clear();
	_LOG(LINF, "Network Stat Send OK!! " );

	return true;
}

///@brief 이 함수에서 컨트롤 로직이 표현된다.
void CNetworkStatSendThread::Execute()
{	
	time_t currentTime;        // 현재 시간 정보를 저장하기 위한 변수..

	unsigned long nowTimestamp;
	unsigned long lastSendTimestamp = 0;  // rcts 로 전송 시도한 마지막 timestamp 값 (5분단위)
	unsigned long lastPopTimestamp = 0;  // shard memory 에서  마지막 pop한 timestamp 값 (5분단위)

	while (*m_sighandle == 0)
	{
		// 1. 현재 시간 정보 추출..
		currentTime = time(0);

		// 2. 현재 timestamp 값을 계산한다...
		//    (5초마다 전송위해)4 sec 보정 처리하여 5분 단위 timestamp 값을 추출한다.
		nowTimestamp = (currentTime - 4 + 299) / 300;


		// 3. 최초 기동 또는 시간 동기화 이상으로 reset 처리된 경우... lastSendTimestamp = 0 
		if (lastSendTimestamp == 0)
		{
			lastSendTimestamp = nowTimestamp;
			sleep(1);
			continue;
		}

		// 4. 마지막 timestamp 값과 현재 timestamp 계산 값이 같은 경우...
		//    이미 전송한 것으로 판단하고.. 쉰다.
		if (nowTimestamp == lastSendTimestamp)
		{
			sleep(1);
			continue;
		}

		// 만약 lastSendTimestamp > nowTimestamp 인 경우...
		//  - 시간 동기화 이상으로 이전에 계산된 timestamp 값이 잘못된 경우....  보정처리..
		if (nowTimestamp < lastSendTimestamp)
		{
			_LOG(LWAR, "Network Stat Send : Last timestamp not valid. skip and reset. now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp);
			lastSendTimestamp = 0;

			sleep(10);
			continue;
		}
		
		if (lastPopTimestamp < nowTimestamp)
		{
			lastPopTimestamp = nowTimestamp;
			GetNetworkStat();			
		}			

		// 보낼 data가 있을 경우
		if (m_vecNetworkStat.size() > 0)
		{
			// 함수내에서 logging 처리 함.
			NetworkStatSend();

			if (m_vecNetworkStat.empty())
			{ 
				lastSendTimestamp = nowTimestamp;
			}				
			else
			{
				// 재시도
				sleep(5);
			}
		}
		else {
			sleep(1);
		}
	}

	LOG(LINF, "Thread end.");
}

void* CNetworkStatSendThread::EntryPoint(void* arg)
{
    CNetworkStatSendThread* pObject = reinterpret_cast<CNetworkStatSendThread *>(arg);
	pthread_detach( pthread_self() );
    
	pObject->Execute();
	
	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;

	return 0;
}

bool CNetworkStatSendThread::Start()
{
	int nRet = ::pthread_create(&m_threadHandle, 0, CNetworkStatSendThread::EntryPoint, this);

	if( nRet )
	{
		LOG(LERR, "Thread create failed.: errno:%d errmsg:%s", errno, strerror(errno));
		return false;
	}
    LOG(LDBG, "Thread create succeed");
	sleep(0);
	return true;
}

