/***************************************************************************
				Service Info Updater
                -----------------------------------------

	begin      : 2013/09/16
	copyright  : (C) 2005 Solbox Inc.
	author     : Development 1 Team
					- 2013/09/16
	email      : dev1@solbox.com
	version    : 3.2.0

	CopyRight(C) 2005 Solbox Inc. All Rights reserved.
	Redistribution and use in source and binary forms, with or with out
	modification, are not permitted in outside of Solbox Inc.
***************************************************************************/

#include <fcntl.h>
#include <errno.h>
#include "Database.h"
#include "pthread.h"
#include "time.h"
#include "ServiceInfoUpdater.h"

extern struct st_config g_envConfig;

#define MX_LOCK_DATA()\
do \
{\
	pthread_mutex_lock(&m_mxData);\
} while( 0 )

#define MX_UNLOCK_DATA()\
do \
{\
	pthread_mutex_unlock(&m_mxData);\
} while( 0 )

#define DEFAULT_INSERT_INTERVAL  ((1)  * (180) ) // 180sec
//#define DEFAULT_INSERT_INTERVAL  ((1)  * (10) ) // 5sec

#define DEFAULT_BUFFER_SIZE 2048

CServiceInfoUpdater::CServiceInfoUpdater()
{
	m_mapCustomerInfoTemp = NULL;
	m_mapCustomerInfo = NULL;
	pthread_mutex_init(&m_mxData, NULL);
}

CServiceInfoUpdater::~CServiceInfoUpdater()
{
	if( m_pThreadHandle != NULL )
	{
		pthread_cancel( *m_pThreadHandle );
		delete m_pThreadHandle;
		m_pThreadHandle = NULL;
	}
	pthread_mutex_destroy(&m_mxData);
    //pthread_cancel(m_threadHandle);
}

/// @brief
/// 입력받은 데이터가 유효한지만 확인 하기 위해 DataBase를 로컬로 선언한다.
bool CServiceInfoUpdater::Init(const std::string &szHost, const int nPort, const std::string &szDBName, const std::string &szAcct, const std::string &szPasswd)
{
	DataBase * pPgSQL = new DataBase;
	char szQuery[DEFAULT_BUFFER_SIZE];

	if( szHost.empty() 
		|| nPort < 0
		|| szDBName.empty()
		|| szAcct.empty()
		|| szPasswd.empty() )
	{
		// invalid argument
		return false;
	}
	/// @brief DB connection information
	m_szHost = szHost;
	m_nPort = nPort;
	m_szDBName = szDBName;
	m_szAcct = szAcct;
	m_szPasswd = szPasswd;

	if( pPgSQL == NULL )
	{
		LOG( LERR, "Creating new Database has failed.");
		return false;
	}
	if( pPgSQL->PgOpenDB(m_szHost, m_nPort, m_szDBName, m_szAcct, m_szPasswd) == NULL )
	{
		LOG( LERR, "Connecting to Database has failed");
		if (pPgSQL != NULL) 
		{
			delete pPgSQL;
			pPgSQL = NULL;
		}
		return false;
	}

	// 이부분에서 cs_ccinfo의 base_domain값을 멤버로 갖고 있도록 한다.
    // 1. 쿼리를 생성.
    snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
            "SELECT base_domain FROM cs_service.cs_cc_info limit1;"); 
         
    // 2. 쿼리 수행.
    pPgSQL->PgDoExec(szQuery);
    if ((pPgSQL->PgResult(DataBase::NOT_CLEAR)) < 0) 
    {
    	LOG(LERR, "Query failed. errmsg:%s\nQuery:%s", pPgSQL->GetErrorMessage().c_str(), szQuery);
    	if (pPgSQL != NULL) 
    	{
    		delete pPgSQL;
    		pPgSQL = NULL;
    	}
		// 실패 한 경우는 DB Connect 부터 다시 하도록 종료 한다.
    	return false;
    }

	if( pPgSQL->GetNoTuples() <= 0 )
	{
		LOG(LERR,"cs_cc_info is not exist.");
		if( pPgSQL != NULL )
		{
			pPgSQL->PgClear();
			delete pPgSQL;
			pPgSQL = NULL;
		}

		return false;
	}

	m_szBaseDomain = pPgSQL->GetValue(0,0);
	LOG(LDEV,"BaseDomain : %s", m_szBaseDomain.c_str());

	if (pPgSQL != NULL) 
	{
		delete pPgSQL;
		pPgSQL = NULL;
	}

	return true;
}

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

