use std::{
    cell::RefCell,
    panic,
    rc::Rc,
    sync::{Arc, Mutex},
    thread,
};

use futures_executor::block_on;
use glib::clone;

struct State {
    count: i32,
    started: bool,
}

impl State {
    fn new() -> Self {
        Self {
            count: 0,
            started: false,
        }
    }
}

#[test]
fn clone_and_references() {
    let state = Rc::new(RefCell::new(State::new()));
    let ref_state = &state;
    assert!(!ref_state.borrow().started);

    let closure = {
        clone!(@weak ref_state => move || {
            ref_state.borrow_mut().started = true;
        })
    };

    closure();
    assert!(ref_state.borrow().started);
}

#[test]
fn subfields_renaming() {
    struct Foo {
        v: Rc<usize>,
    }

    impl Foo {
        fn foo(&self) {
            let state = Rc::new(RefCell::new(State::new()));

            let closure = clone!(@strong self.v as v, @weak state as hello => move |_| {
                println!("v: {v}");
                hello.borrow_mut().started = true;
            });
            closure(2);
        }
    }

    Foo { v: Rc::new(0) }.foo();
}

#[test]
fn renaming() {
    let state = Rc::new(RefCell::new(State::new()));
    assert!(!state.borrow().started);

    let closure = {
        clone!(@weak state as hello => move || {
            hello.borrow_mut().started = true;
        })
    };

    closure();
    assert!(state.borrow().started);
}

#[test]
fn clone_closure() {
    let state = Rc::new(RefCell::new(State::new()));
    assert!(!state.borrow().started);

    let closure = {
        clone!(@weak state => move || {
            state.borrow_mut().started = true;
        })
    };

    closure();

    assert!(state.borrow().started);
    assert_eq!(state.borrow().count, 0);

    let closure = {
        let state2 = Rc::new(RefCell::new(State::new()));
        assert!(state.borrow().started);

        clone!(@weak state, @strong state2 => move || {
            state.borrow_mut().count += 1;
            state.borrow_mut().started = true;
            state2.borrow_mut().started = true;
        })
    };

    closure();

    assert_eq!(state.borrow().count, 1);
    assert!(state.borrow().started);
}

#[test]
fn clone_default_value() {
    let closure = {
        let state = Rc::new(RefCell::new(State::new()));
        clone!(@weak state => @default-return 42, move |_| {
            state.borrow_mut().started = true;
            10
        })
    };

    assert_eq!(42, closure(50));
}

#[test]
fn clone_panic() {
    let state = Arc::new(Mutex::new(State::new()));
    state.lock().expect("Failed to lock state mutex").count = 20;

    let closure = {
        let state2 = Arc::new(Mutex::new(State::new()));
        clone!(@weak state2, @strong state => @default-return panic!(), move |_| {
            state.lock().expect("Failed to lock state mutex").count = 21;
            state2.lock().expect("Failed to lock state2 mutex").started = true;
            10
        })
    };

    let result = panic::catch_unwind(|| {
        closure(50);
    });

    assert!(result.is_err());

    assert_eq!(state.lock().expect("Failed to lock state mutex").count, 20);
}

#[test]
fn clone_import_rename() {
    import_rename::test();
}

mod import_rename {
    use glib::clone as clone_g;

    #[allow(unused_macros)]
    macro_rules! clone {
        ($($anything:tt)*) => {
            |_, _| panic!("The clone! macro doesn't support renaming")
        };
    }

    #[allow(unused_variables)]
    pub fn test() {
        let n = 2;

        let closure: Box<dyn Fn(u32, u32)> = Box::new(clone_g!(
            @strong n
            => move |_, _|
            println!("The clone! macro does support renaming")
        ));

        closure(0, 0);
    }
}

#[test]
fn test_clone_macro_self_rename() {
    #[derive(Debug)]
    struct Foo {
        v: u8,
    }

    impl Foo {
        #[allow(dead_code)]
        fn foo(&self) {
            let closure = clone!(@strong self as this => move |_x| {
                println!("v: {this:?}");
            });
            closure(0i8); // to prevent compiler error for unknown `x` type.
            let _ = clone!(@strong self as this => move || {
                println!("v: {this:?}");
            });
            let closure = clone!(@strong self as this => move |_x| println!("v: {this:?}"));
            closure(0i8); // to prevent compiler error for unknown `x` type.
            let _ = clone!(@strong self as this => move || println!("v: {this:?}"));

            // Fields now!
            let closure = clone!(@strong self.v as v => move |_x| {
                println!("v: {v:?}");
            });
            closure(0i8); // to prevent compiler error for unknown `x` type.
            let _ = clone!(@strong self.v as v => move || println!("v: {v:?}"));

            // With @default-panic
            let closure = clone!(@strong self.v as v => @default-panic, move |_x| {
                println!("v: {v:?}");
            });
            closure(0i8); // to prevent compiler error for unknown `x` type.
            let _ = clone!(@strong self.v as v => @default-panic, move || println!("v: {v:?}"));

            // With @default-return
            let closure = clone!(@strong self.v as _v => @default-return true, move |_x| {
                false
            });
            closure(0i8); // to prevent compiler error for unknown `x` type.
            let _ = clone!(@strong self.v as _v => @default-return true, move || false);
        }
    }
}

