#include "MetaCopy.h"

#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>

#include <string>
#include <vector>

#include "Logger.h"
#include "RcdbInfo.h"
#include "ProcessConfig.h"


#define SQLITE_DB_LOCK_TIMEOUT       1000                       // SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec)
#define SQLITE_DB_LOCK_MAX_RETRY       60                       // SQLITE_BUSY 발생시 재시도 최대 횟수


// Query 문장 저장을 위한 Buffer 크기
#define DEFAULT_QUERY_BUFFER_SIZE   2048


// 생성자
// - pLocalDb : sqlite memory db 객체에 대한 포인트
CMetaCopy::CMetaCopy( sqlite3 * pLocalDb )
	: m_pLocalStmt(NULL)
{
	m_pLocalDb = pLocalDb;
	
	m_ullTotalSelectCount = 0;
}


// 소멸자
CMetaCopy::~CMetaCopy()
{
	// RCDB 와 연결 해제 처리.
	m_rcdb.PgClear();
	m_rcdb.PgCloseDB();

	// Local DB  stmt clear 처리.
	// - local db 객체는 memory db 로서 전역 객체이므로.. close 하지 않는다. 
	if( m_pLocalStmt != NULL )
	{
		sqlite3_finalize( m_pLocalStmt );
		m_pLocalStmt = NULL;
	}
}


// 객체 초기화 함수
// - RCDB 와 연결 생성
// - sqlite local memory db 관련 table, index 생성 및 insert stmt 생성 처리.
bool CMetaCopy::Init()
{
	// RCDB 접속 정보 저장 관련 객체 생성 및 초기화...
	CRcdbInfo rcdbInfo;
	rcdbInfo.Load();

	// sqlite 를 이용하여 local 장비에 저장 목적 file DB 생성 처리 및 세션 연결.
	if( CreateLocalDb() == false )
		return false;
	
	// RCDB 와 연결 생성.
	if( m_rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw ) == NULL )
	{
		// 연결 실패시...
		LOG( LERR, "RCDB connection failed." );
		return false;
	}
	else
	{
		_LOG( LINF, "RCDB connection success." );
	}

	
	return true;
}


// sqlite 를 이용하여 local db 생성 작업을 수행.
bool CMetaCopy::CreateLocalDb()
{

	// sqlite local memory db 객체는 Main 모듈에서 생성 했으므로...
	// 그 외 작업을 수행한다.
	
	// 1. local db 상에 관련 table 생성 작업 수행 처리.
	int nResult = 0;
	std::string strQuery;
	strQuery = "CREATE TABLE meta( resource_id integer, filename_hash text, get_content_length integer, deleted_yn text )";
	
	char * pStrDbError;
	int nRetryCount = 0;

	nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError );

	while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
	{
		sqlite3_free( pStrDbError );
		sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
		++nRetryCount;

		_LOG( LWAR, "Local db table create SQLITE_BUSY. Retry[%d]", nRetryCount );

		nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError );
	}

	// Table Create Query 가 최종 실패한 경우....	
	if( nResult != SQLITE_OK )
	{
		LOG( LERR, "sqlite3 table create failed. [%d][%s]", nResult, pStrDbError );

		sqlite3_free( pStrDbError );
		
		return false;
	}
	else
	{
		_LOG( LINF, "Local db table create success.");
	}
	
	// 2. garbage clean 시 select 속도 향상을 위한 Index 생성
	if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true )
	{
		strQuery = "CREATE INDEX index_filename_hash ON meta( filename_hash ) ;";
		nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError );

		while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
		{
			sqlite3_free( pStrDbError );
			sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
			++nRetryCount;

			_LOG( LWAR, "Local db index create SQLITE_BUSY. Retry[%d]", nRetryCount );

			nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError );
		}

		// Index Create Query 가 최종 실패한 경우....	
		if( nResult != SQLITE_OK )
		{
			LOG( LERR, "sqlite3 index create failed. [%d][%s]", nResult, pStrDbError );

			sqlite3_free( pStrDbError );			
			return false;
		}
		else
		{
			_LOG( LINF, "Local db index create success." );
		}
	}
	
	// 3. Meta 정보 조회 결과를 local db 에 저장 처리시 사용할 prepared 객체에 대한 초기화 수행.
	// - prepared sql 을 사용해야... insert 속도가 향상됨.
	strQuery = "INSERT INTO meta( resource_id, filename_hash, get_content_length, deleted_yn ) VALUES ( ?, ?, ?, ? );";

	nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL );

	// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
	while( nResult == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
	{		
		sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
		++nRetryCount;

		_LOG( LWAR, "Local db prepare sql SQLITE_BUSY. Retry[%d]", nRetryCount );

		nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL );
	}

	// 최종 실패시...
	if( nResult != SQLITE_OK )
	{
		LOG( LERR, "sqlite3 prepare sql stmt create failed. [%d][%s]", nResult, sqlite3_errmsg(m_pLocalDb) );
				
		m_pLocalStmt = NULL;
				
		return false;
	}
	
	return true;
}


