#![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 FooWithWhere<X>
where
    X: std::fmt::Display,
{
    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 foobar1<A>(&self, x: X) -> Result<A, Self::C>;
    fn foobar2<A>(&self) -> Result<A, Self::C>;
}

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

#[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_callback0<G>(&self, a: G)
    where
        G: Fn(Self::A);

    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);
}

#[dynamize::dynamize]
trait TypeTraitBound {
    type A: std::error::Error;

    fn foobar(&self, text: &str) -> Result<i32, Self::A>;
}

fn type_trait_bound_test<T: TypeTraitBound>(t: T) {
    let dyn_trait: &dyn DynTypeTraitBound = &t;
    let _: Result<i32, Box<dyn std::error::Error>> = dyn_trait.foobar("test");
}

#[dynamize::dynamize]
trait TypeTraitBoundStatic {
    type A: std::error::Error + 'static;

    fn foobar(&self, text: &str) -> Result<i32, Self::A>;
}

fn type_trait_bound_static_test(
    t: Box<dyn DynTypeTraitBoundStatic>,
) -> Option<Box<dyn std::error::Error>> {
    t.foobar("test").err()
}

#[dynamize::dynamize]
trait TypeWithSuper: std::fmt::Display + Sized {
    type A: std::error::Error;

    fn foobar(&self, text: &str) -> Result<i32, Self::A>;
}

fn test3<T: TypeWithSuper>(some: T) {
    let dyn_trait: &dyn DynTypeWithSuper = &some;
    println!("{}", dyn_trait);
}

use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};

#[dynamize::dynamize]
trait Collections {
    type A: std::error::Error;

    fn vec(&self) -> Vec<Self::A>;

    fn vec_deque(&self) -> VecDeque<Self::A>;

    fn linked_list(&self) -> LinkedList<Self::A>;

    fn vec_opt(&self) -> Vec<Option<Self::A>>;
}

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

    fn hashset(&self) -> HashSet<Self::A>;

    fn binary_heap(&self) -> BinaryHeap<Self::A>;

    fn btree_set(&self) -> BTreeSet<Self::A>;

    fn hash_map(&self) -> HashMap<Self::A, Self::A>;

    fn btree_map(&self) -> BTreeMap<Self::A, Self::A>;
}

#[dynamize::dynamize]
trait ReturnIter {
    type A: std::error::Error;

    fn foobar(&mut self, text: &str) -> &mut dyn Iterator<Item = Self::A>;
}

#[dynamize::dynamize]
trait FunIter {
    type A: std::error::Error + Send;

    fn foobar<F: Fn(&mut dyn Iterator<Item = Self::A>)>(&mut self, f: F);

    fn foobar1<G: Fn(Option<&mut dyn Iterator<Item = Self::A>>)>(&mut self, f: G);

    fn foobar2<H: Fn(&mut dyn Iterator<Item = &mut dyn Iterator<Item = Self::A>>)>(&mut self, f: H);
}