#[test]
fn test_clone_macro_rename() {
    let v = Rc::new(1);

    let closure = clone!(@weak v as y => @default-panic, move |_x| {
        println!("v: {y}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v as y => @default-panic, move || println!("v: {y}"));

    let closure = clone!(@strong v as y => @default-panic, move |_x| {
        println!("v: {y}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v as y => @default-panic, move || println!("v: {y}"));

    let closure = clone!(@weak v as y => move |_x| {
        println!("v: {y}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v as y => move || println!("v: {y}"));

    let closure = clone!(@strong v as y => move |_x| {
        println!("v: {y}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v as y => move || println!("v: {y}"));

    let closure = clone!(@weak v as _y => @default-return true, move |_x| {
        false
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v as _y => @default-return true, move || false);

    let closure = clone!(@strong v as _y => @default-return true, move |_x| false);
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v as _y => @default-return true, move || false);
}

#[test]
fn test_clone_macro_simple() {
    let v = Rc::new(1);

    let closure = clone!(@weak v => @default-panic, move |_x| {
        println!("v: {v}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v => @default-panic, move || println!("v: {v}"));

    let closure = clone!(@strong v => @default-panic, move |_x| {
        println!("v: {v}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v => @default-panic, move || println!("v: {v}"));

    let closure = clone!(@weak v => move |_x| {
        println!("v: {v}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v => move || println!("v: {v}"));

    let closure = clone!(@strong v => move |_x| {
        println!("v: {v}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v => move || println!("v: {v}"));

    let closure = clone!(@weak v => @default-return true, move |_x| {
        false
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v => @default-return true, move || false);

    let closure = clone!(@strong v => @default-return true, move |_x| false);
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v => @default-return true, move || false);
}

#[test]
fn test_clone_macro_double_simple() {
    let v = Rc::new(1);
    let w = Rc::new(2);

    let closure = clone!(@weak v, @weak w => @default-panic, move |_x| {
        println!("v: {v}, w: {w}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v, @weak w => @default-panic, move || println!("v: {v}, w: {w}"));

    let closure = clone!(@strong v, @strong w => @default-panic, move |_x| {
        println!("v: {v}, w: {w}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v, @strong w => @default-panic, move || println!("v: {v}, w: {w}"));

    let closure = clone!(@weak v, @weak w => move |_x| {
        println!("v: {v}, w: {w}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v, @weak w => move || println!("v: {v}, w: {w}"));

    let closure = clone!(@strong v, @strong w => move |_x| {
        println!("v: {v}, w: {w}");
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v, @strong w => move || println!("v: {v}, w: {w}"));

    let closure = clone!(@weak v, @weak w => @default-return true, move |_x| {
        false
    });
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@weak v, @weak w => @default-return true, move || false);

    let closure = clone!(@strong v, @strong w => @default-return true, move |_x| false);
    closure(0i8); // to prevent compiler error for unknown `x` type.
    let _ = clone!(@strong v, @strong w => @default-return true, move || false);
}

#[test]
fn test_clone_macro_double_rename() {
    let v = Rc::new(1);
    let w = Rc::new(2);
    let done = Rc::new(RefCell::new(0));

    let closure = clone!(@weak v as x, @weak w => @default-panic, move |z| {
        z + *x + *w
    });
    assert_eq!(closure(1i8), 4i8);
    let closure = clone!(@weak v as x, @weak w => @default-panic, move || 1);
    assert_eq!(closure(), 1);

    let closure = clone!(@weak v, @weak w as x => @default-panic, move |z| {
        z + *v + *x
    });
    assert_eq!(closure(10i8), 13i8);
    let closure = clone!(@weak v, @weak w as x => @default-panic, move || 2 + *x);
    assert_eq!(closure(), 4);

    let closure = clone!(@strong v as x, @strong w => @default-panic, move |z| {
        z + *x + *w
    });
    assert_eq!(closure(3i8), 6i8);
    let closure = clone!(@strong v as x, @strong w => @default-panic, move || 4 + *w);
    assert_eq!(closure(), 6);

    let closure = clone!(@strong v, @strong w as x => @default-panic, move |z| {
        z + *v + *x
    });
    assert_eq!(closure(0i8), 3i8);
    let closure = clone!(@strong v, @strong w as x => @default-panic, move || 5);
    assert_eq!(closure(), 5);

    let t_done = done.clone();
    let closure = clone!(@weak v as x, @weak w => move |z| {
        *t_done.borrow_mut() = z + *x + *w;
    });
    closure(4i8);
    assert_eq!(*done.borrow(), 7);
    let t_done = done.clone();
    let closure = clone!(@weak v as x, @weak w => move || *t_done.borrow_mut() = *x + *w);
    closure();
    assert_eq!(*done.borrow(), 3);

    let t_done = done.clone();
    let closure = clone!(@weak v, @weak w as x => move |z| {
        *t_done.borrow_mut() = z + *v + *x;
    });
    closure(8i8);
    assert_eq!(*done.borrow(), 11i8);
    let t_done = done.clone();
    let closure = clone!(@weak v, @weak w as x => move || *t_done.borrow_mut() = *v * *x);
    closure();
    assert_eq!(*done.borrow(), 2);

    let t_done = done.clone();
    let closure = clone!(@strong v as x, @strong w => move |z| {
        *t_done.borrow_mut() = z + *x + *w;
    });
    closure(9i8);
    assert_eq!(*done.borrow(), 12i8);
    let t_done = done.clone();
    let closure = clone!(@strong v as x, @strong w => move || *t_done.borrow_mut() = *x - *w);
    closure();
    assert_eq!(*done.borrow(), -1);

    let t_done = done.clone();
    let closure = clone!(@strong v, @strong w as x => move |z| {
        *t_done.borrow_mut() = *v + *x * z;
    });
    closure(2i8);
    assert_eq!(*done.borrow(), 5);
    let t_done = done.clone();
    let closure = clone!(@strong v, @strong w as x => move || *t_done.borrow_mut() = *x - *v);
    closure();
    assert_eq!(*done.borrow(), 1);

    let closure = clone!(@weak v as _x, @weak w => @default-return true, move |_| {
        false
    });
    assert!(!closure(0u8));
    let closure = clone!(@weak v as _x, @weak w => @default-return true, move || false);
    assert!(!closure());

    let closure = clone!(@weak v, @weak w as _x => @default-return true, move |_| {
        false
    });
    assert!(!closure("a"));
    let closure = clone!(@weak v, @weak w as _x => @default-return true, move || false);
    assert!(!closure());

    let closure = clone!(@strong v as _x, @strong w => @default-return true, move |_| {
        false
    });
    assert!(!closure('a'));
    let closure = clone!(@strong v as _x, @strong w => @default-return true, move || false);
    assert!(!closure());

    let closure = clone!(@strong v, @strong w as _x => @default-return true, move |_| {
        false
    });
    assert!(!closure(12.));
    let closure = clone!(@strong v, @strong w as _x => @default-return true, move || false);
    assert!(!closure());
}

#[test]
fn test_clone_macro_typed_args() {
    macro_rules! test_closure {
        ($kind:tt, panic) => {{
            // We need Arc and Mutex to use them below in the thread.
            let check = Arc::new(Mutex::new(0));
            let v = Arc::new(Mutex::new(1));
            let w = Arc::new(Mutex::new(1));

            let closure = clone!(@$kind v as x, @$kind w, @weak check => @default-panic, move |arg: i8| {
                *x.lock().unwrap() += arg;
                *w.lock().unwrap() += arg;
                *check.lock().unwrap() += 1;
            });
            closure(1);
            assert_eq!(2, *v.lock().unwrap());
            assert_eq!(2, *w.lock().unwrap());
            assert_eq!(1, *check.lock().unwrap());

            let closure2 = clone!(@$kind v, @$kind w as x, @weak check => @default-panic, move |arg: i8| {
                *v.lock().unwrap() += arg;
                *x.lock().unwrap() += arg;
                *check.lock().unwrap() += 1;
            });
            closure2(1);
            assert_eq!(3, *v.lock().unwrap());
            assert_eq!(3, *w.lock().unwrap());
            assert_eq!(2, *check.lock().unwrap());

            #[allow(unused_macro_rules)]
            macro_rules! inner {
                (strong) => {{}};
                (weak) => {{
                    std::mem::drop(v);
                    std::mem::drop(w);

                    // We use the threads to ensure that the closure panics as expected.
                    assert!(thread::spawn(move || {
                        closure(1);
                    }).join().is_err());
                    assert_eq!(2, *check.lock().unwrap());
                    assert!(thread::spawn(move || {
                        closure2(1);
                    }).join().is_err());
                    assert_eq!(2, *check.lock().unwrap());
                }}
            }

            inner!($kind);
        }};
        ($kind:tt) => {{
            let check = Rc::new(RefCell::new(0));
            let v = Rc::new(RefCell::new(1));
            let w = Rc::new(RefCell::new(1));

            let closure = clone!(@$kind v as x, @$kind w, @weak check => move |arg: i8| {
                *x.borrow_mut() += arg;
                *w.borrow_mut() += arg;
                *check.borrow_mut() += 1;
            });
            closure(1);
            assert_eq!(2, *v.borrow());
            assert_eq!(2, *w.borrow());
            assert_eq!(1, *check.borrow());

            let closure2 = clone!(@$kind v, @$kind w as x, @weak check => move |arg: i8| {
                *v.borrow_mut() += arg;
                *x.borrow_mut() += arg;
                *check.borrow_mut() += 1;
            });
            closure2(1);
            assert_eq!(3, *v.borrow());
            assert_eq!(3, *w.borrow());
            assert_eq!(2, *check.borrow());

            #[allow(unused_macro_rules)]
            macro_rules! inner {
                (strong) => {{}};
                (weak) => {{
                    std::mem::drop(v);
                    std::mem::drop(w);

                    closure(1);
                    assert_eq!(2, *check.borrow());
                    closure2(1);
                    assert_eq!(2, *check.borrow());
                }}
            }

            inner!($kind);
        }};
    }

    test_closure!(weak, panic);
    test_closure!(strong, panic);
    test_closure!(weak);
    test_closure!(strong);

    let check = Rc::new(RefCell::new(0));
    let v = Rc::new(RefCell::new(1));
    let w = Rc::new(RefCell::new(1));
    let closure = clone!(@weak v, @weak w as x, @weak check => move |arg: i8, arg2| {
        *v.borrow_mut() = arg;
        *x.borrow_mut() = arg2;
        *check.borrow_mut() += 1;
    });
    closure(0, 9);
    assert_eq!(0, *v.borrow());
    assert_eq!(9, *w.borrow());
    assert_eq!(1, *check.borrow());

    std::mem::drop(v);
    std::mem::drop(w);
    assert_eq!(1, *check.borrow());
}

#[test]
#[allow(clippy::bool_assert_comparison)]
#[allow(clippy::nonminimal_bool)]
fn test_clone_macro_default_return() {
    macro_rules! test_default {
        ($ret:expr, $($closure_body:tt)*) => {{
            let v = Rc::new(1);
            let tmp = clone!(@weak v => @default-return $ret, move || $($closure_body)*);
            assert_eq!(tmp(), $($closure_body)*, "shouldn't use default-return value!");
            ::std::mem::drop(v);
            assert_eq!(tmp(), $ret, "should use default-return value!");
        }}
    }

    #[derive(PartialEq, Debug)]
    struct Foo(i32);

    test_default!(Foo(0), Foo(1));

    #[derive(PartialEq, Debug)]
    struct Bar {
        x: i32,
    }

    test_default!(Bar { x: 0 }, Bar { x: 1 });

    #[derive(PartialEq, Debug)]
    enum Enum {
        A,
        B(i32),
        C { x: i32 },
    }
    test_default!(Enum::A, Enum::B(0));
    test_default!(Enum::B(0), Enum::A);
    test_default!(Enum::C { x: 0 }, Enum::A);
    test_default!(
        {
            let x = 12;
            x + 2
        },
        19
    );
    // This one is simply to check that we wait for the comma for the default-return value.
    test_default!(Enum::A == Enum::B(0) || false, true);
}

#[test]
fn test_clone_macro_body() {
    let v = Arc::new(Mutex::new(0));

    let closure = clone!(@weak v => move || {
        std::thread::spawn(move || {
            let mut lock = v.lock().expect("failed to lock");
            for _ in 1..=10 {
                *lock += 1;
            }
        }).join().expect("thread::spawn failed");
    });
    closure();
    assert_eq!(10, *v.lock().expect("failed to lock"));
}

#[test]
fn test_clone_macro_async_kinds() {
    let v = Rc::new(RefCell::new(1));

    // This one is still a rust unstable feature.
    // let _closure = clone!(@weak v => async move || 0);
    let closure = clone!(@weak v => move || async move { *v.borrow_mut() += 1; });
    block_on(closure());
    assert_eq!(*v.borrow(), 2);
    block_on(clone!(@weak v => async move { *v.borrow_mut() += 1; }));
    assert_eq!(*v.borrow(), 3);
}
