/*
    PIP - Platform Independent Primitives
    Timer
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pitimer.h"
#include "pisystemtests.h"


/*! \class PITimer
 * \brief Timer
 * 
 * \section PITimer_sec0 Synopsis
 * This class implements timer function. PIP timers supports 3 way to tick notify,
 * frequency delimiters and time measurements.
 * \section PITimer_sec1 Notify variants
 *  Notify variants:
 * * "slot" - static function with format void func(void * data, int delimiter);
 * * event - \a void timeout(void * data, int delimiter);
 * * virtual function - \a void tick(void * data, int delimiter).
 * All this variant are equivalent, use most applicable.
 * \section PITimer_sec2 Frequency delimiters
 * Frequency delimiter is an integer number and "slot" function. If "slot" function is null
 * timer main "slot" will be used. Each delimiter numbers tick timer will be execute
 * delimiters or timer main "slot" function with \b delimiter value = delimiter number.
 * Example: \snippet pitimer.cpp delimiter
 * \section PITimer_sec3 Time measurements
 * PITimer can be used as time measurer. Function \a reset() set time mark to current
 * system time, then functions double elapsed_*() returns time elapsed from this mark.
 * These functions can returns nano-, micro-, milli- and seconds with suffixes "n", "u", "m"
 * and "s";
 * Example: \snippet pitimer.cpp elapsed
*/


#ifdef PIP_TIMER_RT
PITimer::TimerPool * pool = 0;
#endif


