mod function;
mod string;
mod table;
mod thread;
mod types;
mod userdata;

use std::{error, fmt};
use std::iter::FromIterator;
use std::rc::Rc;
use std::cell::Cell;
use std::sync::Arc;
use std::panic::catch_unwind;

use {Error, ExternalError, Function, Lua, Nil, Result, Table, UserData, UserDataMethods, Value,
     Variadic};

#[test]
fn test_load() {
    let lua = Lua::new();
    let func = lua.load("return 1+2", None).unwrap();
    let result: i32 = func.call(()).unwrap();
    assert_eq!(result, 3);

    assert!(lua.load("§$%§&$%&", None).is_err());
}

#[test]
fn test_debug() {
    let lua = unsafe { Lua::new_with_debug() };
    match lua.eval("debug", None).unwrap() {
        Value::Table(_) => {}
        val => panic!("Expected table for debug library, got {:#?}", val),
    }
    let traceback_output = lua.eval::<String>("debug.traceback()", None).unwrap();
    assert_eq!(
        traceback_output.split("\n").next(),
        "stack traceback:".into()
    );
}

#[test]
fn test_exec() {
    let lua = Lua::new();
    let globals = lua.globals();
    lua.exec::<()>(
        r#"
            res = 'foo'..'bar'
        "#,
        None,
    ).unwrap();
    assert_eq!(globals.get::<_, String>("res").unwrap(), "foobar");

    let module: Table = lua.exec(
        r#"
            local module = {}

            function module.func()
                return "hello"
            end

            return module
        "#,
        None,
    ).unwrap();
    assert!(module.contains_key("func").unwrap());
    assert_eq!(
        module
            .get::<_, Function>("func")
            .unwrap()
            .call::<_, String>(())
            .unwrap(),
        "hello"
    );
}

#[test]
fn test_eval() {
    let lua = Lua::new();
    assert_eq!(lua.eval::<i32>("1 + 1", None).unwrap(), 2);
    assert_eq!(lua.eval::<bool>("false == false", None).unwrap(), true);
    assert_eq!(lua.eval::<i32>("return 1 + 2", None).unwrap(), 3);
    match lua.eval::<()>("if true then", None) {
        Err(Error::SyntaxError {
            incomplete_input: true,
            ..
        }) => {}
        r => panic!(
            "expected SyntaxError with incomplete_input=true, got {:?}",
            r
        ),
    }
}

#[test]
fn test_lua_multi() {
    let lua = Lua::new();
    let globals = lua.globals();
    lua.exec::<()>(
        r#"
            function concat(arg1, arg2)
                return arg1 .. arg2
            end

            function mreturn()
                return 1, 2, 3, 4, 5, 6
            end
        "#,
        None,
    ).unwrap();

    let concat = globals.get::<_, Function>("concat").unwrap();
    let mreturn = globals.get::<_, Function>("mreturn").unwrap();

    assert_eq!(concat.call::<_, String>(("foo", "bar")).unwrap(), "foobar");
    let (a, b) = mreturn.call::<_, (u64, u64)>(()).unwrap();
    assert_eq!((a, b), (1, 2));
    let (a, b, v) = mreturn.call::<_, (u64, u64, Variadic<u64>)>(()).unwrap();
    assert_eq!((a, b), (1, 2));
    assert_eq!(v[..], [3, 4, 5, 6]);
}

#[test]
fn test_coercion() {
    let lua = Lua::new();
    let globals = lua.globals();
    lua.exec::<()>(
        r#"
            int = 123
            str = "123"
            num = 123.0
        "#,
        None,
    ).unwrap();

    assert_eq!(globals.get::<_, String>("int").unwrap(), "123");
    assert_eq!(globals.get::<_, i32>("str").unwrap(), 123);
    assert_eq!(globals.get::<_, i32>("num").unwrap(), 123);
}

