use crate::marker::Destruct;
#[cfg(not(bootstrap))]
use crate::marker::Tuple;

/// Struct representing a closure with mutably borrowed data.
///
/// Example:
/// ```no_build
/// #![feature(const_mut_refs)]
/// use crate::const_closure::ConstFnMutClosure;
/// const fn imp(state: &mut i32, (arg,): (i32,)) -> i32 {
///   *state += arg;
///   *state
/// }
/// let mut i = 5;
/// let mut cl = ConstFnMutClosure::new(&mut i, imp);
///
/// assert!(7 == cl(2));
/// assert!(8 == cl(1));
/// ```
pub(crate) struct ConstFnMutClosure<CapturedData, Function> {
    /// The Data captured by the Closure.
    /// Must be either a (mutable) reference or a tuple of (mutable) references.
    pub data: CapturedData,
    /// The Function of the Closure, must be: Fn(CapturedData, ClosureArgs) -> ClosureReturn
    pub func: Function,
}
impl<'a, CapturedData: ?Sized, Function> ConstFnMutClosure<&'a mut CapturedData, Function> {
    /// Function for creating a new closure.
    ///
    /// `data` is the a mutable borrow of data that is captured from the environment.
    /// If you want Data to be a tuple of mutable Borrows, the struct must be constructed manually.
    ///
    /// `func` is the function of the closure, it gets the data and a tuple of the arguments closure
    ///   and return the return value of the closure.
    pub(crate) const fn new<ClosureArguments, ClosureReturnValue>(
        data: &'a mut CapturedData,
        func: Function,
    ) -> Self
    where
        Function: ~const Fn(&mut CapturedData, ClosureArguments) -> ClosureReturnValue,
    {
        Self { data, func }
    }
}

macro_rules! impl_fn_mut_tuple {
    ($($var:ident)*) => {
        #[cfg(bootstrap)]
        #[allow(unused_parens)]
        impl<'a, $($var,)* ClosureArguments, Function, ClosureReturnValue> const
            FnOnce<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
        where
            Function: ~const Fn(($(&mut $var),*), ClosureArguments) -> ClosureReturnValue+ ~const Destruct,
        {
            type Output = ClosureReturnValue;

            extern "rust-call" fn call_once(mut self, args: ClosureArguments) -> Self::Output {
            self.call_mut(args)
            }
        }
        #[cfg(bootstrap)]
        #[allow(unused_parens)]
        impl<'a, $($var,)* ClosureArguments, Function, ClosureReturnValue> const
            FnMut<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
        where
            Function: ~const Fn(($(&mut $var),*), ClosureArguments)-> ClosureReturnValue,
        {
            extern "rust-call" fn call_mut(&mut self, args: ClosureArguments) -> Self::Output {
                #[allow(non_snake_case)]
                let ($($var),*) = &mut self.data;
                (self.func)(($($var),*), args)
            }
        }
        #[cfg(not(bootstrap))]
        #[allow(unused_parens)]
        impl<'a, $($var,)* ClosureArguments: Tuple, Function, ClosureReturnValue> const
            FnOnce<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
        where
            Function: ~const Fn(($(&mut $var),*), ClosureArguments) -> ClosureReturnValue+ ~const Destruct,
        {
            type Output = ClosureReturnValue;

            extern "rust-call" fn call_once(mut self, args: ClosureArguments) -> Self::Output {
            self.call_mut(args)
            }
        }
        #[cfg(not(bootstrap))]
        #[allow(unused_parens)]
        impl<'a, $($var,)* ClosureArguments: Tuple, Function, ClosureReturnValue> const
            FnMut<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
        where
            Function: ~const Fn(($(&mut $var),*), ClosureArguments)-> ClosureReturnValue,
        {
            extern "rust-call" fn call_mut(&mut self, args: ClosureArguments) -> Self::Output {
                #[allow(non_snake_case)]
                let ($($var),*) = &mut self.data;
                (self.func)(($($var),*), args)
            }
        }
    };
}
impl_fn_mut_tuple!(A);
impl_fn_mut_tuple!(A B);
impl_fn_mut_tuple!(A B C);
impl_fn_mut_tuple!(A B C D);
impl_fn_mut_tuple!(A B C D E);
