/**
 * @file event_loop.h
 * @brief      Furi Event Loop
 *
 *             This module is designed to handle application event loop in fully
 *             asynchronous, reactive nature. On the low level this modules is
 *             inspired by epoll/kqueue concept, on the high level by asyncio
 *             event loop.
 *
 *             This module is trying to best fit into Furi OS, so we don't
 *             provide any compatibility with other event driven APIs. But
 *             programming concepts are the same, except some runtime
 *             limitations from our side.
 *
 * @warning Only ONE instance of FuriEventLoop per thread is possible. ALL FuriEventLoop
 * functions MUST be called from the same thread that the instance was created in.
 */
#pragma once

#include "base.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Enumeration of event types, flags and masks.
 *
 * Only one event direction (In or Out) can be used per subscription.
 * An object can have no more than one subscription for each direction.
 *
 * Additional flags that modify the behaviour can be
 * set using the bitwise OR operation (see flag description).
 */
typedef enum {
    /**
     * @brief Subscribe to In events.
     *
     * In events occur on the following conditions:
     * - One or more items were inserted into a FuriMessageQueue,
     * - Enough data has been written to a FuriStreamBuffer,
     * - A FuriSemaphore has been released at least once,
     * - A FuriMutex has been released.
     */
    FuriEventLoopEventIn = 0x00000001U,
    /**
     * @brief Subscribe to Out events.
     *
     * Out events occur on the following conditions:
     * - One or more items were removed from a FuriMessageQueue,
     * - Any amount of data has been read out of a FuriStreamBuffer,
     * - A FuriSemaphore has been acquired at least once,
     * - A FuriMutex has been acquired.
     */
    FuriEventLoopEventOut = 0x00000002U,
    /**
     * @brief Special value containing the event direction bits, used internally.
     */
    FuriEventLoopEventMask = 0x00000003U,
    /**
     * @brief Use edge triggered events.
     *
     * By default, level triggered events are used. A level above zero
     * is reported based on the following conditions:
     *
     * In events:
     * - a FuriMessageQueue contains one or more items,
     * - a FuriStreamBuffer contains one or more bytes,
     * - a FuriSemaphore can be acquired at least once,
     * - a FuriMutex can be acquired.
     *
     * Out events:
     * - a FuriMessageQueue has at least one item of free space,
     * - a FuriStreamBuffer has at least one byte of free space,
     * - a FuriSemaphore has been acquired at least once,
     * - a FuriMutex has been acquired.
     *
     * If this flag is NOT set, the event will be generated repeatedly until
     * the level becomes zero (e.g. all items have been removed from
     * a FuriMessageQueue in case of the "In" event, etc.)
     *
     * If this flag IS set, then the above check is skipped and the event
     * is generated ONLY when a change occurs, with the event direction
     * (In or Out) taken into account.
     */
    FuriEventLoopEventFlagEdge = 0x00000004U,
    /**
     * @brief Automatically unsubscribe from events after one time.
     *
     * By default, events will be generated each time the specified conditions
     * have been met. If this flag IS set, the event subscription will be cancelled
     * upon the first occurred event and no further events will be generated.
     */
    FuriEventLoopEventFlagOnce = 0x00000008U,
    /**
     * @brief Special value containing the event flag bits, used internally.
     */
    FuriEventLoopEventFlagMask = 0xFFFFFFFCU,
    /**
     * @brief Special value to force the enum to 32-bit values.
     */
    FuriEventLoopEventReserved = UINT32_MAX,
} FuriEventLoopEvent;

/** Anonymous message queue type */
typedef struct FuriEventLoop FuriEventLoop;

/** Allocate Event Loop instance
 *
 * Couple things to keep in mind:
 * - You can have 1 event_loop per 1 thread
 * - You can not use event_loop instance in the other thread
 * - Do not use blocking API to query object delegated to Event Loop
 *
 * @return     The Event Loop instance
 */
FuriEventLoop* furi_event_loop_alloc(void);

/** Free Event Loop instance
 *
 * @param      instance  The Event Loop instance
 */
void furi_event_loop_free(FuriEventLoop* instance);

/** Continuously poll for events
 *
 * Can be stopped with `furi_event_loop_stop`
 *
 * @param      instance  The Event Loop instance
 */
void furi_event_loop_run(FuriEventLoop* instance);

/** Stop Event Loop instance
 *
 * @param      instance  The Event Loop instance
 */
void furi_event_loop_stop(FuriEventLoop* instance);

/*
 * Tick related API
 */

/** Tick callback type
 *
 * @param      context  The context for callback
 */
typedef void (*FuriEventLoopTickCallback)(void* context);

/** Set Event Loop tick callback
 *
 * Tick callback is called periodically after specified inactivity time.
 * It acts like a low-priority timer: it will only fire if there is time
 * left after processing the synchronization primitives and the regular timers.
 * Therefore, it is not monotonic: ticks will be skipped if the event loop is busy.
 *
 * @param      instance  The Event Loop instance
 * @param[in]  interval  The tick interval
 * @param[in]  callback  The callback to call
 * @param      context   The context for callback
 */
void furi_event_loop_tick_set(
    FuriEventLoop* instance,
    uint32_t interval,
    FuriEventLoopTickCallback callback,
    void* context);

/*
 * Deferred function call API
 */

/**
 * @brief Timer callback type for functions to be called in a deferred manner.
 *
 * @param[in,out] context pointer to a user-specific object that was provided during
 *                        furi_event_loop_pend_callback() call
 */