PITimer::PITimer(TimerEvent slot, void * data_, bool threaded_)
#ifndef PIP_TIMER_RT
: PIThread() {
#else
: PIObject() {
#endif
	ret_func = slot;
	data = data_;
	running_ = false;
	interval_ = 0.;
#ifdef PIP_TIMER_RT
	piMonitor.timers++;
	ti = -1;
	threaded = threaded_;
	memset(&se, 0, sizeof(se));
	se.sigev_notify = SIGEV_THREAD;
	se.sigev_value.sival_ptr = this;
	se.sigev_notify_function = PITimer::timer_event;
	se.sigev_notify_attributes = 0;
	lockRun = false;
#else
	deferred_ = false;
#endif
	reset();
}


PITimer::PITimer(bool threaded_)
#ifndef PIP_TIMER_RT
: PIThread() {
#else
: PIObject() {
#endif
	ret_func = 0;
	data = 0;
	running_ = false;
	interval_ = 0.;
#ifdef PIP_TIMER_RT
	piMonitor.timers++;
	ti = -1;
	threaded = threaded_;
	memset(&se, 0, sizeof(se));
	se.sigev_notify = SIGEV_THREAD;
	se.sigev_value.sival_ptr = this;
	se.sigev_notify_function = PITimer::timer_event;
	se.sigev_notify_attributes = 0;
	lockRun = false;
#else
	deferred_ = false;
#endif
	reset();
}


PITimer::~PITimer() {
#ifdef PIP_TIMER_RT
	piMonitor.timers--;
#endif
	stop();
}


#ifdef PIP_TIMER_RT
void PITimer::start(double msecs) {
	if (ti != -1 || msecs < 0 || running_) return;
	interval_ = msecs;
	if (!threaded) {
		ticks = int(msecs);
		if (pool == 0) pool = new TimerPool();
		pool->add(this);
		//cout << "not threaded timer start " << msecs << " msecs\n";
		if (!pool->isRunning()) pool->start();
		running_ = true;
		return;
	}
	spec.it_interval.tv_nsec = ((int)(msecs * 1000) % 1000000) * 1000;
	spec.it_interval.tv_sec = (time_t)(msecs / 1000);
	spec.it_value = spec.it_interval;
	ti = timer_create(CLOCK_REALTIME, &se, &timer);
	//cout << "***create timer " << msecs << " msecs\n";
	if (ti == -1) {
		piCoutObj << "Can`t create timer for " << msecs << " msecs: " << errorString();
		return;
	}
	timer_settime(timer, 0, &spec, 0);
	running_ = true;
}


void PITimer::deferredStart(double interval_msecs, double delay_msecs) {
	if (ti != -1 || interval_msecs < 0 || running_) return;
	interval_ = interval_msecs;
	spec.it_interval.tv_nsec = ((int)(interval_msecs * 1000) % 1000000) * 1000;
	spec.it_interval.tv_sec = (time_t)(interval_msecs / 1000);
	spec.it_value.tv_nsec = ((int)(delay_msecs * 1000) % 1000000) * 1000;
	spec.it_value.tv_sec = (time_t)(delay_msecs / 1000);
	ti = timer_create(CLOCK_REALTIME, &se, &timer);
	//cout << "***create timer\n";
	if (ti == -1) {
		piCoutObj << "Can`t create timer for " << interval_msecs << " msecs: " << errorString();
		return;
	}
	timer_settime(timer, 0, &spec, 0);
	running_ = true;
}


void PITimer::deferredStart(double interval_msecs, const PIDateTime & start_datetime) {
	if (ti != -1 || interval_msecs < 0 || running_) return;
	interval_ = interval_msecs;
	spec.it_interval.tv_nsec = ((int)(interval_msecs * 1000) % 1000000) * 1000;
	spec.it_interval.tv_sec = (time_t)(interval_msecs / 1000);
	struct tm dtm;
	memset(&dtm, 0, sizeof(dtm));
	dtm.tm_sec = start_datetime.seconds;
	dtm.tm_min = start_datetime.minutes;
	dtm.tm_hour = start_datetime.hours;
	dtm.tm_mday = start_datetime.day;
	dtm.tm_mon = start_datetime.month - 1;
	dtm.tm_year = start_datetime.year - 1900;
	spec.it_value.tv_nsec = 0;
	spec.it_value.tv_sec = mktime(&dtm);
	ti = timer_create(CLOCK_REALTIME, &se, &timer);
	//cout << "***create timer\n";
	if (ti == -1) {
		piCoutObj << "Can`t create timer for " << interval_msecs << " msecs: " << errorString();
		return;
	}
	timer_settime(timer, TIMER_ABSTIME, &spec, 0);
	running_ = true;
}



void PITimer::TimerPool::remove(PITimer * t) {
	mutex.lock();
	for (int i = 0; i < timers.size_s(); ++i)
		if (timers[i].first == t) {
			timers.remove(i);
			mutex.unlock();
			return;
		}
	mutex.unlock();
}


void PITimer::TimerPool::begin() {
	//cout << "pool begin\n";
	/*struct sigaction sa;
	sa.sa_flags = 0;
	sa.sa_handler = empty_handler;
	sigemptyset(&sa.sa_mask);
	if (sigaction(SIGALRM, &sa, 0) == -1) {
		piCoutObj << "sigaction error: " << errorString();
		stop();
		return;
	}*/
	sigemptyset(&ss);
	sigaddset(&ss, SIGALRM);
	memset(&se, 0, sizeof(se));
	se.sigev_notify = SIGEV_SIGNAL;
	se.sigev_signo = SIGALRM;
	spec.it_interval.tv_nsec = 1000000;
	spec.it_interval.tv_sec = 0;
	spec.it_value = spec.it_interval;
	//cout << "***create pool timer\n";
	if (timer_create(CLOCK_REALTIME, &se, &timer) == -1) {
		piCoutObj << "Can`t create timer for pool: " << errorString();
		stop();
		return;
	}
	if (timer_settime(timer, 0, &spec, 0) == -1) {
		piCoutObj << "Can`t set timer for pool: " << errorString();
		stop();
		return;
	}
	ti = 1;
}


void PITimer::TimerPool::run() {
	//cout << "wait ...\n";
	sigwait(&ss, &si);
	//cout << "ok\n";
	mutex.lock();
	//cout << "* pool tick , pool = " << this <<", timers = " << timers.size()<<"\n";
	for (int i = 0; i < timers.size_s(); ++i) {
		TimerPair & ct(timers[i]);
		sv.sival_ptr = ct.first;
		ct.second++;
		//cout << "** pool tick for " << ct.first << ", cnt " << ct.second << ", " << ct.first->ticks << "\n";
		if (ct.second >= ct.first->ticks) {
			//cout << "*** timer "<<ct.first<<" tick start\n";
			PITimer::timer_event(sv);
			//cout << "*** timer "<<ct.first<<" tick end\n";
			ct.second = 0;
		}
	}
	mutex.unlock();
}



void PITimer::stop() {
	if (!running_) return;
	running_ = false;
	if (!threaded) {
		if (pool != 0) {
			pool->remove(this);
			if (pool->isEmpty()) pool->terminate();
		}
	}
	if (ti != -1) timer_delete(timer);
	ti = -1;
	interval_ = 0.;
}


void PITimer::timer_event(sigval e) {
	PITimer * ct = (PITimer * )e.sival_ptr;
	if (!ct->running_) return;
	if (ct->lockRun) ct->lock();
	if (ct->ret_func != 0) ct->ret_func(ct->data, 1);
	ct->timeout(ct->data, 1);
	ct->tick(ct->data, 1);
	piForeach (TimerSlot & i, ct->ret_funcs) {
		if (i.delim > ++(i.tick)) continue;
		i.tick = 0;
		if (i.slot != 0) i.slot(ct->data, i.delim);
		else if (ct->ret_func != 0) ct->ret_func(ct->data, i.delim);
		ct->timeout(ct->data, i.delim);
		ct->tick(ct->data, i.delim);
	}
	if (ct->lockRun) ct->unlock();
}


bool PITimer::waitForFinish(int timeout_msecs) {
	if (timeout_msecs < 0) {
		while (running_)
			msleep(1);
		return true;
	}
	int cnt = 0;
	while (running_ && cnt < timeout_msecs) {
		msleep(1);
		++cnt;
	}
	return cnt < timeout_msecs;
}

#else

void PITimer::start(double msecs) {
	if (msecs < 0 || running_) return;
	interval_ = msecs;
	inc_time = PISystemTime::fromMilliseconds(msecs);
	st_time = PISystemTime::current() + inc_time;
	deferred_ = false;
	running_ = true;
	PIThread::start();
}


void PITimer::deferredStart(double interval_msecs, double delay_msecs) {
	//piCoutObj << "defStart exec with" << delay_msecs << interval_msecs;
	if (interval_msecs < 0 || running_) return;
	interval_ = interval_msecs;
	PISystemTime cst = PISystemTime::current();
	inc_time = PISystemTime::fromMilliseconds(interval_msecs);
	st_time = PISystemTime::current() + PISystemTime::fromMilliseconds(delay_msecs);
	if (st_time < cst) st_time = cst;
	running_ = deferred_ = true;
	PIThread::start();
	//piCoutObj << "timer start def";
}


void PITimer::deferredStart(double interval_msecs, const PIDateTime & start_datetime) {
	//piCoutObj << "defStart exec to" << start_datetime.toString() << interval_msecs;
	if (interval_msecs < 0 || running_) return;
	interval_ = interval_msecs;
	PISystemTime cst = PISystemTime::current();
	inc_time = PISystemTime::fromMilliseconds(interval_msecs);
	st_time = start_datetime.toSystemTime();
	if (st_time < cst) st_time = cst;
	running_ = deferred_ = true;
	PIThread::start();
	//piCoutObj << "timer start def";
}


void PITimer::run() {
	if (!running_) return;
	while (deferred_) {
		PISystemTime tst = st_time - PISystemTime::current();
		if (tst.seconds > 0) {
			piMSleep(100);
			if (!running_) return;
			continue;
		}
		if (tst.nanoseconds > 100000000) {
			piMSleep(100);
			if (!running_) return;
			continue;
		}
		tst.sleep();
		deferred_ = false;
		if (!running_) return;
	}
#ifdef WINDOWS
	tt_st = __PIQueryPerformanceCounter();
#else
	(st_time - PISystemTime::current()).sleep();
	st_time += inc_time;
#endif
	//if (lockRun) lock();
	if (ret_func != 0) ret_func(data, 1);
	timeout(data, 1);
	tick(data, 1);
	piForeach (TimerSlot & i, ret_funcs) {
		if (i.delim > ++(i.tick)) continue;
		i.tick = 0;
		if (i.slot != 0) i.slot(data, i.delim);
		else if (ret_func != 0) ret_func(data, i.delim);
		timeout(data, i.delim);
		tick(data, i.delim);
	}
#ifdef WINDOWS
	tt_cur = __PIQueryPerformanceCounter();
	double cdelay = interval_ - ((tt_cur - tt_st) / double(__pi_perf_freq) * 1.E+3);
	if (cdelay > 0.) Sleep(piRoundd(cdelay));
#endif
	//if (lockRun) unlock();
}

#endif

double PITimer::elapsed_n() {
#ifdef WINDOWS
	pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? ((pc_cur - pc_st) / double(__pi_perf_freq) * 1.E+9) : -1.);
	//t_cur = GetCurrentTime();
	//return (t_cur - t_st) * 1000000.;
#else
#  ifdef MAC_OS
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec - t_st.tv_sec) * 1.e+9 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns);
#endif
}


