/***************************************************************************
                Database Connection Pool ( DbConnPool.cpp )
                -----------------------------------------

	begin      : 2013/02/26
	copyright  : (C) 2013 Solbox Inc.
	author     : Development 1 Team
					- 2013/02/26 - 1st dadamin
	email      : dev1@solbox.com
	version    : 3.2.0
	
    CopyRight(C) 2005 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 "rc_mngd.h"
#include "Configs.h"
#include "DbConnPool.h"
#include "Database.h"
#include "Logger.h"
#include "UtilFn.h"

#include <unistd.h>



#define  GET_SLEEP 1000 // microsecond

CMasterDBpool *CMasterDBpool::m_inst = NULL;
CSlaveDBpool *CSlaveDBpool::m_inst = NULL;

CConnDBpool::CConnDBpool(CDatabaseInfo info)
	: m_dbinfo(info), m_exitpool(false), m_lastset(NULL)
	 ,m_poolcnt(0), m_isrecreate(false), m_alivetime(-1)
	 ,m_tabletype(-1)
{
	pthread_mutex_init(&m_mutex, NULL);
	pthread_mutex_init(&m_alivemutex, NULL);
	pthread_cond_init(&m_alivecond, NULL);
}

CConnDBpool::~CConnDBpool()
{
	pthread_mutex_destroy(&m_mutex);
	pthread_mutex_destroy(&m_alivemutex);
	pthread_cond_destroy(&m_alivecond);
}

int CConnDBpool::MakeConnect( int pool )
{
	ostringstream msg;
	for(int i=0 ; i < pool ; i++)
	{
		DataBase * tmp = new DataBase();

		if( tmp->PgOpenDB( m_dbinfo.m_szRcdbIp, m_dbinfo.m_nRcdbPort, 
			m_dbinfo.m_szRcdbName, m_dbinfo.m_szRcdbAcct, m_dbinfo.m_szRcdbAcctPw ) == NULL )
		{
			msg << "Database connect error : " << tmp->GetErrorMessage();
			LOGACONSOLE(LERR, msg);
			delete tmp;
			continue;
		}

		// XML library(UTF-8 자동 변환) 인코딩 문자를 지원하기 위해서 DB client UTF-8 설정함
		if (tmp->PgSetClientEncoding("UTF-8") != 0)
		{
			msg << "Database Set Client Encoding error : " << tmp->GetErrorMessage();
			LOGACONSOLE(LERR, msg);
			delete tmp;
			continue;
		}

		// 2014.06.06 dadamin
		// Pool 생성 시 현재 생성된 DB의 형상을 체크한다.
		if (m_tabletype < 0)
		{
			if (SetTargetTableType(tmp) == false)
			{
				delete tmp;
				DestroyPool();
				break;
			}
		}
		
		m_poolmap.insert(make_pair(tmp, CConnDBpool::POOL_FREE));
	}

	return m_poolmap.size();
}

int CConnDBpool::CreatePool( int poolcnt /* = 4 */ )
{
	ostringstream msg;
	// keep alive thread
	int nRet = pthread_create(&m_keepalive, 0, CConnDBpool::KeepPoolAlive, this);
	if( nRet )
	{
		msg << "Thread create failed.: errno: " << errno;
		LOGACONSOLE(LERR, msg);
		return -1;
	}

	// pool
	m_poolcnt = MakeConnect(poolcnt);

	if (m_poolcnt == 0)
	{
		// alive thread end
		m_exitpool = true;
		pthread_cond_signal(&m_alivecond);
		pthread_join(m_keepalive, NULL);
		m_exitpool = false;
		return -1;
	}
	
	return m_poolcnt;
}

