//! Thread priority. A library for changing thread's priority.
//!
//! # Usage
//!
//! Setting thread priority to minimum:
//!
//! ```rust
//! use thread_priority::*;
//!
//! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok());
//! // Or like this:
//! assert!(ThreadPriority::Min.set_for_current().is_ok());
//! ```
//!
//! # More examples
//!
//! ### Minimal cross-platform examples
//! Setting current thread's priority to minimum:
//!
//! ```rust,no_run
//! use thread_priority::*;
//!
//! assert!(set_current_thread_priority(ThreadPriority::Min).is_ok());
//! ```
//!
//! The same as above but using a specific value:
//!
//! ```rust,no_run
//! use thread_priority::*;
//! use std::convert::TryInto;
//!
//! // The lower the number the lower the priority.
//! assert!(set_current_thread_priority(ThreadPriority::Crossplatform(0.try_into().unwrap())).is_ok());
//! ```
//!
//! ### Building a thread using the [`ThreadBuilderExt`] trait
//!
//! ```rust,no_run
//! use thread_priority::*;
//! use thread_priority::ThreadBuilderExt;
//!
//! let thread = std::thread::Builder::new()
//!     .name("MyNewThread".to_owned())
//!     .spawn_with_priority(ThreadPriority::Max, |result| {
//!         // This is printed out from within the spawned thread.
//!         println!("Set priority result: {:?}", result);
//!         assert!(result.is_ok());
//! }).unwrap();
//! thread.join();
//!
//! // This also support scoped thread.
//! let x = 0;
//! std::thread::scope(|s|{
//!     std::thread::Builder::new()
//!         .name("MyNewThread".to_owned())
//!         .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| {
//!             // This is printed out from within the spawned thread.
//!             println!("Set priority result: {:?}", result);
//!             assert!(result.is_ok());
//!             dbg!(&x);
//!     }).unwrap();
//! });
//! ```
//!
//! ### Building a thread using the [`ThreadScopeExt`] trait
//!
//! ```rust,no_run
//! use thread_priority::*;
//! let x = 0;
//! std::thread::scope(|s|{
//!     s.spawn_with_priority(ThreadPriority::Max, |result| {
//!             // This is printed out from within the spawned thread.
//!             println!("Set priority result: {:?}", result);
//!             assert!(result.is_ok());
//!             dbg!(&x);
//!     });
//! });
//! ```
//!
//! ### Building a thread using the [`ThreadBuilder`].
//!
//! ```rust,no_run
//! use thread_priority::*;
//!
//! let thread = ThreadBuilder::default()
//!     .name("MyThread")
//!     .priority(ThreadPriority::Max)
//!     .spawn(|result| {
//!         // This is printed out from within the spawned thread.
//!         println!("Set priority result: {:?}", result);
//!         assert!(result.is_ok());
//! }).unwrap();
//! thread.join();
//!
//! // Another example where we don't care about the priority having been set.
//! let thread = ThreadBuilder::default()
//!     .name("MyThread")
//!     .priority(ThreadPriority::Max)
//!     .spawn_careless(|| {
//!         // This is printed out from within the spawned thread.
//!         println!("We don't care about the priority result.");
//! }).unwrap();
//! thread.join();
//!
//! // Scoped thread is also supported if the compiler version is at least 1.63.
//! let mut x = 0;
//! std::thread::scope(|s|{
//!     let thread = ThreadBuilder::default()
//!         .name("MyThread")
//!         .priority(ThreadPriority::Max)
//!         .spawn_scoped(s, |result| {
//!             // This is printed out from within the spawned thread.
//!             println!("Set priority result: {:?}", result);
//!             assert!(result.is_ok());
//!             x += 1;
//!     }).unwrap();
//!     thread.join();
//! });
//! assert_eq!(x, 1);
//!
//! // Scoped thread also has a "careless" mode.
//! std::thread::scope(|s|{
//!     let thread = ThreadBuilder::default()
//!         .name("MyThread")
//!         .priority(ThreadPriority::Max)
//!         .spawn_scoped_careless(s, || {
//!             // This is printed out from within the spawned thread.
//!             println!("We don't care about the priority result.");
//!             x += 1;
//!     }).unwrap();
//!     thread.join();
//! });
//! assert_eq!(x, 2);
//! ```
//!
//! ### Using [`ThreadExt`] trait on the current thread
//!
//! ```rust,no_run
//! use thread_priority::*;
//!
//! assert!(std::thread::current().get_priority().is_ok());
//! println!("This thread's native id is: {:?}", std::thread::current().get_native_id());
//! ```
//!
#![warn(missing_docs)]
#![deny(warnings)]

