#include "CategorizeThread.h"

#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <list>

using namespace std;

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


// 2013-10-15
// report log를 위한 추가 내역
#define REPORT_LOG( format, ...) \
        m_LoggerReport.Write( LOG_ALERT, format,  ##__VA_ARGS__ )

CCategorizeThread::CCategorizeThread(CQueue<CFileInfo>* pQueueFileInfo
		, CQueue<CFileInfo>* pQueueDeleteJob
		, CQueue<CFileInfo>* pQueueCompleteJob)
	:m_pQueueFileInfo(pQueueFileInfo)
	, m_pQueueDeleteJob(pQueueDeleteJob)
	, m_pQueueCompleteJob(pQueueCompleteJob)
{
	m_pPgSQL = NULL;

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

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

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

bool CCategorizeThread::ThreadInit()
{
    FUNC_BEGIN();
    
	// 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, "CCategorizeThread: t_dav_resource table exist.[Old RCDB Type]" );
		}
		else
		{
			// t_dav_resource 테이블이 존재하지 않는 서비스별 테이블 분리 형상인 경우.
			m_bServiceTableSeparate = true;
			_LOG( LINF, "CCategorizeThread: t_dav_resource table not exist.[New RCDB Type]" );
		}
	}

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

bool CCategorizeThread::Categorize()
{
	FUNC_BEGIN();
	CFileInfo objFileInfo;
	int nCheck = 0;
	
	// Query 문장 저장을 위한 버퍼 생성
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];
	int nResult = 0;
	
	if( m_pPgSQL == NULL )
	{
		LOG(LWAR, "Pgsql is not initialized.");
		return false;
	}
    
    while( m_pQueueFileInfo->GetSize() > 0 )
    {
	    // 1. QueuePop
    	objFileInfo = m_pQueueFileInfo->Front();
    	
    	// Front 는 참조만하고 실제 pop은 Pop() 함수를 통해 한다.
    	m_pQueueFileInfo->Pop();
    	
    	// 2. resource type 을 체크 한다.
    	//    경로의 경우는 바로 Compelete Job 으로 분류한다.
    	if( objFileInfo.m_nType == 1 )
    	{
    		m_pQueueCompleteJob->Push(objFileInfo);
    		continue;
    	}
    	
		//!! webting 수정 필요
		if(m_bServiceTableSeparate == false)
		{
			// 구형 DB 스키마 용
			snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"SELECT  uri ,host_name "
					"FROM t_dav_resource "
					"WHERE filename_hash = '%s' AND host_name= '%s' AND deleted_yn = 'N'; "
					, objFileInfo.m_szFileNameHash.c_str()
					, objFileInfo.m_szHostName.c_str());
		}
		else
		{
			// 신규형상.
			snprintf(szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
					"SELECT  uri ,host_name "
					"FROM t_meta_%s "
					"WHERE filename_hash = '%s' AND host_name= '%s' AND deleted_yn = 'N'; "
					, objFileInfo.m_szSvcTranid.c_str()
					, objFileInfo.m_szFileNameHash.c_str()
					, objFileInfo.m_szHostName.c_str());
		}

		LOG( LDEV, "Checking Query : [%s]", szQuery );
	    
		// Query 실행 
		m_pPgSQL->PgDoExec(szQuery);
		nResult = m_pPgSQL->PgResult( DataBase::NOT_CLEAR );
		
		if( nResult < 0 )
		{
			// RCDB t_sms_sp_svc_product 테이블상에 preserv_day 컬럼이 존재하지 않는 경우
			// Replication 처리를 당장 수행할 필요가 없으므로 루프 대기처리후 다음에 다시 조회처리.		
			if( m_pPgSQL != NULL )
			{
				LOG(LWAR, "RCDB Query failed.[%d][%s]", nResult, m_pPgSQL->GetErrorMessage().c_str() );
				m_pPgSQL->PgClear();
	            DbClose();
			}
			
			return false;
		}
		nResult = m_pPgSQL->GetNoTuples();
	    
	    
		for( int i =0 ; i< nResult; i++ )
		{
			string szUri = m_pPgSQL->GetValue(i,0);
			string szHostName = m_pPgSQL->GetValue(i,1);
			
			// uri 가 같은것이 존재하면 Metda Data 만 삭제하도록 Compelete Job 으로 분류한다.
		    if( objFileInfo.m_szUri.compare(szUri.c_str()) != 0
		    	&& objFileInfo.m_szHostName.compare(szHostName.c_str()) == 0)
		    {
		        LOG( LINF ,"files is exist link....%s , %s ", objFileInfo.m_szFileNameHash.c_str(), m_pPgSQL->GetValue(i,0));
				nCheck = 1;
		        // 존재 한다면 다음 컨텐츠를 확인 하도록 loop 시작으로 보낸다.
		        break;
		    } 
		    
			if( objFileInfo.m_szUri.compare(szUri.c_str()) == 0
		    	&& objFileInfo.m_szHostName.compare(szHostName.c_str()) == 0)
		    {
			    nCheck = -1;
			}
		}
		

		
		// copy 또는 link 된 파일이 있는가? 
		if( nCheck == 1 )
		{
			// 있으면 Meta 만 삭제..
			m_pQueueCompleteJob->Push(objFileInfo);
		}
		else if( nCheck == -1 )
		{
			// 2013-10-15
			// app. log에도 상황을 기록하고..
			// report log에도 기록하도록 조정한다.
			LOG(LALT, "Duplicate content event. HostName:[%s], Uri:[%s]", objFileInfo.m_szHostName.c_str(), objFileInfo.m_szUri.c_str() );

			// 2014-04-17
			// #18785 이슈에 의해 Meta Data 삭제 처리 등록
			m_pQueueCompleteJob->Push(objFileInfo);
			
		}
		else
		{
			// 없으면 물리파일도 삭제..
			m_pQueueDeleteJob->Push(objFileInfo);
		}
		
		nCheck = 0;
		
		// Query Result set Clear.
		m_pPgSQL->PgClear();
	}
	FUNC_END();
	return true;
}

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

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

	delete pObject;
	FUNC_END();
	return 0;
}

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