/*
    PIP - Platform Independent Primitives
    Timer
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "pitimer.h"

#include "piconditionvar.h"
#include "piliterals_time.h"
#include "pithread.h"


//! \addtogroup Thread
//! \{
//! \class PITimer pitimer.h
//!
//! \~\brief
//! \~english Timer
//! \~russian Таймер
//!
//! \~\details
//!
//! \~english \section PITimer_sec0 Synopsis
//! \~russian \section PITimer_sec0 Краткий обзор
//! \~english
//! This class implements timer function. PIP timers supports 3 way to tick notify,
//! deferred start and frequency delimiters.
//!
//! \~russian
//! Этот класс реализует таймер. Таймер PIP поддерживает 3 варианта уведомления,
//! отложенный старт и делители частоты.
//!
//!
//! \~english \section PITimer_sec1 Notify variants
//! \~russian \section PITimer_sec1 Варианты уведомления
//! \~english
//!  Notify variants:
//! * "slot" - static function with format void func(int delimiter) or [lambda
//! expression](https://en.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * virtual function - \a tick().
//!
//! Lambda should be [ ]( ){ } or [ ](int){ } format.
//!
//! All these variants are equivalent, use most applicable.
//! \~russian
//! Варианты уведомления:
//! * "slot" - статический метод в формате void func(int delimiter) или
//! [лямбда-выражение](https://ru.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * виртуальный метод - \a tick().
//!
//! Лямбда-функция должна быть в формате [ ]( ){ } или [ ](int){ }.
//!
//! Все варианты аналогичны друг другу, используйте самый удобный.
//!
//!
//! \~english \section PITimer_sec2 Frequency delimiters
//! \~russian \section PITimer_sec2 Делители частоты
//! \~english
//! 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:
//!
//! \~russian
//! Делитель частоты это целое число и необязательный метод "slot". Если метод не указан,
//! то будет использован основной "slot". Каждые \b delimiter тиков будет дополнительно вызван
//! "slot" делителя либо основной "slot" с аргументом \b delimiter равным значению делителя.
//!
//! Пример:
//!
//! \~\code
//! void tfunc(int delim) {
//! 	piCout << "tick with delimiter" << delim;
//! };
//! void tfunc4(int delim) {
//! 	piCout << "tick4 with delimiter" << delim;
//! };
//! int main() {
//! 	PITimer timer(tfunc);
//! 	timer.addDelimiter(2);
//! 	timer.addDelimiter(4, tfunc4);
//! 	timer.start(50);
//! 	piMSleep(200);
//! 	timer.stopAndWait();
//! 	return 0;
//! };
//! /* Result:
//! tick with delimiter 1
//! tick with delimiter 1
//! tick with delimiter 2
//! tick with delimiter 1
//! tick with delimiter 1
//! tick with delimiter 2
//! tick4 with delimiter 4
//! */
//! \endcode
//!
//! \}


PITimer::PITimer(): PIObject() {
	initFirst();
}


PITimer::PITimer(std::function<void(int)> func) {
	initFirst();
	ret_func_delim = std::move(func);
}


PITimer::PITimer(std::function<void()> func) {
	initFirst();
	ret_func = std::move(func);
}


PITimer::~PITimer() {
	stopAndWait();
	piDeleteSafety(thread);
}


PISystemTime PITimer::interval() const {
	return m_interval;
}


void PITimer::setInterval(PISystemTime interval) {
	if (m_interval == interval) return;
	m_interval = interval;
	if (isRunning()) {
		// piCout << "change interval runtime";
		stopAndWait();
		start();
	}
}


bool PITimer::isRunning() const {
	return thread->isRunning();
}


bool PITimer::isStopping() const {
	return thread->isStopping();
}


bool PITimer::waitForFinish(PISystemTime timeout) {
	return thread->waitForFinish(timeout);
}


void PITimer::initFirst() {
	thread = new PIThread([this] { threadFunc(); });
	thread->setName("_S.PITimer.thread");
	setProperty("interval", PISystemTime());
}


void PITimer::threadFunc() {
	PISystemTime st_wait = m_time_next - PISystemTime::current(true);
	// piCout << "wait" << this << st_wait;
	if (st_wait.abs() > m_interval_x5 || st_wait.seconds <= -5) {
		// piCout << &thread_ << "adjust" << "...";
		adjustTimes();
		// piCout << &thread_ << "adjust" << "ok";
		return;
	}
	if (thread->isStopping()) return;
	if (st_wait.isPositive()) {
		thread->mutex().lock();
		event.waitFor(thread->mutex(), st_wait);
		thread->mutex().unlock();
	}
	if (thread->isStopping()) return;
	m_time_next += m_interval;
	// piCout << &thread_ << "tfunc" << "...";
	execTick();
}


void PITimer::adjustTimes() {
	PISystemTime cst = PISystemTime::current(true);
	if (m_time_next < cst) {
		int rs = (cst - m_time_next).toSeconds() / m_interval.toSeconds();
		if (rs >= 100)
			m_time_next = cst + m_interval;
		else {
			while (m_time_next < cst)
				m_time_next += m_interval;
		}
	} else {
		int rs = (m_time_next - cst).toSeconds() / m_interval.toSeconds();
		if (rs >= 100)
			m_time_next = cst - m_interval;
		else {
			cst += m_interval;
			while (m_time_next > cst)
				m_time_next -= m_interval;
		}
	}
}


void PITimer::execTick() {
	if (!isRunning()) return;
	if (lockRun) lock();
	if (ret_func) ret_func();
	if (ret_func_delim) ret_func_delim(1);
	tick(1);
	tickEvent(1);
	if (callEvents) maybeCallQueuedEvents();
	for (Delimiter & i: delims) {
		if (i.delim > ++(i.tick)) continue;
		i.tick = 0;
		if (i.func)
			i.func(i.delim);
		else if (ret_func_delim)
			ret_func_delim(i.delim);
		tick(i.delim);
		tickEvent(i.delim);
	}
	if (lockRun) unlock();
}


bool PITimer::start() {
	if (isRunning()) return true;
	m_interval_x5 = m_interval * 5;
	if (m_interval_x5.toSeconds() < 1.) m_interval_x5 = 1_s;
	m_time_next = PISystemTime::current(true) + m_interval;
	if (!thread->start()) return false;
	return thread->waitForStart();
}


bool PITimer::start(PISystemTime interval) {
	if (isRunning()) stopAndWait();
	setInterval(interval);
	return start();
}


bool PITimer::start(PISystemTime interval, std::function<void()> func) {
	if (isRunning()) stopAndWait();
	setInterval(interval);
	setSlot(std::move(func));
	return start();
}


void PITimer::stopAndWait(PISystemTime timeout) {
	stop();
	thread->waitForFinish(timeout);
}


void PITimer::setSlot(std::function<void()> func) {
	ret_func_delim = nullptr;
	ret_func       = std::move(func);
}


void PITimer::setSlot(std::function<void(int)> func) {
	ret_func       = nullptr;
	ret_func_delim = std::move(func);
}


void PITimer::addDelimiter(int delim, std::function<void(int)> func) {
	delims << Delimiter(std::move(func), delim);
}


void PITimer::addDelimiter(int delim, std::function<void()> func) {
	delims << Delimiter([func](int) { func(); }, delim);
}


void PITimer::removeDelimiter(int delim) {
	for (int i = 0; i < delims.size_s(); ++i)
		if (delims[i].delim == delim) {
			delims.remove(i);
			i--;
		}
}


bool PITimer::restart() {
	stopAndWait();
	return start();
}


void PITimer::stop() {
	thread->stop();
	event.notifyAll();
}
