#include "CompleteThread.h"

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

using namespace std;

#define DB_INFORMATION_UPDATE_INTERVAL      300 // sec
//#define DB_INFORMATION_UPDATE_INTERVAL      1 // sec
#define DEFAULT_QUERY_BUFFER_SIZE           1024

string StringReplace(const string& source, const string search, const string replacement)
{
	string r = source;

	string::size_type pos = 0;

	while ( (pos = r.find(search, pos)) != string::npos ) 
	{
		r.replace( pos, search.size(), replacement );
		pos = pos + replacement.size();		
	}

	return r;
}

CCompleteThread::CCompleteThread(CQueue<CFileInfo>* pQueueCompleteJob)
	: m_pQueueCompleteJob(pQueueCompleteJob)
{
	m_pPgSQL = NULL;
	// 멤버 변수 초기화
	m_threadHandle = 0;
	m_bServiceTableSeparate = false;
}

CCompleteThread::~CCompleteThread()
{
	DbClose();
	// Thread 동작 정지 처리
	// - 만약 Thread 가 이미 종료된 경우 m_threadHandle 이 다른 Thread Handle 일 수 있으므로 
	//   업무 Flow 수정시 주의할 것
	if( m_threadHandle != 0 )
		pthread_cancel( m_threadHandle );
}

/// @brief DB 연결 함수.
bool CCompleteThread::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 CCompleteThread::DbClose()
{
	if( m_pPgSQL != NULL )
	{
		delete m_pPgSQL;
        m_pPgSQL = NULL;
	}
}

bool CCompleteThread::ThreadInit()
{
    FUNC_BEGIN();
 
	// 조회 정보를 저장할 Queue 에 대한 참조 포인터가 NULL 경우 오류 처리
	if( m_pQueueCompleteJob == NULL )
	{
		LOG(LERR, "FileInfo Queue Pointer is NULL");
		return false;
	}
    
	// RCDB 접속 처리를 위한 객체 생성
	DataBase rcdb;
   
	m_szHost = CProcessStatus::GetInstance()->GetRcdbIp();
	m_nPort = CProcessStatus::GetInstance()->GetRcdbPort(); 
	m_szDBName = CProcessStatus::GetInstance()->GetRcdbName(); 
	m_szAcct = CProcessStatus::GetInstance()->GetRcdbAcct(); 
	m_szPasswd = CProcessStatus::GetInstance()->GetRcdbAcctPw();
	
	// 최초 초기화 시에는 DB연결 정보들이 제대로 되는것인지에 대한 
	// 확인만 처리 한다.
    if (rcdb.PgOpenDB(m_szHost, m_nPort, m_szDBName, m_szAcct, m_szPasswd) == NULL )
	{
		LOG(LERR, "Connecting to Database has failed.");
		return false;
	}
	
	// 접속 성공한 경우...
	LOG( LINF, "RCDB connection test ok..." );

	// 서비스 table 분리된 신규 형상인지.. 구형 형상인지 체크하여
	// m_bServiceTableSeparate 변수 설정 처리.
	// 해당 변수 초기화는 생성자에서 1차로 수행함.
	char szQuery[1024];
	snprintf( szQuery, 1023,"SELECT * FROM pg_tables WHERE schemaname = 'public' AND tablename = 't_dav_resource' " );

	rcdb.PgDoExec( szQuery );
	if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
	{
		// Query 수행 결과 오류 발생시...
		// - pg_tables 가 존재하지 않을 경우 오류 발생 가능...
		// - 이는 RCDB 가 아직 적절하게 구성되지 않았다는 의미이므로... Init()함수를 오류로 처리한다.
		LOG( LERR, "RCDB t_dav_resource exist check failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );

		rcdb.PgClear();
		rcdb.PgCloseDB();

		return false;
	}
	else
	{
		// Query 수행이 정상인 경우.
		int  nResult = rcdb.GetNoTuples();
		if( nResult > 0 )
		{
			// t_dav_resource 테이블이 존재하는 경우...
			m_bServiceTableSeparate = false;
			_LOG( LINF, "CCompleteThread: t_dav_resource table exist.[Old RCDB Type]" );
		}
		else
		{
			// t_dav_resource 테이블이 존재하지 않는 서비스별 테이블 분리 형상인 경우.
			m_bServiceTableSeparate = true;
			_LOG( LINF, "CCompleteThread: t_dav_resource table not exist.[New RCDB Type]" );
		}
	}

	// RCDB 조회 결과 set 를 clear 처리 후 RCDB 접속 해제.
	rcdb.PgClear();
	rcdb.PgCloseDB();
    
	FUNC_END();
	return true;
}


bool CCompleteThread::DeleteMeta()
{
	FUNC_BEGIN();
	// Query 문장 저장을 위한 버퍼 생성
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];
	int nResult = 0;
	
	if( m_pPgSQL == NULL )
	{
		LOG(LWAR, "Not initialized.");
		return false;
	}
	
	while(1)
	{
		CFileInfo objDelFileInfo;
		std::string m_szURI;
		
        if( m_pQueueCompleteJob->GetSize() <= 0 )
        {
        	LOG(LDBG, "CompleteQueue Job is Empty...");
        	sleep(1);
        	continue;
        }
        
	    // 1. QueuePop
    	objDelFileInfo = m_pQueueCompleteJob->Front();
    	m_pQueueCompleteJob->Pop();
		
		m_szURI = StringReplace(objDelFileInfo.m_szUri, "'", "''");
		
		if(m_bServiceTableSeparate == false)
		{
				// 구형 DB 스키마 용
			if ( objDelFileInfo.m_nType == 0 )
			{
				
				snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"DELETE FROM t_dav_resource "
					"WHERE host_name = '%s' AND uri = '%s' AND filename_hash = '%s' AND deleted_yn = 'Y'; "
					, objDelFileInfo.m_szHostName.c_str()
					, m_szURI.c_str()
					, objDelFileInfo.m_szFileNameHash.c_str() );
			}
			else 
			{
				snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"DELETE FROM t_dav_resource "
					"WHERE host_name = '%s' AND uri = '%s' AND deleted_yn = 'Y'; "
					, objDelFileInfo.m_szHostName.c_str()
					, m_szURI.c_str() );
			}
		}
		else
		{
			// 신규형상.
			if ( objDelFileInfo.m_nType == 0 )
			{
				snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"DELETE FROM t_meta_%s "
					"WHERE host_name = '%s' AND uri = '%s' AND filename_hash = '%s' AND deleted_yn = 'Y'; "
					, objDelFileInfo.m_szSvcTranid.c_str()
					, objDelFileInfo.m_szHostName.c_str()
					, m_szURI.c_str()
					, objDelFileInfo.m_szFileNameHash.c_str() );
			}
			else
			{
				snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"DELETE FROM t_meta_%s "
					"WHERE host_name = '%s' AND uri = '%s' AND deleted_yn = 'Y'; "
					, objDelFileInfo.m_szSvcTranid.c_str()
					, objDelFileInfo.m_szHostName.c_str()
					, m_szURI.c_str() );
				}
		}
		
		LOG( LDEV, "Delete Query : [%s]", szQuery );
		
		// Query 실행 
		m_pPgSQL->PgDoExec(szQuery);
		nResult = m_pPgSQL->PgResult( DataBase::CLEAR );
		if( nResult < 0 )
		{
			LOG(LWAR, "RCDB Query failed.[%d][%s]", nResult, m_pPgSQL->GetErrorMessage().c_str() );
	        DbClose();
			// 실패 해도 변경 주기 만큼 슬립한다.
			return false;
		}
		
		LOG(LDBG, "Delete!! sp_svc_tran_id = %s, host_name = %s, uri = %s, filename_hash = %s;",
					objDelFileInfo.m_szSvcTranid.c_str()
					, objDelFileInfo.m_szHostName.c_str()
					, objDelFileInfo.m_szUri.c_str()
					, objDelFileInfo.m_szFileNameHash.c_str() );
	}
	FUNC_END();
	return true;
}

