use crate::{Error, ErrorKind, TulispContext, TulispObject, destruct_bind};

pub(crate) fn add(ctx: &mut TulispContext) {
    ctx.defun("error", |msg: String| -> Result<TulispObject, Error> {
        Err(Error::lisp_error(msg))
    });

    ctx.defspecial("catch", |ctx, args| {
        destruct_bind!((tag &rest body) = args);
        let res = ctx.eval_progn(&body);
        if let Err(ref e) = res {
            let tag = ctx.eval(&tag)?;
            if let ErrorKind::Throw(obj) = e.kind_ref()
                && let Ok(true) = obj.car_and_then(|e_tag| Ok(e_tag.eq(&tag)))
            {
                return obj.cdr();
            }
        }
        res
    });

    ctx.defun(
        "throw",
        |tag: TulispObject, value: TulispObject| -> Result<TulispObject, Error> {
            Err(Error::throw(tag, value))
        },
    );
}

#[cfg(test)]
mod tests {
    use crate::TulispContext;
    use crate::test_utils::{eval_assert_equal, eval_assert_error};

    #[test]
    fn test_error_handling() {
        let mut ctx = TulispContext::new();
        eval_assert_equal(
            &mut ctx,
            "(catch 'my-tag (setq x 42) (throw 'my-tag x))",
            "42",
        );
        eval_assert_error(
            &mut ctx,
            "(catch 'my-tag (throw 'other-tag 42))",
            r#"ERR Throw((other-tag . 42)):
<eval_string>:1.16-1.36:  at (throw 'other-tag 42)
<eval_string>:1.1-1.37:  at (catch 'my-tag (throw 'other-tag 42))
"#,
        );
        eval_assert_error(
            &mut ctx,
            r#"(error "Something went wrong!")"#,
            r#"ERR LispError: Something went wrong!
<eval_string>:1.1-1.31:  at (error "Something went wrong!")
"#,
        );
    }
}
