use std::ffi::{c_char, c_void, CString};
use std::ptr;
use std::ptr::null;
use crate::ffi::{VclEvent, VCL_BACKEND, VCL_BOOL, VCL_TIME};
use crate::vcl::{Buffer, Ctx, VclResult};
use crate::{ffi, validate_director};
use super::{BackendRef, ProbeResult};
pub trait VclDirector {
fn resolve(&self, ctx: &mut Ctx) -> Option<BackendRef>;
fn probe(&self, ctx: &mut Ctx) -> ProbeResult;
fn report(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
fn report_details(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
fn report_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
let _ = vsb.write(&"{}");
}
fn report_details_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
let _ = vsb.write(&"{}");
}
fn event(&self, event: VclEvent) {
let _ = event;
}
}
#[derive(Debug)]
pub struct Director<D: VclDirector> {
#[expect(dead_code)]
methods: Box<ffi::vdi_methods>,
inner: Box<D>,
#[expect(dead_code)]
ctype: CString,
backend_ref: BackendRef,
}
impl<D: VclDirector> Director<D> {
pub fn new(ctx: &mut Ctx, director_type: &str, vcl_name: &str, inner: D) -> VclResult<Self> {
let mut inner = Box::new(inner);
let ctype = CString::new(director_type).map_err(|e| e.to_string())?;
let cname = CString::new(vcl_name).map_err(|e| e.to_string())?;
let methods = Box::new(ffi::vdi_methods {
type_: ctype.as_ptr(),
magic: ffi::VDI_METHODS_MAGIC,
http1pipe: None,
healthy: Some(wrap_director_healthy::<D>),
resolve: Some(wrap_director_resolve::<D>),
gethdrs: None,
getip: None,
finish: None,
event: Some(wrap_director_event::<D>),
release: None,
destroy: None,
panic: None,
list: Some(wrap_director_list::<D>),
});
let bep = unsafe {
ffi::VRT_AddDirector(
ctx.raw,
&raw const *methods,
ptr::from_mut::<D>(&mut *inner).cast::<c_void>(),
c"%.*s".as_ptr(),
cname.as_bytes().len(),
cname.as_ptr().cast::<c_char>(),
)
};
if bep.0.is_null() {
return Err(format!("VRT_AddDirector returned null while creating {vcl_name}").into());
}
unsafe {
assert_eq!((*bep.0).magic, ffi::DIRECTOR_MAGIC);
}
let backend_ref = unsafe {
BackendRef::new_withouth_refcount(bep).expect("Backend pointer should never be null")
};
Ok(Director {
methods,
inner,
ctype,
backend_ref,
})
}
pub fn get_inner(&self) -> &D {
&self.inner
}
pub fn get_inner_mut(&mut self) -> &mut D {
&mut self.inner
}
pub fn resolve(&self, ctx: &Ctx) -> VCL_BACKEND {
unsafe { ffi::VRT_DirectorResolve(ctx.raw, self.backend_ref.vcl_ptr()) }
}
pub fn probe(&self, ctx: &Ctx) -> ProbeResult {
self.backend_ref.probe(ctx)
}
}
impl<D: VclDirector> Drop for Director<D> {
fn drop(&mut self) {
unsafe {
let mut bep = self.backend_ref.vcl_ptr();
ffi::VRT_DelDirector(&raw mut bep);
}
}
}
impl<D: VclDirector> AsRef<BackendRef> for Director<D> {
fn as_ref(&self) -> &BackendRef {
&self.backend_ref
}
}
unsafe extern "C" fn wrap_director_resolve<D: VclDirector>(
ctxp: *const ffi::vrt_ctx,
director: VCL_BACKEND,
) -> VCL_BACKEND {
let mut ctx = Ctx::from_ptr(ctxp);
let dir = validate_director(director);
let dir_impl: &D = &*dir.priv_.cast::<D>();
dir_impl
.resolve(&mut ctx)
.map_or(VCL_BACKEND(null()), |backend_ref| backend_ref.vcl_ptr())
}
unsafe extern "C" fn wrap_director_healthy<D: VclDirector>(
ctxp: *const ffi::vrt_ctx,
director: VCL_BACKEND,
changed: *mut VCL_TIME,
) -> VCL_BOOL {
let mut ctx = Ctx::from_ptr(ctxp);
let dir = validate_director(director);
let dir_impl: &D = &*dir.priv_.cast::<D>();
let result = dir_impl.probe(&mut ctx);
if !changed.is_null() {
*changed = result.last_changed.try_into().unwrap();
}
result.healthy.into()
}
unsafe extern "C" fn wrap_director_list<D: VclDirector>(
ctxp: *const ffi::vrt_ctx,
director: VCL_BACKEND,
vsbp: *mut ffi::vsb,
detailed: i32,
json: i32,
) {
let mut ctx = Ctx::from_ptr(ctxp);
let mut vsb = Buffer::from_ptr(vsbp);
let dir = validate_director(director);
let dir_impl: &D = &*dir.priv_.cast::<D>();
match (json != 0, detailed != 0) {
(true, true) => dir_impl.report_details_json(&mut ctx, &mut vsb),
(true, false) => dir_impl.report_json(&mut ctx, &mut vsb),
(false, true) => dir_impl.report_details(&mut ctx, &mut vsb),
(false, false) => dir_impl.report(&mut ctx, &mut vsb),
}
}
unsafe extern "C" fn wrap_director_event<D: VclDirector>(director: VCL_BACKEND, ev: VclEvent) {
let dir = validate_director(director);
let dir_impl: &D = &*dir.priv_.cast::<D>();
dir_impl.event(ev);
}