int CConnDBpool::DestroyPool()
{
	m_exitpool = true;
	map<DataBase*, short>::iterator iter;
	pthread_mutex_lock(&m_mutex);
	while(m_poolmap.size() > 0 )
	{
		iter =  m_poolmap.begin();
		if(iter->second == CConnDBpool::POOL_FREE 
			|| iter->second == CConnDBpool::POOL_ERR
			|| iter->second == CConnDBpool::POOL_CLOSE )
		{
			DataBase *data = static_cast<DataBase *>(iter->first);
			delete (DataBase *) data;
			m_poolmap.erase(iter);
		}
		else
			sleep(1);
	}
	pthread_mutex_unlock(&m_mutex);

	//
	pthread_cond_signal(&m_alivecond);
	pthread_join(m_keepalive, NULL);

	/*
	for( iter = m_poolmap.begin(); !m_poolmap.empty()&& iter != m_poolmap.end(); iter++ )
	{
		if(iter->second == conndbpool::POOL_FREE)
		{
			DataBase *data = static_cast<DataBase *>(iter->first);
			delete (DataBase *) data;
			m_poolmap.f
			m_poolmap.erase(iter);
		}
	}
	*/

	return m_poolmap.size();
}

DataBase * CConnDBpool::GetConnFromPool(int timeout)
{
	ostringstream msg;
	DataBase * r = NULL;

	int64_t usetime = 0;
	int64_t out = timeout*1000*1000;

	time_t t = time(NULL);
	pthread_mutex_lock(&m_mutex);
	map<DataBase*, short>::iterator iter = m_poolmap.begin();
	if(m_lastset)
	{
		iter = m_poolmap.find(m_lastset);
		if( iter != m_poolmap.end() )
		{
			++iter;
		}
	}

	do
	{		

		if( m_poolmap.empty() )
		{
			int make = 0;
			if(m_isrecreate)
			{
#ifdef _DEBUG
				msg << "DB Pool ReCeate.";
				LOGACONSOLE(LDBG, msg);
#endif // _DEBUG
				make = MakeConnect(m_poolcnt);
			}
			
			if (make == 0)			
			{
				msg << "DB Pool empty.";
				LOGACONSOLE(LERR, msg);
				break;
			}
		}

		if( iter == m_poolmap.end() )
		{
			iter = m_poolmap.begin();
		}

		for( ; !m_poolmap.empty()&& iter != m_poolmap.end(); )
		{
			if(iter->second == CConnDBpool::POOL_FREE)
			{
				iter->second = CConnDBpool::POOL_USE;
				r = iter->first;
				m_lastset = r;
				break;
			}
			else if (iter->second == CConnDBpool::POOL_ERR 
				|| iter->second == CConnDBpool::POOL_CLOSE)
			{
				bool is_closed = (iter->second == CConnDBpool::POOL_CLOSE);
				DataBase * d = iter->first;

				if(is_closed)
				{
					msg << "Force Closed. And Add Database object.";
					LOGACONSOLE(LDBG, msg);
					MakeConnect(1);
				}
				
				m_poolmap.erase(iter++);
				if(d == m_lastset)
					m_lastset = NULL;
				delete d;
			}
			else 
				iter++;
		}
		
		if( r == NULL)
		{
			solusleep(GET_SLEEP);
			if( usetime > 10*1000*1000)
			{
#ifdef _DEBUG
				msg << "GetConnFromPool have waited longer than 10 seconds.";
				LOGACONSOLE(LDBG, msg);
#endif // _DEBUG
			}

			if( out > 0 )
			{			
				usetime += GET_SLEEP;

				if(usetime > out)
				{				
					msg << "GetConnFromPool Timeout." << usetime << "," << out << 
						"," << time(NULL) -t;
					LOGACONSOLE(LWAR, msg);
					break;
				}
			}
		}	

	} while (r == NULL);
	pthread_mutex_unlock(&m_mutex);

	return r;
}

