/***************************************************************************
                Source Data
                -----------------------------------------
	begin      : 2011/10/27
	copyright  : (C) 2011 SolutionBox Inc.
	author     : Service 1 Team
	email      : svc1@solbox.com
	version    : 3.0.1

    CopyRight(C) 2011 SolutionBox Inc. All Rights reserved.
    Redistribution and use in source and binary forms, with or with out
    modification, are not permitted in outside of SolutionBox Inc.
***************************************************************************/
#include "SourceData.h"
#include "Database.h"
#include "DataFile.h"
#include "SyncList.h"
#include "ServiceConfig.h"
#include "RcdbInfo.h"
#include "Util.h"
#include "Logger.h"

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

// 전체 자료량 구하지 못했을 때 해당 값 사용
#define MAX_LIMIT_OFFSET_COUNT		1000000

//	쿼리문에 사용할 임시 버퍼 Size
#define DEFAULT_BUFFER_SIZE	1024

CSourceData::CSourceData()
	: m_bDELETED_YN(false)
	, m_usedisplay(false)
	, m_ismaster(false)
{
}

CSourceData::~CSourceData()
{
}

bool CSourceData::Init(bool master, CSyncInfo sourceParam)
{
	bool r = false;
	m_ismaster = master;
	m_sourceParam = sourceParam;
	if ( m_sourceParam.sync_type == SYNC_TYPE::NORMA_N_ONLY ||
		 m_sourceParam.sync_type == SYNC_TYPE::CHK_ONLY_N_ONLY )
		m_bDELETED_YN = true;
	else
		m_bDELETED_YN = false;

	m_strErrorMessage.clear();

	if (m_ismaster)
		r = m_dataFile.Init(m_sourceParam.file_master_name.c_str());
	else
		r = m_dataFile.Init(m_sourceParam.file_slave_name.c_str());

	if (r == false)
	{
		m_strErrorMessage = "Failed Create source file.";
	}
	return r;
}

bool CSourceData::Execute()
{
	string errmsg;
	if (isError(errmsg) == true)
	{
		LOG(LERR, "Failed Initialization.");
		return false;
	}

	ostringstream msg;
	int nRet = ::pthread_create(&m_handleThread, NULL, CSourceData::Run, this);
	
	if(nRet != 0)
	{
		msg << "Thread create failed.: errno: " << errno;
		SetErrorMessage(msg.str());
		LOG(LERR, msg.str().c_str());
		return false;
	}

	return true;
}

