//! Contains tools (workarounds) to make implementing `const fn`s easier.

/// Copies first `$len` bytes from `$slice` and returns them as an array.
///
/// Returns `None` if `$len > $slice.len()`. `$len` must be (obviously) statically known.
/// Calling from non-const context doesn't affect performance.
#[macro_export]
macro_rules! copy_byte_array_from_slice {
    ($slice:expr, $len:expr) => {
        if $len > $slice.len() {
            None
        } else {
            let mut array = [0u8; $len];
            // Note: produces same assemble as copy_from_slice
            let mut i = 0;
            while i < $len {
                array[i] = $slice[i];
                i += 1;
            }
            Some(array)
        }
    };
}
pub use copy_byte_array_from_slice;

/// Concatenates two byte slices or byte arrays (or combination) to a single array.
///
/// # Panics
///
/// This macro panics if `$len` is not equal to the sum of `$a.len()` and `$b.len()`.
#[macro_export]
macro_rules! concat_bytes_to_arr {
    ($a:expr, $b:expr, $len:expr) => {{
        // avoid repeated eval
        let a = $a;
        let b = $b;

        #[allow(unconditional_panic)]
        let _ = [(); 1][($len != a.len() + b.len()) as usize];

        let mut output = [0u8; $len];
        let mut i = 0;
        while i < a.len() {
            output[i] = a[i];
            i += 1;
        }
        while i < a.len() + b.len() {
            output[i] = b[i - a.len()];
            i += 1;
        }
        output
    }};
}
pub use concat_bytes_to_arr;

#[macro_export]
/// Enables const fn in specified Rust version
macro_rules! cond_const {
    ($($(#[$attr:meta])* $vis:vis const(in $version:tt) fn $name:ident$(<$($gen:tt)*>)?($($args:tt)*) $(-> $ret:ty)? $body:block)+ ) => {
        $(
            $crate::rust_version::rust_version! {
                if >= $version {
                    $(#[$attr])*
                    #[doc = concat!("\nNote: the function is only `const` in Rust ", stringify!($version), ".")]
                    $vis const fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
                } else {
                    $(#[$attr])*
                    #[doc = concat!("\nNote: the function is `const` in Rust ", stringify!($version), ".")]
                    $vis fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
                }
            }
        )+
    };
    ($($(#[$attr:meta])* $vis:vis const(in $version:tt) unsafe fn $name:ident$(<$($gen:tt)*>)?($($args:tt)*) $(-> $ret:ty)? $body:block)+ ) => {
        $(
            $crate::rust_version::rust_version! {
                if >= $version {
                    $(#[$attr])*
                    #[doc = concat!("\nNote: the function is only `const` in Rust ", stringify!($version), ".")]
                    $vis const unsafe fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
                } else {
                    $(#[$attr])*
                    #[doc = concat!("\nNote: the function is `const` in Rust ", stringify!($version), ".")]
                    $vis unsafe fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
                }
            }
        )+
    };
}
pub use cond_const;
