#![allow(clippy::new_without_default)]
use std::cell::UnsafeCell;
use std::ffi::{c_int, c_longlong, c_ulonglong, c_void, CStr, CString};
use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_uint};
use std::ptr;
use std::sync::atomic::{self, AtomicPtr, Ordering};
use std::sync::Mutex;
use bindings::THD;
use cstr::cstr;
use log::trace;
use mariadb_sys as bindings;
#[repr(i32)]
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq)]
#[allow(clippy::cast_possible_wrap)]
pub enum SysVarOpt {
    ReadOnly = bindings::PLUGIN_VAR_READONLY as i32,
    NoServerVariable = bindings::PLUGIN_VAR_NOSYSVAR as i32,
    NoCliOption = bindings::PLUGIN_VAR_NOCMDOPT as i32,
    NoCliArg = bindings::PLUGIN_VAR_NOCMDARG as i32,
    RequiredCliArg = bindings::PLUGIN_VAR_RQCMDARG as i32,
    OptionalCliArg = bindings::PLUGIN_VAR_OPCMDARG as i32,
    Deprecated = bindings::PLUGIN_VAR_DEPRECATED as i32,
    }
type SVInfoInner<T> = ManuallyDrop<UnsafeCell<T>>;
#[repr(C)]
#[allow(dead_code)]
pub union SysVarInfoU {
    bool_t: SVInfoInner<bindings::sysvar_bool_t>,
    str_t: SVInfoInner<bindings::sysvar_str_t>,
    enum_t: SVInfoInner<bindings::sysvar_enum_t>,
    set_t: SVInfoInner<bindings::sysvar_set_t>,
    int_t: SVInfoInner<bindings::sysvar_int_t>,
    long_t: SVInfoInner<bindings::sysvar_long_t>,
    longlong_t: SVInfoInner<bindings::sysvar_longlong_t>,
    uint_t: SVInfoInner<bindings::sysvar_uint_t>,
    ulong_t: SVInfoInner<bindings::sysvar_ulong_t>,
    ulonglong_t: SVInfoInner<bindings::sysvar_ulonglong_t>,
    double_t: SVInfoInner<bindings::sysvar_double_t>,
    }
impl SysVarOpt {
    pub const fn as_plugin_var_info(self) -> i32 {
        self as i32
    }
}
#[allow(dead_code)]
type SvUpdateFn =
    unsafe extern "C" fn(*mut THD, *mut bindings::st_mysql_sys_var, *mut c_void, *const c_void);