#[cfg(any(
    target_os = "linux",
    target_os = "macos",
    target_os = "ios",
    target_os = "dragonfly",
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "vxworks",
    target_os = "netbsd",
    target_os = "android",
    target_arch = "wasm32",
))]
pub mod unix;
use std::ops::Deref;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::time::Duration;

#[cfg(any(
    target_os = "linux",
    target_os = "macos",
    target_os = "ios",
    target_os = "dragonfly",
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "vxworks",
    target_os = "netbsd",
    target_os = "android",
    target_arch = "wasm32",
))]
pub use unix::*;

#[cfg(windows)]
pub mod windows;
#[cfg(windows)]
pub use windows::*;

/// A error type
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Error {
    /// A value which describes why it is impossible to use such a priority.
    Priority(&'static str),
    /// Indicates that the priority isn't in range and it should be within the provided range.
    /// This may happen on different operating systems following a single standard of API but
    /// allowing different priority values for different scheduling policies.
    PriorityNotInRange(std::ops::RangeInclusive<i32>),
    /// Target OS' error type. In most systems it is an integer which
    /// later should be used with target OS' API for understanding the value.
    /// On Linux there is an integer containing an error code from errno.
    /// For Windows it contains a number used in Windows for the same purpose.
    OS(i32),
    /// FFI failure.
    Ffi(&'static str),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::Priority(s) => write!(f, "unable to set priority: {s}"),
            Error::PriorityNotInRange(range) => {
                write!(f, "priority must be within the range: {range:?}")
            }
            Error::OS(i) => write!(f, "the operating system returned error code {i}"),
            Error::Ffi(s) => write!(f, "FFI error: {s}"),
        }
    }
}

impl std::error::Error for Error {}

/// Platform-independent thread priority value.
/// Should be in `[0; 100)` range. The higher the number is - the higher
/// the priority.
///
/// The only way to create such a value is a safe conversion from an 8-bit
/// unsigned integer ([`u8`]):
///
/// ```rust
/// use thread_priority::*;
/// use std::convert::{TryFrom, TryInto};
///
/// // Create the lowest possible priority value.
/// assert!(ThreadPriorityValue::try_from(0u8).is_ok());
/// // Create it implicitly via `TryInto`:
/// let _priority = ThreadPriority::Crossplatform(0u8.try_into().unwrap());
/// ```
///
/// In case you need to get the raw value out of it, use the `Into<u8>` trait:
///
/// ```rust
/// use thread_priority::*;
/// use std::convert::TryFrom;
///
/// // Create the lowest possible priority value.
/// let priority = ThreadPriorityValue::try_from(0u8).unwrap();
/// // Create it implicitly via `TryInto`:
/// let raw_value: u8 = priority.into();
/// assert_eq!(raw_value, 0);
/// ```
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ThreadPriorityValue(pub(crate) u8);
impl ThreadPriorityValue {
    /// The maximum value for a thread priority.
    pub const MAX: Self = Self(if cfg!(target_os = "vxworks") { 255 } else { 99 });
    /// The minimum value for a thread priority.
    pub const MIN: Self = Self(0);
}

impl Deref for ThreadPriorityValue {
    type Target = u8;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl PartialOrd<u8> for ThreadPriorityValue {
    fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
        self.0.partial_cmp(other)
    }
}

impl PartialOrd<ThreadPriorityValue> for u8 {
    fn partial_cmp(&self, other: &ThreadPriorityValue) -> Option<std::cmp::Ordering> {
        self.partial_cmp(&other.0)
    }
}

impl PartialEq<u8> for ThreadPriorityValue {
    fn eq(&self, other: &u8) -> bool {
        self.0 == *other
    }
}

impl PartialEq<ThreadPriorityValue> for u8 {
    fn eq(&self, other: &ThreadPriorityValue) -> bool {
        *self == other.0
    }
}

