
use crate::utils::ffi;

pub const OBJ_DEVICE_TYPE: u16 = 0x1000;
pub const OBJ_DEVICE_NAME: u16 = 0x1008;
pub const OBJ_HW_VERSION: u16 = 0x1009;
pub const OBJ_SW_VERSION: u16 = 0x100A;
pub const OBJ_IDENTITY: u16 = 0x1018;
pub const OBJ_DETECT_MODULES: u16 = 0xF002;
pub const OBJ_MASTER_DIAG_DATA: u16 = 0xF120;
pub const OBJ_DIAG_INTERFACE_CTRL: u16 = 0xF200;

pub struct MasterOdEtg1510 {
    master_index: u16,

    pub vendor_id: u32,

    pub product_code: u32,

    pub serial_number: u32,
}

impl MasterOdEtg1510 {

    pub fn new(master_index: u16, instance_id: u32) -> Self {
        Self {
            master_index,
            vendor_id: 0x00001164,
            product_code: 0x00000001,
            serial_number: instance_id,
        }
    }

    pub fn revision_number(&self) -> u32 {
        let ver = crate::statics::version_info::dll_version().unwrap_or_default();
        ((ver.major as u32) << 16) | (ver.minor as u32)
    }

    pub fn read_device_type(&self) -> Vec<u8> {

        0x00001389u32.to_le_bytes().to_vec()
    }

    pub fn read_device_name(&self) -> Vec<u8> {
        b"Darra EtherCAT Master".to_vec()
    }

    pub fn read_hw_version(&self) -> Vec<u8> {
        if cfg!(target_pointer_width = "64") {
            b"x64".to_vec()
        } else {
            b"x86".to_vec()
        }
    }

    pub fn read_sw_version(&self) -> Vec<u8> {
        let ver = crate::statics::version_info::dll_version().unwrap_or_default();
        format!("{}.{}.{}.{}", ver.major, ver.minor, ver.patch, ver.build)
            .into_bytes()
    }

    pub fn read_identity(&self, subindex: u8) -> Option<Vec<u8>> {
        match subindex {
            0 => Some(vec![4]),
            1 => Some(self.vendor_id.to_le_bytes().to_vec()),
            2 => Some(self.product_code.to_le_bytes().to_vec()),
            3 => Some(self.revision_number().to_le_bytes().to_vec()),
            4 => Some(self.serial_number.to_le_bytes().to_vec()),
            _ => None,
        }
    }

    pub fn master_identity(&self) -> Option<crate::utils::ffi::MasterIdentity> {
        let mut identity = crate::utils::ffi::MasterIdentity::default();
        unsafe {
            if ffi::GetMasterIdentity(self.master_index, &mut identity) != 0 {
                Some(identity)
            } else {
                None
            }
        }
    }

    pub fn master_diag_data(&self) -> Option<crate::utils::ffi::MasterDiagData> {
        let mut diag = crate::utils::ffi::MasterDiagData::default();
        unsafe {
            if ffi::GetMasterDiagData(self.master_index, &mut diag) != 0 {
                Some(diag)
            } else {
                None
            }
        }
    }

    pub fn read_object(&self, index: u16, subindex: u8) -> Option<Vec<u8>> {
        match index {
            OBJ_DEVICE_TYPE => {
                if subindex == 0 { Some(self.read_device_type()) } else { None }
            }
            OBJ_DEVICE_NAME => {
                if subindex == 0 { Some(self.read_device_name()) } else { None }
            }
            OBJ_HW_VERSION => {
                if subindex == 0 { Some(self.read_hw_version()) } else { None }
            }
            OBJ_SW_VERSION => {
                if subindex == 0 { Some(self.read_sw_version()) } else { None }
            }
            OBJ_IDENTITY => self.read_identity(subindex),

            0x8000..=0x8FFF => {
                self.read_slave_object(index, subindex)
            }

            0x9000..=0x9FFF => {
                self.read_slave_object(index, subindex)
            }

            0xA000..=0xAFFF => {
                self.read_slave_object(index, subindex)
            }

            OBJ_DETECT_MODULES => {
                self.read_command_object(index, subindex)
            }

            OBJ_MASTER_DIAG_DATA => {
                self.read_command_object(index, subindex)
            }

            OBJ_DIAG_INTERFACE_CTRL => {
                self.read_command_object(index, subindex)
            }
            _ => None,
        }
    }

    fn read_slave_object(&self, _index: u16, _subindex: u8) -> Option<Vec<u8>> {

        None
    }

    fn read_command_object(&self, _index: u16, _subindex: u8) -> Option<Vec<u8>> {

        None
    }

    pub fn get_supported_object_indices(&self) -> Vec<u16> {
        let mut indices = vec![
            OBJ_DEVICE_TYPE,
            OBJ_DEVICE_NAME,
            OBJ_HW_VERSION,
            OBJ_SW_VERSION,
            OBJ_IDENTITY,
            OBJ_DETECT_MODULES,
            OBJ_MASTER_DIAG_DATA,
            OBJ_DIAG_INTERFACE_CTRL,
        ];

        for slave_idx in 0u16..64 {
            let config_index = 0x8000 + slave_idx * 0x10;
            if self.read_slave_object(config_index, 0).is_some() {
                indices.push(config_index);

                indices.push(0x9000 + slave_idx * 0x10);
                indices.push(0xA000 + slave_idx * 0x10);
            } else {
                break;
            }
        }

        indices
    }

