use crate::injector::Injector;
use std::any::{type_name, TypeId};

/// A function/closure whose arguments can each be resolved from the
/// [`Injector`] and which returns `R` directly.
///
/// This is what lets `inject_fn` accept plain multi-argument closures
/// (`|a, b, c| ...`) instead of forcing a single tuple argument
/// (`|(a, b, c)| ...`). There is one implementation per arity, generated by
/// the `impl_inject_fn!` macro below.
pub trait InjectFn<Args, R> {
    fn run_inject(&self, injector: &Injector) -> anyhow::Result<R>;
    fn dependencies_names() -> Vec<(TypeId, &'static str)>;
}

/// Like [`InjectFn`], but for fallible factories returning `anyhow::Result<R>`.
pub trait InjectFnOk<Args, R> {
    fn run_inject_ok(&self, injector: &Injector) -> anyhow::Result<R>;
    fn dependencies_names() -> Vec<(TypeId, &'static str)>;
}

macro_rules! impl_inject_fn {
    ($($arg:ident),*) => {
        impl<R, FN, $($arg),*> InjectFn<($($arg,)*), R> for FN
        where
            FN: Fn($($arg),*) -> R,
            $($arg: Clone + 'static,)*
        {
            #[allow(non_snake_case, unused_variables)]
            fn run_inject(&self, injector: &Injector) -> anyhow::Result<R> {
                $(let $arg = injector.get::<$arg>()?;)*
                Ok(self($($arg),*))
            }

            fn dependencies_names() -> Vec<(TypeId, &'static str)> {
                vec![$((TypeId::of::<$arg>(), type_name::<$arg>()),)*]
            }
        }

        impl<R, FN, $($arg),*> InjectFnOk<($($arg,)*), R> for FN
        where
            FN: Fn($($arg),*) -> anyhow::Result<R>,
            $($arg: Clone + 'static,)*
        {
            #[allow(non_snake_case, unused_variables)]
            fn run_inject_ok(&self, injector: &Injector) -> anyhow::Result<R> {
                $(let $arg = injector.get::<$arg>()?;)*
                self($($arg),*)
            }

            fn dependencies_names() -> Vec<(TypeId, &'static str)> {
                vec![$((TypeId::of::<$arg>(), type_name::<$arg>()),)*]
            }
        }
    }
}

impl_inject_fn!();
impl_inject_fn!(A1);
impl_inject_fn!(A1, A2);
impl_inject_fn!(A1, A2, A3);
impl_inject_fn!(A1, A2, A3, A4);
impl_inject_fn!(A1, A2, A3, A4, A5);
impl_inject_fn!(A1, A2, A3, A4, A5, A6);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15);
impl_inject_fn!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16);

/// A tuple whose elements can each be read from the [`Injector`].
///
/// Used both by [`Injector::get_tuple`] and by the tuple-style
/// `inject_fn_tuple` (`|(a, b, c)| ...`) factories.
pub trait TupleInjectTypes
where
    Self: Sized,
{
    fn read_from_injector(injector: &Injector) -> anyhow::Result<Self>;
    fn dependencies_names() -> Vec<(TypeId, &'static str)>;
}

impl TupleInjectTypes for () {
    fn read_from_injector(_: &Injector) -> anyhow::Result<Self> {
        Ok(())
    }

    fn dependencies_names() -> Vec<(TypeId, &'static str)> {
        vec![]
    }
}

impl<Arg1: std::clone::Clone + 'static> TupleInjectTypes for (Arg1,) {
    fn read_from_injector(injector: &Injector) -> anyhow::Result<Self> {
        Ok((injector.get::<Arg1>()?,))
    }

    fn dependencies_names() -> Vec<(TypeId, &'static str)> {
        vec![(TypeId::of::<Arg1>(), type_name::<Arg1>())]
    }
}

macro_rules! build_tuple_injector {
    ($($tuple_type:ident),*) => {
        impl<$($tuple_type: std::clone::Clone + 'static),*> TupleInjectTypes for ($($tuple_type),* ) {
           fn read_from_injector(injector: &Injector) -> anyhow::Result<Self> {
               Ok(($(injector.get::<$tuple_type>()?),* ))
           }

           fn dependencies_names() -> Vec<(TypeId, &'static str)> {
               vec![$( (TypeId::of::<$tuple_type>(), type_name::<$tuple_type>()) ),*]
           }
       }
    }
}

build_tuple_injector!(Arg1, Arg2);
build_tuple_injector!(Arg1, Arg2, Arg3);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11);
build_tuple_injector!(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13
);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14
);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15
);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15,
    Arg16
);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15,
    Arg16, Arg17
);
build_tuple_injector!(
    Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15,
    Arg16, Arg17, Arg18
);
