#include "FhsStatUpdateThread.h"
#include "DaemonConfigs.h"

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

using namespace std;

#define DEFAULT_QUERY_BUFFER_SIZE           1024
#define QUERY_LIMIT_COUNT    2000
#define UPDATE_TERM 5

int random(int nRange)
{	
	return rand() % nRange;
}

CFhsStatUpdateThread::CFhsStatUpdateThread()
{
	// 멤버 변수 초기화
	m_threadHandle = 0;
	m_myindex = -1;
	memset(m_szHostname, 0, HOSTNAME_LEN);
}

CFhsStatUpdateThread::~CFhsStatUpdateThread()
{

}

bool CFhsStatUpdateThread::ThreadInit(const sig_atomic_t *sighandle, CSharedMem* pShmemFhsStat, CSharedMem* pShmemFhsCount) 
{
	srand(time(NULL));
	m_sighandle = sighandle;
	m_pShmemFhsStat = pShmemFhsStat;
	m_pShmemFhsCount = pShmemFhsCount;

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

	GetMyHostname();

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

	return true;
}

void CFhsStatUpdateThread::UpdateSharedMemory()
{
	struct fhs_stat* pSharedHeader = (struct fhs_stat*)m_pShmemFhsStat->GetData();
	int *pFhsCount = (int*)m_pShmemFhsCount->GetData();

	int nIndex = 0;
	
	int nMax = CDeamonConfig::GetInstance()->GetMaxFhs();

	_LOG(LINF, "FHS stat updated [fhs count: %zu] ", m_mapFhsStat.size());
	// 공유 메모리 기준으로 
	// Maxcount는 설정 값에서 갖고 온다.
	for(int i = 0; i < nMax; i++ )
	{
		// 끝이면???:147

		if( (pSharedHeader + i)->szHostname[0] == '\0' )
		{
			LOG(LDEV, "Shared Memory End. [index: %d, %d]", i, (int)m_mapFhsStat.size());
			// 맵에 데이터가 남을 경우 해당 인덱스부터 추가해 주기 위해 저장한다.
			nIndex = i;
			break;
		}

		struct fhs_stat* pCurFhsStat = (pSharedHeader + i);

		if (strcmp(pCurFhsStat->szHostname, m_szHostname) == 0)
			m_myindex = i;

		// 맵에서 해당 데이터를 찾는다.
		std::map<string, struct fhs_stat>::iterator it;
		it = m_mapFhsStat.find(pCurFhsStat->szHostname);

		if( it == m_mapFhsStat.end() )
		{
			if (pCurFhsStat->nActionCode != -1 )
				_LOG(LNOT, "!!!! FHS dead. [%s]", pCurFhsStat->szHostname);

			// 맵에서 데이터를 못찾았다면, 유효하지 않은 장비라고 판단한다.
			pCurFhsStat->nActionCode = -1;
			continue;
		}

		struct fhs_stat fhsStat = it->second;
		memcpy(pCurFhsStat, &fhsStat, sizeof(struct fhs_stat));
		
		// 맵에서 삭제
		m_mapFhsStat.erase(it);
	}

	int nFhsCount = nIndex;
	// 맵에 데이터가 남았는지 체크 한다.
	if( m_mapFhsStat.size() <= 0 )
	{
		*pFhsCount = nFhsCount;
		return;
	}
	
	// 신규 등록되는 장비들이 shared memory에 random하게 등록 되도록 처리 함
	std::vector<struct fhs_stat> vecAliveFhs;
	std::map<string, struct fhs_stat>::iterator it;
	for (it=m_mapFhsStat.begin(); it!=m_mapFhsStat.end(); ++it)
	{
		struct fhs_stat stFhsStat = it->second;
		vecAliveFhs.push_back( stFhsStat );
	}
	m_mapFhsStat.clear();

	//  vercot 목록에서 random하게 shared memory에 기록한다.
	while( vecAliveFhs.size() != 0 )
	{
		int nResult = random( vecAliveFhs.size() );
		struct fhs_stat stFhsStat = vecAliveFhs[nResult]; 
		
		LOG(LDBG, "host : %s, Action Code : %d index : %d",stFhsStat.szHostname, stFhsStat.nActionCode, nIndex);

		memcpy((pSharedHeader + nIndex), &stFhsStat, sizeof(struct fhs_stat));
		
		if (strcmp(stFhsStat.szHostname, m_szHostname) == 0)
			m_myindex = nIndex;

		vecAliveFhs.erase(vecAliveFhs.begin()+nResult);
		nIndex++;
		nFhsCount++;
	}

}

bool CFhsStatUpdateThread::GetFhsStat()
{
	// 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: Connected to the monitor server failed.[%s][%d]", m_szHost.c_str(), m_nPort );
			return false;
		}
	}

	// 2. 유효한 fhs 목록 요청 
	// get hostname
	if (GetMyHostname() == false)
    {
        LOG(LWAR, "gethostname() failed." );
        return false;
    }
	if (m_objSocket.SendAliveFhs(CDeamonConfig::GetInstance()->GetRCID(), m_szHostname) == false)
	{
		// SendAliveFhs() 함수 내부에서 로깅처리 됨.
		m_objSocket.Close();
		return false;
	}

	// 3. 목록 수신
	if( m_objSocket.GetAliveFhs(m_mapFhsStat) == false )
	{
		// GetAliveFhs() 함수 내부에서 로깅처리 됨.
		m_objSocket.Close();
		return false;
	}

	return true;
}

bool CFhsStatUpdateThread::GetMyHostname()
{
	if (strlen(m_szHostname) > 0) 
		return true;

	if (gethostname(m_szHostname, HOSTNAME_LEN) != 0)
	{
		memset(m_szHostname, 0, HOSTNAME_LEN);
		LOG(LWAR, "gethostname() failed.");
		return false;
	}

	if (m_szHostname == NULL || strlen(m_szHostname) <= 0)
	{
		LOG(LWAR, "m_szHostname is NULL.");
		return false;
	}

	LOG(LDBG, "My Hostname is %s", m_szHostname);
	return true;
}

void CFhsStatUpdateThread::SetSelfDead()
{
	if ( m_myindex != -1 )
	{
		struct fhs_stat* pSharedHeader = (struct fhs_stat*)m_pShmemFhsStat->GetData();
		(pSharedHeader + m_myindex)->nActionCode = -1;
		_LOG(LNOT, "Set myself Dead.[%s]", (pSharedHeader + m_myindex)->szHostname);
	}
}

///@brief 이 함수에서 컨트롤 로직이 표현된다.
void CFhsStatUpdateThread::Execute()
{	
	// socket control object 생성 및 소켓 연결
	while (*m_sighandle == 0)
	{
		// rc_statd로 부터 이웃의 FHS 상태정보를 취한다.
		if( GetFhsStat() == false )
		{
			m_objSocket.Close();
			// 5초 후 재시도
			if (*m_sighandle == 0)
				sleep(UPDATE_TERM);
			LOG(LDBG, "Retry...");
			continue;
		}
		UpdateSharedMemory();
		sleep(UPDATE_TERM);
	}

	_LOG(LINF, "CFhsStatUpdateThread Thread end.: errno:%d errmsg:%s", errno, strerror(errno));
}

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

	return 0;
}

bool CFhsStatUpdateThread::Start()
{
	int nRet = ::pthread_create(&m_threadHandle, 0, CFhsStatUpdateThread::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;
}