impl std::fmt::Display for ThreadPriorityValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::convert::TryFrom<u8> for ThreadPriorityValue {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        if (Self::MIN..=Self::MAX).contains(&value) {
            Ok(Self(value))
        } else {
            Err(format!(
                "The value is not in the range of [{}; {}]",
                Self::MIN,
                Self::MAX
            ))
        }
    }
}

impl From<ThreadPriorityValue> for u8 {
    fn from(value: ThreadPriorityValue) -> Self {
        value.0
    }
}

/// Platform-specific thread priority value.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ThreadPriorityOsValue(u32);

/// Thread priority enumeration.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum ThreadPriority {
    /// Holds a value representing the minimum possible priority.
    #[cfg_attr(
        target_os = "windows",
        doc = "\
The [`ThreadPriority::Min`] value is mapped to [`WinAPIThreadPriority::Lowest`] and not
[`WinAPIThreadPriority::Idle`] to avoid unexpected drawbacks. Use the specific value
to set it to [`WinAPIThreadPriority::Idle`] when it is really needed.
"
    )]
    Min,
    /// Holds a platform-independent priority value.
    /// Usually used when setting a value, for sometimes it is not possible to map
    /// the operating system's priority to this value.
    Crossplatform(ThreadPriorityValue),
    /// Holds an operating system specific value. If it is not possible to obtain the
    /// [`ThreadPriority::Crossplatform`] variant of the value, this is returned instead.
    #[cfg_attr(
        target_os = "windows",
        doc = "\
The value is matched among possible values in Windows from [`WinAPIThreadPriority::Idle`] till
[`WinAPIThreadPriority::TimeCritical`]. This is due to windows only having from 7 to 9 possible
thread priorities and not `100` as it is allowed to have in the [`ThreadPriority::Crossplatform`]
variant.
"
    )]
    Os(ThreadPriorityOsValue),
    /// Holds scheduling parameters for Deadline scheduling. These are, in order,
    /// the nanoseconds for runtime, deadline, and period. Please note that the
    /// kernel enforces runtime <= deadline <= period.
    ///
    ///   arrival/wakeup                    absolute deadline
    ///        |    start time                    |
    ///        |        |                         |
    ///        v        v                         v
    ///   -----x--------xooooooooooooooooo--------x--------x---
    ///                 |<-- Runtime ------->|
    ///        |<----------- Deadline ----------->|
    ///        |<-------------- Period ------------------->|
    #[cfg(any(target_os = "linux", target_os = "android"))]
    Deadline {
        /// Set this to something larger than the average computation time
        /// or to the worst-case computation time for hard real-time tasks.
        runtime: Duration,
        /// Set this to the relative deadline.
        deadline: Duration,
        /// Set this to the period of the task.
        period: Duration,
        /// Deadline flags.
        flags: crate::unix::DeadlineFlags,
    },
    /// Holds a value representing the maximum possible priority.
    /// Should be used with caution, it solely depends on the target
    /// os where the program is going to be running on, how it will
    /// behave. On some systems, the whole system may become frozen
    /// if not used properly.
    #[cfg_attr(
        target_os = "windows",
        doc = "\
The [`ThreadPriority::Max`] value is mapped to [`WinAPIThreadPriority::Highest`] and not
[`WinAPIThreadPriority::TimeCritical`] to avoid unexpected drawbacks. Use the specific value
to set it to [`WinAPIThreadPriority::TimeCritical`] when it is really needed.
"
    )]
    Max,
}

impl ThreadPriority {
    /// Sets current thread's priority to this value.
    pub fn set_for_current(self) -> Result<(), Error> {
        set_current_thread_priority(self)
    }
}

/// Represents an OS thread.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Thread {
    /// Thread's priority.
    pub priority: ThreadPriority,
    /// Thread's ID (or handle).
    pub id: ThreadId,
}

impl Thread {
    /// Get current thread.
    ///
    /// # Usage
    ///
    /// ```rust
    /// use thread_priority::*;
    ///
    /// assert!(Thread::current().is_ok());
    /// ```
    pub fn current() -> Result<Thread, Error> {
        Ok(Thread {
            priority: get_current_thread_priority()?,
            id: thread_native_id(),
        })
    }
}

/// A wrapper producing a closure where the input priority set result is logged on error, but no other handling is performed
fn careless_wrapper<F, T>(f: F) -> impl FnOnce(Result<(), Error>) -> T
where
    F: FnOnce() -> T + Send,
    T: Send,
{
    |priority_set_result| {
        if let Err(e) = priority_set_result {
            log::warn!(
                "Couldn't set the priority for the thread with Rust Thread ID {:?} named {:?}: {:?}",
                std::thread::current().id(),
                std::thread::current().name(),
                e,
            );
        }

        f()
    }
}

