/** @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
 */

#ifndef _task_h_
#define _task_h_

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

#include <stdint.h>
#include <stdbool.h>


//--type definitions------------------------------------------------------------

/**
 * Available triggers for a task
 */
enum TaskTrigger {
	TASK_TRIGGER_ANY,
	TASK_TRIGGER_STK,
	TASK_TRIGGER_RTC,
	TASK_TRIGGER_BOTH,
};

/**
 * State of a task at any given time. Every single task is described by such a
 * struct
 */
struct TaskState {
	uint32_t timestamp;         //timestamp at wich to wakeup the task, if any
	uint8_t count:5;            //task counter: active step of task
	enum TaskTrigger trigger:2; //triggers on wich to execute the task
	uint8_t timeout_mode:1;     //whether the timestamp is a timeout or a delay
};

/**
 * Function prototype of tasks
 */
typedef void(*TaskFunction)(struct TaskState*, uint32_t);

/**
 * Full definition of a task. Contains the function supporting the task as well
 * as the state of said task
 */
struct Task {
	TaskFunction function;
	struct TaskState state;
};


//--functions-------------------------------------------------------------------

/**
 * Task declaration macro, to be used to declare and define a task instead of a
 * regular function declaration/defintion
 */
#define TASK(fct_name) void fct_name(struct TaskState* restrict __task_state,  \
		uint32_t __task_time)

/**
 * Task entry macro, must be present at the begin of every task. Setup code to
 * be run indepently of the task state may be put before that (static variables,
 * init code, ...)
 */
#define TASK_ENTRY                                                             \
	_TASK_COUNT_INIT;                                                          \
	(void) __task_time;                                                       \
	switch (__task_state->count) {

/**
 * Task cleanup macro. Option, can be use right before TASK_EXIT. This step
 * will be executed before exiting the task when task_stop() is called. As the
 * name suggest, this is mainly usefull to implement cleanup code and allow for
 * gracefull shutdowns
 */
#define TASK_CLEANUP                                                           \
	case (_TASK_COUNT_CLEANUP):                                                \
		/* fall through */

/**
 * Tasks exit macro, must be present at the end of every task. Any code written
 * after that will never be executed
 */
#define TASK_EXIT                                                              \
	}                                                                          \
	__task_state->count = _TASK_COUNT_EXIT & 0x1F;                             \
	return;

/**
 * Returns whether the task was timed-out or not. This macro can be used after
 * TASK_PAUSE_UNTIL and TASK_SLEEP_UNTIL to know if the task resumed because of
 * the condition or because of the timeout. Does not correspond to anything
 * when called after any other step
 */
#define TASK_TIMEOUT (__task_state->timeout == 0)

/**
 * Give up the cpu, allowing the other tasks to run. The task will resume at the
 * next event. Between events, the cpu can enter various power saving states
 * depending on the other tasks running
 */
#define TASK_YIELD() _TASK_YIELD(_TASK_COUNT_INCR)

/**
 * Suspend the task for the specified amount of milliseconds. The systick will
 * remain active while the task is suspended to provide milliseconds counting,
 * limiting the ability of the cpu to save power
 */
#define TASK_PAUSE(delay_ms) _TASK_PAUSE(delay_ms, _TASK_COUNT_INCR)

/**
 * Suspend the task for the specified amount of seconds. The RTC will be used to
 * provide seconds counting. If no other tasks requires it, the systick is
 * disabled to save more power
 */
#define TASK_SLEEP(delay_s) _TASK_SLEEP(delay_s, _TASK_COUNT_INCR)

/**
 * Execute TASK_YIELD until the provided condition is reached. The condition
 * will be checked on every event
 */
#define TASK_YIELD_UNTIL(cond) _TASK_YIELD_UNTIL(cond, _TASK_COUNT_INCR)

/**
 * Execute TASK_PAUSE until either the condition or the delay is reached.
 * TASK_TIMEOUT can be used to know what cause the task to resume. The condition
 * will be checked every millisecond
 */
#define TASK_PAUSE_UNTIL(cond, delay_ms)                                       \
	_TASK_PAUSE_UNTIL(cond, delay_ms, _TASK_COUNT_INCR)

/**
 * Execute TASK_SLEEP until either the condition or the delay is reached.
 * TASK_TIMEOUT can be used to know what cause the task to resume. The condition
 * will be checked every seconds
 */
#define TASK_SLEEP_UNTIL(cond, delay_s)                                        \
	_TASK_SLEEP_UNTIL(cond, delay_s, _TASK_COUNT_INCR)

/**
 * Execute the specified task, suspending the current one until the task exits
 */
