#include "AnonyUpdateThread.h"
#include "DaemonConfigs.h"

#include <unistd.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					180
#define ANONY_SEPARATOR				"@"

CAnonyUpdateThread::CAnonyUpdateThread()
{
	m_pPgSQL = NULL;

	// 멤버 변수 초기화
	m_threadHandle = 0;
}

CAnonyUpdateThread::~CAnonyUpdateThread()
{
	DbClose();

}

/// @brief DB 연결 함수.
bool CAnonyUpdateThread::DbConnect()
{
    if( m_pPgSQL != NULL )
    {
        DbClose();
    }
    m_pPgSQL = new DataBase;
	if( m_pPgSQL == NULL )
	{
		LOG(LERR, "Creating new Database has failed.");
		return false;
	}
	if( m_pPgSQL->PgOpenDB(m_szHost, m_nPort, m_szDBName, m_szAcct, m_szPasswd) == NULL )
	{
		LOG(LERR, "Connecting to Database has failed.");
		DbClose();
		return false;
	}
    return true;
}

/// @breif DB close 함수.
void CAnonyUpdateThread::DbClose()
{
	if( m_pPgSQL != NULL )
	{
		m_pPgSQL->PgClear();
		delete m_pPgSQL;
        m_pPgSQL = NULL;
	}
}

bool CAnonyUpdateThread::ThreadInit(const sig_atomic_t *sighandle, CSharedMem* pShmemAnony) 
{
	m_sighandle = sighandle;
	m_pShmemAnony = pShmemAnony;

	m_szHost = CDeamonConfig::GetInstance()->GetDBInfo().m_szRcdbIp;
	m_nPort = CDeamonConfig::GetInstance()->GetDBInfo().m_nRcdbPort;
	m_szDBName = CDeamonConfig::GetInstance()->GetDBInfo().m_szRcdbName;
	m_szAcct = CDeamonConfig::GetInstance()->GetDBInfo().m_szRcdbAcct;
	m_szPasswd = CDeamonConfig::GetInstance()->GetDBInfo().m_szRcdbAcctPw;
	
	// 최초 초기화 시에는 DB연결 정보들이 제대로 되는것인지에 대한 
	// 확인만 처리 한다.
    if (DbConnect() == false )
	{
		LOG(LERR, "Connecting to Database has failed. Check your configuration or RCDB.");
		return false;
	}
	
	// 접속 성공한 경우...
	_LOG( LINF, "CAnonyUpdate... RCDB connection ok..." );
	// DB연결 test 후 정리 한다.
	DbClose();
	return true;
}

bool CAnonyUpdateThread::GetData()
{
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];

	if( m_pPgSQL == NULL )
	{
		LOG(LWAR, "PgSQL is not initialized.");
		return false;
	}
	snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"SELECT a.sp_svc_id, b.uri "
					"FROM t_dav_anonymous AS b "
					"INNER JOIN t_sms_sp_svc_product AS a "
					"ON b.sp_svc_tran_id = a.sp_svc_tran_id "
					);

	// Query 실행 
	if( m_pPgSQL->PgDoExec(szQuery) < 0)
	{
		// 재연결 되도록 조치 해야한다.
		LOG( LERR, "RCDB error.[%s][%s]", m_pPgSQL->GetErrorMessage().c_str(), szQuery );
		return false;
	}

	
	// RCDB로부터 얻은 정보들을 Map에 등록한다.
	for( int i = 0; i < m_pPgSQL->GetNoTuples(); i++ )
	{		
		struct anonymous stAnony;
		memset(&stAnony, 0x00, sizeof(stAnony));
				
		strncpy(stAnony.domain, m_pPgSQL->GetValue(i, 0), DOMAIN_SIZE - 1);
		strncpy(stAnony.uri, m_pPgSQL->GetValue(i, 1), URI_SIZE_MAX - 1);
		// 2016.01.18 dadamin
		// 다른 서비스 간 동일한 디렉토리 무인증 설정 지원 
		string skey = stAnony.domain;
		skey += ANONY_SEPARATOR;
		skey += stAnony.uri;

		// 맵에 등록 처리
		m_mapAnony.insert(std::pair<string, struct anonymous>(skey, stAnony));
	}

	m_pPgSQL->PgClear();

	return true;
}