double PITimer::elapsed_u() {
#ifdef WINDOWS
	pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? ((pc_cur - pc_st) / double(__pi_perf_freq) * 1.E+6) : -1.);
	//t_cur = GetCurrentTime();
	//return (t_cur - t_st) * 1000.;
#else
#  ifdef MAC_OS
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec - t_st.tv_sec) * 1.e+6 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+3;
#endif
}


double PITimer::elapsed_m() {
#ifdef WINDOWS
	pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? ((pc_cur - pc_st) / double(__pi_perf_freq) * 1.E+3) : -1.);
	//t_cur = GetCurrentTime();
	//return (double)(t_cur - t_st);
#else
#  ifdef MAC_OS
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec - t_st.tv_sec) * 1.e+3 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+6;
#endif
}


double PITimer::elapsed_s() {
#ifdef WINDOWS
	pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? ((pc_cur - pc_st) / double(__pi_perf_freq)) : -1.);
	//t_cur = GetCurrentTime();
	//return (t_cur - t_st) / 1000.;
#else
#  ifdef MAC_OS
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec - t_st.tv_sec) + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+9;
#endif
}


double PITimer::reset_time_n() {
#ifdef WINDOWS
	return t_st * 1.e+6;
#else
	return t_st.tv_sec * 1.e+9 + t_st.tv_nsec;
#endif
}


