//! This module allows hooking `esp-wifi` into an external scheduler, instead
//! of using the integrated one as provided by the `preempt` module.
//!
//! In order to hook up an external scheduler, enable the `preempt-extern`
//! feature, implement the `Scheduler` trait for a struct, and create the
//! necessary `extern` functions using the `scheduler_impl!()` macro.
//!
//! Example:
//!
//! ```ignore
//! use esp_wifi::preempt::Scheduler;
//!
//! struct MyScheduler {}
//!
//! impl Scheduler for MyScheduler {
//!     // impl goes here
//! }
//!
//! esp_wifi::scheduler_impl!(static SCHEDULER: MyScheduler = MyScheduler {});
//! ```
use core::ffi::c_void;

pub trait Scheduler: Send + Sync + 'static {
    /// This function is called by `esp-wifi` when starting up the WiFi stack.
    fn enable(&self);

    /// This function is called by `esp-wifi` when shutting down the WiFi stack.
    fn disable(&self);

    /// This function is called by threads and should switch to the next thread.
    fn yield_task(&self);

    /// This function is called by threads and should return an opaque handle
    /// for the calling thread. The same handle will be passed to
    /// `esp_wifi_preempt_schedule_task_deletion`.
    fn current_task(&self) -> *mut c_void;

    /// This function is used to create threads.
    /// It should allocate the stack.
    fn task_create(
        &self,
        task: extern "C" fn(*mut c_void),
        param: *mut c_void,
        task_stack_size: usize,
    ) -> *mut c_void;

    /// This function is called to let the scheduler know this thread is not
    /// needed anymore and should be deleted. After this function is called,
    /// the thread should not be scheduled anymore. The thread stack can be
    /// free'ed.
    fn schedule_task_deletion(&self, task_handle: *mut c_void);

    /// This function should return an opaque per-thread pointer to an
    /// usize-sized memory location, which will be used to store a pointer
    /// to a semaphore for this thread.
    fn current_task_thread_semaphore(&self) -> *mut c_void;
}

unsafe extern "Rust" {
    fn esp_wifi_preempt_enable();
    fn esp_wifi_preempt_disable();
    fn esp_wifi_preempt_yield_task();
    fn esp_wifi_preempt_current_task() -> *mut c_void;
    fn esp_wifi_preempt_task_create(
        task: extern "C" fn(*mut c_void),
        param: *mut c_void,
        task_stack_size: usize,
    ) -> *mut c_void;
    fn esp_wifi_preempt_schedule_task_deletion(task_handle: *mut c_void);
    fn esp_wifi_preempt_current_task_thread_semaphore() -> *mut c_void;
}

pub(crate) fn enable() {
    unsafe { esp_wifi_preempt_enable() }
}

pub(crate) fn disable() {
    unsafe { esp_wifi_preempt_disable() }
}

pub(crate) fn yield_task() {
    unsafe { esp_wifi_preempt_yield_task() }
}

pub(crate) fn current_task() -> *mut c_void {
    unsafe { esp_wifi_preempt_current_task() }
}

pub(crate) fn task_create(
    task: extern "C" fn(*mut c_void),
    param: *mut c_void,
    task_stack_size: usize,
) -> *mut c_void {
    unsafe { esp_wifi_preempt_task_create(task, param, task_stack_size) }
}

pub(crate) fn schedule_task_deletion(task_handle: *mut c_void) {
    unsafe { esp_wifi_preempt_schedule_task_deletion(task_handle) }
}

pub(crate) fn current_task_thread_semaphore() -> *mut c_void {
    unsafe { esp_wifi_preempt_current_task_thread_semaphore() }
}

/// Set the Scheduler implementation.
///
/// See the module documentation for an example.
#[macro_export]
macro_rules! scheduler_impl {
    (static $name:ident: $t: ty = $val:expr) => {
        static $name: $t = $val;

        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_enable() {
            <$t as $crate::preempt::Scheduler>::enable(&$name)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_disable() {
            <$t as $crate::preempt::Scheduler>::disable(&$name)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_yield_task() {
            <$t as $crate::preempt::Scheduler>::yield_task(&$name)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_current_task() -> *mut c_void {
            <$t as $crate::preempt::Scheduler>::current_task(&$name)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_task_create(
            task: extern "C" fn(*mut c_void),
            param: *mut c_void,
            task_stack_size: usize,
        ) -> *mut c_void {
            <$t as $crate::preempt::Scheduler>::task_create(&$name, task, param, task_stack_size)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_schedule_task_deletion(task_handle: *mut c_void) {
            <$t as $crate::preempt::Scheduler>::schedule_task_deletion(&$name, task_handle)
        }
        #[unsafe(no_mangle)]
        fn esp_wifi_preempt_current_task_thread_semaphore() -> *mut c_void {
            <$t as $crate::preempt::Scheduler>::current_task_thread_semaphore(&$name)
        }
    };
}
