#![cfg(target_os = "windows")]

//! Bindings to the Microsoft Detours API.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

// Rebuild with:
// bindgen ..\build\wrapper.h --whitelist-function "Detour.*" -o bundled_bindings.rs  -- "-fms-compatibility" "-fms-extensions" --target=x86_64-pc-windows-msvc -I ..\..\ext\detours\src\
include!("bundled_bindings.rs");

#[cfg(test)]
mod tests {
    use super::*;
    use std::{ffi, ptr};
    use winapi::{
        shared::minwindef::LPVOID,
        um::{processthreadsapi::GetCurrentThread, synchapi::Sleep, sysinfoapi::GetTickCount},
    };

    static mut TRUE_SLEEP: unsafe extern "system" fn(DWORD) = Sleep;
    static mut SLEPT: LONG = 0;

    // Detour function that replaces the Sleep API.
    unsafe extern "system" fn TimedSleep(dwMilliseconds: DWORD) {
        // Save the before and after times around calling the Sleep API.
        let dwBeg: DWORD = GetTickCount();
        TRUE_SLEEP(dwMilliseconds);
        let dwEnd: DWORD = GetTickCount();

        SLEPT = (dwEnd - dwBeg) as i32;
    }

    extern "system" fn DllMain(_: HINSTANCE, reason: DWORD, _: LPVOID) -> BOOL {
        if unsafe { DetourIsHelperProcess() } == 1 {
            return 1;
        }

        let tru = unsafe { &mut TRUE_SLEEP as *mut _ as *mut *mut ffi::c_void };
        let new = TimedSleep as *mut ffi::c_void;

        match reason {
            // DLL_PROCESS_ATTACH
            1 => unsafe {
                DetourRestoreAfterWith();

                DetourTransactionBegin();
                DetourUpdateThread(GetCurrentThread() as _);
                DetourAttach(tru, new);
                DetourTransactionCommit();
            },
            // DLL_PROCESS_DETACH
            0 => unsafe {
                DetourTransactionBegin();
                DetourUpdateThread(GetCurrentThread() as _);
                DetourDetach(tru, new);
                DetourTransactionCommit();
            },
            _ => (),
        }
        1
    }

    #[test]
    fn hook_self() {
        unsafe {
            DllMain(ptr::null_mut(), 1, ptr::null_mut());
            let slept;

            Sleep(500);
            slept = SLEPT;
            assert_ne!(SLEPT, 0);

            DllMain(ptr::null_mut(), 0, ptr::null_mut());
            Sleep(500);
            assert_eq!(slept, SLEPT);
        }
    }
}
