use libc::c_char;
use once_cell::sync::Lazy;
use std::{
	ffi::{CStr, CString},
	sync::Mutex,
};

struct Command {
	name: String,
	callback: Box<dyn Fn(&str, &str) + 'static>,
}

unsafe impl Send for Command {}
unsafe impl Sync for Command {}

static COMMAND_REGISTRY: Lazy<Mutex<Vec<Command>>> = Lazy::new(|| Mutex::new(Vec::new()));

unsafe extern "C" fn command_handler(cmd: *const c_char, args: *mut c_char) {
	let cmd_name = CStr::from_ptr(cmd as *const _).to_string_lossy();
	let args = CStr::from_ptr(args as *const _).to_string_lossy();
	let registry = COMMAND_REGISTRY
		.lock()
		.expect("failed to lock command registry");
	if let Some(cmd) = registry.iter().find(|cmd| cmd.name == cmd_name) {
		(cmd.callback)(&cmd_name, &args);
	}
}

pub fn register_command<N, D, F>(name: N, desc: D, callback: F)
where
	N: AsRef<str>,
	D: AsRef<str>,
	F: Fn(&str, &str) + 'static,
{
	let name = name.as_ref().to_string();
	let desc = desc.as_ref();
	let c_name = CString::new(name.as_str())
		.expect("cstring failed")
		.into_raw();
	let c_desc = CString::new(desc).expect("cstring failed").into_raw();
	COMMAND_REGISTRY
		.lock()
		.expect("failed to lock command registry")
		.push(Command {
			name,
			callback: Box::new(callback),
		});
	unsafe {
		api::command_register(c_name as *const _, c_desc as *const _, command_handler);
	}
}

pub mod api {
	use libc::c_char;

	extern "C" {
		pub fn command_register(
			name: *const c_char,
			desc: *const c_char,
			callback: unsafe extern "C" fn(cmd: *const c_char, args: *mut c_char),
		);
	}
}