/// A copy of the [`std::thread::Builder`] builder allowing to set priority settings.
///
/// ```rust
/// use thread_priority::*;
///
/// let thread = ThreadBuilder::default()
///     .name("MyThread")
///     .priority(ThreadPriority::Max)
///     .spawn(|result| {
///         // This is printed out from within the spawned thread.
///         println!("Set priority result: {:?}", result);
///         assert!(result.is_ok());
/// }).unwrap();
/// thread.join();
///
/// // Another example where we don't care about the priority having been set.
/// let thread = ThreadBuilder::default()
///     .name("MyThread")
///     .priority(ThreadPriority::Max)
///     .spawn_careless(|| {
///         // This is printed out from within the spawned thread.
///         println!("We don't care about the priority result.");
/// }).unwrap();
/// thread.join();
/// ```
///
/// If the compiler version is at least 1.63, the scoped thread support is also enabled.
///
/// ```rust
/// use thread_priority::*;
///
/// // Scoped thread is also supported if the compiler version is at least 1.63.
/// let mut x = 0;
/// std::thread::scope(|s|{
///     let thread = ThreadBuilder::default()
///         .name("MyThread")
///         .priority(ThreadPriority::Max)
///         .spawn_scoped(s, |result| {
///             // This is printed out from within the spawned thread.
///             println!("Set priority result: {:?}", result);
///             assert!(result.is_ok());
///             x += 1;
///     }).unwrap();
///     thread.join();
/// });
/// assert_eq!(x, 1);
///
/// // Scoped thread also has a "careless" mode.
/// std::thread::scope(|s|{
///     let thread = ThreadBuilder::default()
///         .name("MyThread")
///         .priority(ThreadPriority::Max)
///         .spawn_scoped_careless(s, || {
///             // This is printed out from within the spawned thread.
///             println!("We don't care about the priority result.");
///             x += 1;
///     }).unwrap();
///     thread.join();
/// });
/// assert_eq!(x, 2);
/// ```
#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct ThreadBuilder {
    name: Option<String>,
    stack_size: Option<usize>,
    priority: Option<ThreadPriority>,

    #[cfg(unix)]
    policy: Option<ThreadSchedulePolicy>,

    #[cfg(windows)]
    winapi_priority: Option<WinAPIThreadPriority>,
    #[cfg(windows)]
    boost_enabled: bool,
    #[cfg(windows)]
    ideal_processor: Option<IdealProcessor>,
}

impl ThreadBuilder {
    /// Names the thread-to-be. Currently the name is used for identification
    /// only in panic messages.
    ///
    /// The name must not contain null bytes (`\0`).
    ///
    /// For more information about named threads, see
    /// [`std::thread::Builder::name()`].
    pub fn name<VALUE: Into<String>>(mut self, value: VALUE) -> Self {
        self.name = Some(value.into());
        self
    }

    /// Sets the size of the stack (in bytes) for the new thread.
    ///
    /// The actual stack size may be greater than this value if
    /// the platform specifies a minimal stack size.
    ///
    /// For more information about the stack size for threads, see
    /// [`std::thread::Builder::stack_size()`].
    pub fn stack_size<VALUE: Into<usize>>(mut self, value: VALUE) -> Self {
        self.stack_size = Some(value.into());
        self
    }

    /// The thread's custom priority.
    ///
    /// For more information about the stack size for threads, see
    /// [`ThreadPriority`].
    pub fn priority<VALUE: Into<ThreadPriority>>(mut self, value: VALUE) -> Self {
        self.priority = Some(value.into());
        self
    }

    /// The thread's unix scheduling policy.
    ///
    /// For more information, see
    /// [`crate::unix::ThreadSchedulePolicy`] and [`crate::unix::set_thread_priority_and_policy`].
    #[cfg(unix)]
    pub fn policy<VALUE: Into<unix::ThreadSchedulePolicy>>(mut self, value: VALUE) -> Self {
        self.policy = Some(value.into());
        self
    }

