/**
 * @file thread.h
 * @brief Furi: Furi Thread API
 */

#pragma once

#include "base.h"
#include "common_defines.h"

#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Enumeration of possible FuriThread states.
 *
 * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED.
 */
typedef enum {
    FuriThreadStateStopped, /**< Thread is stopped and is safe to release. Event delivered from system init thread(TCB cleanup routine). It is safe to release thread instance. */
    FuriThreadStateStopping, /**< Thread is stopping. Event delivered from child thread. */
    FuriThreadStateStarting, /**< Thread is starting. Event delivered from parent(self) thread. */
    FuriThreadStateRunning, /**< Thread is running. Event delivered from child thread. */
} FuriThreadState;

/**
 * @brief Enumeration of possible FuriThread priorities.
 */
typedef enum {
    FuriThreadPriorityIdle = 0, /**< Idle priority */
    FuriThreadPriorityInit = 4, /**< Init System Thread Priority */
    FuriThreadPriorityLowest = 14, /**< Lowest */
    FuriThreadPriorityLow = 15, /**< Low */
    FuriThreadPriorityNormal = 16, /**< Normal, system default */
    FuriThreadPriorityHigh = 17, /**< High */
    FuriThreadPriorityHighest = 18, /**< Highest */
    FuriThreadPriorityIsr =
        (FURI_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */
} FuriThreadPriority;

/**
 * @brief FuriThread opaque type.
 */
typedef struct FuriThread FuriThread;

/** FuriThreadList type */
typedef struct FuriThreadList FuriThreadList;

/**
 * @brief Unique thread identifier type (used by the OS kernel).
 */
typedef void* FuriThreadId;

/**
 * @brief Thread callback function pointer type.
 *
 * The function to be used as a thread callback MUST follow this signature.
 *
 * @param[in,out] context pointer to a user-specified object
 * @return value to be used as the thread return code
 */
typedef int32_t (*FuriThreadCallback)(void* context);

/**
 * @brief Standard output callback function pointer type.
 *
 * The function to be used as a standard output callback MUST follow this signature.
 *
 * @warning The handler MUST process ALL of the provided data before returning.
 *
 * @param[in] data pointer to the data to be written to the standard out
 * @param[in] size size of the data in bytes
 * @param[in] context optional context
 */
typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context);

/**
 * @brief Standard input callback function pointer type
 * 
 * The function to be used as a standard input callback MUST follow this signature.
 * 
 * @param[out] buffer buffer to read data into
 * @param[in] size maximum number of bytes to read into the buffer
 * @param[in] timeout how long to wait for (in ticks) before giving up
 * @param[in] context optional context
 * @returns number of bytes that was actually read into the buffer
 */
typedef size_t (
    *FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context);

/**
 * @brief         State change callback function pointer type.
 *
 *                The function to be used as a state callback MUST follow this
 *                signature.
 *
 * @param[in]     thread   to the FuriThread instance that changed the state
 * @param[in]     state    identifier of the state the thread has transitioned
 *                         to
 * @param[in,out] context  pointer to a user-specified object
 */
typedef void (*FuriThreadStateCallback)(FuriThread* thread, FuriThreadState state, void* context);

/**
 * @brief Signal handler callback function pointer type.
 *
 * The function to be used as a signal handler callback MUS follow this signature.
 *
 * @param[in] signal value of the signal to be handled by the recipient
 * @param[in,out] arg optional argument (can be of any value, including NULL)
 * @param[in,out] context pointer to a user-specified object
 * @returns true if the signal was handled, false otherwise
 */
typedef bool (*FuriThreadSignalCallback)(uint32_t signal, void* arg, void* context);

/**
 * @brief Create a FuriThread instance.
 *
 * @return pointer to the created FuriThread instance
 */
FuriThread* furi_thread_alloc(void);

/**
 * @brief Create a FuriThread instance (service mode).
 *
 * Service threads are more memory efficient, but have
 * the following limitations:
 *
 * - Cannot return from the callback
 * - Cannot be joined or freed
 * - Stack size cannot be altered
 *
 * @param[in] name human-readable thread name (can be NULL)
 * @param[in] stack_size stack size in bytes (cannot be changed later)
 * @param[in] callback pointer to a function to be executed in this thread
 * @param[in] context pointer to a user-specified object (will be passed to the callback)
 * @return pointer to the created FuriThread instance
 */
FuriThread* furi_thread_alloc_service(
    const char* name,
    uint32_t stack_size,
    FuriThreadCallback callback,
    void* context);

/**
 * @brief Create a FuriThread instance w/ extra parameters.
 * 
 * @param[in] name human-readable thread name (can be NULL)
 * @param[in] stack_size stack size in bytes (can be changed later)
 * @param[in] callback pointer to a function to be executed in this thread
 * @param[in] context pointer to a user-specified object (will be passed to the callback)
 * @return pointer to the created FuriThread instance
 */