double PITimer::reset_time_u() {
#ifdef WINDOWS
	return t_st * 1.e+3;
#else
	return t_st.tv_sec * 1.e+6 + t_st.tv_nsec / 1.e+3;
#endif
}


double PITimer::reset_time_m() {
#ifdef WINDOWS
	return (double)t_st;
#else
	return t_st.tv_sec * 1.e+3 + t_st.tv_nsec / 1.e+6;
#endif
}


double PITimer::reset_time_s() {
#ifdef WINDOWS
	return t_st / 1000.;
#else
	return t_st.tv_sec + t_st.tv_nsec / 1.e+9;
#endif
}


PISystemTime PITimer::reset_time() {
#ifdef WINDOWS
	return PISystemTime(t_st / 1000, (t_st % 1000) * 1000000);
#else
	return PISystemTime(t_st.tv_sec, t_st.tv_nsec);
#endif
}


double PITimer::elapsed_system_n() {
#ifdef WINDOWS
	llong pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? (pc_cur / double(__pi_perf_freq) * 1.E+9) : -1.);
	//long t_cur = GetCurrentTime();
	//return (t_cur * 1000000.);
#else
#  ifdef MAC_OS
	mach_timespec_t t_cur;
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	timespec t_cur;
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec * 1.e+9 + t_cur.tv_nsec);
#endif
}


double PITimer::elapsed_system_u() {
#ifdef WINDOWS
	llong pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? (pc_cur / double(__pi_perf_freq) * 1.E+6) : -1.);
	//long t_cur = GetCurrentTime();
	//return (t_cur * 1000.);
#else
#  ifdef MAC_OS
	mach_timespec_t t_cur;
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	timespec t_cur;
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec * 1.e+6 + (t_cur.tv_nsec / 1.e+3));
#endif
}


double PITimer::elapsed_system_m() {
#ifdef WINDOWS
	llong pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? (pc_cur / double(__pi_perf_freq) * 1.E+3) : -1.);
	//long t_cur = GetCurrentTime();
	//return (double)t_cur;
#else
#  ifdef MAC_OS
	mach_timespec_t t_cur;
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	timespec t_cur;
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec * 1.e+3 + (t_cur.tv_nsec / 1.e+6));
#endif
}


double PITimer::elapsed_system_s() {
#ifdef WINDOWS
	llong pc_cur = __PIQueryPerformanceCounter();
	return ((__pi_perf_freq > 0) ? (pc_cur / double(__pi_perf_freq)) : -1.);
	//long t_cur = GetCurrentTime();
	//return (t_cur / 1000.);
#else
#  ifdef MAC_OS
	mach_timespec_t t_cur;
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	timespec t_cur;
	clock_gettime(0, &t_cur);
#  endif
	return (t_cur.tv_sec + (t_cur.tv_nsec / 1.e+9));
#endif
}
