/** @file task.h
 * Module handling the task creation and management
 *
 * The module provides an API to create, run and manage lightweight, stack-less
 * threads (tasks). This system is based on protothreads,
 * see https://dunkels.com/adam/pt/index.html
 *
 * Task can be viewed as a lightweight, somewhat restricted, cooperative OS.
 * Tasks are runned on every events. Since the RTC is enabled by the scheduler,
 * every task should be run at least once per second. If at least one task is
 * currently paused, the systick is enabled and all tasks are updated every
 * millisecond
 *
 * State machine should mainly yield (see TASK_YIELD) while time sensitive
 * applications should pause (see TASK_PAUSE) instead. This configuration allows
 * state machines to be updated every time something changes in the system while
 * allowing the cpu to enter low power mode when possible. When time sensitive
 * applications are paused, they cause the systick to start which stops the cpu
 * from entering low power but making sure that the task is polled quickly
 * enough. Tasks requiring longer delay and less sensitive to timings can sleep
 * (see TASK_SLEEP) instead, which relies on the RTC rather than the systick
 */

//--includes--------------------------------------------------------------------

#include "task.h"
#include "error.h"
#include "../drv/stk.h"
#include "../drv/pwr.h"
#include "../drv/rcc.h"
#include "../drv/bkp.h"
#include "../drv/exti.h"


//--local definitions-----------------------------------------------------------

#define MAX_TASK_NB 10

static bool execute_task(struct Task* restrict task, uint8_t triggers);
static void callback_stk(void);
static void callback_rtc(void);


//--local variables-------------------------------------------------------------

static struct Task task_list[MAX_TASK_NB];
static uint8_t task_nb;
static volatile bool stk_irq;
static volatile bool rtc_irq;
static volatile uint32_t timestamp;


//--public functions------------------------------------------------------------

void task_start_scheduler(void)
{
	stk_configure(1000, callback_stk);

	rcc_configure_lsi(true);
	pwr_configure_bkp_write(true);
	bkp_configure_rtc(1000, BKP_RTC_CLOCK_SRC_LSI, BKP_RTC_IRQ_NONE, nullptr);
	pwr_configure_bkp_write(false);
	exti_configure(EXTI_LINE_RTC, EXTI_CONFIG_RISING_EDGE, callback_rtc);

	while (true) {
		uint8_t triggers = stk_irq << 0 | rtc_irq << 1;
		stk_irq = false;
		rtc_irq = false;
		bool stk_needed = false;

		for (uint8_t i = 0; i < task_nb; ++i) {
			stk_needed |= execute_task(&task_list[i], triggers);
		}

		if (stk_needed) {
			stk_start();
			pwr_sleep();
		} else {
			pwr_configure_bkp_write(true);
			bkp_set_rtc_alam(1);
			pwr_configure_bkp_write(false);
			stk_stop();
			pwr_sleep();
		}
	}
}

uint32_t task_current_time(void)
{
	return timestamp;
}

void task_start(TaskFunction function)
{
	for (uint8_t i = 0; i < MAX_TASK_NB; ++i) {
		if (task_list[i].function == nullptr) {

			task_list[i].function = function;
			task_list[i].state.timestamp = 0;
			task_list[i].state.count = 0;

			++task_nb;
			return;
		}
	}

	error_trigger("task list is full");
}

void task_stop(TaskFunction function)
{
	for (uint8_t i = 0; i < task_nb; ++i) {
		if (task_list[i].function == function) {
			task_list[i].state.count = _TASK_COUNT_CLEANUP & 0x1F;
			task_list[i].function(&task_list[i].state, timestamp);
			task_list[i].function = nullptr;

			return;
		}
	}

	error_trigger("task does not exist");
}

bool task_is_running(TaskFunction function)
{
	for (uint8_t i = 0; i < task_nb; ++i) {
		if (task_list[i].function == function) {
			return true;
		}
	}

	return false;
}

//--local functions-------------------------------------------------------------

static bool execute_task(struct Task* restrict task, uint8_t triggers)
{
	if (task->function != nullptr) {
		if (task->state.trigger == TASK_TRIGGER_ANY) {
			task->function(&task->state, timestamp);
		} else {
			if ((task->state.trigger & triggers) != 0) {

				if (task->state.timestamp <= timestamp) {
					task->state.timestamp = 0;
				}

				if ((task->state.timestamp == 0) ||
						task->state.timeout_mode)  {
					task->function(&task->state,
							timestamp);
				}
			}
		}

		return (task->state.trigger & TASK_TRIGGER_STK) != 0;
	}

	return false;
}

static void callback_stk(void)
{
	stk_irq = true;
	++timestamp;
}

static void callback_rtc(void)
{
	rtc_irq = true;
	timestamp = bkp_read_rtc() * 1000;
}