FuriThread* furi_thread_alloc_ex(
    const char* name,
    uint32_t stack_size,
    FuriThreadCallback callback,
    void* context);

/**
 * @brief Delete a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @warning see furi_thread_join for caveats on stopping a thread.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be deleted
 */
void furi_thread_free(FuriThread* thread);

/**
 * @brief Set the name of a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] name human-readable thread name (can be NULL)
 */
void furi_thread_set_name(FuriThread* thread, const char* name);

/**
 * @brief Set the application ID of a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function.
 *
 * Technically, it is like a "process id", but it is not a system-wide unique identifier.
 * All threads spawned by the same app will have the same appid.
 * 
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] appid thread application ID (can be NULL)
 */
void furi_thread_set_appid(FuriThread* thread, const char* appid);

/**
 * @brief Set the stack size of a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function. Additionally, it is NOT possible
 * to change the stack size of a service thread under any circumstances.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] stack_size stack size in bytes
 */
void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size);

/**
 * @brief Set the user callback function to be executed in a FuriThread.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] callback pointer to a user-specified function to be executed in this thread
 */
void furi_thread_set_callback(FuriThread* thread, FuriThreadCallback callback);

/**
 * @brief Set the callback function context.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL)
 */
void furi_thread_set_context(FuriThread* thread, void* context);

/**
 * @brief Set the priority of a FuriThread.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] priority priority level value
 */
void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority);

/**
 * @brief Get the priority of a FuriThread.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return priority level value
 */
FuriThreadPriority furi_thread_get_priority(FuriThread* thread);

/**
 * @brief Set the priority of the current FuriThread.
 *
 * @param priority priority level value
 */
void furi_thread_set_current_priority(FuriThreadPriority priority);

/**
 * @brief Get the priority of the current FuriThread.
 *
 * @return priority level value
 */
FuriThreadPriority furi_thread_get_current_priority(void);

/**
 * Set the callback function to be executed upon a state thransition of a FuriThread.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] callback pointer to a user-specified callback function
 */
void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback);

/**
 * @brief Set the state change callback context.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL)
 */
void furi_thread_set_state_context(FuriThread* thread, void* context);

/**
 * @brief Get the state of a FuriThread isntance.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return thread state value
 */
FuriThreadState furi_thread_get_state(FuriThread* thread);

/**
 * @brief Set a signal handler callback for a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function if calling it from another thread.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 * @param[in] callback pointer to a user-specified callback function
 * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL)
 */
void furi_thread_set_signal_callback(
    FuriThread* thread,
    FuriThreadSignalCallback callback,
    void* context);

/**
 * @brief Get a signal callback for a FuriThread instance.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return pointer to the callback function or NULL if none has been set
 */
FuriThreadSignalCallback furi_thread_get_signal_callback(const FuriThread* thread);

/**
 * @brief Send a signal to a FuriThread instance.
 *
 * @param[in] thread pointer to the FuriThread instance to be signaled
 * @param[in] signal signal value to be sent
 * @param[in,out] arg optional argument (can be of any value, including NULL)
 */
bool furi_thread_signal(const FuriThread* thread, uint32_t signal, void* arg);

/**
 * @brief Start a FuriThread instance.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be started
 */
void furi_thread_start(FuriThread* thread);

/**
 * @brief Wait for a FuriThread to exit.
 *
 * The thread callback function must return in order for the FuriThread instance to become joinable.
 *
 * @warning Use this method only when the CPU is not busy (i.e. when the
 *          Idle task receives control), otherwise it will wait forever.
 *
 * @param[in] thread pointer to the FuriThread instance to be joined
 * @return always true
 */
bool furi_thread_join(FuriThread* thread);

/**
 * @brief Get the unique identifier of a FuriThread instance.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return unique identifier value or NULL if thread is not running
 */
FuriThreadId furi_thread_get_id(FuriThread* thread);

/**
 * @brief Enable heap usage tracing for a FuriThread.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 */
void furi_thread_enable_heap_trace(FuriThread* thread);

/**
 * @brief Disable heap usage tracing for a FuriThread.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in,out] thread pointer to the FuriThread instance to be modified
 */
void furi_thread_disable_heap_trace(FuriThread* thread);

/**
 * @brief Get heap usage by a FuriThread instance.
 *
 * The heap trace MUST be enabled before callgin this function.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return heap usage in bytes
 */
size_t furi_thread_get_heap_size(FuriThread* thread);

/**
 * @brief Get the return code of a FuriThread instance.
 *
 * This value is equal to the return value of the thread callback function.
 *
 * The thread MUST be stopped when calling this function.
 *
 * @param[in] thread pointer to the FuriThread instance to be queried
 * @return return code value
 */
int32_t furi_thread_get_return_code(FuriThread* thread);

/**
 * @brief Get the unique identifier of the current FuriThread.
 *
 * @return unique identifier value
 */
FuriThreadId furi_thread_get_current_id(void);

