/***************************************************************************
                Work Pool
                -----------------------------------------
	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 "WorkPool.h"
#include "Validation.h"
#include "Logger.h"
#include "Util.h"

#include <unistd.h>

#include <errno.h>
#include <iostream>

#define	DIFF_SPLIT_CNT	6		// diff file format split count
#define SLEEP_TIME		1000	//microsecond

#define  _LOGMSG(level, msg) \
	_LOG(level, "%s", msg.str().c_str());\
	msg.str("");

CWorkPool *CWorkPool::m_inst = NULL;

// work
CWork::CWork()
	: m_step(-5), m_exit(false), m_enablesync("0000")
{
	m_nofi = NULL;
	pthread_mutex_init(&m_workmutex, NULL);
	pthread_cond_init(&m_workcond, NULL);	
}

CWork::~CWork()
{
	pthread_mutex_destroy(&m_workmutex);
	pthread_cond_destroy(&m_workcond);
}

void* CWork::working(void * pdata)
{
	ostringstream msg;
	CWork* pObject = reinterpret_cast<CWork *>(pdata);

	//pthread_detach( pthread_self() );
#ifdef _DEBUG
	cout << "work::working start - " << pthread_self() << endl;
#endif // _DEBUG
	while(pObject->m_exit == false)
	{
		pthread_mutex_lock(&pObject->m_workmutex);
		pObject->m_step = -1;
		int err = pthread_cond_wait(&pObject->m_workcond, &pObject->m_workmutex);
		pObject->m_step = 0;
#ifdef _DEBUG
		cerr << "work::working - signal : " << pthread_self() << "," <<  pObject->m_exit << endl;
#endif // _DEBUG
		if ( err == 0 && pObject->m_exit == false)
		{
			pObject->m_step = 1;
			short success = 0;
						
			msg << "Valid Start : " << pObject->m_rundata;
			_LOGMSG(LDBG, msg);

			do
			{
				// add uri 
				pObject->m_step = 2;
				if (CWorkPool::getInstance()->addworkuri(pObject->m_syncinfo.sync_master,
														pObject->m_rundata, 
														pObject->m_workuri, true)
					== false)
				{
					LOG(LERR, "Failed work uri map add [%s].", pObject->m_rundata.c_str());
					success = -1;
					break;
				}

				// validation	
				pObject->m_step = 3;
				CValidation v(pObject->m_syncinfo, pObject->m_rundata);

				if (v.CheckValidation() == false)
					success = -2;

				pObject->m_step = 4;
			} while (false);

			if(pObject->m_nofi)
			{
				pObject->m_step = 6;
#ifdef _DEBUG
				cerr << "Work Thread Notify Call."<< endl;
#endif //_DEBUG
				pObject->m_nofi(pObject->m_parm, success);
			}
			pObject->m_step = 7;
			
			msg << "Valid End : " << pObject->m_rundata;
			_LOGMSG(LDBG, msg);
			
			if(pObject->m_workuri.size())
			{
				CWorkPool::getInstance()->delworkuri(pObject->m_workuri);
			}
			CWorkPool::getInstance()->ReleaseWork(pObject);
		}
		else
		{	
			pObject->m_step = -3;
			if(pObject->m_exit == false)
			{
				pObject->m_step = -4;
				msg << "Work Thread Error.";
				//LOGACERR(LCRT, msg);
				LOG(LCRT, msg.str().c_str());
			}			
			else
			{
				pObject->m_step = -5;
				/* nothing -- signal exit */
			}
		}
		pObject->m_step = -6;
		pthread_mutex_unlock(&pObject->m_workmutex);
	}
#ifdef _DEBUG
	cerr << "work::working end - " << pthread_self() << endl;
#endif // _DEBUG
	return NULL;
}

bool CWork::init()
{
	int nRet = pthread_create(&m_thread, 0, CWork::working, this);
	if( nRet )
	{
		cerr << "Thread create failed.: errno: " << errno << endl;
		return false;
	}

	sleep(0);

	return true;
}

void CWork::setexit(bool v)
{
	m_exit = v;
	pthread_mutex_lock(&m_workmutex);
	pthread_cond_signal(&m_workcond);
	pthread_mutex_unlock(&m_workmutex);
}

