
use std::sync::{Arc, Mutex};
use std::os::raw::c_void;
use crate::utils::ffi;

pub const MAX_GROUPS: usize = 32;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HotConnectStatus {

    Unknown = -1,

    Absent = 0,

    Present = 1,
}

impl HotConnectStatus {

    pub fn from_raw(v: i32) -> Self {
        match v {
            1 => Self::Present,
            0 => Self::Absent,
            _ => Self::Unknown,
        }
    }
}

#[derive(Clone, Debug)]
pub struct HotConnectGroup {

    pub group_id: u16,

    pub alias_address: u16,

    pub vendor_id: u32,

    pub product_code: u32,

    pub is_present: bool,

    pub detected_slave_index: u16,
}

impl HotConnectGroup {

    pub fn status(&self) -> HotConnectStatus {
        if self.is_present {
            HotConnectStatus::Present
        } else {
            HotConnectStatus::Absent
        }
    }
}

#[repr(C, packed)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct HotConnectGroupNative {
    group_id: u16,
    alias_address: u16,
    vendor_id: u32,
    product_code: u32,
    is_present: i32,
    detected_slave_idx: u16,
    reserved: u16,
}

pub struct HotConnect {
    master_index: u16,
}

impl HotConnect {

    pub fn new(master_index: u16) -> Self {
        Self { master_index }
    }

    pub fn add_group(&self, group_id: u16, alias: u16,
                     vendor_id: u32, product_code: u32) -> bool {
        unsafe {
            ffi::HotConnectAddGroup(self.master_index, group_id, alias, vendor_id, product_code) != 0
        }
    }

    pub fn remove_group(&self, group_id: u16) -> bool {
        unsafe { ffi::HotConnectRemoveGroup(self.master_index, group_id) != 0 }
    }

    pub fn group_status(&self, group_id: u16) -> HotConnectStatus {
        HotConnectStatus::from_raw(unsafe {
            ffi::HotConnectGetGroupStatus(self.master_index, group_id)
        })
    }

    pub fn clear_all(&self) {
        unsafe { ffi::HotConnectClearAll(self.master_index) };
    }

    pub fn group_count(&self) -> i32 {
        unsafe { ffi::HotConnectGetGroupCount(self.master_index) }
    }

    pub fn enumerate(&self) -> Vec<HotConnectGroup> {

        let mut buf = [HotConnectGroupNative {
            group_id: 0, alias_address: 0, vendor_id: 0, product_code: 0,
            is_present: 0, detected_slave_idx: 0, reserved: 0,
        }; MAX_GROUPS];
        let n = unsafe {
            ffi::HotConnectEnumerate(
                self.master_index, buf.as_mut_ptr() as *mut c_void, MAX_GROUPS as i32,
            )
        };
        if n <= 0 { return Vec::new(); }
        let cnt = (n as usize).min(MAX_GROUPS);
        let mut out = Vec::with_capacity(cnt);
        for i in 0..cnt {
            let entry = &buf[i];

            let gid = unsafe { std::ptr::addr_of!(entry.group_id).read_unaligned() };
            let alias = unsafe { std::ptr::addr_of!(entry.alias_address).read_unaligned() };
            let vid = unsafe { std::ptr::addr_of!(entry.vendor_id).read_unaligned() };
            let pid = unsafe { std::ptr::addr_of!(entry.product_code).read_unaligned() };
            let present = unsafe { std::ptr::addr_of!(entry.is_present).read_unaligned() };
            let det = unsafe { std::ptr::addr_of!(entry.detected_slave_idx).read_unaligned() };
            out.push(HotConnectGroup {
                group_id: gid,
                alias_address: alias,
                vendor_id: vid,
                product_code: pid,
                is_present: present != 0,
                detected_slave_index: det,
            });
        }
        out
    }
}

#[derive(Clone, Copy, Debug)]
pub struct HotPlugIdentityMismatch {
    pub master_index: u16,
    pub slave_index: u16,
    pub expected_vendor: u32,
    pub expected_product: u32,
    pub expected_revision: u32,
    pub actual_vendor: u32,
    pub actual_product: u32,
    pub actual_revision: u32,
}

type HotPlugList = Arc<Mutex<Vec<Box<dyn Fn(HotPlugIdentityMismatch) + Send + 'static>>>>;

fn hotplug_list() -> &'static HotPlugList {
    use std::sync::OnceLock;
    static INSTANCE: OnceLock<HotPlugList> = OnceLock::new();
    INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}

pub fn on_hotplug<F>(callback: F)
where
    F: Fn(HotPlugIdentityMismatch) + Send + 'static,
{
    if let Ok(mut v) = hotplug_list().lock() {
        v.push(Box::new(callback));
    }
}

pub fn clear_hotplug_listeners() {
    if let Ok(mut v) = hotplug_list().lock() {
        v.clear();
    }
}

pub fn dispatch_hotplug(ev: HotPlugIdentityMismatch) {
    let listeners = match hotplug_list().lock() {
        Ok(g) => g,
        Err(p) => p.into_inner(),
    };
    for cb in listeners.iter() {
        cb(ev);
    }
}
