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

    NotUsed = 0,

    Mii = 1,

    EBus = 2,

    HotConnect = 3,
}

impl From<u8> for EsiPhysicsPortType {
    fn from(val: u8) -> Self {
        match val {
            0 => Self::NotUsed,
            1 => Self::Mii,
            2 => Self::EBus,
            3 => Self::HotConnect,
            _ => Self::NotUsed,
        }
    }
}

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

    None,

    Eq,

    EqOrG,

    LwEq,

    HwEq,

    LwEqOrG,

    HwEqOrG,
}

pub fn parse_uint_hex_or_dec(s: &str) -> Option<u32> {
    let s = s.trim();
    if s.is_empty() {
        return None;
    }

    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
        return u32::from_str_radix(hex, 16).ok();
    }
    if let Some(hex) = s.strip_prefix("#x").or_else(|| s.strip_prefix("#X")) {
        return u32::from_str_radix(hex, 16).ok();
    }
    if let Some(hex) = s.strip_prefix('#') {
        return u32::from_str_radix(hex, 16).ok();
    }

    if let Ok(val) = s.parse::<u32>() {
        return Some(val);
    }

    if s.chars().all(|c| c.is_ascii_hexdigit()) && s.chars().any(|c| c.is_ascii_alphabetic()) {
        return u32::from_str_radix(s, 16).ok();
    }

    None
}

pub fn parse_u16_hex_or_dec(s: &str) -> Option<u16> {
    parse_uint_hex_or_dec(s).map(|v| v as u16)
}

#[derive(Debug, Clone, Default)]
pub struct EsiBootstrapInfo {
    pub recv_mailbox_offset: u16,
    pub recv_mailbox_size: u16,
    pub send_mailbox_offset: u16,
    pub send_mailbox_size: u16,
}

#[derive(Debug, Clone, Default)]
pub struct EsiCoEDetails {
    pub complete_access: bool,
    pub diag_history: bool,
    pub pdo_assign: bool,
    pub pdo_config: bool,
    pub sdo_info: bool,
    pub segmented_sdo: bool,
}

#[derive(Debug, Clone, Default)]
pub struct EsiDcOpMode {
    pub assign_activate: u16,
    pub cycle_time_sync0: u32,
    pub cycle_time_sync0_factor: i32,
    pub cycle_time_sync1: u32,
    pub cycle_time_sync1_factor: i32,
    pub description: String,
    pub name: String,
    pub shift_time_sync0: i32,
    pub shift_time_sync1: i32,
}

impl EsiDcOpMode {

    pub fn is_dc_sync(&self) -> bool { self.assign_activate != 0 }

    pub fn sync0_enabled(&self) -> bool { (self.assign_activate & 0x0100) != 0 }

    pub fn sync1_enabled(&self) -> bool { (self.assign_activate & 0x0400) != 0 }
}

#[derive(Debug, Clone, Default)]
pub struct EsiDcConfiguration {
    pub op_modes: Vec<EsiDcOpMode>,
    pub unknown_64bit: bool,
}

impl EsiDcConfiguration {

    pub fn dc_sync_op_mode(&self) -> Option<&EsiDcOpMode> {
        self.op_modes.iter().find(|m| m.assign_activate != 0)
    }
}

#[derive(Debug, Clone, Default)]
pub struct EsiEepromConfiguration {
    pub bootstrap: Option<EsiBootstrapInfo>,
    pub byte_size: i32,
    pub config_data: String,
}

#[derive(Debug, Clone, Default)]
pub struct EsiElectricalInfo {
    pub ebus_current: i32,
}

impl EsiElectricalInfo {
    pub fn is_power_supply(&self) -> bool { self.ebus_current < 0 }
    pub fn power_consumption(&self) -> i32 { if self.is_power_supply() { 0 } else { self.ebus_current } }
}

#[derive(Debug, Clone, Default)]
pub struct EsiIdentification {
    pub identification_ado: u16,
    pub identification_value: u16,
}

#[derive(Debug, Clone)]
pub struct EsiMailboxTimeout {
    pub request_timeout: i32,
    pub response_timeout: i32,
}