    /// The WinAPI priority representation.
    ///
    /// For more information, see
    /// [`crate::windows::WinAPIThreadPriority`].
    #[cfg(windows)]
    pub fn winapi_priority<VALUE: Into<windows::WinAPIThreadPriority>>(
        mut self,
        value: VALUE,
    ) -> Self {
        self.winapi_priority = Some(value.into());
        self
    }

    /// Disables or enables the ability of the system to temporarily boost the priority of a thread.
    ///
    /// For more information, see
    /// [`crate::windows::set_thread_priority_boost`].
    #[cfg(windows)]
    pub fn boost_enabled(mut self, value: bool) -> Self {
        self.boost_enabled = value;
        self
    }

    /// Sets a preferred processor for a thread. The system schedules threads on their preferred
    /// processors whenever possible.
    ///
    /// For more information, see
    /// [`crate::windows::set_thread_ideal_processor`].
    #[cfg(windows)]
    pub fn ideal_processor<VALUE: Into<windows::IdealProcessor>>(mut self, value: VALUE) -> Self {
        self.ideal_processor = Some(value.into());
        self
    }

    #[cfg(unix)]
    fn spawn_wrapper<F, T>(self, f: F) -> impl FnOnce() -> T
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send,
        T: Send,
    {
        move || match (self.priority, self.policy) {
            (Some(priority), Some(policy)) => f(set_thread_priority_and_policy(
                thread_native_id(),
                priority,
                policy,
            )),
            (Some(priority), None) => f(priority.set_for_current()),
            (None, Some(_policy)) => {
                unimplemented!("Setting the policy separately isn't currently supported.");
            }
            _ => f(Ok(())),
        }
    }

    #[cfg(windows)]
    fn spawn_wrapper<F, T>(self, f: F) -> impl FnOnce() -> T
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send,
        T: Send,
    {
        move || {
            let mut result = match (self.priority, self.winapi_priority) {
                (Some(priority), None) => set_thread_priority(thread_native_id(), priority),
                (_, Some(priority)) => set_winapi_thread_priority(thread_native_id(), priority),
                _ => Ok(()),
            };
            if result.is_ok() && self.boost_enabled {
                result = set_current_thread_priority_boost(self.boost_enabled);
            }
            if result.is_ok() {
                if let Some(ideal_processor) = self.ideal_processor {
                    result = set_current_thread_ideal_processor(ideal_processor).map(|_| ());
                }
            }
            f(result)
        }
    }

    /// Spawns a new thread by taking ownership of the `Builder`, and returns an
    /// [`std::io::Result`] to its [`std::thread::JoinHandle`].
    ///
    /// See [`std::thread::Builder::spawn`]
    pub fn spawn<F, T>(mut self, f: F) -> std::io::Result<std::thread::JoinHandle<T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'static,
        T: Send + 'static,
    {
        self.build_std().spawn(self.spawn_wrapper(f))
    }

    /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an
    /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`].
    ///
    /// See [`std::thread::Builder::spawn_scoped`]
    #[rustversion::since(1.63)]
    pub fn spawn_scoped<'scope, 'env, F, T>(
        mut self,
        scope: &'scope std::thread::Scope<'scope, 'env>,
        f: F,
    ) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'scope,
        T: Send + 'scope,
    {
        self.build_std().spawn_scoped(scope, self.spawn_wrapper(f))
    }

    fn build_std(&mut self) -> std::thread::Builder {
        let mut builder = std::thread::Builder::new();

        if let Some(name) = &self.name {
            builder = builder.name(name.to_owned());
        }

        if let Some(stack_size) = self.stack_size {
            builder = builder.stack_size(stack_size);
        }

        builder
    }

    /// Spawns a new thread by taking ownership of the `Builder`, and returns an
    /// [`std::io::Result`] to its [`std::thread::JoinHandle`].
    ///
    /// See [`std::thread::Builder::spawn`]
    pub fn spawn_careless<F, T>(self, f: F) -> std::io::Result<std::thread::JoinHandle<T>>
    where
        F: FnOnce() -> T,
        F: Send + 'static,
        T: Send + 'static,
    {
        self.spawn(careless_wrapper(f))
    }

    /// Spawns a new scoped thread by taking ownership of the `Builder`, and returns an
    /// [`std::io::Result`] to its [`std::thread::ScopedJoinHandle`].
    ///
    /// See [`std::thread::Builder::spawn_scoped`]
    #[rustversion::since(1.63)]
    pub fn spawn_scoped_careless<'scope, 'env, F, T>(
        self,
        scope: &'scope std::thread::Scope<'scope, 'env>,
        f: F,
    ) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
    where
        F: FnOnce() -> T,
        F: Send + 'scope,
        T: Send + 'scope,
    {
        self.spawn_scoped(scope, careless_wrapper(f))
    }
}

/// Adds thread building functions using the priority.
pub trait ThreadBuilderExt {
    /// Spawn a thread with set priority. The passed functor `f` is executed in the spawned thread and
    /// receives as the only argument the result of setting the thread priority.
    /// See [`std::thread::Builder::spawn`] and [`ThreadPriority::set_for_current`] for more info.
    ///
    /// # Example
    ///
    /// ```rust
    /// use thread_priority::*;
    /// use thread_priority::ThreadBuilderExt;
    ///
    /// let thread = std::thread::Builder::new()
    ///     .name("MyNewThread".to_owned())
    ///     .spawn_with_priority(ThreadPriority::Max, |result| {
    ///         // This is printed out from within the spawned thread.
    ///         println!("Set priority result: {:?}", result);
    ///         assert!(result.is_ok());
    /// }).unwrap();
    /// thread.join();
    /// ```
    fn spawn_with_priority<F, T>(
        self,
        priority: ThreadPriority,
        f: F,
    ) -> std::io::Result<std::thread::JoinHandle<T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'static,
        T: Send + 'static;