#[test]
fn test_error() {
    #[derive(Debug)]
    pub struct TestError;

    impl fmt::Display for TestError {
        fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
            write!(fmt, "test error")
        }
    }

    impl error::Error for TestError {
        fn description(&self) -> &str {
            "test error"
        }

        fn cause(&self) -> Option<&error::Error> {
            None
        }
    }

    let lua = Lua::new();
    let globals = lua.globals();
    lua.exec::<()>(
        r#"
            function no_error()
            end

            function lua_error()
                error("this is a lua error")
            end

            function rust_error()
                rust_error_function()
            end

            function return_error()
                local status, res = pcall(rust_error_function)
                assert(not status)
                return res
            end

            function return_string_error()
                return "this should be converted to an error"
            end

            function test_pcall()
                local testvar = 0

                pcall(function(arg)
                    testvar = testvar + arg
                    error("should be ignored")
                end, 3)

                local function handler(err)
                    testvar = testvar + err
                    return "should be ignored"
                end

                local status, res = xpcall(function()
                    error(5)
                end, handler)
                assert(not status)

                if testvar ~= 8 then
                    error("testvar had the wrong value, pcall / xpcall misbehaving "..testvar)
                end
            end

            function understand_recursion()
                understand_recursion()
            end
        "#,
        None,
    ).unwrap();

    let rust_error_function = lua.create_function(|_, ()| -> Result<()> {
        Err(TestError.to_lua_err())
    }).unwrap();
    globals
        .set("rust_error_function", rust_error_function)
        .unwrap();

    let no_error = globals.get::<_, Function>("no_error").unwrap();
    let lua_error = globals.get::<_, Function>("lua_error").unwrap();
    let rust_error = globals.get::<_, Function>("rust_error").unwrap();
    let return_error = globals.get::<_, Function>("return_error").unwrap();
    let return_string_error = globals.get::<_, Function>("return_string_error").unwrap();
    let test_pcall = globals.get::<_, Function>("test_pcall").unwrap();
    let understand_recursion = globals.get::<_, Function>("understand_recursion").unwrap();

    assert!(no_error.call::<_, ()>(()).is_ok());
    match lua_error.call::<_, ()>(()) {
        Err(Error::RuntimeError(_)) => {}
        Err(_) => panic!("error is not RuntimeError kind"),
        _ => panic!("error not returned"),
    }
    match rust_error.call::<_, ()>(()) {
        Err(Error::CallbackError { .. }) => {}
        Err(_) => panic!("error is not CallbackError kind"),
        _ => panic!("error not returned"),
    }

    match return_error.call::<_, Value>(()) {
        Ok(Value::Error(_)) => {}
        _ => panic!("Value::Error not returned"),
    }

    assert!(return_string_error.call::<_, Error>(()).is_ok());

    match lua.eval::<()>("if youre happy and you know it syntax error", None) {
        Err(Error::SyntaxError {
            incomplete_input: false,
            ..
        }) => {}
        Err(_) => panic!("error is not LuaSyntaxError::Syntax kind"),
        _ => panic!("error not returned"),
    }
    match lua.eval::<()>("function i_will_finish_what_i()", None) {
        Err(Error::SyntaxError {
            incomplete_input: true,
            ..
        }) => {}
        Err(_) => panic!("error is not LuaSyntaxError::IncompleteStatement kind"),
        _ => panic!("error not returned"),
    }

    test_pcall.call::<_, ()>(()).unwrap();

    assert!(understand_recursion.call::<_, ()>(()).is_err());

    match catch_unwind(|| -> Result<()> {
        let lua = Lua::new();
        let globals = lua.globals();

        lua.exec::<()>(
            r#"
                function rust_panic()
                    pcall(function () rust_panic_function() end)
                end
            "#,
            None,
        )?;
        let rust_panic_function = lua.create_function(|_, ()| -> Result<()> {
            panic!("expected panic, this panic should be caught in rust")
        }).unwrap();
        globals.set("rust_panic_function", rust_panic_function)?;

        let rust_panic = globals.get::<_, Function>("rust_panic")?;

        rust_panic.call::<_, ()>(())
    }) {
        Ok(Ok(_)) => panic!("no panic was detected, pcall caught it!"),
        Ok(Err(e)) => panic!("error during panic test {:?}", e),
        Err(_) => {}
    };

    match catch_unwind(|| -> Result<()> {
        let lua = Lua::new();
        let globals = lua.globals();

        lua.exec::<()>(
            r#"
                function rust_panic()
                    xpcall(function() rust_panic_function() end, function() end)
                end
            "#,
            None,
        )?;
        let rust_panic_function = lua.create_function(|_, ()| -> Result<()> {
            panic!("expected panic, this panic should be caught in rust")
        }).unwrap();
        globals.set("rust_panic_function", rust_panic_function)?;

        let rust_panic = globals.get::<_, Function>("rust_panic")?;

        rust_panic.call::<_, ()>(())
    }) {
        Ok(Ok(_)) => panic!("no panic was detected, xpcall caught it!"),
        Ok(Err(e)) => panic!("error during panic test {:?}", e),
        Err(_) => {}
    };
}