void CConnDBpool::ReleaseConnToPool(DataBase * t, short nMode /* = CConnDBpool::POOL_USE */)
{
	//pthread_mutex_lock(&m_mutex);

	map<DataBase*, short>::iterator iter = m_poolmap.find(t);
	if( iter != m_poolmap.end() )
	{
		if(iter->second==CConnDBpool::POOL_USE)
		{
			if( nMode == CConnDBpool::POOL_USE)
			{
				iter->second = CConnDBpool::POOL_FREE;
			}
			else 
			{
				iter->second = nMode;
			}
		}
	}

	//pthread_mutex_unlock(&m_mutex);
}

bool CConnDBpool::IsTimeoutAlive()
{
	bool r = false;
	pthread_mutex_lock(&m_alivemutex);
	struct timespec to;

	if(m_alivetime > 0 )
		to.tv_sec = time(NULL) + m_alivetime;
	else
		to.tv_sec = time(NULL) + 5;

	to.tv_nsec = 0;

	int err = pthread_cond_timedwait(&m_alivecond, &m_alivemutex, &to);

	if (err == ETIMEDOUT)
	{
		if( m_alivetime > 0 )
			r = true;
	}
	else if ( err == 0 )
	{
#ifdef _DEBUG
		cout << "Set Keep alive Timeout : " << m_alivetime << endl;
#endif //_DEBUG
		r = false;
	}
	else
	{
		/* nothing */
	}

	pthread_mutex_unlock(&m_alivemutex);

	return r;
}

bool CConnDBpool::SendAliveMsg()
{
	ostringstream msg;
	ostringstream sql;
	
	pthread_mutex_lock(&m_mutex);
	map<DataBase*, short>::iterator iter = m_poolmap.begin();
	map<DataBase*, short>::iterator enditer = m_poolmap.end();

	sql << "SELECT sp_user_seq, sp_svc_tran_id FROM t_sms_sp_svc_product LIMIT 1";
	while(iter != enditer)
	{
		DataBase * d = iter->first;
		if(iter->second == CConnDBpool::POOL_FREE)
		{
			iter->second = CConnDBpool::POOL_USE;
			d->PgDoExec( const_cast<char*> (sql.str().c_str()) );
			if( d->PgResult(DataBase::CLEAR) < 0 )
			{				
				msg << "Send alive : message failed : " << iter->first->GetErrorMessage();
				LOGACONSOLE(LERR, msg);
				iter->second=CConnDBpool::POOL_ERR;
				iter++;
				//m_poolmap.erase(iter++);
				//delete d;
			}
			else
			{
				iter->second = CConnDBpool::POOL_FREE;
				msg << "Send alive : message success.";
				LOGACONSOLE(LDBG, msg);
				++iter;
			}
		}
		else if (iter->second == CConnDBpool::POOL_ERR)
		{
			msg << "Send alive : Error Pool remove.";
			LOGACONSOLE(LINF, msg);
			m_poolmap.erase(iter++);
			delete d;
		}
		else 
			++iter;		
	}

	if( m_poolmap.empty() )
	{
		int make = 0;
		if(m_isrecreate)
		{
#ifdef _DEBUG
			msg << "DB Pool ReCeate.";
			LOGACONSOLE(LDBG, msg);
#endif // _DEBUG
			make = MakeConnect(m_poolcnt);
		}

		if (make == 0)			
		{
			msg << "DB Pool empty.";
			LOGACONSOLE(LERR, msg);
		}
	}

	pthread_mutex_unlock(&m_mutex);
	return true;
}

void* CConnDBpool::KeepPoolAlive( void* pdata )
{
	CConnDBpool* pObject = reinterpret_cast<CConnDBpool *>(pdata);
	ostringstream msg;

	while( pObject->IsExit() == false )
	{
		bool b = pObject->IsTimeoutAlive();
		msg << "KeepPoolAlive Timeout - run : " << b;
		LOGACONSOLE(LDBG, msg);
		if( pObject->IsExit() == false && b )
		{
			// send keep alive message
			pObject->SendAliveMsg();
		}
	}
	return 0;
}