impl Default for EsiMailboxTimeout {
    fn default() -> Self { Self { request_timeout: 100, response_timeout: 2000 } }
}

#[derive(Debug, Clone, Default)]
pub struct EsiPdoEntry {
    pub bit_length: u8,
    pub data_type: String,
    pub index: u16,
    pub name: String,
    pub sub_index: u8,
}

#[derive(Debug, Clone, Default)]
pub struct EsiPdoInfo {
    pub entries: Vec<EsiPdoEntry>,
    pub exclude_indices: Vec<u16>,
    pub index: u16,
    pub is_fixed: bool,
    pub is_mandatory: bool,
    pub name: String,
    pub sync_manager: u8,
}

#[derive(Debug, Clone, Default)]
pub struct EsiPdoConfiguration {
    pub rx_pdos: Vec<EsiPdoInfo>,
    pub tx_pdos: Vec<EsiPdoInfo>,
}

#[derive(Debug, Clone)]
pub struct EsiPhysicsPort {
    pub code: char,
    pub description: String,
    pub index: usize,
    pub port_type: EsiPhysicsPortType,
}

#[derive(Debug, Clone, Default)]
pub struct EsiPhysicsInfo {
    pub ports: Vec<EsiPhysicsPort>,
    pub raw_value: String,
}

impl EsiPhysicsInfo {

    pub fn parse(physics: &str) -> Self {
        let mut info = Self { raw_value: physics.to_string(), ports: Vec::new() };
        for (i, ch) in physics.chars().enumerate() {
            let (pt, desc) = match ch.to_ascii_uppercase() {
                'Y' => (EsiPhysicsPortType::Mii, "MII (100Mbit)"),
                'K' => (EsiPhysicsPortType::EBus, "E-Bus"),
                'H' => (EsiPhysicsPortType::HotConnect, "Hot Connect"),
                _ => (EsiPhysicsPortType::NotUsed, "Not Used"),
            };
            info.ports.push(EsiPhysicsPort { code: ch, description: desc.to_string(), index: i, port_type: pt });
        }
        info
    }
}

#[derive(Debug, Clone, Default)]
pub struct EsiPortInfo {
    pub index: i32,
    pub label: String,
    pub port_type: String,
}

#[derive(Debug, Clone, Default)]
pub struct EsiStartupSdo {
    pub data_type: String,
    pub index: u16,
    pub name: String,
    pub sub_index: u8,
    pub value: String,
}

#[derive(Debug, Clone, Default)]
pub struct EsiStatistics {
    pub total_devices: i32,
    pub total_files: i32,
    pub vendors: Vec<String>,
}

#[derive(Debug, Clone, Default)]
pub struct EsiSyncManagerInfo {
    pub control_byte: u8,
    pub default_size: u16,
    pub enable: bool,
    pub index: i32,
    pub max_size: u16,
    pub min_size: u16,
    pub name: String,
    pub start_address: u16,
}

impl EsiSyncManagerInfo {

    pub fn sdo_index(&self) -> u16 { 0x1C10 + self.index as u16 }
}

#[derive(Debug, Clone, Default)]
pub struct EsiDeviceInfo {
    pub coe_details: Option<EsiCoEDetails>,
    pub dc_configuration: Option<EsiDcConfiguration>,
    pub device_class: String,
    pub eeprom_configuration: Option<EsiEepromConfiguration>,
    pub electrical: Option<EsiElectricalInfo>,
    pub file_name: String,
    pub group_type: String,
    pub identification: Option<EsiIdentification>,
    pub mailbox_timeout: Option<EsiMailboxTimeout>,
    pub physics: String,
    pub physics_info: Option<EsiPhysicsInfo>,
    pub ports: Vec<EsiPortInfo>,
    pub product_id: u32,
    pub product_name: String,
    pub product_text: String,
    pub product_url: String,
    pub profile_number: String,
    pub revision_check_strategy: EsiRevisionCheckStrategy,
    pub revision_id: u32,
    pub supports_coe: bool,
    pub supports_eoe: bool,
    pub supports_foe: bool,
    pub supports_soe: bool,
    pub supports_voe: bool,
    pub supports_frame_repeat: bool,
    pub vendor_comment: String,
    pub vendor_id: u32,
    pub vendor_name: String,
}