void CAnonyUpdateThread::UpdateSharedMemory()
{
	struct anonymous* pSharedHeader = (struct anonymous*)m_pShmemAnony->GetData();

	int nIndex = 0;
	
	int nMax = CDeamonConfig::GetInstance()->GetMaxAnonyMous();
	// 공유 메모리 기준으로 
	// Maxcount는 설정 값에서 갖고 온다.
	for(int i = 0; i < nMax; i++ )
	{
		// 끝이면???
		if( (pSharedHeader + i)->uri[0] == '\0' )
		{
			LOG(LDBG, "Shared Memory End. [index: %d, %d]", i, (int)m_mapAnony.size());
			// 맵에 데이터가 남을 경우 해당 인덱스부터 추가해 주기 위해 저장한다.
			nIndex = i;
			break;
		}

		struct anonymous* pCurAnony = (pSharedHeader + i);

		// 2016.01.18 dadamin
		// 다른 서비스 간 동일한 디렉토리 무인증 설정 지원 
		string skey = pCurAnony->domain;
		skey += ANONY_SEPARATOR;
		skey += pCurAnony->uri;

		// 맵에서 해당 데이터를 찾는다.
		std::map<string, struct anonymous>::iterator it;
		it = m_mapAnony.find(skey);

		if( it == m_mapAnony.end() )
		{
			// 맵에서 데이터를 못찾았다면, 무인증에서 인증으로 변경된 것이라고 판단한다.
			pCurAnony->nFlag = 1;
			continue;
		}

		struct anonymous anony = it->second;
		memcpy(pCurAnony, &anony, sizeof(struct anonymous));
		_LOG(LINF, "Anony update [Service: %s] [uri: %s] ", anony.domain, anony.uri);

		// 맵에서 삭제
		m_mapAnony.erase(it);
	}

	// 맵에 데이터가 남았는지 체크 한다.
	if( m_mapAnony.size() <= 0 )
		return;
	
	// 맵에 데이터가 남았다면... 신규로 추가된 무인증 폴더가 있다는 뜻이므로 이를 shared 메모리에 등록한다.
	// shared 메모리의 맨 마지막 인덱스(nIndex)부터 사용한다.
	std::map<string, struct anonymous>::iterator it;
	for (it=m_mapAnony.begin(); it!=m_mapAnony.end(); ++it, ++nIndex)
	{
		if( nIndex >= nMax )
		{
			LOG(LWAR, "Anony shared memory is full.[MaxCount: %d]", nMax);
			break;
		}
		struct anonymous anony = it->second;

		_LOG(LINF, "Anony Add  [Service: %s] [uri: %s] ", anony.domain, anony.uri);
		memcpy((pSharedHeader + nIndex), &anony, sizeof(struct anonymous));
	}
	m_mapAnony.clear();
}

///@brief 이 함수에서 컨트롤 로직이 표현된다.
void CAnonyUpdateThread::Execute()
{	
	// 최초라서 실패 하더라도 아래 로직에서 reconnect가 진행 됨.
    DbConnect();

	int nCount = 0;
	while (*m_sighandle == 0)
	{
		// job을 실행한다.
		if( GetData() == false )
		{
			DbConnect();

			// 3회 재시도...
			nCount++;
			if( nCount >= 3 )
			{
				nCount = 0;
			}
			// 5초 후 재시도
			if (*m_sighandle == 0)
				sleep(5);

			continue;
		}
		UpdateSharedMemory();
		nCount = 0;
		sleep(UPDATE_TERM);
	}
	
	// 쓰레드 종료 될 때 DB 세션 정리 한다.
	DbClose();
	
	LOG(LINF, "Thread end...");
}

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

	return 0;
}

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

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