int	CConnDBpool::SetKeepAliveTimeout(int sec)
{
	m_alivetime = sec;
	pthread_cond_signal(&m_alivecond);
	return m_alivetime;
}

int CConnDBpool::GetFreePoolCnt()
{
	int r = 0;
	map<DataBase*, short>::iterator iter;
//	pthread_mutex_lock(&m_mutex);
	for( iter = m_poolmap.begin(); !m_poolmap.empty()&& iter != m_poolmap.end(); iter++ )
	{
		if(iter->second == CConnDBpool::POOL_FREE)
		{
			r++;
		}
	}
//	pthread_mutex_unlock(&m_mutex);
	return r;
}

void CConnDBpool::printstatus(string prefixed)
{	
	int f = 0, u = 0;
	map<DataBase*, short>::iterator iter;
	
	if(m_exitpool == true ) return;
	//	pthread_mutex_lock(&m_mutex);
	for( iter = m_poolmap.begin(); !m_poolmap.empty()&& iter != m_poolmap.end(); iter++ )
	{
		if(iter->second == CConnDBpool::POOL_FREE)
		{
			f++;
		}
		else 
		{
			u++;
		}
	}

	ostringstream msg;
	if( prefixed.empty() == false )
		msg << "["<< prefixed <<"]";

	msg <<"DB connection Pool - " <<"total : " << m_poolmap.size() << "(" << u << 
		"/" << f << ")";
	
	LOGACONSOLE(LINF, msg);
}

// 2014.06.07 dadamin
// 현재 DB 형상 체크
bool CConnDBpool::SetTargetTableType(DataBase * d)
{
	ostringstream msg;

	if (d == NULL)
		return false;

	d->PgDoExec("SELECT *  FROM pg_tables WHERE schemaname =  'public' AND tablename =  't_dav_resource'");
	if (d->PgResult(DataBase::NOT_CLEAR) < 0)
	{
		d->PgClear();
		msg << "Set Target Table Type failed : " << d->GetErrorMessage();
		LOGACONSOLE(LERR, msg);

		return false;
	}

	if (d->GetNoTuples() > 0)
	{
		m_tabletype = 0;
	}
	else
	{
		m_tabletype = 1;
	}

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

	LOG(LDBG, msg.str().c_str());

	return true;
}

// master db pool
CMasterDBpool::CMasterDBpool(CDatabaseInfo info)
	: CConnDBpool(info)
{
}

CMasterDBpool::~CMasterDBpool()
{
}

void CMasterDBpool::init(CDatabaseInfo info)
{
	if( CMasterDBpool::m_inst == NULL )
	{
		CMasterDBpool::m_inst = new CMasterDBpool(info);
	}
}

CMasterDBpool* CMasterDBpool::getInstance()
{
	return CMasterDBpool::m_inst;
}

int CMasterDBpool::release()
{
	int r = -1;
	if( CMasterDBpool::m_inst != NULL )
	{
		r = CMasterDBpool::m_inst->DestroyPool();
		delete CMasterDBpool::m_inst;
		CMasterDBpool::m_inst = NULL;
	}

	return r;
}

// slave db pool
CSlaveDBpool::CSlaveDBpool(CDatabaseInfo info)
	: CConnDBpool(info)
{
}

CSlaveDBpool::~CSlaveDBpool()
{
}

void CSlaveDBpool::init(CDatabaseInfo info)
{
	if( CSlaveDBpool::m_inst == NULL )
	{
		CSlaveDBpool::m_inst = new CSlaveDBpool(info);
	}
}

CSlaveDBpool* CSlaveDBpool::getInstance()
{
	return CSlaveDBpool::m_inst;
}

int CSlaveDBpool::release()
{
	int r = -1;
	if(CSlaveDBpool::m_inst != NULL )
	{
		r = CSlaveDBpool::m_inst->DestroyPool();
		delete CSlaveDBpool::m_inst;
		CSlaveDBpool::m_inst = NULL;
	}

	return r;
}