impl EsiDeviceInfo {

    pub fn dc_assign_activate(&self) -> u16 {
        self.dc_configuration.as_ref()
            .and_then(|dc| dc.dc_sync_op_mode())
            .map(|m| m.assign_activate)
            .unwrap_or(0)
    }

    pub fn supports_dc(&self) -> bool {
        self.dc_configuration.as_ref()
            .map(|dc| dc.op_modes.iter().any(|m| m.is_dc_sync()))
            .unwrap_or(false)
    }

    pub fn protocol_summary(&self) -> String {
        let mut p = Vec::new();
        if self.supports_coe { p.push("CoE"); }
        if self.supports_foe { p.push("FoE"); }
        if self.supports_eoe { p.push("EoE"); }
        if self.supports_soe { p.push("SoE"); }
        if self.supports_voe { p.push("VoE"); }
        if p.is_empty() { "None".to_string() } else { p.join(", ") }
    }
}

impl Default for EsiRevisionCheckStrategy {
    fn default() -> Self { Self::None }
}

pub fn calculate_eeprom_crc(data: &[u8], length: usize) -> u8 {
    let mut crc: u8 = 0xFF;
    for i in 0..length.min(data.len()) {
        crc ^= data[i];
        for _ in 0..8 {
            if crc & 0x80 != 0 {
                crc = (crc << 1) ^ 0x07;
            } else {
                crc <<= 1;
            }
        }
    }
    crc
}

pub fn validate_eeprom_crc(eeprom_data: &[u8]) -> bool {
    if eeprom_data.len() < 15 { return false; }
    calculate_eeprom_crc(eeprom_data, 14) == eeprom_data[14]
}

pub fn get_config_data_bytes(config_data_hex: &str) -> Vec<u8> {
    let hex = config_data_hex.trim();
    if hex.is_empty() {
        return Vec::new();
    }
    let mut result = Vec::with_capacity(hex.len() / 2);
    for i in (0..hex.len()).step_by(2) {
        if i + 1 < hex.len() {
            if let Ok(b) = u8::from_str_radix(&hex[i..i + 2], 16) {
                result.push(b);
            }
        }
    }
    result
}

pub fn match_revision(actual: u32, expected: u32, strategy: EsiRevisionCheckStrategy) -> bool {
    if expected == 0 { return true; }
    match strategy {
        EsiRevisionCheckStrategy::None => true,
        EsiRevisionCheckStrategy::Eq => actual == expected,
        EsiRevisionCheckStrategy::EqOrG => actual >= expected,
        EsiRevisionCheckStrategy::LwEq => (actual & 0xFFFF) == (expected & 0xFFFF),
        EsiRevisionCheckStrategy::HwEq => (actual >> 16) == (expected >> 16),
        EsiRevisionCheckStrategy::LwEqOrG => (actual & 0xFFFF) >= (expected & 0xFFFF),
        EsiRevisionCheckStrategy::HwEqOrG => (actual >> 16) >= (expected >> 16),
    }
}

use std::collections::HashSet;
use std::ffi::CString;
use std::sync::Mutex;

static ESI_MANAGER_FILES: Mutex<Option<HashSet<String>>> = Mutex::new(None);

fn ensure_files_init(set: &mut Option<HashSet<String>>) -> &mut HashSet<String> {
    if set.is_none() {
        *set = Some(HashSet::new());
    }
    set.as_mut().unwrap()
}

pub struct EsiManager;

impl EsiManager {

    pub fn default_path() -> String {
        if let Ok(cwd) = std::env::current_dir() {
            let dir = cwd.join("ESI");
            if !dir.exists() {
                let _ = std::fs::create_dir_all(&dir);
            }
            return dir.to_string_lossy().into_owned();
        }
        "./ESI".to_string()
    }

    pub fn add_file(file_path: &str) -> i32 {
        if file_path.is_empty() {
            return 0;
        }
        if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
            ensure_files_init(&mut g).insert(file_path.to_string());
        }
        let cpath = match CString::new(file_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe { crate::utils::ffi::EcEsi_LoadFile(cpath.as_ptr()) }
    }

