use core::ffi::c_int;

pub type __jmp_buf = [usize; 8];
pub type __sigjmp_buf = [usize; 8];

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn sigsetjmp(buf: *mut __sigjmp_buf, savesigs: c_int) -> c_int {
    // TODO: I don't yet have signal masking
    core::arch::naked_asm!("jmp setjmp")
}

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn setjmp(buf: *mut __jmp_buf) -> c_int {
    // %rdi -- jmp_buf pointer
    core::arch::naked_asm!(
        r#"
        // Preserve callee-saved registers
        movq %rbx, 0(%rdi)      // jmp_buf[0] = %rbx
        movq %rbp, 8(%rdi)      // jmp_buf[1] = %rbp
        movq %r12, 16(%rdi)     // jmp_buf[2] = %r12
        movq %r13, 24(%rdi)     // jmp_buf[3] = %r13
        movq %r14, 32(%rdi)     // jmp_buf[4] = %r14
        movq %r15, 40(%rdi)     // jmp_buf[5] = %r15
        // Calculate return stack and rip
        leaq 8(%rsp), %rax
        movq %rax, 48(%rdi)     // jmp_buf[6] = %rsp + 8 (return stack)
        movq (%rsp), %rax       // jmp_buf[7] = return address
        movq %rax, 56(%rdi)

        movq $0, %rax
        ret
    "#,
        options(att_syntax)
    )
}

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn _setjmp(buf: *mut __jmp_buf) -> c_int {
    core::arch::naked_asm!("jmp setjmp", options(att_syntax))
}

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn longjmp(buf: *mut __jmp_buf, val: c_int) -> ! {
    // %rdi -- jmp_buf pointer
    // %rsi -- return value
    core::arch::naked_asm!(
        r#"
        // Restore the registers
        movq 0(%rdi), %rbx
        movq 8(%rdi), %rbp
        movq 16(%rdi), %r12
        movq 24(%rdi), %r13
        movq 32(%rdi), %r14
        movq 40(%rdi), %r15
        movq 48(%rdi), %rsp
        movq 56(%rdi), %rax
        pushq %rax              // Setup fake return address

        // Setup return value
        movq %rsi, %rax

        ret
    "#,
        options(att_syntax)
    )
}

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn siglongjmp(buf: *mut __sigjmp_buf, val: c_int) -> ! {
    core::arch::naked_asm!("jmp longjmp")
}

#[no_mangle]
#[unsafe(naked)]
unsafe extern "C" fn _longjmp(buf: *mut __jmp_buf, val: c_int) -> c_int {
    core::arch::naked_asm!("jmp longjmp", options(att_syntax))
}