void* CSourceData::Run(void* pVoid)
{
	ostringstream msg;
	CSourceData* pSourceData = reinterpret_cast<CSourceData*>(pVoid);
//	pthread_detach( pthread_self() );	// 호출한 위치에서 pthread_join을 사용한다.

	// 1. RCDB t_sms_sp_svc_product 정보를 조회하여 각 고객사별 svcsync count 정보를 가져와 저장처리한다.
	// RCDB 접속 처리를 위한 객체 생성
	DataBase* pPgSQL = new DataBase;
	if( pPgSQL == NULL )
	{		
		msg << "Cannot create DataBase object";
		pSourceData->SetErrorMessage(msg.str());
		LOG(LERR, msg.str().c_str());
		return NULL;
	}

	CRcdbInfo info;
	info.Load();

	// RCDB 접속 시도 및 접속 실패시
	if (pPgSQL->PgOpenDB(info.m_strRcdbIp, info.m_nRcdbPort, info.m_strRcdbName, info.m_strRcdbAcct, info.m_strRcdbAcctPw) == NULL)
	{
		if (pPgSQL != NULL)
		{
			msg << "RCDB Connect failed [" << pPgSQL->GetErrorMessage() << "]";
			pSourceData->SetErrorMessage(msg.str());
			LOG(LERR, msg.str().c_str());

			delete pPgSQL;
			pPgSQL = NULL;
		}
		return NULL;
	}

	// 2014.06.07 dadamin
	// 작업 대상 테이블명 가져오기
	string tblname = pSourceData->GetTargetTable(pPgSQL);

	if ( tblname.size() <= 0 )
	{
		if (pPgSQL != NULL)
		{
			string tranid = pSourceData->m_sourceParam.sync_slave;
			if (pSourceData->m_ismaster)	tranid = pSourceData->m_sourceParam.sync_master;
			msg << "Failed to fetch Target table.[" << tranid << "]";
			pSourceData->SetErrorMessage(msg.str());
			LOG(LERR, msg.str().c_str());

			delete pPgSQL;
			pPgSQL = NULL;
		}

		return NULL;
	}	
	// 2014.08.06 dadamin
	// 해당테이블 전체 자료량에 10% 값을 limit 값으로 사용한다. 
	int64_t total = pSourceData->GetRows(pPgSQL, tblname.c_str());

	int64_t getlimit = (total > 0 ? int64_t(total*0.1) : MAX_LIMIT_OFFSET_COUNT);
	getlimit = (getlimit == 0 ? MAX_LIMIT_OFFSET_COUNT : getlimit);
	
	int nResult = 0;

	// Query Resultset Clear.
	pPgSQL->PgClear();

	///////////////////////////////////////////////////////////////////////////////////////////////////

	int nNowResult = 0;
	
	string strResultUri, strResultType, strResultLength;
	string strResultLastDate, strResultDeletedYN, strResultFilenameHash;
	char* szResultUriTmp1 = NULL;
	char* szResultHasNameTmp1 = NULL;

	// Slave의 경우 파일이 없는 경우가 발생할 수 있으므로 미리 빈파일을 생성해 둔다.
	pSourceData->m_dataFile.Write("");

	do
	{
			
		ostringstream sql;

		pPgSQL->PgClear();

		// 2014.06.13 dadamin
		// 신규 형상(9.3) 경우 display_name 사용하여 대소문자 기능을 지원하며,
		// 기존 형상의 uri와 동일한 값을 가지고 있으므로 해당 형상인 경우
		// uri 대신 display_name으로 소스 데이터를 가져오게 한다.
		
		if(pSourceData->m_usedisplay)
			sql << "SELECT  display_name, resource_type, get_content_length, file_lastmodified, deleted_yn, filename_hash ";
		else
			sql << "SELECT  uri, resource_type, get_content_length, file_lastmodified, deleted_yn, filename_hash ";

		sql << "FROM " << tblname << " ";

		sql << "WHERE get_lastmodified >= $1 AND get_lastmodified < $2 ";
		sql << "AND depth >= 0 AND depth <= 1000 ";
		sql << "AND uri LIKE '/' || $3 || '/%' ";

		if( pSourceData->m_bDELETED_YN )
			sql << "AND deleted_yn = 'N' ";

		sql << "AND is_cache = 'N' ";

		if(pSourceData->m_usedisplay)
			sql << "ORDER BY display_name, get_lastmodified ";
		else
			sql << "ORDER BY uri, get_lastmodified ";
		sql << "LIMIT $4 OFFSET $5 ";


#ifdef _DEBUG
		if(nNowResult == 0)
			cout << szQuery << endl;
#endif // _DEBUG		
		//
		//MAX_LIMIT_OFFSET_COUNT
		//nNowResult

		const char *paramValues[5];
		ostringstream tmp;
		string strTime1, strTime2;
		string strLimit, strOffSet;

		tmp << str2longtime(pSourceData->m_sourceParam.start_time);
		strTime1 = tmp.str(); tmp.str("");

		tmp << str2longtime(pSourceData->m_sourceParam.end_time);
		strTime2 = tmp.str(); tmp.str("");

		tmp << getlimit;
		strLimit = tmp.str(); tmp.str("");

		tmp << nNowResult;
		strOffSet = tmp.str(); tmp.str("");

		paramValues[0] = strTime1.c_str();
		paramValues[1] = strTime2.c_str();
		if (pSourceData->m_ismaster)
			paramValues[2] = pSourceData->m_sourceParam.sync_master.c_str();
		else 
			paramValues[2] = pSourceData->m_sourceParam.sync_slave.c_str();

		paramValues[3] = strLimit.c_str();
		paramValues[4] = strOffSet.c_str();

		nNowResult += getlimit;

		// Query 실행
		//pPgSQL->PgDoExec(szQuery);
		pPgSQL->PgDoExecParams((char*) sql.str().c_str(), 5, paramValues);

		// Query 결과를 가져온다.
		nResult = pPgSQL->PgResult(DataBase::NOT_CLEAR);
		if(nResult < 0)
		{
			pPgSQL->PgClear();
			// RCDB t_dav_resource 테이블 조회 실패
			msg << "RCDB Meta row get failed.[" << nResult << "][" << pPgSQL->GetErrorMessage() << "]";
			pSourceData->SetErrorMessage(msg.str());
			LOG(LERR, msg.str().c_str());
			break;
		}

		// 결과 Row 수 확인
		nResult = pPgSQL->GetNoTuples();
	
		// 조회 결과가 존재하는 경우 조회된 결과를 파일에 저장
		for (int n = 0; n < nResult; n++)
		{
			szResultUriTmp1 = NULL;
			szResultUriTmp1 = strchr(pPgSQL->GetValue(n, 0)+1, '/');				// URI (앞의 tran_id를 삭제한다.)

			// skip 키워드 포함되어 있는 검사한다.
			if(pSourceData->IsSkipKeyword(szResultUriTmp1) == true)
			{
				msg << "SKIP Source data." << szResultUriTmp1;
				_LOG(LNOT, "%s", msg.str().c_str());
				msg.str("");
				
				//REPORT_SUCCESS("[%s] [SKIP] %s (source data has skip keyword.)", 
				//	pSourceData->m_sourceParam.m_tranid.c_str(), szResultUriTmp1);
				continue;
			}

			strResultUri			= szResultUriTmp1;
			strResultType			= pPgSQL->GetValue(n, 1);	// resource_type
			strResultLength			= pPgSQL->GetValue(n, 2);	// get_content_length
			strResultLastDate		= pPgSQL->GetValue(n, 3);	// file_lastmodified
			strResultDeletedYN		= pPgSQL->GetValue(n, 4);	// deleted_yn

			// 디렉토리 경우 hash가 없는 경우도 있으므로 파일만 hash 사용
			szResultHasNameTmp1 = NULL;
			if ( strResultType.compare("0") == 0 )
				szResultHasNameTmp1 = strrchr(pPgSQL->GetValue(n, 5) + 1, '/');				// filename_hash ( 볼륨 정보 삭제함)

			if (szResultHasNameTmp1)
				strResultFilenameHash = szResultHasNameTmp1;
			else
				strResultFilenameHash = "";

			if(strResultUri.size() <= 0)				// URI가 없는 경우 저장 안함
				continue;

			if(strResultType.size() <= 0)				// 파일 타입값이 NULL 일 경우 0으로 기본 셋팅
				strResultType = "0";

			if (strResultLastDate.size() <= 0)				// 파일 크기값이 NULL 일 경우 0으로 기본 셋팅
				strResultLastDate = "0";

			// 해당 정보를 찾은 경우
			pSourceData->m_dataFile.Write("%s|%s|%s|%s|%s|%s", 
										strResultUri.c_str(), 
										strResultFilenameHash.c_str(),
										strResultType.c_str(), 
										strResultLength.c_str(), 
										strResultLastDate.c_str(),
										strResultDeletedYN.c_str());
		}
#ifdef _DEBUG
		cout << "Generate tmp file : "	<< pSourceData->m_dataFile.GetFilePath()
			 << ","						<< pSourceData->m_dataFile.GetFileName()
			 << ", run count="			<< nResult
			 << " / "					<< nNowResult - MAX_LIMIT_OFFSET_COUNT
			 << endl;
#endif // _DEBUG		
	} while (getlimit == nResult);	// do while()

	//////////////////////////////////////////////////////////////////////////////////////////////////
		
	// DB Object 삭제 처리.
	if( pPgSQL != NULL )
	{
		pPgSQL->PgClear();
		pPgSQL->PgCloseDB();
		delete pPgSQL;
		pPgSQL = NULL;
	}

	return NULL;
}