#[test]
fn test_result_conversions() {
    let lua = Lua::new();
    let globals = lua.globals();

    let err = lua.create_function(|_, ()| {
        Ok(Err::<String, _>(
            format_err!("only through failure can we succeed").to_lua_err(),
        ))
    }).unwrap();
    let ok = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))
        .unwrap();

    globals.set("err", err).unwrap();
    globals.set("ok", ok).unwrap();

    lua.exec::<()>(
        r#"
            local r, e = err()
            assert(r == nil)
            assert(tostring(e) == "only through failure can we succeed")

            local r, e = ok()
            assert(r == "!")
            assert(e == nil)
        "#,
        None,
    ).unwrap();
}

#[test]
fn test_num_conversion() {
    let lua = Lua::new();
    let globals = lua.globals();

    globals.set("n", "1.0").unwrap();
    assert_eq!(globals.get::<_, i64>("n").unwrap(), 1);
    assert_eq!(globals.get::<_, f64>("n").unwrap(), 1.0);
    assert_eq!(globals.get::<_, String>("n").unwrap(), "1.0");

    globals.set("n", "1.5").unwrap();
    assert!(globals.get::<_, i64>("n").is_err());
    assert_eq!(globals.get::<_, f64>("n").unwrap(), 1.5);
    assert_eq!(globals.get::<_, String>("n").unwrap(), "1.5");

    globals.set("n", 1.5).unwrap();
    assert!(globals.get::<_, i64>("n").is_err());
    assert_eq!(globals.get::<_, f64>("n").unwrap(), 1.5);
    assert_eq!(globals.get::<_, String>("n").unwrap(), "1.5");

    lua.exec::<()>("a = math.huge", None).unwrap();
    assert!(globals.get::<_, i64>("n").is_err());
}

#[test]
fn test_pcall_xpcall() {
    let lua = Lua::new();
    let globals = lua.globals();

    // make sure that we handle not enough arguments
    assert!(lua.exec::<()>("pcall()", None).is_err());
    assert!(lua.exec::<()>("xpcall()", None).is_err());
    assert!(lua.exec::<()>("xpcall(function() end)", None).is_err());

    // Make sure that the return values from are correct on success
    assert_eq!(
        lua.eval::<(bool, String)>("pcall(function(p) return p end, 'foo')", None)
            .unwrap(),
        (true, "foo".to_owned())
    );
    assert_eq!(
        lua.eval::<(bool, String)>("xpcall(function(p) return p end, print, 'foo')", None)
            .unwrap(),
        (true, "foo".to_owned())
    );

    // Make sure that the return values are correct on errors, and that error handling works

    lua.exec::<()>(
        r#"
            pcall_error = nil
            pcall_status, pcall_error = pcall(error, "testerror")

            xpcall_error = nil
            xpcall_status, _ = xpcall(error, function(err) xpcall_error = err end, "testerror")
        "#,
        None,
    ).unwrap();

    assert_eq!(globals.get::<_, bool>("pcall_status").unwrap(), false);
    assert_eq!(
        globals.get::<_, String>("pcall_error").unwrap(),
        "testerror"
    );

    assert_eq!(globals.get::<_, bool>("xpcall_statusr").unwrap(), false);
    assert_eq!(
        globals.get::<_, String>("xpcall_error").unwrap(),
        "testerror"
    );

    // Make sure that weird xpcall error recursion at least doesn't cause unsafety or panics.
    lua.exec::<()>(
        r#"
            function xpcall_recursion()
                xpcall(error, function(err) error(err) end, "testerror")
            end
        "#,
        None,
    ).unwrap();
    let _ = globals
        .get::<_, Function>("xpcall_recursion")
        .unwrap()
        .call::<_, ()>(());
}