#define TASK_EXECUTE(task) _TASK_EXECUTE(task, _TASK_COUNT_INCR)

/**
 * Starts the task scheduler and run it until the system is shutdown. This
 * function never returns.
 *
 * All tasks started using task_start() are run according to their current
 * state (see TASK_* macros). Since this system is cooperative, the scheduler
 * does not preempt the tasks when running. The RTC is automatically configured
 * and started to provide events every seconds. The systick is automatically
 * started and stop to provide events every milliseconds when needed
 */
void task_start_scheduler(void);

/**
 * Returns the current system time. The epoc is undefined. The time is provided
 * in milliseconds, though the millisecond precision is only available while the
 * systick is running (at least one task paused)
 */
uint32_t task_current_time(void);

/**
 * Starts the specified task. The task will only trully be runned after
 * task_start_scheduler() is called.
 * A task already started will be left as-is
 */
void task_start(TaskFunction task);

/**
 * Stops the specified task. The task's cleanup state will be executed before
 * it exits
 */
void task_stop(TaskFunction task);

/**
 * Returns whether the specified task is currently running or not
 */
bool task_is_running(TaskFunction task);


//--internal_functions----------------------------------------------------------

#define _TASK_COUNT_INIT enum { TASK_COUNTER_BASE = __COUNTER__ }
#define _TASK_COUNT_INCR (uint8_t)(__COUNTER__ - TASK_COUNTER_BASE - 1)
#define _TASK_COUNT_EXIT (UINT8_MAX & 0x3F)
#define _TASK_COUNT_CLEANUP (UINT8_MAX - 1)

#define _TASK_YIELD(count_val) do {                                            \
	__task_state->count = count_val;                                           \
	__task_state->trigger = TASK_TRIGGER_ANY;                                  \
	return;                                                                    \
	case (count_val):                                                          \
		/* fall through */                                                     \
	} while (0)

#define _TASK_PAUSE(delay_ms, count_val) do {                                  \
	__task_state->count = count_val;                                           \
	__task_state->timestamp = __task_time + delay_ms;                          \
	__task_state->trigger = TASK_TRIGGER_STK;                                  \
	return;                                                                    \
	case (count_val):                                                          \
		/* fall through */                                                     \
	} while (0)

#define _TASK_SLEEP(delay_s, count_val) do {                                   \
	__task_state->count = count_val;                                           \
	__task_state->timestamp = __task_time + delay_s * 1000;                    \
	__task_state->trigger = TASK_TRIGGER_RTC;                                  \
	return;                                                                    \
	case (count_val):                                                          \
		/* fall through */                                                     \
	} while (0)

#define _TASK_YIELD_UNTIL(cond, count_val) do {                                \
	__task_state->count = count_val;                                           \
	__task_state->trigger = TASK_TRIGGER_ANY;                                  \
	case (count_val):                                                          \
		if (!(cond)) {                                                         \
			return;                                                            \
		}                                                                      \
		/* fall through */                                                     \
	} while (0)

#define _TASK_PAUSE_UNTIL(cond, delay_ms, count_val) do {                      \
	__task_state->count = count_val;                                           \
	__task_state->timestamp = __task_time + delay_ms;                          \
	__task_state->trigger = TASK_TRIGGER_STK;                                  \
	__task_state->timeout_mode = true;                                         \
	case (count_val):                                                          \
		if (!(cond) && __task_state->timestamp != 0) {                         \
			return;                                                            \
		} else {                                                               \
			__task_state->timeout_mode = false;                                \
		}                                                                      \
		/* fall through */                                                     \
	} while (0)

#define _TASK_SLEEP_UNTIL(cond, delay_s, count_val) do {                       \
	__task_state->count = count_val;                                           \
	__task_state->timestamp = __taks_time + delay_s * 1000;                    \
	__task_state->trigger = TASK_TRIGGER_RTC;                                  \
	__task_state->timeout_mode = true;                                         \
	case (count_val):                                                          \
		if (!(cond) && __task_state->timestamp != 0) {                         \
			return;                                                            \
		} else {                                                               \
			__task_state->timeout_mode = false;                                \
		}                                                                      \
		/* fall through */                                                     \
	} while (0)

#define _TASK_EXECUTE(task, count_val) do {                                    \
	__task_state->count = count_val;                                           \
	__task_state->trigger = TASK_TRIGGER_ANY;                                  \
	task_start(Task task);                                                     \
	return;                                                                    \
	case (count_val):                                                          \
		if (task_is_running(task)) {                                           \
		return;                                                                \
	}                                                                          \
		/* fall through */                                                     \
	} while (0)

#endif //_task_h_