bool CSourceData::IsSkipKeyword(string key)
{
	bool r = false;
	vector<string> skipwords = CServiceConfig::GetInstance()->GetSkipWord();
	int keycnt = skipwords.size();
	

	for( int i=0; i< keycnt; ++i )
	{
		string k = skipwords[i];
		vector<string> v;
		StringSplit(k, "|", v);

		string::size_type pos;

		pos = key.find(v[1]);
		if ( pos != string::npos )
		{
			switch (v[0][0]) 
			{
			case 'h':
				if (pos == 0)
				{
					if (v[1] == key || key.at(v[1].size()) == '/')
						r = true;
				}
					
				break;
			case 'm':
				if(pos > 0 && key.size() > pos + v[1].size())
					r = true;
				break;
			case 't':
				if(pos > 0 && key.size() == pos + v[1].size())
					r = true;
				break;
			default:
				break;
			}
		}
	}

	return r;
}

// 2014.06.07 dadamin
// 작업 대상 테이블
string CSourceData::GetTargetTable(DataBase *d)
{
	string r;

	ostringstream msg;

	if (d == NULL)
		return "";

	string sql = "SELECT * FROM pg_tables WHERE schemaname = 'public' AND tablename = 't_dav_resource'";
	d->PgDoExec(sql);
	if (d->PgResult(DataBase::NOT_CLEAR) < 0)
	{
		d->PgClear();
		msg << "SetTargetTableType failed : " << d->GetErrorMessage();
		LOG(LERR, msg.str().c_str());

		return "";
	}

	if (d->GetNoTuples() > 0)
	{
		m_usedisplay = false;
		r = "t_dav_resource";
	}
	else
	{
		m_usedisplay = true;
		if (m_ismaster)
			r = "t_meta_" + m_sourceParam.sync_master;
		else
			r = "t_meta_" + m_sourceParam.sync_slave;
	}

	d->PgClear();
	msg << "Set Target Table : " << r;

	LOG(LDBG, msg.str().c_str());
		
	return r;
}