    pub fn load_path(dir_path: &str) -> i32 {
        if dir_path.is_empty() {
            return 0;
        }
        if let Ok(entries) = std::fs::read_dir(dir_path) {
            if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
                let set = ensure_files_init(&mut g);
                for e in entries.flatten() {
                    let path = e.path();
                    let name_lc = path
                        .file_name()
                        .and_then(|n| n.to_str())
                        .unwrap_or("")
                        .to_lowercase();
                    if name_lc.ends_with(".xml") || name_lc.ends_with(".esi") {
                        set.insert(path.to_string_lossy().into_owned());
                    }
                }
            }
        }
        let cpath = match CString::new(dir_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe { crate::utils::ffi::EcEsi_LoadDirectory(cpath.as_ptr()) }
    }

    pub fn match_revision(actual: u32, expected: u32, strategy: EsiRevisionCheckStrategy) -> bool {
        match_revision(actual, expected, strategy)
    }

    pub fn bind_to_slave(master_index: u16, slave_index: u16, file_path: &str) -> i32 {
        let cpath = match CString::new(file_path) {
            Ok(c) => c,
            Err(_) => return 0,
        };
        unsafe {
            crate::utils::ffi::EcEsi_BindToSlave(master_index, slave_index, cpath.as_ptr())
        }
    }

    pub fn apply_all_slaves(master_index: u16) -> i32 {
        unsafe { crate::utils::ffi::EcEsi_ApplyAllSlaves(master_index) }
    }

    pub fn get_loaded_count() -> i32 {
        let dll_n = unsafe { crate::utils::ffi::EcEsi_GetLoadedCount() };
        if dll_n > 0 {
            return dll_n;
        }
        if let Ok(g) = ESI_MANAGER_FILES.lock() {
            return g.as_ref().map(|s| s.len() as i32).unwrap_or(0);
        }
        0
    }

    pub fn get_files() -> Vec<String> {
        if let Ok(g) = ESI_MANAGER_FILES.lock() {
            if let Some(set) = g.as_ref() {
                return set.iter().cloned().collect();
            }
        }
        Vec::new()
    }

    pub fn clear() {
        if let Ok(mut g) = ESI_MANAGER_FILES.lock() {
            if let Some(set) = g.as_mut() {
                set.clear();
            }
        }
        unsafe { crate::utils::ffi::EcEsi_Clear() }
    }
}

pub fn load_eni(path: &str) -> Result<crate::utils::xml::MasterXmlConfiguration, String> {
    crate::utils::xml::load_xml_configuration(path)
}