bool CWork::run(CSyncInfo info, string rundata, work_notifyfn nofi /* = NULL */, void * parm /* = NULL */)
{	
	m_syncinfo = info;
	m_rundata = rundata;
	
	m_parm = parm;
	m_nofi = nofi;
	
	ostringstream msg;
	//msg << "Work Run : TranID(" <<m_master.m_tranid <<"=>" << m_slave.m_tranid << 
	//	"), UserSeq(" << m_master.m_userseq << "=>" << m_slave.m_userseq << ")" <<
	//	", Sync Data - " << m_rundata;

	//_LOGACOUT(LDBG, msg);

	//spin lock
	int64_t looptime = 0;
	do 
	{
		solusleep(SLEEP_TIME);
		looptime += SLEEP_TIME;
		if( looptime > (10 *1000*1000) )
		{
			msg << "Work Loop.... 10 sec over :" << m_rundata;
			LOG(LNOT, "%s", msg.str().c_str());
			break;
		}
	} while (m_step != -1);
	
	pthread_cond_signal(&m_workcond);

	return true;
}

// work pool
CWorkPool::CWorkPool()
{
	pthread_mutex_init(&m_mutex, NULL);
	pthread_mutex_init(&m_mutexuri, NULL);
	pthread_mutex_init(&m_mutextry, NULL);	
}

CWorkPool::~CWorkPool()
{
	pthread_mutex_destroy(&m_mutex);
	pthread_mutex_destroy(&m_mutexuri);
	pthread_mutex_destroy(&m_mutextry);	
}

CWorkPool* CWorkPool::getInstance()
{
	if(CWorkPool::m_inst == NULL)
	{
		CWorkPool::m_inst = new CWorkPool();
	}
	return CWorkPool::m_inst;
}

void CWorkPool::release()
{
	if( CWorkPool::m_inst != NULL)
	{
		m_inst->DestroyPool();
		delete CWorkPool::m_inst;
		CWorkPool::m_inst = NULL;
	}
}

int CWorkPool::CreatePool( int poolcnt /* = 10 */ )
{
	// pool
	for(int i=0 ; i < poolcnt ; i++)
	{
		srand ( time(NULL) );
		CWork * w = new CWork();
		if( w->init() == false)
		{
			delete w;
			break;
		}

		//m_pool.insert(make_pair(w, CWorkPool::NOTWORK));
		m_pool.insert(pair<CWork *, short>(w, CWorkPool::NOTWORK));
	}

	if( m_pool.size() < static_cast<unsigned int>(poolcnt) )
	{
		DestroyPool();
		return -1;
	}

	return 0;
}

int CWorkPool::DestroyPool()
{
	map<CWork*, short>::iterator iter;
	while(m_pool.size() > 0 )
	//for( iter = m_pool.begin(); !m_pool.empty()&& iter != m_pool.end(); iter++ )
	{
		iter =  m_pool.begin();
		if(iter->second == CWorkPool::NOTWORK)
		{
			CWork *data = static_cast<CWork *>(iter->first);
			data->setexit(true);
			pthread_join(*data->getworkhandle(), NULL);
			m_pool.erase(iter);
			delete (CWork *)data;
		}

		//sleep(1);
	}
	//m_pool.clear();

	return m_pool.size();
}

CWork* CWorkPool::GetWorkPool(int timeout)
{
	CWork * r = NULL;

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

	do
	{
		pthread_mutex_lock(&m_mutex);
		map<CWork*, short>::iterator iter;
		for( iter = m_pool.begin(); !m_pool.empty()&& iter != m_pool.end(); iter++ )
		{
			if(iter->second == CWorkPool::NOTWORK)
			{
				iter->second = CWorkPool::WORKING;
				r = iter->first;
				break;
			}
		}		

		if( r == NULL )
		{
			solusleep(SLEEP_TIME);	
			if( out > 0 )
			{
				usetime += SLEEP_TIME;
				if( usetime > out)
				{
					ostringstream msg;
					msg << "Get Work Pool Timeout.";
					//LOGACERR(LERR, msg);
					LOG(LERR, msg.str().c_str());
					pthread_mutex_unlock(&m_mutex);
					break;
				}
			}
		}
		pthread_mutex_unlock(&m_mutex);
	} while (r == NULL);

	return r;
}

