/***************************************************************************
                Scheduler
                -----------------------------------------
	begin      : 2011/11/01
	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 "Scheduler.h"
#include "ServiceConfig.h"
#include "Logger.h"
#include "Util.h"
#include "Running.h"


#define SHC_SLEEP_TIME		(1000*1000)//  microseconds
#define DAY_SECOND			(60*60*24) //

CScheduler::CScheduler()
	: m_exit(false)
{
	pthread_mutex_init(&m_timermutex, NULL);
	pthread_cond_init(&m_timercond, NULL);

	pthread_mutex_init(&m_workmutex, NULL);
	pthread_cond_init(&m_workcond, NULL);

	pthread_mutex_init(&m_updatemutex, NULL);
}

CScheduler::~CScheduler()
{
	setexit(true);
	pthread_mutex_destroy(&m_timermutex);
	pthread_cond_destroy(&m_timercond);

	pthread_mutex_destroy(&m_workmutex);
	pthread_cond_destroy(&m_workcond);

	pthread_mutex_destroy(&m_updatemutex);
}

void* CScheduler::WorkFn(void* pdata)
{
	CScheduler* pObject = reinterpret_cast<CScheduler *>(pdata);

	ostringstream msg;
	pthread_detach( pthread_self() );
	while( pObject->isexit() == false )
	{
		msg.str("");
		//pObject->whetherdetach();
		pthread_mutex_lock(&pObject->m_workmutex);
		int err = pthread_cond_wait(&pObject->m_workcond, &pObject->m_workmutex);

		if ( err == 0 && pObject->isexit() == false)
		{
			pObject->whetherdetach();
		}
		else
		{
			if(pObject->isexit())
			{
				msg << "Timer Work Thread exit.";
				cerr << msg.str() << endl;
				LOG(LERR, msg.str().c_str());
			}
		}
		pthread_mutex_unlock(&pObject->m_workmutex);
	}

	return NULL;
}

void* CScheduler::TimerFn(void* pdata)
{
	CScheduler* pObject = reinterpret_cast<CScheduler *>(pdata);

	struct timeval nowtime;
	struct timespec timeout;
	ostringstream msg;
#ifdef _DEBUG
	time_t t = time(NULL);
#endif // _DEBUG

	while( pObject->m_exit == false )
	{
		msg.str("");
#ifdef _DEBUG
		cout << "scheduler::TimerFn..." << strtime_now() <<"("<< time(NULL)-t <<")" << endl;
		t = time(NULL);
#endif // _DEBUG

		int rc =  pthread_mutex_lock(&pObject->m_timermutex);
		if(rc)
		{
			msg << "Timer pthread_mutex_lock error.[" << errno << "]. Timer exit.";
			cerr << msg.str() << endl;
			LOG(LCRT, msg.str().c_str());
			pthread_exit(NULL);
		}

		gettimeofday(&nowtime, NULL);

		timeout.tv_sec = nowtime.tv_sec + (SHC_SLEEP_TIME/1000000);
		timeout.tv_nsec = nowtime.tv_usec*1000 + (SHC_SLEEP_TIME%1000000*1000);
		rc = pthread_cond_timedwait(&pObject->m_timercond, &pObject->m_timermutex, &timeout);

		switch(rc)
		{
		case ETIMEDOUT:
			msg << "Timer wake up. Thread signal workfn.";
#ifdef _DEBUG
			cout << " " << msg.str() <<endl;
#endif // _DEBUG
			LOG(LDEV2, msg.str().c_str());
			pthread_mutex_lock(&pObject->m_workmutex);
			pthread_cond_signal(&pObject->m_workcond);
			pthread_mutex_unlock(&pObject->m_workmutex);
			break;
		case 0:
			msg << "Timer Kill signaled.[m_exit :" << pObject->m_exit << "]";
			cerr <<  msg.str() << endl;
			LOG(LCRT, msg.str().c_str());
			break;
		default:
			msg << "Timer some error occurred.";
			cerr <<  msg.str() << endl;
			LOG(LALT, msg.str().c_str());
			break;
		}
		pthread_mutex_unlock(&pObject->m_timermutex);
	}

	return 0;
}

int CScheduler::run()
{
	ostringstream msg;
	int nRet = pthread_create(&m_htimerthread, 0, CScheduler::TimerFn, this);
	if( nRet )
	{
		msg << "Timer Thread create failed.[ " << errno <<"]";
		cerr << msg << endl;
		LOG(LCRT, msg.str().c_str());
		return -1;
	}

	nRet = pthread_create(&m_hworkthread, 0, CScheduler::WorkFn, this);
	if( nRet )
	{
		msg << "Timer Work Thread create failed.[ " << errno <<"]";
		cerr << msg << endl;
		LOG(LCRT, msg.str().c_str());
		return -1;
	}
	return 0;
}

bool CScheduler::addtask(short tasktype, int val)
{
	task_info info;
	info.tasktype = tasktype;
	info.timeval.interval = val;

	int i = m_taskmap.size();
	m_taskmap.insert(make_pair(i,info));
	return true;
}

void CScheduler::printtasklist()
{
	map<int, task_info>::iterator iter;
	for( iter = m_taskmap.begin(); !m_taskmap.empty()&& iter != m_taskmap.end(); iter++ )
	{
		cout << "Task List index : "<< iter->first << ", Taks info : " << iter->second.tasktype << "," <<
			iter->second.timeval.interval << "," << iter->second.lastendtime << "," << iter->second.lastruntime << endl;
	}
}

bool CScheduler::whetherdetach()
{
	ostringstream msg;
	time_t nowtime;
	time ( &nowtime );
	map<int, task_info>::iterator iter;

	for( iter = m_taskmap.begin(); !m_taskmap.empty()&& iter != m_taskmap.end(); iter++ )
	{
		msg.str("");
		task_info &task = iter->second;
		task.spendtime += SHC_SLEEP_TIME;

		if( task.lastruntime - nowtime > 0 )
		{
			msg << "Task last runtime correction. " << 
				time_t2string(task.lastruntime) << " => " << time_t2string(nowtime);
#ifdef _DEBUG
			cout << msg.str() << endl;
#endif // _DEBUG
			LOG(LNOT, msg.str().c_str());
			task.lastruntime = nowtime;
		}
		
#ifdef _DEBUG
		cout << " now time - " << time_t2string(nowtime) << ", task index  - " << iter->first <<
			", spend time - " << task.spendtime << ",(" << nowtime- task.lastruntime << ")"
			", last run time - " << time_t2string(task.lastruntime) << ", time value - " <<
			(task.tasktype == CScheduler::TIME_INTERVAL ? "interval" : "once") << "," <<
			(task.tasktype == CScheduler::TIME_INTERVAL ? task.timeval.interval : task.timeval.hour) << endl;
#endif // _DEBUG

		bool detach = false;
		switch( task.tasktype )
		{
		case CScheduler::TIME_INTERVAL:
			{
				int spendtime = nowtime - task.lastruntime;
				//int64_t spendtime = task.spendtime/1000000;

				if( (task.lastruntime == 0) || ( spendtime >= task.timeval.interval ) )
				{
					// 첫번째 등록된 작업이 먼저 수행될 수 있도록 sleep 수행함
					solusleep(1000);

					task.spendtime = 0;
					//task.spendtime = SHC_SLEEP_TIME;
					task.lastruntime = nowtime;
					detach = true;
				}
			}
			break;
		case CScheduler::TIME_ONCE:
			{
				string nowstr = time_t2string(nowtime);
				int hours = atoi(nowstr.substr(8,2).c_str());
				string nowhour = nowstr.substr(0,8);
				string lastrunhour = time_t2string(task.lastruntime).substr(0,8);

				if( hours == task.timeval.hour &&  nowhour > lastrunhour)
				{
					task.spendtime = 0;
					task.lastruntime = nowtime;
					detach = true;
				}
			}
			break;
		default:
			msg.str("");
			msg << "Unknown Task info.";
			cerr << msg.str() << endl;
			LOG(LWAR, msg.str().c_str());
			break;
		}

		if( detach == true )
		{
			msg.str("");
			msg << "Task index : " << iter->first << ", now time - "<< nowtime <<
				"," << iter->second.lastruntime <<", Running detach....";
#ifdef _DEBUG
			cout << msg.str() << endl;
#endif // _DEBUG
			LOG(LDBG, msg.str().c_str());

			time_t start, end;
			CRunning * prun = new CRunning(this, iter->first, task.lastruntime);
			pthread_mutex_lock(&m_updatemutex);

			end = nowtime - CServiceConfig::GetInstance()->GetSafetyFactor();

			if(task.lastendtime == 0)
			{
				if (task.tasktype == CScheduler::TIME_ONCE)
				{
					start = end - (CServiceConfig::GetInstance()->GetOnceSize() * DAY_SECOND);
				}
				else
				{
					start = end - task.timeval.interval;
				}
			}
			else
			{
				start = task.lastendtime;
				
				if (task.tasktype == CScheduler::TIME_ONCE)
				{
					time_t newstart = end - (CServiceConfig::GetInstance()->GetOnceSize() * DAY_SECOND);
					if( start < newstart )
						start = newstart;
				}
			}

			if( prun->run(start, end) == false)
			{
			}
			pthread_mutex_unlock(&m_updatemutex);
		}
	}
	return true;
}

void CScheduler::setexit(bool v)
{
	LOG(LINF, "setexit %d", v);
	m_exit = v;
	// kill timer
	pthread_mutex_lock(&m_timermutex);
	pthread_cond_signal(&m_timercond);
	pthread_mutex_unlock(&m_timermutex);

	// kill work
	pthread_mutex_lock(&m_workmutex);
	pthread_cond_signal(&m_workcond);
	pthread_mutex_unlock(&m_workmutex);
}


bool CScheduler::updatelastendtime(int taskindex, time_t newtime)
{
	pthread_mutex_lock(&m_updatemutex);

	map<int, task_info>::iterator iter = m_taskmap.find(taskindex);
	if( iter != m_taskmap.end() )
	{
		LOG(LDBG, "Taks index %d,%s => %s", taskindex,
			time_t2string(iter->second.lastendtime).c_str(), 
			time_t2string(newtime).c_str());

		if( iter->second.lastendtime < newtime )
		{
			iter->second.lastendtime = newtime;
		}
		else
		{
#ifdef _DEBUG
			cout << "The current setting(Task index - ("<< taskindex <<
				"), last endtime) is less than new endtime." << endl;
#endif // _DEBUG
		}
	}

	pthread_mutex_unlock(&m_updatemutex);
	return true;
}