bool CServiceInfoUpdater::Execute()
{
	char szQuery[DEFAULT_BUFFER_SIZE];
	int nResult = 0;
    if ( DbConnect() == false)
	{
		LOG(LERR,  "DbConnect() has returned false, so Execute() return false." );
        return false;
	}
	
	m_mapCustomerInfoTemp = new CCustomerInfoMap;
    // 1. CCDB에 등록된 고객정보중 삭제되지 않은 고객정보를 가져온다.
	// 1-1. 쿼리 생성
	memset(szQuery, 0x00, DEFAULT_BUFFER_SIZE);
    snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
			"SELECT user_seq, id, passwd "
                "FROM cs_service.cs_customer "
                "WHERE del_yn = 'N';");
         
    // 1-2. 쿼리 수행
    m_pPgSQL->PgDoExec(szQuery);
	// 1-3. 쿼리 결과확인
    if ((m_pPgSQL->PgResult(DataBase::NOT_CLEAR)) < 0) 
    {
    	LOG(LERR, "Query failed. errmsg:%s\nQuery:%s", m_pPgSQL->GetErrorMessage().c_str(), szQuery);
    	if (m_pPgSQL != NULL) 
    	{
    		delete m_pPgSQL;
    		m_pPgSQL = NULL;
    	}
		// 실패 한 경우는 DB Connect 부터 다시 하도록 종료 한다.
    	return false;
    }

	// 1-3. 쿼리 결과확인
	nResult = m_pPgSQL->GetNoTuples();
	if( nResult <= 0 )
	{
		LOG(LERR,"cs_cc_info is not exist.");
		if( m_pPgSQL != NULL )
		{
			m_pPgSQL->PgClear();
			delete m_pPgSQL;
			m_pPgSQL = NULL;
		}

		return false;
	}

	// 1-4. Data object에 담아야 한다.
	for(int i = 0; i < nResult; i++ )
	{
		CCustomerInfo objCustomerInfo;

		objCustomerInfo.m_szUserSeq		= m_pPgSQL->GetValue(i,0) ;
		objCustomerInfo.m_szUserID		= m_pPgSQL->GetValue(i,1) ;
		objCustomerInfo.m_szPassWord	= m_pPgSQL->GetValue(i,2) ;

		LOG(LDEV, "m_szUserSeq :%s", objCustomerInfo.m_szUserSeq.c_str());
		LOG(LDEV, "m_szUserID :%s", objCustomerInfo.m_szUserID.c_str());
		LOG(LDEV, "m_szPassWord :%s", objCustomerInfo.m_szPassWord.c_str());

		m_mapCustomerInfoTemp->insert(CCustomerInfoPair(objCustomerInfo.m_szUserID, objCustomerInfo));
	}
	// 1-5. 쿼리의 결과를 정리한다.
	m_pPgSQL->PgClear();

	// 1-6. DB에서 고객정보로 각 고객의 ServiceList를 얻어온다.
	LOG(LDEV, "------------------ Get Service Info -------------------------------" );
	for (CCustomerInfoMap::iterator it=m_mapCustomerInfoTemp->begin(); it!=m_mapCustomerInfoTemp->end(); ++it)
	{
		LOG(LDEV, "UserID :%s", it->first.c_str());

		CCustomerInfo* pobjCustomerInfo = &it->second;

		LOG(LDEV, "xxx_m_szUserSeq :%s", pobjCustomerInfo->m_szUserSeq.c_str());
		LOG(LDEV, "xxx_m_szUserID :%s", pobjCustomerInfo->m_szUserID.c_str());
		LOG(LDEV, "xxx_m_szPassWord :%s", pobjCustomerInfo->m_szPassWord.c_str());
		//고객의 서비스들을 DB로 부터 얻어온다.
		if( GetServiceInfo(pobjCustomerInfo->m_szUserSeq, &pobjCustomerInfo->mapServiceInfo) == false)
		{
			LOG(LDBG, "DB connection is not normal.");
			return false;
		}

		for (CServiceInfoMap::iterator iter=pobjCustomerInfo->mapServiceInfo.begin(); iter!=pobjCustomerInfo->mapServiceInfo.end(); ++iter)
        {
                CServiceInfo objServiceInfo = iter->second;
                LOG(LDBG, "m_szServiceID :%s", objServiceInfo.m_szServiceID.c_str());
        }
	}
	
	// 1-7 m_mapCustomerInfoTemp, 와 m_mapCustomerInfo를 바꿔준다.
	MX_LOCK_DATA();
	swap(m_mapCustomerInfoTemp, m_mapCustomerInfo);
	MX_UNLOCK_DATA();
	if( m_mapCustomerInfoTemp == NULL )
	{
		LOG(LDBG, "It's First Time");
	}
	else
	{
		if( m_mapCustomerInfoTemp->size() > 0 )
		{
			m_mapCustomerInfoTemp->clear();
			delete m_mapCustomerInfoTemp;
		}
	}

	DbClose();

	return true;
}