pub fn save_eni(path: &str,
                config: &crate::utils::xml::MasterXmlConfiguration) -> Result<(), String> {
    use quick_xml::writer::Writer;
    use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
    use std::io::Cursor;

    if path.trim().is_empty() {
        return Err("save_eni: path 为空".to_string());
    }

    if let Some(parent) = std::path::Path::new(path).parent() {
        if !parent.as_os_str().is_empty() && !parent.exists() {
            std::fs::create_dir_all(parent)
                .map_err(|e| format!("save_eni: 创建父目录失败: {}", e))?;
        }
    }

    let mut buf: Vec<u8> = Vec::new();
    {
        let cursor = Cursor::new(&mut buf);
        let mut w = Writer::new_with_indent(cursor, b' ', 2);

        w.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
            .map_err(|e| format!("save_eni: 写 XML 声明失败: {}", e))?;

        fn write_text<W: std::io::Write>(
            w: &mut Writer<W>, tag: &str, text: &str,
        ) -> Result<(), String> {
            w.write_event(Event::Start(BytesStart::new(tag)))
                .map_err(|e| format!("save_eni: 写 <{}> 失败: {}", tag, e))?;
            w.write_event(Event::Text(BytesText::new(text)))
                .map_err(|e| format!("save_eni: 写 <{}> 文本失败: {}", tag, e))?;
            w.write_event(Event::End(BytesEnd::new(tag)))
                .map_err(|e| format!("save_eni: 写 </{}> 失败: {}", tag, e))?;
            Ok(())
        }

        let mut root = BytesStart::new("EtherCATConfig");
        root.push_attribute(("Version", "1.0"));
        root.push_attribute(("xmlns:deni", "http://www.darra-tech.com/schemas/eni/v3.0"));
        w.write_event(Event::Start(root))
            .map_err(|e| format!("save_eni: 写 <EtherCATConfig> 失败: {}", e))?;

        w.write_event(Event::Start(BytesStart::new("Config")))
            .map_err(|e| format!("save_eni: 写 <Config> 失败: {}", e))?;

        w.write_event(Event::Start(BytesStart::new("Master")))
            .map_err(|e| format!("save_eni: 写 <Master> 失败: {}", e))?;

        w.write_event(Event::Start(BytesStart::new("Info")))
            .map_err(|e| format!("save_eni: 写 <Info> 失败: {}", e))?;
        write_text(&mut w, "Name", "DarraEtherCAT Master")?;
        write_text(&mut w, "Source", "DarraEtherCAT Rust SDK SaveENI")?;
        write_text(&mut w, "VendorId", "#x00001164")?;
        write_text(&mut w, "ProductCode", "#x00000001")?;
        w.write_event(Event::End(BytesEnd::new("Info")))
            .map_err(|e| format!("save_eni: 写 </Info> 失败: {}", e))?;

        write_text(&mut w, "CycleTime", &config.cycle_time_us.to_string())?;
        write_text(&mut w, "DcCycleTime", &config.dc_cycle_ns.to_string())?;

        let mut deni_master = BytesStart::new("deni:MasterConfig");
        deni_master.push_attribute(("xmlns:deni", "http://www.darra-tech.com/schemas/eni/v3.0"));
        w.write_event(Event::Start(deni_master))
            .map_err(|e| format!("save_eni: 写 <deni:MasterConfig> 失败: {}", e))?;
        {
            let mut ct = BytesStart::new("deni:CycleTime");
            ct.push_attribute(("unit", "us"));
            w.write_event(Event::Start(ct))
                .map_err(|e| format!("save_eni: 写 deni:CycleTime 失败: {}", e))?;
            w.write_event(Event::Text(BytesText::new(&config.cycle_time_us.to_string())))
                .map_err(|e| format!("save_eni: 写 deni:CycleTime 文本失败: {}", e))?;
            w.write_event(Event::End(BytesEnd::new("deni:CycleTime")))
                .map_err(|e| format!("save_eni: 写 </deni:CycleTime> 失败: {}", e))?;
            let mut dct = BytesStart::new("deni:DcCycleTime");
            dct.push_attribute(("unit", "ns"));
            w.write_event(Event::Start(dct))
                .map_err(|e| format!("save_eni: 写 deni:DcCycleTime 失败: {}", e))?;
            w.write_event(Event::Text(BytesText::new(&config.dc_cycle_ns.to_string())))
                .map_err(|e| format!("save_eni: 写 deni:DcCycleTime 文本失败: {}", e))?;
            w.write_event(Event::End(BytesEnd::new("deni:DcCycleTime")))
                .map_err(|e| format!("save_eni: 写 </deni:DcCycleTime> 失败: {}", e))?;
            write_text(&mut w, "deni:ExpectedSlaveCount",
                       &config.expected_slave_count.to_string())?;
        }
        w.write_event(Event::End(BytesEnd::new("deni:MasterConfig")))
            .map_err(|e| format!("save_eni: 写 </deni:MasterConfig> 失败: {}", e))?;
        w.write_event(Event::End(BytesEnd::new("Master")))
            .map_err(|e| format!("save_eni: 写 </Master> 失败: {}", e))?;

        let mut auto_inc_counter: i32 = 0;
        for sl in &config.slaves {
            w.write_event(Event::Start(BytesStart::new("Slave")))
                .map_err(|e| format!("save_eni: 写 <Slave> 失败: {}", e))?;

            w.write_event(Event::Start(BytesStart::new("Info")))
                .map_err(|e| format!("save_eni: 写 <Slave>/<Info> 失败: {}", e))?;
            write_text(&mut w, "Name", &sl.name)?;
            write_text(&mut w, "PhysAddr", &sl.config_addr.to_string())?;

            write_text(&mut w, "AutoIncAddr", &(-auto_inc_counter).to_string())?;
            auto_inc_counter += 1;
            write_text(&mut w, "AliasAddr", &sl.alias_addr.to_string())?;
            write_text(&mut w, "VendorId", &format!("#x{:08X}", sl.vendor_id))?;
            write_text(&mut w, "ProductCode", &format!("#x{:08X}", sl.product_code))?;
            write_text(&mut w, "RevisionNo", &format!("#x{:08X}", sl.revision))?;
            w.write_event(Event::End(BytesEnd::new("Info")))
                .map_err(|e| format!("save_eni: 写 </Info> 失败: {}", e))?;

            if sl.input_size > 0 || sl.output_size > 0 {
                w.write_event(Event::Start(BytesStart::new("ProcessData")))
                    .map_err(|e| format!("save_eni: 写 <ProcessData> 失败: {}", e))?;
                if sl.output_size > 0 {
                    let mut sm = BytesStart::new("Sm");
                    sm.push_attribute(("No", "2"));
                    w.write_event(Event::Start(sm))
                        .map_err(|e| format!("save_eni: 写 <Sm No=2> 失败: {}", e))?;
                    write_text(&mut w, "DefaultSize", &sl.output_size.to_string())?;
                    write_text(&mut w, "StartAddress", "0")?;
                    w.write_event(Event::End(BytesEnd::new("Sm")))
                        .map_err(|e| format!("save_eni: 写 </Sm> 失败: {}", e))?;
                }
                if sl.input_size > 0 {
                    let mut sm = BytesStart::new("Sm");
                    sm.push_attribute(("No", "3"));
                    w.write_event(Event::Start(sm))
                        .map_err(|e| format!("save_eni: 写 <Sm No=3> 失败: {}", e))?;
                    write_text(&mut w, "DefaultSize", &sl.input_size.to_string())?;
                    write_text(&mut w, "StartAddress", "0")?;
                    w.write_event(Event::End(BytesEnd::new("Sm")))
                        .map_err(|e| format!("save_eni: 写 </Sm> 失败: {}", e))?;
                }
                w.write_event(Event::End(BytesEnd::new("ProcessData")))
                    .map_err(|e| format!("save_eni: 写 </ProcessData> 失败: {}", e))?;
            }

            let dc_match = config.slave_dc_configs.iter()
                .find(|c| c.slave_index as i32 == sl.index);
            if let Some(dc) = dc_match {
                if dc.sync0_cycle_ns != 0 || dc.sync1_cycle_ns != 0 {
                    w.write_event(Event::Start(BytesStart::new("Dc")))
                        .map_err(|e| format!("save_eni: 写 <Dc> 失败: {}", e))?;

                    let aa: u16 = if dc.sync1_cycle_ns != 0 { 0x0300 } else { 0x0100 };
                    write_text(&mut w, "AssignActivate", &format!("#x{:04X}", aa))?;
                    write_text(&mut w, "CycleTimeSync0", &dc.sync0_cycle_ns.to_string())?;
                    write_text(&mut w, "CycleTimeSync1", &dc.sync1_cycle_ns.to_string())?;
                    write_text(&mut w, "ShiftTimeSync0", &dc.shift_ns.to_string())?;
                    w.write_event(Event::End(BytesEnd::new("Dc")))
                        .map_err(|e| format!("save_eni: 写 </Dc> 失败: {}", e))?;
                }
            }

            w.write_event(Event::End(BytesEnd::new("Slave")))
                .map_err(|e| format!("save_eni: 写 </Slave> 失败: {}", e))?;
        }

        w.write_event(Event::End(BytesEnd::new("Config")))
            .map_err(|e| format!("save_eni: 写 </Config> 失败: {}", e))?;
        w.write_event(Event::End(BytesEnd::new("EtherCATConfig")))
            .map_err(|e| format!("save_eni: 写 </EtherCATConfig> 失败: {}", e))?;
    }

    std::fs::write(path, &buf)
        .map_err(|e| format!("save_eni: 写文件 {} 失败: {}", path, e))?;
    Ok(())
}