#[test]
fn test_recursive_mut_callback_error() {
    let lua = Lua::new();

    let mut v = Some(Box::new(123));
    let f = lua.create_function_mut::<_, (), _>(move |lua, mutate: bool| {
        if mutate {
            v = None;
        } else {
            // Produce a mutable reference
            let r = v.as_mut().unwrap();
            // Whoops, this will recurse into the function and produce another mutable reference!
            lua.globals().get::<_, Function>("f")?.call::<_, ()>(true)?;
            println!("Should not get here, mutable aliasing has occurred!");
            println!("value at {:p}", r as *mut _);
            println!("value is {}", r);
        }

        Ok(())
    }).unwrap();
    lua.globals().set("f", f).unwrap();
    match lua.globals()
        .get::<_, Function>("f")
        .unwrap()
        .call::<_, ()>(false)
    {
        Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
            Error::CallbackError { ref cause, .. } => match *cause.as_ref() {
                Error::RecursiveMutCallback { .. } => {}
                ref other => panic!("incorrect result: {:?}", other),
            },
            ref other => panic!("incorrect result: {:?}", other),
        },
        other => panic!("incorrect result: {:?}", other),
    };
}

#[test]
fn test_set_metatable_nil() {
    let lua = Lua::new();
    lua.exec::<()>(
        r#"
        a = {}
        setmetatable(a, nil)
    "#,
        None,
    ).unwrap();
}

#[test]
fn test_gc_error() {
    let lua = Lua::new();
    match lua.exec::<()>(
        r#"
                val = nil
                table = {}
                setmetatable(table, {
                    __gc = function()
                        error("gcwascalled")
                    end
                })
                table = nil
                collectgarbage("collect")
            "#,
        None,
    ) {
        Err(Error::GarbageCollectorError(_)) => {}
        Err(e) => panic!("__gc error did not result in correct error, instead: {}", e),
        Ok(()) => panic!("__gc error did not result in error"),
    }
}

#[test]
fn test_named_registry_value() {
    let lua = Lua::new();

    lua.set_named_registry_value::<i32>("test", 42).unwrap();
    let f = lua.create_function(move |lua, ()| {
        assert_eq!(lua.named_registry_value::<i32>("test")?, 42);
        Ok(())
    }).unwrap();

    f.call::<_, ()>(()).unwrap();

    lua.unset_named_registry_value("test").unwrap();
    match lua.named_registry_value("test").unwrap() {
        Nil => {}
        val => panic!("registry value was not Nil, was {:?}", val),
    };
}

#[test]
fn test_registry_value() {
    let lua = Lua::new();

    let mut r = Some(lua.create_registry_value::<i32>(42).unwrap());
    let f = lua.create_function_mut(move |lua, ()| {
        if let Some(r) = r.take() {
            assert_eq!(lua.registry_value::<i32>(&r)?, 42);
            lua.remove_registry_value(r).unwrap();
        } else {
            panic!();
        }
        Ok(())
    }).unwrap();

    f.call::<_, ()>(()).unwrap();
}