    /// Spawn a scoped thread with set priority. The passed functor `f` is executed in the spawned thread and
    /// receives as the only argument the result of setting the thread priority.
    /// See [`std::thread::Builder::spawn_scoped`] and [`ThreadPriority::set_for_current`] for more info.
    ///
    /// # Example
    ///
    /// ```rust
    /// use thread_priority::*;
    /// use thread_priority::ThreadBuilderExt;
    ///
    /// let x = 0;
    ///
    /// std::thread::scope(|s|{
    ///     std::thread::Builder::new()
    ///         .name("MyNewThread".to_owned())
    ///         .spawn_scoped_with_priority(s, ThreadPriority::Max, |result| {
    ///             // This is printed out from within the spawned thread.
    ///             println!("Set priority result: {:?}", result);
    ///             assert!(result.is_ok());
    ///             dbg!(&x);
    ///     }).unwrap();
    /// });
    /// ```
    #[rustversion::since(1.63)]
    fn spawn_scoped_with_priority<'scope, 'env, F, T>(
        self,
        scope: &'scope std::thread::Scope<'scope, 'env>,
        priority: ThreadPriority,
        f: F,
    ) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'scope,
        T: Send + 'scope;
}

impl ThreadBuilderExt for std::thread::Builder {
    fn spawn_with_priority<F, T>(
        self,
        priority: ThreadPriority,
        f: F,
    ) -> std::io::Result<std::thread::JoinHandle<T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'static,
        T: Send + 'static,
    {
        self.spawn(move || f(priority.set_for_current()))
    }

    #[rustversion::since(1.63)]
    fn spawn_scoped_with_priority<'scope, 'env, F, T>(
        self,
        scope: &'scope std::thread::Scope<'scope, 'env>,
        priority: ThreadPriority,
        f: F,
    ) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'scope,
        T: Send + 'scope,
    {
        self.spawn_scoped(scope, move || f(priority.set_for_current()))
    }
}

/// Adds scoped thread building functions using the priority.
#[rustversion::since(1.63)]
pub trait ThreadScopeExt<'scope> {
    /// Spawn a scoped thread with set priority. The passed functor `f` is executed in the spawned thread and
    /// receives as the only argument the result of setting the thread priority.
    /// See [`std::thread::Scope::spawn`] and [`ThreadPriority::set_for_current`] for more info.
    ///
    /// # Example
    ///
    /// ```rust
    /// use thread_priority::*;
    ///
    /// let x = 0;
    ///
    /// std::thread::scope(|s|{
    ///     s.spawn_with_priority(ThreadPriority::Max, |result| {
    ///             // This is printed out from within the spawned thread.
    ///             println!("Set priority result: {:?}", result);
    ///             assert!(result.is_ok());
    ///             dbg!(&x);
    ///     });
    /// });
    /// ```
    fn spawn_with_priority<F, T>(
        &'scope self,
        priority: ThreadPriority,
        f: F,
    ) -> std::thread::ScopedJoinHandle<'scope, T>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'scope,
        T: Send + 'scope;
}