bool CServiceInfoUpdater::GetServiceInfo(string szUserSeq, CServiceInfoMap *pmapServiceInfo)
{
	char szQuery[DEFAULT_BUFFER_SIZE];
	int nResult = 0;
	// 2. 각 고객별로 
	// 2-1. 쿼리 생성
	memset(szQuery, 0x00, DEFAULT_BUFFER_SIZE);
	snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
			"SELECT svc_seq, svc_id, status_code, code "
                "FROM cs_service.cs_service "
                "WHERE del_yn = 'N' AND status_code != 'SVC_STATUS_REQUEST' AND status_code != 'SVC_STATUS_CLOSE' AND user_seq= '%s';", szUserSeq.c_str());
         
    // 2-2. 쿼리 수행
    m_pPgSQL->PgDoExec(szQuery);
	// 2-3. 쿼리 결과확인
    if ((m_pPgSQL->PgResult(DataBase::NOT_CLEAR)) < 0) 
    {
    	LOG(LERR, "Query failed. errmsg:%s\nQuery:%s", m_pPgSQL->GetErrorMessage().c_str(), szQuery);
    	if (m_pPgSQL != NULL) 
    	{
    		delete m_pPgSQL;
    		m_pPgSQL = NULL;
    	}
		// 실패 한 경우는 DB Connect 부터 다시 하도록 종료 한다.
		return false;
    }

	// 2-3. 쿼리 결과확인
	nResult = m_pPgSQL->GetNoTuples();
	if( nResult <= 0 )
	{
		LOG(LWAR,"%s user is no have data.", szUserSeq.c_str());
		return true;
	}

	LOG(LDEV, "xxxxxxx Result : %d", nResult);
	// 2-4. Data object에 담아야 한다.
	for(int i = 0; i < nResult; i++ )
	{
		CServiceInfo objServiceInfo;

		objServiceInfo.m_szServiceSeq		= m_pPgSQL->GetValue(i,0) ;
		objServiceInfo.m_szServiceID		= m_pPgSQL->GetValue(i,1) ;
		objServiceInfo.m_szServiceDomain	= m_pPgSQL->GetValue(i,1) + string(".") + m_szBaseDomain;
		objServiceInfo.m_szStatusCode		= m_pPgSQL->GetValue(i,2) ;
		objServiceInfo.m_szCode				= m_pPgSQL->GetValue(i,3) ;

		LOG(LDEV, "m_szServiceSeq :%s", objServiceInfo.m_szServiceSeq.c_str());
		LOG(LDEV, "m_szServiceID :%s", objServiceInfo.m_szServiceID.c_str());
		LOG(LDEV, "m_szServiceDomain :%s", objServiceInfo.m_szServiceDomain.c_str());
		LOG(LDEV, "m_szStatusCode :%s", objServiceInfo.m_szStatusCode.c_str());

		pmapServiceInfo->insert(CServiceInfoPair(objServiceInfo.m_szServiceID, objServiceInfo));
	}

	// 이전 쿼리 결과를 지워준다.
	m_pPgSQL->PgClear();

	for (CServiceInfoMap::iterator it=pmapServiceInfo->begin(); it!=pmapServiceInfo->end(); ++it)
	{
		LOG(LDEV, "ServiceSeq :%s", it->first.c_str());

		CServiceInfo* pobjServiceInfo = &it->second;
		LOG(LDBG, "m_szServiceID :%s", pobjServiceInfo->m_szServiceID.c_str());

		// Cloud Streaming은 cs_service_config table에 정보가 두개 있다.
		memset(szQuery, 0x00, DEFAULT_BUFFER_SIZE);
		snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
				"SELECT * FROM cs_service.cs_service_config WHERE svc_seq = '%s';"
				, pobjServiceInfo->m_szServiceSeq.c_str() );

		// 3-2. 쿼리 수행
		m_pPgSQL->PgDoExec(szQuery);
		// 3-3. 쿼리 결과확인
		if ((m_pPgSQL->PgResult(DataBase::NOT_CLEAR)) < 0) 
		{
    		LOG(LERR, "Query failed. errmsg:%s\nQuery:%s", m_pPgSQL->GetErrorMessage().c_str(), szQuery);
    		if (m_pPgSQL != NULL) 
    		{
    			delete m_pPgSQL;
    			m_pPgSQL = NULL;
    		}
			// 실패 한 경우는 DB Connect 부터 다시 하도록 종료 한다.
    		return false;
		}
		
		int nServiceCount = m_pPgSQL->GetNoTuples();
		
		// 이전 쿼리 결과를 지워준다.
		m_pPgSQL->PgClear();
		LOG(LDEV, "Service Count : %d ", nServiceCount );
		// Cloud Streaming 서비스 타입이고, upload url에 정보가 없을 경우에는..
		// Cloud Streaming의 경우에는 Master 정보만 전달 해주면 된다.
		// 그러므로, Slave 서비스 이므로 서비스 정보를 map에서 지워버린다.
		//		if( strcmp(pobjServiceInfo->m_szCode.c_str(), "SVC_TYPE_C_01") == 0 )
		if( nServiceCount > 1)
		{
			LOG(LDEV, "kkkkkkkkkkkkkkkkkkkkkkkk");
			// 3-1. 쿼리 생성
			memset(szQuery, 0x00, DEFAULT_BUFFER_SIZE);
			snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
					"SELECT rc_domain, rcts_domain, upload_domain "
						"FROM cs_service.cs_service_config "
						"WHERE svc_seq = '%s' AND slave_svc_seq = '%s' "
					, pobjServiceInfo->m_szServiceSeq.c_str()
					, pobjServiceInfo->m_szServiceSeq.c_str() );
		}
		else
		{
			// 3-1. 쿼리 생성
			memset(szQuery, 0x00, DEFAULT_BUFFER_SIZE);
			snprintf(szQuery, DEFAULT_BUFFER_SIZE - 1,
					"SELECT rc_domain, rcts_domain, upload_domain "
						"FROM cs_service.cs_service_config "
						"WHERE svc_seq = '%s';", pobjServiceInfo->m_szServiceSeq.c_str() );
		}
         
		LOG(LDEV, "kkkkkkkkkkkkkkkkkkkkkkkk, %s", szQuery);
		// 3-2. 쿼리 수행
		m_pPgSQL->PgDoExec(szQuery);
		// 3-3. 쿼리 결과확인
		if ((m_pPgSQL->PgResult(DataBase::NOT_CLEAR)) < 0) 
		{
    		LOG(LERR, "Query failed. errmsg:%s\nQuery:%s", m_pPgSQL->GetErrorMessage().c_str(), szQuery);
    		if (m_pPgSQL != NULL) 
    		{
    			delete m_pPgSQL;
    			m_pPgSQL = NULL;
    		}
			// 실패 한 경우는 DB Connect 부터 다시 하도록 종료 한다.
    		return false;
		}

		// 3-3. 쿼리 결과확인
		if( m_pPgSQL->GetNoTuples() <= 0 )
		{
			LOG(LWAR,"%s service is no have data on cs_service_config tables.", pobjServiceInfo->m_szServiceID.c_str());
			m_pPgSQL->PgClear();
			continue;
		}

		pobjServiceInfo->m_szRcDomain		= m_pPgSQL->GetValue(0,0);
		pobjServiceInfo->m_szRctsDomain		= m_pPgSQL->GetValue(0,1);
		string szUploadUrl = m_pPgSQL->GetValue(0,2);

		m_pPgSQL->PgClear();
		LOG(LDEV, "m_szRcDomain :%s", pobjServiceInfo->m_szRcDomain.c_str());
		LOG(LDEV, "m_szRctsDomain :%s", pobjServiceInfo->m_szRctsDomain.c_str());
	}

	return true;
}