#[test]
fn test_drop_registry_value() {
    struct MyUserdata(Arc<()>);

    impl UserData for MyUserdata {}

    let lua = Lua::new();

    let rc = Arc::new(());

    let r = lua.create_registry_value(MyUserdata(rc.clone())).unwrap();
    assert_eq!(Arc::strong_count(&rc), 2);

    drop(r);
    lua.expire_registry_values();

    lua.exec::<()>(r#"collectgarbage("collect")"#, None)
        .unwrap();

    assert_eq!(Arc::strong_count(&rc), 1);
}

#[test]
#[should_panic]
fn test_mismatched_lua_ref() {
    let lua1 = Lua::new();
    let lua2 = Lua::new();

    let s = lua1.create_string("hello").unwrap();
    let f = lua2.create_function(|_, _: String| Ok(())).unwrap();

    f.call::<_, ()>(s).unwrap();
}

#[test]
fn test_lua_registry_ownership() {
    let lua1 = Lua::new();
    let lua2 = Lua::new();

    let r1 = lua1.create_registry_value("hello").unwrap();
    let r2 = lua2.create_registry_value("hello").unwrap();

    assert!(lua1.owns_registry_value(&r1));
    assert!(!lua2.owns_registry_value(&r1));
    assert!(lua2.owns_registry_value(&r2));
    assert!(!lua1.owns_registry_value(&r2));
}

#[test]
fn test_mismatched_registry_key() {
    let lua1 = Lua::new();
    let lua2 = Lua::new();

    let r = lua1.create_registry_value("hello").unwrap();
    match lua2.remove_registry_value(r) {
        Err(Error::MismatchedRegistryKey) => {}
        r => panic!("wrong result type for mismatched registry key, {:?}", r),
    };
}

#[test]
fn scope_func() {
    let rc = Rc::new(Cell::new(0));

    let lua = Lua::new();
    lua.scope(|scope| {
        let r = rc.clone();
        let f = scope
            .create_function(move |_, ()| {
                r.set(42);
                Ok(())
            })
            .unwrap();
        lua.globals().set("bad", f.clone()).unwrap();
        f.call::<_, ()>(()).unwrap();
        assert_eq!(Rc::strong_count(&rc), 2);
    });
    assert_eq!(rc.get(), 42);
    assert_eq!(Rc::strong_count(&rc), 1);

    match lua.globals()
        .get::<_, Function>("bad")
        .unwrap()
        .call::<_, ()>(())
    {
        Err(Error::CallbackError { .. }) => {}
        r => panic!("improper return for destructed function: {:?}", r),
    };
}

#[test]
fn scope_drop() {
    struct MyUserdata(Rc<()>);
    impl UserData for MyUserdata {
        fn add_methods(methods: &mut UserDataMethods<Self>) {
            methods.add_method("method", |_, _, ()| Ok(()));
        }
    }

    let rc = Rc::new(());

    let lua = Lua::new();
    lua.scope(|scope| {
        lua.globals()
            .set(
                "test",
                scope.create_userdata(MyUserdata(rc.clone())).unwrap(),
            )
            .unwrap();
        assert_eq!(Rc::strong_count(&rc), 2);
    });
    assert_eq!(Rc::strong_count(&rc), 1);

    match lua.exec::<()>("test:method()", None) {
        Err(Error::CallbackError { .. }) => {}
        r => panic!("improper return for destructed userdata: {:?}", r),
    };
}

#[test]
fn scope_capture() {
    let mut i = 0;

    let lua = Lua::new();
    lua.scope(|scope| {
        scope
            .create_function_mut(|_, ()| {
                i = 42;
                Ok(())
            })
            .unwrap()
            .call::<_, ()>(())
            .unwrap();
    });
    assert_eq!(i, 42);
}

#[test]
fn too_many_returns() {
    let lua = Lua::new();
    let f = lua.create_function(|_, ()| Ok(Variadic::from_iter(1..1000000)))
        .unwrap();
    assert!(f.call::<_, Vec<u32>>(()).is_err());
}

#[test]
fn too_many_arguments() {
    let lua = Lua::new();
    lua.exec::<()>("function test(...) end", None).unwrap();
    let args = Variadic::from_iter(1..1000000);
    assert!(
        lua.globals()
            .get::<_, Function>("test")
            .unwrap()
            .call::<_, ()>(args)
            .is_err()
    );
}

#[test]
fn too_many_recursions() {
    let lua = Lua::new();

    let f = lua.create_function(move |lua, ()| {
        lua.globals().get::<_, Function>("f")?.call::<_, ()>(())
    }).unwrap();
    lua.globals().set("f", f).unwrap();

    assert!(
        lua.globals()
            .get::<_, Function>("f")
            .unwrap()
            .call::<_, ()>(())
            .is_err()
    );
}

#[test]
fn too_many_binds() {
    let lua = Lua::new();
    let globals = lua.globals();
    lua.exec::<()>(
        r#"
            function f(...)
            end
        "#,
        None,
    ).unwrap();

    let concat = globals.get::<_, Function>("f").unwrap();
    assert!(concat.bind(Variadic::from_iter(1..1000000)).is_err());
    assert!(
        concat
            .call::<_, ()>(Variadic::from_iter(1..1000000))
            .is_err()
    );
}