///@brief 이 함수에서 컨트롤 로직이 표현된다.
void CCompleteThread::Execute()
{
	FUNC_BEGIN();
	// 아래 함수에서 return false 가 되더라도 무시한다.
	// 본 쓰레드 내에서 계속 재시도 할 수 있도록 한다.
	// Notify : 해당 함수에서 로깅처리됨
	if ( DbConnect() == false){;}
	
	while(1)
	{
	// 아래 함수에서 return false 가 되면 기존 세션에대해 정리한 후
	// DB Connection을 다시 요청한다.
		if( DeleteMeta() == false )
		{
			// 한 번더 정리한 후 
			DbClose();
			LOG(LWAR, "RCDB DeleteMeta Failed.");
			
			// 연결 재시도 함.
			if ( DbConnect() == false)
			{
				LOG(LWAR, "RCDB Connect Failed.");
			}
			sleep( 1 );
			continue;
		}
		sleep( DB_INFORMATION_UPDATE_INTERVAL );
	}
	
	// 쓰레드 종료 될 때 DB 세션 정리 한다.
	DbClose();
	
	LOG(LERR, "Thread create failed.: errno:%d errmsg:%s", errno, strerror(errno));
	FUNC_END();
}

void* CCompleteThread::EntryPoint(void* arg)
{
    CCompleteThread* pObject = reinterpret_cast<CCompleteThread *>(arg);
	pthread_detach( pthread_self() );
    
	pthread_testcancel();
	pObject->Execute();
	pthread_testcancel();

	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;

	delete pObject;
	return 0;
}

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