    pub fn object_name(&self, index: u16) -> &str {
        match index {
            0x1000 => "Device Type",
            0x1008 => "Manufacturer Device Name",
            0x1009 => "Manufacturer Hardware Version",
            0x100A => "Manufacturer Software Version",
            0x1018 => "Identity Object",
            0xF002 => "Detect Modules Command",
            0xF120 => "Master Diag Data",
            0xF200 => "Diag Interface Control",
            _ if index >= 0x8000 && index <= 0x8FFF => "Configuration Data",
            _ if index >= 0x9000 && index <= 0x9FFF => "Information Data",
            _ if index >= 0xA000 && index <= 0xAFFF => "Diagnosis Data",
            _ => "Unknown",
        }
    }

    pub fn subindex_count(&self, index: u16) -> u8 {
        match index {
            0x1000 | 0x1008 | 0x1009 | 0x100A => 0,
            0x1018 => 4,
            i if i >= 0x8000 && i <= 0x8FFF => 40,
            i if i >= 0x9000 && i <= 0x9FFF => 32,
            i if i >= 0xA000 && i <= 0xAFFF => 19,
            0xF002 => 3,
            0xF120 | 0xF200 => 16,
            _ => 0,
        }
    }
}

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

    pub al_status_extended: u16,

    pub last_al_status_code: u16,

    pub last_coe_soe_protocol_error: u32,

    pub cyclic_wc_error_counter: u32,

    pub new_diag_messages_available: bool,

    pub disable_automatic_link_control: bool,
}

pub struct Etg1510DiagInterface {
    master_index: u16,

    od: MasterOdEtg1510,
}

impl Etg1510DiagInterface {

    pub fn new(master_index: u16) -> Self {
        Self {
            master_index,
            od: MasterOdEtg1510::new(master_index, 0),
        }
    }

    pub fn write_master_object(&self, index: u16, subindex: u8, data: &[u8]) -> bool {

        match index {
            OBJ_DEVICE_TYPE | OBJ_DEVICE_NAME | OBJ_HW_VERSION | OBJ_SW_VERSION
            | OBJ_IDENTITY => false,

            OBJ_DETECT_MODULES | OBJ_DIAG_INTERFACE_CTRL
            | 0xA000..=0xAFFF => {
                let ok = unsafe {
                    ffi::SDOwrite_raw(
                        self.master_index, 0, index, subindex, 0,
                        data.as_ptr(), data.len() as core::ffi::c_int,
                    )
                };
                ok != 0
            }
            _ => false,
        }
    }

    pub fn read_master_object(&self, index: u16, subindex: u8) -> Option<Vec<u8>> {

        if let Some(v) = self.od.read_object(index, subindex) {
            return Some(v);
        }

        let mut size: core::ffi::c_int = 0;
        let ptr = unsafe {
            ffi::SDOread(self.master_index, 0, index, subindex, 0, &mut size)
        };
        if !ptr.is_null() && size > 0 {
            let data = unsafe {
                std::slice::from_raw_parts(ptr, size as usize).to_vec()
            };
            unsafe { ffi::FreeMemory(ptr as *mut _) };
            return Some(data);
        }
        if !ptr.is_null() {
            unsafe { ffi::FreeMemory(ptr as *mut _) };
        }
        None
    }

    pub fn al_status_extended(&self, slave_index: u16) -> u16 {
        self.read_master_object(0xA000 + slave_index, 1)
            .and_then(|d| if d.len() >= 2 { Some(u16::from_le_bytes([d[0], d[1]])) } else { None })
            .unwrap_or(0)
    }

    pub fn last_al_status_code(&self, slave_index: u16) -> u16 {
        self.read_master_object(0xA000 + slave_index, 2)
            .and_then(|d| if d.len() >= 2 { Some(u16::from_le_bytes([d[0], d[1]])) } else { None })
            .unwrap_or(0)
    }

    pub fn last_coe_soe_protocol_error(&self, slave_index: u16) -> u32 {
        self.read_master_object(0xA000 + slave_index, 4)
            .and_then(|d| if d.len() >= 4 { Some(u32::from_le_bytes([d[0], d[1], d[2], d[3]])) } else { None })
            .unwrap_or(0)
    }

    pub fn cyclic_wc_error_counter(&self, slave_index: u16) -> u32 {
        self.read_master_object(0xA000 + slave_index, 3)
            .and_then(|d| if d.len() >= 4 { Some(u32::from_le_bytes([d[0], d[1], d[2], d[3]])) } else { None })
            .unwrap_or(0)
    }

    pub fn new_diag_messages_available(&self, slave_index: u16) -> bool {
        self.read_master_object(0xA000 + slave_index, 6)
            .map(|d| d.first().copied().unwrap_or(0) != 0)
            .unwrap_or(false)
    }

    pub fn disable_automatic_link_control(&self, slave_index: u16) -> bool {
        self.read_master_object(0xA000 + slave_index, 5)
            .map(|d| d.first().copied().unwrap_or(0) != 0)
            .unwrap_or(false)
    }
}