static int64_t convert(string &s)
{
	if(s.size() == 0 ) 
		return 0;

	int64_t v = 0;
	size_t i=0;
	char sign = (s[0] == '-' || s[0] == '+') ? (++i, s[0]) : '+';
	for( ; i < s.size(); ++i)
	{
		if ( s[i] < '0' || s[i] > '9' )
			return 0;
		v = v * 10 + s[i] - '0';
	}
	return sign == '-' ? -v : v;
}

// 해당 테이블 전체 rows
int64_t CSourceData::GetRows(DataBase *d, const char* tblname)
{
	int64_t r = 0;
	ostringstream msg;
	// SELECT relname, reltuples from pg_class where relname ='t_meta_346';

	if (d == NULL)
		return 0;
	
	ostringstream sql;

	sql << "SELECT relname, reltuples::bigint from pg_class where relname = "; 
	sql << "'" << tblname << "'";

	d->PgDoExec((char*)sql.str().c_str());
	if (d->PgResult(DataBase::NOT_CLEAR) < 0)
	{
		d->PgClear();
		msg << "Get Table Rows failed : " << d->GetErrorMessage();
		LOG(LERR, msg.str().c_str());
		return 0;
	}

	if (d->GetNoTuples() > 0)
	{
		string t = d->GetValue(0, 1);
		r = convert(t);
	}
	
	msg << "Table : " << tblname << ", Rows : "<< r;
	LOG(LDBG, msg.str().c_str());
	d->PgClear();
	return r;
}