void* CServiceInfoUpdater::EntryPoint(void* arg)
{
    CServiceInfoUpdater* pObject = reinterpret_cast<CServiceInfoUpdater *>(arg);
	pthread_detach( pthread_self() );
    
    while( 1 )
    {
        pthread_testcancel();

		if( pObject->Execute() == false )
		{
			LOG( LWAR, "Execute() has returned false, so retry Execute() after 10 second.");
		}

		sleep(DEFAULT_INSERT_INTERVAL );
        pthread_testcancel();
    }
    
	// pthread_exit();
	return NULL;
}

// DO *NOT* FIX IT
bool CServiceInfoUpdater::Start()
{
	m_pThreadHandle = new pthread_t;

	//int nRet = ::pthread_create(&m_threadHandle, 0, CServiceInfoUpdater::EntryPoint, this);
	int nRet = ::pthread_create(m_pThreadHandle, 0, CServiceInfoUpdater::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;
}

bool CServiceInfoUpdater::GetUserServiceData(string szUserID, CCustomerInfo &objCustomerInfo)
{
	MX_LOCK_DATA();
	CCustomerInfoMap::iterator it =  m_mapCustomerInfo->find(szUserID);
	if (it == m_mapCustomerInfo->end() )
	{
		MX_UNLOCK_DATA();
		return false;
	}
	objCustomerInfo = it->second;

	LOG(LDEV, "xxx555_m_szUserSeq :%s", objCustomerInfo.m_szUserSeq.c_str());
	LOG(LDEV, "xxx555_m_szUserID :%s", objCustomerInfo.m_szUserID.c_str());
	LOG(LDEV, "xxx555_m_szPassWord :%s", objCustomerInfo.m_szPassWord.c_str());
	MX_UNLOCK_DATA();

	for (CServiceInfoMap::iterator iter=objCustomerInfo.mapServiceInfo.begin(); iter!=objCustomerInfo.mapServiceInfo.end(); ++iter)
	{
		LOG(LDEV, "ServiceSeq :%s", iter->first.c_str());
		CServiceInfo objServiceInfo = iter->second;
		LOG(LDBG, "m_szServiceID :%s", objServiceInfo.m_szServiceID.c_str());
	}

	return true;
}
	
