#![allow(dead_code)]

#[test]
fn it_works() {
    use dynamize::dynamize;

    mod some {
        pub mod module {
            pub type Result<T> = std::io::Result<T>;
        }
    }

    #[dynamize]
    /// This is a great trait!
    pub trait MyTrait {
        type A: Into<String>;
        // if there are multiple Into bounds the first one is used
        type B: Into<i32> + Into<u64>;

        fn test1(&self) -> Self::A;
        fn test2(&self) -> Self::B;
        fn test3(&self) -> Option<Self::B>;
        fn test4(&self) -> Result<(), Self::A>;
        fn test5(&self) -> Result<Self::A, ()>;
        /// some method documentation
        fn test6(&self) -> Result<Self::A, Self::B>;

        #[allow(clippy::type_complexity)]
        fn test7(&self) -> Result<Option<Option<Self::A>>, Option<Option<Self::B>>>;

        // also support Result type aliases with a fixed error type
        fn test8(&self) -> some::module::Result<Self::A>;

        // fn test9(&self) -> &dyn Iterator<Item = Self::A>;

        fn mut1(&mut self) -> Self::A;

        fn safe1(&self);
        fn safe2(&self, num: i32) -> i32;
        fn safe3<'a>(&self, text: &'a str) -> &'a str;
        fn safe4(&self) -> Option<i32>;

        // non-dispatchable functions are skipped
        fn non_dispatch1();
        fn non_dispatch2(num: i32);
        fn non_dispatch3(self) -> Self::A;
        fn non_dispatch4(&self)
        where
            Self: Sized;
    }

    fn test<T: MyTrait>(mut some: T) {
        let dyn_trait: &dyn DynMyTrait = &some;
        let _: String = dyn_trait.test1();
        let _: i32 = dyn_trait.test2();
        let _: Option<i32> = dyn_trait.test3();
        let _: Result<(), String> = dyn_trait.test4();
        let _: Result<String, ()> = dyn_trait.test5();
        let _: Result<String, i32> = dyn_trait.test6();
        let _: Result<Option<Option<String>>, Option<Option<i32>>> = dyn_trait.test7();

        let dyn_trait: &mut dyn DynMyTrait = &mut some;
        dyn_trait.mut1();

        let _: () = dyn_trait.safe1();
        let _: i32 = dyn_trait.safe2(0);
        let _: &str = dyn_trait.safe3("test");
        let _: Option<i32> = dyn_trait.safe4();
    }
}

#[dynamize::dynamize]
trait Foo<X> {
    type A: Into<String>;

    fn foobar(&self, x: X) -> Self::A;
}

#[dynamize::dynamize]
trait Bar<X> {
    fn foobar<A>(&self, x: X) -> A;
}

fn test<T: Bar<X>, X, A>(some: T) {
    let _dyn_trait: &dyn DynBar<X, A> = &some;
}

#[dynamize::dynamize]
trait Bar1<X> {
    fn foobar<A>(&self, x: X) -> A;
    fn foobar1<B>(&self, x: X) -> B;
    fn foobar2<C>(&self, x: X) -> C;
}

fn test1<T: Bar1<X>, X, A, B, C>(some: T) {
    let _dyn_trait: &dyn DynBar1<X, A, B, C> = &some;
}

#[dynamize::dynamize]
trait Buz<X> {
    type C: Into<String>;

    fn foobar<A>(&self, x: X) -> Result<A, Self::C>;
}

fn test2<T: Buz<X>, X, A>(some: T, x: X) -> Result<A, String> {
    let dyn_trait: &dyn DynBuz<X, A> = &some;
    dyn_trait.foobar(x)
}

#[dynamize::dynamize]
trait Gen {
    fn foobar<A>(&self, a: A) -> A;
}

use async_trait::async_trait;

#[dynamize::dynamize]
#[dyn_trait_attr(async_trait)]
#[blanket_impl_attr(async_trait)]
#[async_trait]
trait SomeTraitWithAsync: Sync {
    type A: Into<String>;
    async fn test1(&self) -> Self::A;
}

async fn async_test<T: SomeTraitWithAsync>(some: T) {
    let dyn_trait: &dyn DynSomeTraitWithAsync = &some;
    let _: String = dyn_trait.test1().await;
}

#[dynamize::dynamize]
trait TraitWithCallback {
    type A: Into<String>;
    fn fun_with_callback<F: Fn(Self::A)>(&self, a: F);

    fn fun_with_callback1<X: Fn(Option<Self::A>)>(&self, a: X);

    fn fun_with_callback2<Y: Fn(i32, Option<Self::A>, String) -> bool>(&self, a: Y);

    fn fun_with_callback3<Z: Fn(i32)>(&self, a: Z);
}

#[dynamize::dynamize]
#[dyn_trait_attr(async_trait)]
#[blanket_impl_attr(async_trait)]
#[async_trait]
trait AsyncWithCallback: Sync {
    type A: Into<String>;
    async fn test1(&self) -> Self::A;
    async fn fun_with_callback<F: Fn(Self::A) + Sync + Send + 'static>(&self, a: F);
}