#[rustversion::since(1.63)]
impl<'scope> ThreadScopeExt<'scope> for std::thread::Scope<'scope, '_> {
    fn spawn_with_priority<F, T>(
        &'scope self,
        priority: ThreadPriority,
        f: F,
    ) -> std::thread::ScopedJoinHandle<'scope, T>
    where
        F: FnOnce(Result<(), Error>) -> T,
        F: Send + 'scope,
        T: Send + 'scope,
    {
        self.spawn(move || f(priority.set_for_current()))
    }
}

/// Spawns a thread with the specified priority.
///
/// See [`ThreadBuilderExt::spawn_with_priority`].
///
/// ```rust
/// use thread_priority::*;
///
/// let thread = spawn(ThreadPriority::Max, |result| {
///     // This is printed out from within the spawned thread.
///     println!("Set priority result: {:?}", result);
///     assert!(result.is_ok());
/// });
/// thread.join();
/// ```
pub fn spawn<F, T>(priority: ThreadPriority, f: F) -> std::thread::JoinHandle<T>
where
    F: FnOnce(Result<(), Error>) -> T,
    F: Send + 'static,
    T: Send + 'static,
{
    std::thread::spawn(move || f(priority.set_for_current()))
}

/// Spawns a scoped thread with the specified priority.
///
/// See [`ThreadBuilderExt::spawn_with_priority`].
///
/// ```rust
/// use thread_priority::*;
///
/// let x = 0;
///
/// std::thread::scope(|s| {
///     spawn_scoped(s, ThreadPriority::Max, |result| {
///         // This is printed out from within the spawned thread.
///         println!("Set priority result: {:?}", result);
///         assert!(result.is_ok());
///         dbg!(&x);
///     });
/// });
/// ```
#[rustversion::since(1.63)]
pub fn spawn_scoped<'scope, 'env, F, T>(
    scope: &'scope std::thread::Scope<'scope, 'env>,
    priority: ThreadPriority,
    f: F,
) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
where
    F: FnOnce(Result<(), Error>) -> T,
    F: Send + 'scope,
    T: Send + 'scope,
{
    Ok(scope.spawn(move || f(priority.set_for_current())))
}

/// Spawns a thread with the specified priority.
/// This is different from [`spawn`] in a way that the passed function doesn't
/// need to accept the [`ThreadPriority::set_for_current`] result.
/// In case of an error, the error is logged using the logging facilities.
///
/// See [`spawn`].
///
/// ```rust
/// use thread_priority::*;
///
/// let thread = spawn_careless(ThreadPriority::Max, || {
///     // This is printed out from within the spawned thread.
///     println!("We don't care about the priority result.");
/// });
/// thread.join();
/// ```
pub fn spawn_careless<F, T>(priority: ThreadPriority, f: F) -> std::thread::JoinHandle<T>
where
    F: FnOnce() -> T,
    F: Send + 'static,
    T: Send + 'static,
{
    std::thread::spawn(move || careless_wrapper(f)(priority.set_for_current()))
}

/// Spawns a scoped thread with the specified priority.
/// This is different from [`spawn_scoped`] in a way that the passed function doesn't
/// need to accept the [`ThreadPriority::set_for_current`] result.
/// In case of an error, the error is logged using the logging facilities.
///
/// See [`spawn_scoped`].
///
/// ```rust
/// use thread_priority::*;
///
/// let x = 0;
///
/// std::thread::scope(|s| {
///     spawn_scoped_careless(s, ThreadPriority::Max, || {
///         // This is printed out from within the spawned thread.
///         println!("We don't care about the priority result.");
///         dbg!(&x);
///     });
/// });
/// ```
#[rustversion::since(1.63)]
pub fn spawn_scoped_careless<'scope, 'env, F, T>(
    scope: &'scope std::thread::Scope<'scope, 'env>,
    priority: ThreadPriority,
    f: F,
) -> std::io::Result<std::thread::ScopedJoinHandle<'scope, T>>
where
    F: FnOnce() -> T,
    F: Send + 'scope,
    T: Send + 'scope,
{
    Ok(scope.spawn(move || careless_wrapper(f)(priority.set_for_current())))
}
