use std::{
    env::{self, set_current_dir},
    ffi::CString,
    fs,
    io::Write,
    panic,
    process::Command,
    ptr,
    sync::atomic::{AtomicBool, Ordering},
};

static RELOAD: AtomicBool = AtomicBool::new(false);

fn exe_path() -> String {
    env::current_exe()
        .unwrap()
        .to_string_lossy()
        .replace(" (deleted)", "")
}

fn cargo_path() -> Option<String> {
    let exe = exe_path();
    let parts: Vec<_> = exe.split('/').collect();
    for i in (0..parts.len()).rev() {
        let base_dir = format!("/{}", parts[0..i].join("/"));
        let toml_path = format!("/{base_dir}/Cargo.toml");
        if fs::exists(toml_path).unwrap() {
            return Some(base_dir);
        }
    }
    None
}

pub fn begin_reload() {
    let Some(cargo_path) = cargo_path() else {
        return;
    };
    set_current_dir(cargo_path).unwrap();
    let Ok(status) = Command::new("cargo").arg("build").status() else {
        return;
    };
    if !status.success() {
        return;
    }
    RELOAD.store(true, Ordering::SeqCst);
    panic::resume_unwind(Box::new(42));
}

/// ONLY TO BE CALLED FROM MAIN WHEN NOT A SINGLE RESOURCE IS HELD
pub unsafe fn continue_reload() {
    if !RELOAD.load(Ordering::SeqCst) {
        return;
    }

    eprintln!("reloading...");
    let _ = std::io::stdout().lock().flush();

    unsafe { exec() }

    eprintln!("exec failed.");
}

unsafe fn exec() {
    // path to this executable
    let exe_c = CString::new(exe_path()).unwrap();

    // argv
    let args: Vec<CString> = env::args().map(|a| CString::new(a).unwrap()).collect();
    let mut argv: Vec<*const libc::c_char> = args.iter().map(|a| a.as_ptr()).collect();
    argv.push(ptr::null());

    unsafe {
        libc::execv(exe_c.as_ptr(), argv.as_ptr());
    }
}
