/*
 * Periodic.h
 *
 * Создан:	09 окт. 2015 г.
 * Автор:	esaulenko
 *
 * Изменен:	21 ноя. 2019 г.
 * Автор:	gavrilov
 */

#ifndef PERIODIC_H_
#define PERIODIC_H_

#include <cstdint>
#include <cstddef>
#include "SysTimer.h"
#include "Utils.h"

template <uint nTasks>
class CPeriodic
{
private:
	typedef void (* TPeriodicTask) (TCanFwMem * apMem);

public:
// добавить новую функцию для периодического вызова. Обычно выполняется один раз.
	void AddTask(const uint iTask, TPeriodicTask TaskHandler = nullptr, const uint32_t msTime = 0)
	{
		if (iTask < nTasks)
		{
			Tasks[iTask].Handler = TaskHandler;
			SetTimeout(iTask, msTime);
		}
	}

// Установить время, через которое надо будет вызвать периодическую функцию
	void SetTimeout(const uint iTask, uint32_t msTime = 0)
	{
		if (iTask < nTasks)
		{// индекс не превышает размер массива
// Если вызов происходит из <Process>, то местный таймер уже был сброшен.. и, соответственно, нужно только обновить
// значение периода.. а пересчет минимального времени, также произойдет при выходе из <Process>..
// При вызове же вне <Process>, ни местный таймер не сбрасывается, ни пересчет периода не происходит..
			if (!Mutex)
			{// При вызове вне <Process>:
			// 1. Нужно обязательно вызывать метод <SetNextCall>, т.к. следующий период может как удлиниться, так и укоротиться.
			// 2. А для того, чтобы метод <SetNextCall> правильно выставил время следующего вызова <Process>,
			// требуется пересчитать все текущие периоды с учетом набега времени.
			// 3. А раз нам требуется пересчитать все текущие периоды, то значит мы должны пересбросить местный
			// таймер отсчета времени.
			// 4. Однако, т.к. отсюда нельзя вызывать обработчики, то тем периодам, что окончились здесь,
			// приходится ставить минимальное значение =1, чтобы обеспечить как можно скорее вызов обработчика <Process>.
			//
				auto elapsed = Timer.Restart();
				if (elapsed)
					for (auto & Task: Tasks)
					// Пройдемся по всем задачам..
						if (Task.Timeout)
						{
							// task enabled
							if (Task.Timeout > elapsed)
								Task.Timeout -= elapsed;
							else
							// Отсюда мы не можем вызвать обработчик, поэтому просто ставим минимальное время..
								Task.Timeout = 1;
						}
			}
			if (Tasks[iTask].Handler == nullptr)
			// Если обработчик неопределен.. остановить задачу..
				msTime = 0;
		// Установить время..
			Tasks[iTask].Timeout = msTime;
		// Всегда сбрасывать признак выполненной задачи, т.к. только-что для нее был выставлен новый период..
		// ..следовательно, предыдущий период уже стал неактуальным..
			Tasks[iTask].Done = false;
		// Пересчитать интервалы..
			if (!Mutex)
			// ..здесь только для вызывов извне <Process>..
				SetNextCall();
		}
	}

// остановить задачу
	void Stop(const uint iTask)
	{
		if (iTask < nTasks)
		{
			Tasks[iTask].Timeout = 0;
			Tasks[iTask].Done = false;
		}
	}

// проверить, что задача запущена
	bool isActive(const uint iTask) const
	{
		if (iTask < nTasks)
			if (Tasks[iTask].Handler != nullptr)
				if (Tasks[iTask].Timeout)
					return true;
		return false;
	}

private:
	struct {
		TPeriodicTask Handler = nullptr;
		uint32_t Timeout = 0;
		bool Done = false;
	} Tasks[nTasks];

	TimerMs Timer;
	volatile bool Mutex = false;

// Обработчик, вызывается из PeriodicProcess()
	void Process(TCanFwMem * apCanMemory)
	{
		auto elapsed = Timer.Restart();
		if (elapsed)
		{
		// Сначала нужно обновить периоды всех периодических функций..
			for (auto & Task: Tasks)
			// Пройдемся по всем задачам..
				if (Task.Timeout)
				{
					// task enabled
					if (Task.Timeout > elapsed)
						Task.Timeout -= elapsed;
					else
					{
						Task.Done = true;
						Task.Timeout = 0;
					}
				}
		// Затем запустить все сработавшие задачи..
			Mutex = true;		// выставить признак того, что метод <SetTimeout> будет вызван из метода <Process>
			for (auto & Task: Tasks)
			// Пройдемся по всем задачам..
				if (Task.Done)
				{// Если задача сработала..
					Task.Done = false;
					if (Task.Handler != nullptr)
					// task exist
						Task.Handler(apCanMemory);		// ..запустить ее
				}
			Mutex = false;
		// Запустить перерасчет и установку минимального интревала времени для вызова следующей периодической функции
			SetNextCall();
		}
	}
	friend void PeriodicProcess(TCanFwMem * vars);

// Расчитать минимальное значение интервала времени для вызова следующей периодической функции
	void SetNextCall(void)
	{
		uint32_t minTime = UINT32_MAX;
		for (const auto & Task: Tasks)
			if (Task.Timeout)
				if (Task.Timeout < minTime)
					minTime = Task.Timeout;

		if (minTime == UINT32_MAX)
			minTime = 0;				// Остановить вызов <PeriodicProcess>
		CoreFunc->SetPeriod(minTime);
	}
};

#endif /* PERIODIC_H_ */