pub unsafe trait SysVarInterface: Sized {
    type CStructType;
    type Intermediate: Copy;
    const DEFAULT_OPTS: i32;
    const DEFAULT_C_STRUCT: Self::CStructType;
    #[allow(unused_variables)]
    unsafe extern "C" fn update_wrap(
        thd: *mut THD,
        var: *mut bindings::st_mysql_sys_var,
        target: *mut c_void,
        save: *const c_void,
    ) {
        let new_save: *const Self::Intermediate = save.cast();
        assert!(
            !new_save.is_null(),
            "got a null pointer from the C interface"
        );
        Self::update(&*target.cast(), &*var.cast(), *new_save);
    }
    #[allow(unused_variables)]
    unsafe fn update(&self, var: &Self::CStructType, save: Self::Intermediate) {
        unimplemented!()
    }
}
#[repr(transparent)]
pub struct SysVarConstString(AtomicPtr<c_char>);
impl SysVarConstString {
    pub const fn new() -> Self {
        Self(AtomicPtr::new(std::ptr::null_mut()))
    }
    pub fn get(&self) -> &'static str {
        let ptr = self.0.load(Ordering::SeqCst);
        let cs = unsafe { CStr::from_ptr(ptr) };
        cs.to_str()
            .unwrap_or_else(|_| panic!("got non-UTF8 string like {}", cs.to_string_lossy()))
    }
}
unsafe impl SysVarInterface for SysVarConstString {
    type CStructType = bindings::sysvar_str_t;
    type Intermediate = *mut c_char;
    const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32;
    const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
        flags: 0,
        name: ptr::null(),
        comment: ptr::null(),
        check: None,
        update: None,
        value: ptr::null_mut(),
        def_val: cstr!("").as_ptr().cast_mut(),
    };
}
#[repr(C)]
pub struct SysVarString {
    ptr: AtomicPtr<c_char>,
    mutex: Mutex<Option<CString>>,
}
impl SysVarString {
    pub const fn new() -> Self {
        Self {
            ptr: AtomicPtr::new(std::ptr::null_mut()),
            mutex: Mutex::new(None),
        }
    }
    pub fn get(&self) -> Option<String> {
        let guard = &*self.mutex.lock().expect("failed to lock mutex");
        let ptr = self.ptr.load(Ordering::SeqCst);
        if !ptr.is_null() && guard.is_some() {
            let cs = guard.as_ref().unwrap();
            assert!(
                ptr.cast_const() == cs.as_ptr(),
                "pointer and c string unsynchronized"
            );
            Some(cstr_to_string(cs))
        } else if ptr.is_null() && guard.is_none() {
            None
        } else {
            trace!("pointer {ptr:p} mismatch with guard {guard:?} (likely init condition)");
            let cs = unsafe { CStr::from_ptr(ptr) };
            Some(cstr_to_string(cs))
        }
    }
}
unsafe impl SysVarInterface for SysVarString {
    type CStructType = bindings::sysvar_str_t;
    type Intermediate = *mut c_char;
    const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32;
    const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
        flags: 0,
        name: ptr::null(),
        comment: ptr::null(),
        check: None,
        update: Some(Self::update_wrap),
        value: ptr::null_mut(),
        def_val: cstr!("").as_ptr().cast_mut(),
    };
    unsafe fn update(&self, _var: &Self::CStructType, save: Self::Intermediate) {
        let to_save = save
            .as_ref()
            .map(|ptr| unsafe { CStr::from_ptr(ptr).to_owned() });
        let guard = &mut *self.mutex.lock().expect("failed to lock mutex");
        *guard = to_save;
        let new_ptr = guard
            .as_deref()
            .map_or(ptr::null_mut(), |cs| cs.as_ptr().cast_mut());
        self.ptr.store(new_ptr, Ordering::SeqCst);
        trace!("updated sysvar with inner: {guard:?}");
    }
}
fn cstr_to_string(cs: &CStr) -> String {
    cs.to_str()
        .unwrap_or_else(|_| panic!("got non-UTF8 string like {}", cs.to_string_lossy()))
        .to_owned()
}
macro_rules! atomic_svinterface {
    (   $atomic_type:ty,
        $c_struct_type:ty,
        bool,
        $default_options:expr $(,)?
    ) => {
        atomic_svinterface!{
            $atomic_type,
            $c_struct_type,
            bool,
            $default_options,
            { def_val: false }
        }
    };
    (   $atomic_type:ty,
        $c_struct_type:ty,
        $inter_type:ty,
        $default_options:expr $(,)?
    ) => {
        atomic_svinterface!{
            $atomic_type,
            $c_struct_type,
            $inter_type,
            $default_options,
            { def_val: 0, min_val: <$inter_type>::MIN, max_val: <$inter_type>::MAX, blk_sz: 0 }
        }
    };
    (   $atomic_type:ty,                $c_struct_type:ty,              $inter_type:ty,                 $default_options:expr,          { $( $extra_struct_fields:tt )* } $(,)?  ) => {
        unsafe impl SysVarInterface for $atomic_type {
            type CStructType = $c_struct_type;
            type Intermediate = $inter_type;
            const DEFAULT_OPTS: i32 = ($default_options) as i32;
            const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
                flags: 0,
                name: ptr::null(),
                comment: ptr::null(),
                check: None,
                update: Some(Self::update_wrap),
                value: ptr::null_mut(),
                $( $extra_struct_fields )*
            };
            unsafe fn update(&self, _var: &Self::CStructType, save: Self::Intermediate) {
                trace!(
                    "updated {} system variable to '{:?}'",
                    std::any::type_name::<$atomic_type>(), save
                );
                self.store(save, Ordering::SeqCst);
            }
        }
    };
}
atomic_svinterface!(
    atomic::AtomicBool,
    bindings::sysvar_bool_t,
    bool,
    bindings::PLUGIN_VAR_BOOL,
);
atomic_svinterface!(
    atomic::AtomicI32,
    bindings::sysvar_int_t,
    c_int,
    bindings::PLUGIN_VAR_INT,
);
atomic_svinterface!(
    atomic::AtomicU32,
    bindings::sysvar_uint_t,
    c_uint,
    bindings::PLUGIN_VAR_INT | bindings::PLUGIN_VAR_UNSIGNED
);
atomic_svinterface!(
    atomic::AtomicI64,
    bindings::sysvar_longlong_t,
    c_longlong,
    bindings::PLUGIN_VAR_LONGLONG
);
atomic_svinterface!(
    atomic::AtomicU64,
    bindings::sysvar_ulonglong_t,
    c_ulonglong,
    bindings::PLUGIN_VAR_LONGLONG | bindings::PLUGIN_VAR_UNSIGNED
);