/**
 * @brief Get the FuriThread instance associated with the current thread.
 * 
 * @return pointer to a FuriThread instance or NULL if this thread does not belong to Furi
 */
FuriThread* furi_thread_get_current(void);

/**
 * @brief Return control to the scheduler.
 */
void furi_thread_yield(void);

/**
 * @brief Set the thread flags of a FuriThread.
 *
 * Can be used as a simple inter-thread communication mechanism.
 *
 * @param[in] thread_id unique identifier of the thread to be notified
 * @param[in] flags bitmask of thread flags to set
 * @return bitmask combination of previous and newly set flags
 */
uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags);

/**
 * @brief Clear the thread flags of the current FuriThread.
 *
 * @param[in] flags bitmask of thread flags to clear
 * @return bitmask of thread flags before clearing
 */
uint32_t furi_thread_flags_clear(uint32_t flags);

/**
 * @brief Get the thread flags of the current FuriThread.
 * @return current bitmask of thread flags
 */
uint32_t furi_thread_flags_get(void);

/**
 * @brief Wait for some thread flags to be set.
 *
 * @see FuriFlag for option and error flags.
 *
 * @param[in] flags bitmask of thread flags to wait for
 * @param[in] options combination of option flags determining the behavior of the function
 * @param[in] timeout maximum time to wait in milliseconds (use FuriWaitForever to wait forever)
 * @return bitmask combination of received thread and error flags
 */
uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);

/**
 * @brief      Enumerate all threads.
 *
 * @param[out] thread_list  pointer to the FuriThreadList container
 *
 * @return     true on success, false otherwise
 */
bool furi_thread_enumerate(FuriThreadList* thread_list);

/**
 * @brief Get the name of a thread based on its unique identifier.
 * 
 * @param[in] thread_id unique identifier of the thread to be queried
 * @return pointer to a zero-terminated string or NULL
 */
const char* furi_thread_get_name(FuriThreadId thread_id);

/**
 * @brief Get the application id of a thread based on its unique identifier.
 * 
 * @param[in] thread_id unique identifier of the thread to be queried
 * @return pointer to a zero-terminated string
 */
const char* furi_thread_get_appid(FuriThreadId thread_id);

/**
 * @brief Get thread stack watermark.
 * 
 * @param[in] thread_id unique identifier of the thread to be queried
 * @return stack watermark value
 */
uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);

/**
 * @brief Get the standard output callback for the current thead.
 *
 * @param[out] callback where to store the stdout callback
 * @param[out] context where to store the context
 */
void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context);

/**
 * @brief Get the standard input callback for the current thead.
 *
 * @param[out] callback where to store the stdin callback
 * @param[out] context where to store the context
 */
void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context);

/** Set standard output callback for the current thread.
 *
 * @param[in] callback pointer to the callback function or NULL to clear
 * @param[in] context context to be passed to the callback
 */
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context);

/** Set standard input callback for the current thread.
 * 
 * @param[in] callback pointer to the callback function or NULL to clear
 * @param[in] context context to be passed to the callback
 */
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context);

/** Write data to buffered standard output.
 * 
 * @note You can also use the standard C `putc`, `puts`, `printf` and friends.
 * 
 * @param[in] data pointer to the data to be written
 * @param[in] size data size in bytes
 * @return number of bytes that was actually written
 */
size_t furi_thread_stdout_write(const char* data, size_t size);

/**
 * @brief Flush buffered data to standard output.
 * 
 * @return error code value
 */
int32_t furi_thread_stdout_flush(void);

/** Read data from the standard input
 * 
 * @note You can also use the standard C `getc`, `gets` and friends.
 * 
 * @param[in] buffer pointer to the buffer to read data into
 * @param[in] size how many bytes to read into the buffer
 * @param[in] timeout how long to wait for (in ticks) before giving up
 * @return number of bytes that was actually read
 */
size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout);

/** Puts data back into the standard input buffer
 * 
 * `furi_thread_stdin_read` will return the bytes in the same order that they
 * were supplied to this function.
 * 
 * @note You can also use the standard C `ungetc`.
 * 
 * @param[in] buffer pointer to the buffer to get data from
 * @param[in] size how many bytes to read from the buffer
 */
void furi_thread_stdin_unread(char* buffer, size_t size);

/**
 * @brief Suspend a thread.
 *
 * Suspended threads are no more receiving any of the processor time.
 * 
 * @param[in] thread_id unique identifier of the thread to be suspended
 */
void furi_thread_suspend(FuriThreadId thread_id);

/**
 * @brief Resume a thread.
 * 
 * @param[in] thread_id unique identifier of the thread to be resumed
 */
void furi_thread_resume(FuriThreadId thread_id);

/**
 * @brief Test if a thread is suspended.
 * 
 * @param[in] thread_id unique identifier of the thread to be queried
 * @return true if thread is suspended, false otherwise
 */
bool furi_thread_is_suspended(FuriThreadId thread_id);

#ifdef __cplusplus
}
#endif