typedef void (*FuriEventLoopPendingCallback)(void* context);

/**
 * @brief Call a function when all preceding timer commands are processed
 *
 * This function may be useful to call another function when the event loop has been started.
 *
 * @param[in,out] instance pointer to the current FuriEventLoop instance
 * @param[in] callback pointer to the callback to be executed when previous commands have been processed
 * @param[in,out] context pointer to a user-specific object (will be passed to the callback)
 */
void furi_event_loop_pend_callback(
    FuriEventLoop* instance,
    FuriEventLoopPendingCallback callback,
    void* context);

/*
 * Event subscription/notification APIs
 */

typedef void FuriEventLoopObject;

/** Callback type for event loop events
 *
 * @param      object   The object that triggered the event
 * @param      context  The context that was provided upon subscription
 */
typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context);

/** Callback type for event loop thread flag events
 * 
 * @param context The context that was provided upon subscription
 */
typedef void (*FuriEventLoopThreadFlagsCallback)(void* context);

/** Opaque event flag type */
typedef struct FuriEventFlag FuriEventFlag;

/** Subscribe to event flag events
 *
 * @warning you can only have one subscription for one event type.
 *
 * @param      instance       The Event Loop instance
 * @param      event_flag     The event flag to add
 * @param[in]  event          The Event Loop event to trigger on
 * @param[in]  callback       The callback to call on event
 * @param      context        The context for callback
 */

void furi_event_loop_subscribe_event_flag(
    FuriEventLoop* instance,
    FuriEventFlag* event_flag,
    FuriEventLoopEvent event,
    FuriEventLoopEventCallback callback,
    void* context);

/** Opaque message queue type */
typedef struct FuriMessageQueue FuriMessageQueue;

/** Subscribe to message queue events
 * 
 * @warning you can only have one subscription for one event type.
 *
 * @param      instance       The Event Loop instance
 * @param      message_queue  The message queue to add
 * @param[in]  event          The Event Loop event to trigger on
 * @param[in]  callback       The callback to call on event
 * @param      context        The context for callback
 */
void furi_event_loop_subscribe_message_queue(
    FuriEventLoop* instance,
    FuriMessageQueue* message_queue,
    FuriEventLoopEvent event,
    FuriEventLoopEventCallback callback,
    void* context);

/** Opaque stream buffer type */
typedef struct FuriStreamBuffer FuriStreamBuffer;

/** Subscribe to stream buffer events
 *
 * @warning you can only have one subscription for one event type.
 *
 * @param      instance       The Event Loop instance
 * @param      stream_buffer  The stream buffer to add
 * @param[in]  event          The Event Loop event to trigger on
 * @param[in]  callback       The callback to call on event
 * @param      context        The context for callback
 */
void furi_event_loop_subscribe_stream_buffer(
    FuriEventLoop* instance,
    FuriStreamBuffer* stream_buffer,
    FuriEventLoopEvent event,
    FuriEventLoopEventCallback callback,
    void* context);

/** Opaque semaphore type */
typedef struct FuriSemaphore FuriSemaphore;

/** Subscribe to semaphore events
 *
 * @warning you can only have one subscription for one event type.
 *
 * @param      instance       The Event Loop instance
 * @param      semaphore      The semaphore to add
 * @param[in]  event          The Event Loop event to trigger on
 * @param[in]  callback       The callback to call on event
 * @param      context        The context for callback
 */
void furi_event_loop_subscribe_semaphore(
    FuriEventLoop* instance,
    FuriSemaphore* semaphore,
    FuriEventLoopEvent event,
    FuriEventLoopEventCallback callback,
    void* context);

/** Opaque mutex type */
typedef struct FuriMutex FuriMutex;

/** Subscribe to mutex events
 *
 * @warning you can only have one subscription for one event type.
 *
 * @param      instance       The Event Loop instance
 * @param      mutex          The mutex to add
 * @param[in]  event          The Event Loop event to trigger on
 * @param[in]  callback       The callback to call on event
 * @param      context        The context for callback
 */
void furi_event_loop_subscribe_mutex(
    FuriEventLoop* instance,
    FuriMutex* mutex,
    FuriEventLoopEvent event,
    FuriEventLoopEventCallback callback,
    void* context);

/** Subscribe to thread flag events of the current thread
 * 
 * @param instance The Event Loop instance
 * @param callback The callback to call when a flag has been set
 * @param context  The context for callback
 */
void furi_event_loop_subscribe_thread_flags(
    FuriEventLoop* instance,
    FuriEventLoopThreadFlagsCallback callback,
    void* context);

/** Unsubscribe from thread flag events of the current thread
 * 
 * @param instance The Event Loop instance
 */
void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance);

/** Unsubscribe from events (common)
 *
 * @param      instance       The Event Loop instance
 * @param      object         The object to unsubscribe from
 */
void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object);

/**
 * @brief Checks if the loop is subscribed to an object of any kind
 * 
 * @param      instance       Event Loop instance
 * @param      object         Object to check
 */
bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object);

/**
 * @brief Convenience function for `if(is_subscribed()) unsubscribe()`
 */
static inline void
    furi_event_loop_maybe_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) {
    if(furi_event_loop_is_subscribed(instance, object))
        furi_event_loop_unsubscribe(instance, object);
}

#ifdef __cplusplus
}
#endif