bool CMetaCopy::Start()
{
	// 작업 소요 시간 정보 계산을 위한 변수.
	// - 시간 동기화로 인한 문제를 해결하기 위해 clock_gettime() 함수 사용.
	struct timespec timeStart;
	struct timespec timeEnd;
	double	dTimeInterval = 0.0;

	memset( &timeStart, 0x00, sizeof( struct timespec ) );
	memset( &timeEnd, 0x00, sizeof( struct timespec ) );


	// 작업 시작 로깅 및 시작 시간 정보 추출.
	_LOG( LINF, "Meta copy start.");	
	clock_gettime( CLOCK_MONOTONIC, &timeStart );
	
	
	// Query 문장 저장을 위한 버퍼 생성
	char szQuery[DEFAULT_QUERY_BUFFER_SIZE];

	// RCDB Query 수행 결과값 저장을 위한 변수..	
	int nResult = 0;


	// 1. t_sms_sp_svc_product 에서 각 고객사 정보를 가져온다.
	snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
			  "SELECT sp_svc_tran_id FROM t_sms_sp_svc_product ORDER BY sp_svc_tran_id ASC;");

	// Query 실행 
	m_rcdb.PgDoExec( szQuery );
	nResult = m_rcdb.PgResult( DataBase::NOT_CLEAR );
	if( nResult < 0 )
	{
		// t_sms_sp_svc_product 테이블 조회시 오류가 발생한 경우...
		// - 해당 table 또는 sp_svc_tran_id 컬럼 존재 안할 경우..
		LOG( LERR, "RCDB t_sms_sp_svc_product table select failed. [%s][%s]", m_rcdb.GetErrorMessage().c_str(), szQuery );

		m_rcdb.PgClear();
		m_rcdb.PgCloseDB();

		return false;
	}

	// 조회된 각 고객사 정보 확인
	nResult = m_rcdb.GetNoTuples();
	if( nResult <= 0 )
	{
		// 작업을 수행할 고객사 정보가 RCDB 에 존재하지 않는 경우...
		// - 고객사가 존재하지 않는 빈깡통 RCDB 인 경우.. 오류일 가능성이 많으므로.. 오류 처리.
		LOG( LERR, "RCDB t_sms_sp_svc_product table empty. Not found service. Check." );

		m_rcdb.PgClear();
		m_rcdb.PgCloseDB();

		return false;
	}

	// 루프를 돌면서.. 고객사 정보를 저장 처리.
	std::vector<std::string> vecService;
	std::string strTemp;
	for( int i = 0; i < nResult; i++ )
	{
		strTemp = m_rcdb.GetValue( i, 0 );
		if( strTemp.size() > 0 )
			vecService.push_back( strTemp );
	}
	
	// Query Resultset Clear.
	m_rcdb.PgClear();

	// 변수에 저장된 서비스 정보의 수를 최종 확인
	if( vecService.empty() == true )
	{
		LOG( LERR, "RCDB t_sms_sp_svc_product table not empty. but valid service not found. Check." );
				
		m_rcdb.PgCloseDB();
		return false;
	}

	// 사용자가 작업대상 경로를 지정한 경우...
	// /stg/node0
	// - t_sms_sp_svc_product 테이블을 조회하여 서비스 목록 추출해야 함.
	// /stg/node0/346
	// - 서비스 폴더를 지정했으므로.... 다른 서비스 table 까지 불필요하게 조회할 필요 없음.
	// - 따라서.. t_sms_sp_svc_product 조회 결과에서 
	// - 사용자가 지정한 서비스 seq 가 존재하는 경우.... 해당 table 만 조회하도록 처리.
	// - 사용자가 지정한 서비스 seq 가 존재하지 않는 경우... 서비스 폴더가 아닐 수 있으므로.. 전체 조회 처리.
	
	
	std::vector< std::string >::const_iterator it;
	
	// 사용자가 작업 대상 경로를 지정한 경우...
	if( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true )
	{
		strTemp = CProcessConfig::GetInstance()->GetJobRoot();

		// 맨 끝의 폴더명만을 추출.
		string::size_type pos = strTemp.rfind( '/' );
		if( pos != string::npos )
		{
			// 경로의 마지막 문자열 정보 추출.
			std::string strSvcSeq = strTemp.substr( pos + 1 );
			
			// vecService 목록에 추출된 정보가 존재하는지 확인.
			bool bFound = false;
			for( it = vecService.begin(); it != vecService.end(); ++it )
			{
				if( strcmp( it->c_str(), strSvcSeq.c_str() ) == 0 )
				{
					bFound = true;
					break;
				}
			}

			if( bFound == true )
			{
				// 기존 RCDB 에서 추출된 정보 삭제 
				vecService.clear();

				// 사용자가 지정한 서비스 seq 만 조회되도록 처리.
				vecService.push_back( strSvcSeq );

				_LOG( LINF, "meta copy : user path set [%s]. meta select only service [%s]", CProcessConfig::GetInstance()->GetJobRoot(), strSvcSeq.c_str() );
			}
		}
	}
	
	
	// 2. 사용자 지정 옵션에 따른 조회 구문을 설정 처리한다.
	char szWhere[DEFAULT_QUERY_BUFFER_SIZE];
	memset( szWhere, 0x00, sizeof( szWhere ) );

	// directory 는 빼고  파일만 조회.
	strcat( szWhere, "AND resource_type = 0 " );

	// integrity check 만 수행할 경우...
	// - 삭제된 content 는 조회하지 않는다.
	if( CProcessConfig::GetInstance()->IsRunIntegrityCheck() == true && CProcessConfig::GetInstance()->IsRunGarbageClean() == false )
	{
		strcat( szWhere, "AND deleted_yn = 'N' " );
	}

	// 사용자가 작업 대상 경로를 지정한 경우...
	if( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true )
	{
		char tempBuffer[DEFAULT_QUERY_BUFFER_SIZE];

		snprintf( tempBuffer, DEFAULT_QUERY_BUFFER_SIZE - 1,
				  "AND filename_hash LIKE '%s/%%' "
				  , CProcessConfig::GetInstance()->GetJobRoot() );

		strcat( szWhere, tempBuffer );
	}

	strcat( szWhere, " ;" );
	
	// Local sqlite db 관련 변수
	int nLocalDbResult = 0;
	int nLocalRetryCount = 0;
	

	// 3. 루프를 돌면서 
	// 각 서비스별 t_meta_xx 테이블에서 content 정보를 조회한다.
	//std::vector< std::string >::const_iterator it;	
	for( it = vecService.begin(); it != vecService.end(); ++it )
	{
		_LOG( LINF, "Service [%s] meta copy start.", it->c_str() );

		snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1,
				  "SELECT resource_id, filename_hash, get_content_length, deleted_yn FROM t_meta_%s "
				  "WHERE host_name = '%s' %s"
				  , it->c_str()
				  , CProcessConfig::GetInstance()->GetHostname()
				  , szWhere );


		// Query 실행 
		m_rcdb.PgDoExec( szQuery );
		nResult = m_rcdb.PgResult( DataBase::NOT_CLEAR );
		if( nResult < 0 )
		{
			// t_meta_xxx 테이블 조회시 오류가 발생한 경우...			
			LOG( LERR, "RCDB t_meta_%s table select failed. [%s][%s]", it->c_str(), m_rcdb.GetErrorMessage().c_str(), szQuery );
						
			// local db stmt clear 처리.
			sqlite3_finalize( m_pLocalStmt );			
			m_pLocalStmt = NULL;
			
			// RCDB 연결 해제 처리.
			m_rcdb.PgClear();
			m_rcdb.PgCloseDB();
			
			return false;
		}

		// 조회된 row 정보 확인
		nResult = m_rcdb.GetNoTuples();
		if( nResult <= 0 )
		{
			// 작업 대상 content 가 존재하지 않는 경우....
			_LOG( LINF, "Service [%s] meta content not exist.", it->c_str() );
		}
		else
		{
			// sqlite 의 insert 속도 향상을 위해.. begin - commit  구문 사용....
			// 본 구문을 사용하지 않을 경우.. 초당 180 건 정도 insert 처리됨.
			nLocalDbResult= sqlite3_exec( m_pLocalDb, "BEGIN;", NULL, NULL, NULL );
			nLocalRetryCount = 0;

			while( nLocalDbResult == SQLITE_BUSY  && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
			{				
				sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
				++nLocalRetryCount;

				_LOG( LWAR, "Local db BEGIN query SQLITE_BUSY. Retry[%d]", nLocalRetryCount );

				nLocalDbResult = sqlite3_exec( m_pLocalDb, "BEGIN;", NULL, NULL, NULL );
			}

			// 최종 실패시....
			if( nLocalDbResult != SQLITE_OK )
			{
				LOG( LERR, "sqlite3  BEGIN query failed. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) );
				
				// local db stmt clear 처리.
				sqlite3_finalize( m_pLocalStmt );				
				m_pLocalStmt = NULL;

				// RCDB 연결 해제 처리.
				m_rcdb.PgClear();
				m_rcdb.PgCloseDB();

				return false;				
			}
					

			// 루프를 돌면서 조회된 content 를 local db 에 저장 처리.
			for( int i = 0; i < nResult; i++ )
			{
				// local table 생성 Query
				// strQuery = "CREATE TABLE meta( resource_id integer, filename_hash text, get_content_length integer, deleted_yn text )";
				
				// 조회 Query
				// "SELECT resource_id, filename_hash, get_content_length, deleted_yn FROM t_meta_%s "
				
				// insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 )
				// resource_id
				sqlite3_bind_text( m_pLocalStmt, 1, m_rcdb.GetValue( i, 0 ), strlen( m_rcdb.GetValue( i, 0 ) ), SQLITE_STATIC );

				// filename_hash
				sqlite3_bind_text( m_pLocalStmt, 2, m_rcdb.GetValue( i, 1 ), strlen( m_rcdb.GetValue( i, 1 ) ), SQLITE_STATIC );

				// get_content_length
				sqlite3_bind_text( m_pLocalStmt, 3, m_rcdb.GetValue( i, 2 ), strlen( m_rcdb.GetValue( i, 2 ) ), SQLITE_STATIC );

				// deleted_yn
				sqlite3_bind_text( m_pLocalStmt, 4, m_rcdb.GetValue( i, 3 ), strlen( m_rcdb.GetValue( i, 3 ) ), SQLITE_STATIC );
				
				// insert query 수행.
				nLocalDbResult = sqlite3_step( m_pLocalStmt );
				nLocalRetryCount = 0;


				// 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도
				while( nLocalDbResult == SQLITE_BUSY  && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
				{
					sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
					++nLocalRetryCount;
										
					_LOG( LWAR, "Meta insert to local db. SQLITE_BUSY. Retry[%d]", nLocalRetryCount );

					// query 재시도
					nLocalDbResult = sqlite3_step( m_pLocalStmt );
				}


				if( nLocalDbResult != SQLITE_DONE )
				{
					// INSERT 실패 발생시...
					LOG( LERR, "sqlite3 insert step error. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) );
										
					// local db stmt clear 처리.
					sqlite3_finalize( m_pLocalStmt );					
					m_pLocalStmt = NULL;
					
					// RCDB 와의 연결까지 clear 처리한다.
					m_rcdb.PgClear();
					m_rcdb.PgCloseDB();

					return false;
				}
				else
				{
					// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
					sqlite3_reset( m_pLocalStmt );
				}

			} // for

			// sqlite 의 insert 속도 향상을 위해.. begin - commit  구문 사용....
			// 본 구문을 사용하지 않을 경우.. 초당 180 건 정도 insert 처리됨.
			nLocalDbResult = sqlite3_exec( m_pLocalDb, "COMMIT;", NULL, NULL, NULL );			
			nLocalRetryCount = 0;

			while( nLocalDbResult == SQLITE_BUSY  && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
			{
				sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT );
				++nLocalRetryCount;

				_LOG( LWAR, "Local db COMMIT query SQLITE_BUSY. Retry[%d]", nLocalRetryCount );

				nLocalDbResult = sqlite3_exec( m_pLocalDb, "COMMIT;", NULL, NULL, NULL );
			}

			// 최종 실패시....
			if( nLocalDbResult != SQLITE_OK )
			{
				LOG( LERR, "sqlite3 COMMIT query failed. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) );

				// local db stmt clear 처리.
				sqlite3_finalize( m_pLocalStmt );				
				m_pLocalStmt = NULL;

				// RCDB 연결 해제 처리.
				m_rcdb.PgClear();
				m_rcdb.PgCloseDB();

				return false;
			}			
		}

		// Query Resultset Clear.
		m_rcdb.PgClear();


		// 최종 통계 처리를 위해 누적 처리.
		m_ullTotalSelectCount += nResult;

		_LOG( LINF, "Service [%s] meta count [%d] copy end.", it->c_str(), nResult );

	} // for
	

	// 작업 완료 후... RCDB 와의 연결을 종료 처리한다.
	m_rcdb.PgClear();
	m_rcdb.PgCloseDB();

	// 작업 완료 후 Local DB stmt 객체 clear 처리한다.
	sqlite3_finalize( m_pLocalStmt );	
	m_pLocalStmt = NULL;
	
	// 작업 완료시 시간 정보 추출 후 소요시간 계산.
	clock_gettime( CLOCK_MONOTONIC, &timeEnd );
	
	dTimeInterval = timeEnd.tv_sec - timeStart.tv_sec;
	dTimeInterval += ( timeEnd.tv_nsec - timeStart.tv_nsec ) * 1e-9;

	// memory db 사용으로 인해 
	// sqlite3_memory_used() 함수를 사용하여 memory 사용량을 로깅하려 했으나..
	// FreeBSD 6.X 에 설치되는 sqlite 3.4.1 버전에서 sqlite3_memory_used, sqlite3_status 등의 함수를 지원 안함.
	// 이로 인해 적용 불가함.
	
	_LOG( LINF, "Meta copy complete. path[%s] total count[%llu], elapsed time: %.1lf sec."
		  , CProcessConfig::GetInstance()->GetJobRoot(), m_ullTotalSelectCount, dTimeInterval );
	
	return true;
}