void CWorkPool::ReleaseWork(CWork *w)
{
	//pthread_mutex_lock(&m_mutex);

	map<CWork*, short>::iterator iter = m_pool.find(w);
	if( iter != m_pool.end() )
	{
		if(iter->second==CWorkPool::WORKING)
			iter->second=CWorkPool::NOTWORK;
	}
	else
	{
		ostringstream msg;
		msg << "unknown work pool.";
		//LOGACERR(LWAR, msg);
		LOG(LWAR, msg.str().c_str());
	}

	//pthread_mutex_unlock(&m_mutex);
}

void CWorkPool::printstatus(string prefixed)
{
	int w = 0, n = 0;
	map<CWork*, short>::iterator iter;
	for( iter = m_pool.begin(); !m_pool.empty()&& iter != m_pool.end(); iter++ )
	{
		if(iter->second == CWorkPool::NOTWORK)
		{
			++n;
		}
		else 
		{
			++w;
		}
	}		

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

	msg << "Work Pool - " <<"total : " << m_pool.size() << "(" << w << 
		"/" << n << ")";
	//_LOGACOUT(LINF, msg);
	_LOG(LINF, msg.str().c_str());
}

void CWorkPool::printstatusex(string prefixed)
{
	ostringstream msg;
	map<CWork*, short>::iterator iter;
	for( iter = m_pool.begin(); !m_pool.empty()&& iter != m_pool.end(); iter++ )
	{
		if(iter->second == CWorkPool::NOTWORK)
		{
			msg << "Work Pool(NOTWORK) - " << iter->first->getrundata() << "," <<
				iter->first->getsetp();
		}
		else 
		{
			msg << "Work Pool(WORKING) - " << iter->first->getrundata() << "," <<
				iter->first->getsetp();
		}
		if( prefixed.empty() == false )
			msg << "[" << prefixed << "]";
		//_LOGACOUT(LINF, msg);
		_LOG(LINF, "%s", msg.str().c_str());
	}		
}

bool CWorkPool::addworkuri(string tranid, string uri, string &workuri, 
					bool sourcedata, int timeout)
{
	bool r = true;
	
	string key;
	ostringstream msg;
	
	if(sourcedata)
	{
		vector<string> strSplit;
		StringSplit(uri, "|", strSplit, true);
		if(strSplit.size() != DIFF_SPLIT_CNT)
		{
			pthread_mutex_unlock(&m_mutexuri);
			msg << "addworkuri sync file string split caution : [" << uri << "]";
			//LOGACERR(LERR, msg);
			LOG(LERR, "%s", msg.str().c_str());
			return false;
		}
		key	= "/" + tranid + strSplit[0] + "|[Size=" + strSplit[2] + "]";
	}
	else 
		key = "/"+ tranid + uri;

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

TRY_ADD_WORK_URI:
	pthread_mutex_lock(&m_mutexuri);
	workurimap::iterator find = m_workurimap.find(key);

	if (find != m_workurimap.end()) 
	{
	
		pthread_mutex_unlock(&m_mutexuri);
		
		msg << "Work uri duplication occurred and to add work uri try again." << key;
		_LOGMSG(LDBG, msg);
						
		// try
		solusleep(SLEEP_TIME);
		usetime += SLEEP_TIME;
					
		if(timeout > 0 && usetime > out)
		{
			r = false;
			msg << "Work adduri Timeout." << key;
			//LOGACERR(LERR, msg);
			LOG(LERR, "%s", msg.str().c_str());

			pthread_mutex_unlock(&m_mutexuri);
			return false;
		}
		goto TRY_ADD_WORK_URI;
	}
	else
	{
		// add
		workuri = key;
		m_workurimap[key] = 1;
		//pthread_mutex_unlock(&m_mutexuri);
	}
	
	pthread_mutex_unlock(&m_mutexuri);
	return r;
}

bool CWorkPool::delworkuri(string key)
{
	pthread_mutex_lock(&m_mutexuri);
	// deleted
	m_workurimap.erase(key);
	pthread_mutex_unlock(&m_mutexuri);
	return true;
}
