
use std::ffi::CStr;
use std::os::raw::c_char;

use crate::utils::ffi;

fn cstr_to_string(p: *const c_char, fallback: &str) -> String {
    if p.is_null() {
        return fallback.to_string();
    }
    unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlSeverity {
    None = 0,
    Info = 1,
    Warning = 2,
    Error = 3,
    Fatal = 4,
}

impl AlSeverity {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::None,
            1 => Self::Info,
            2 => Self::Warning,
            3 => Self::Error,
            _ => Self::Fatal,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdoAbortCategory {
    Transfer = 0,
    Access = 1,
    Value = 2,
    Hardware = 3,
    General = 4,
}

impl SdoAbortCategory {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::Transfer,
            1 => Self::Access,
            2 => Self::Value,
            3 => Self::Hardware,
            _ => Self::General,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmcyClass {
    NoError = 0x0000,
    Generic = 0x1000,
    Current = 0x2000,
    Voltage = 0x3000,
    Temperature = 0x4000,
    Hardware = 0x5000,
    Software = 0x6000,
    AdditionalModules = 0x7000,
    Monitoring = 0x8000,
    External = 0x9000,
    AdditionalFunc = 0xF000,
    DeviceSpecific = 0xFF00,
}

impl EmcyClass {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0x0000 => Self::NoError,
            0x1000 => Self::Generic,
            0x2000 => Self::Current,
            0x3000 => Self::Voltage,
            0x4000 => Self::Temperature,
            0x5000 => Self::Hardware,
            0x6000 => Self::Software,
            0x7000 => Self::AdditionalModules,
            0x8000 => Self::Monitoring,
            0x9000 => Self::External,
            0xF000 => Self::AdditionalFunc,
            _ => Self::DeviceSpecific,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTransitionType {
    Invalid = 0,
    Up = 1,
    Down = 2,
    Same = 3,
    Bootstrap = 4,
    JumpUp = 5,
    JumpDown = 6,
}

impl EcTransitionType {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Up,
            2 => Self::Down,
            3 => Self::Same,
            4 => Self::Bootstrap,
            5 => Self::JumpUp,
            6 => Self::JumpDown,
            _ => Self::Invalid,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTopology {
    Unknown = 0,
    Line = 1,
    Pass = 2,
    Branch = 3,
    Cross = 4,
}

impl EcTopology {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Line,
            2 => Self::Pass,
            3 => Self::Branch,
            4 => Self::Cross,
            _ => Self::Unknown,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcDeviceType {
    Unknown = 0,
    Coupler = 1,
    Terminal = 2,
    Drive = 3,
    Sensor = 4,
    Gateway = 5,
    Safety = 6,
    Controller = 7,
}

impl EcDeviceType {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Coupler,
            2 => Self::Terminal,
            3 => Self::Drive,
            4 => Self::Sensor,
            5 => Self::Gateway,
            6 => Self::Safety,
            7 => Self::Controller,
            _ => Self::Unknown,
        }
    }
}

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HomingTrigger {
    NegativeLimit = 0,
    PositiveLimit = 1,
    HomeSwitch = 2,
    IndexPulse = 3,
    HardStop = 4,
    CurrentPos = 5,
    Vendor = 6,
}

impl HomingTrigger {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::NegativeLimit,
            1 => Self::PositiveLimit,
            2 => Self::HomeSwitch,
            3 => Self::IndexPulse,
            4 => Self::HardStop,
            5 => Self::CurrentPos,
            _ => Self::Vendor,
        }
    }
}

pub struct ProtocolCodes;

impl ProtocolCodes {

    pub fn al_status_description(code: u16) -> String {
        unsafe { cstr_to_string(ffi::AL_StatusCode_GetDescription(code), "未知错误") }
    }

    pub fn al_status_severity(code: u16) -> AlSeverity {
        unsafe { AlSeverity::from_i32(ffi::AL_StatusCode_GetSeverity(code)) }
    }

    pub fn al_status_recovery_hint(code: u16) -> String {
        unsafe { cstr_to_string(ffi::AL_StatusCode_GetRecoveryHint(code), "请联系厂商") }
    }

    pub fn al_status_is_vendor_specific(code: u16) -> bool {
        unsafe { ffi::AL_StatusCode_IsVendorSpecific(code) != 0 }
    }

    pub fn sdo_abort_description(abort_code: u32) -> String {
        unsafe { cstr_to_string(ffi::SDOAbort_GetDescription(abort_code), "未知中止码") }
    }

    pub fn sdo_abort_category(abort_code: u32) -> SdoAbortCategory {
        unsafe { SdoAbortCategory::from_i32(ffi::SDOAbort_GetCategory(abort_code)) }
    }

    pub fn sdo_abort_is_retryable(abort_code: u32) -> bool {
        unsafe { ffi::SDOAbort_IsRetryable(abort_code) != 0 }
    }

    pub fn sdo_abort_hint(abort_code: u32) -> String {
        unsafe { cstr_to_string(ffi::SDOAbort_GetHint(abort_code), "请检查从站配置") }
    }

    pub fn emcy_description(error_code: u16) -> String {
        unsafe { cstr_to_string(ffi::EmcyCode_GetDescription(error_code), "未知紧急错误") }
    }

    pub fn emcy_class(error_code: u16) -> EmcyClass {
        unsafe { EmcyClass::from_i32(ffi::EmcyCode_GetClass(error_code)) }
    }

    pub fn emcy_class_name(class_code: EmcyClass) -> String {
        unsafe { cstr_to_string(ffi::EmcyCode_GetClassName(class_code as i32), "未知大类") }
    }

    pub fn emcy_format_error_register(error_register: u8) -> String {
        let mut buf = vec![0u8; 128];
        unsafe {
            ffi::EmcyCode_FormatErrorRegister(error_register, buf.as_mut_ptr(), buf.len() as i32);
        }

        let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
        String::from_utf8_lossy(&buf[..len]).into_owned()
    }

    pub fn emcy_is_recovery(error_code: u16) -> bool {
        unsafe { ffi::EmcyCode_IsRecovery(error_code) != 0 }
    }

    pub fn ec_state_is_valid_transition(from: u16, to: u16) -> bool {
        unsafe { ffi::EcState_IsValidTransition(from, to) != 0 }
    }

    pub fn ec_state_transition_type(from: u16, to: u16) -> EcTransitionType {
        unsafe { EcTransitionType::from_i32(ffi::EcState_GetTransitionType(from, to)) }
    }

    pub fn ec_state_transition_path(from: u16, to: u16) -> Vec<u16> {
        let mut buf = [0u16; 8];
        let n =
            unsafe { ffi::EcState_GetTransitionPath(from, to, buf.as_mut_ptr(), buf.len() as i32) };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    pub fn ec_state_is_bootstrap_required(state: u16) -> bool {
        unsafe { ffi::EcState_IsBootstrapRequired(state) != 0 }
    }

    pub fn ec_state_name(state: u16) -> String {
        unsafe { cstr_to_string(ffi::EcState_GetName(state), "未知状态") }
    }

    pub fn ec_state_name_en(state: u16) -> String {
        unsafe { cstr_to_string(ffi::EcState_GetNameEn(state), "Unknown") }
    }

    pub fn ec_state_has_error_ack(state: u16) -> bool {
        unsafe { ffi::EcState_HasErrorAck(state) != 0 }
    }

    pub fn ec_state_strip_error_ack(state: u16) -> u16 {
        unsafe { ffi::EcState_StripErrorAck(state) }
    }

    pub fn pdo_data_type_bit_size(dt: u16) -> i32 {
        unsafe { ffi::EcPdoCodec_DataTypeBitSize(dt as i32) }
    }

    pub fn pdo_data_type_name(dt: u16) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_DataTypeName(dt as i32), "Unknown") }
    }

    pub fn pdo_extract_u64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<u64> {
        let mut out: u64 = 0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractU64(
                src.as_ptr(),
                src.len() as i32,
                bit_offset,
                bit_length,
                &mut out,
            )
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    pub fn pdo_extract_i64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<i64> {
        let mut out: i64 = 0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractI64(
                src.as_ptr(),
                src.len() as i32,
                bit_offset,
                bit_length,
                &mut out,
            )
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    pub fn pdo_insert_u64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: u64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertU64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
    }

    pub fn pdo_insert_i64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: i64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertI64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
    }

    pub fn pdo_extract_real32(src: &[u8], bit_offset: i32) -> Option<f32> {
        let mut out: f32 = 0.0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractReal32(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    pub fn pdo_extract_real64(src: &[u8], bit_offset: i32) -> Option<f64> {
        let mut out: f64 = 0.0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractReal64(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    pub fn pdo_insert_real32(dst: &mut [u8], bit_offset: i32, value: f32) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertReal32(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
    }

    pub fn pdo_insert_real64(dst: &mut [u8], bit_offset: i32, value: f64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertReal64(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
    }

    pub fn pdo_count_active_ports(active_ports: u8) -> i32 {
        unsafe { ffi::EcPdoCodec_CountActivePorts(active_ports) }
    }

    pub fn pdo_get_topology(active_ports: u8) -> EcTopology {
        unsafe { EcTopology::from_i32(ffi::EcPdoCodec_GetTopology(active_ports)) }
    }

    pub fn pdo_topology_name(topo: EcTopology) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyName(topo as i32), "未知") }
    }

    pub fn pdo_topology_name_en(topo: EcTopology) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyNameEn(topo as i32), "Unknown") }
    }

    pub fn pdo_is_port_active(active_ports: u8, port: i32) -> bool {
        unsafe { ffi::EcPdoCodec_IsPortActive(active_ports, port) != 0 }
    }

    pub fn cia402_mode_to_supported_bit(mode: i8) -> i32 {
        unsafe { ffi::CiA402Modes_ModeToSupportedBit(mode) }
    }

    pub fn cia402_is_mode_supported(mask: u32, mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsModeSupportedInMask(mask, mode) != 0 }
    }

    pub fn cia402_expand_supported_mask(mask: u32) -> Vec<i8> {
        let mut buf = [0i8; 16];
        let n =
            unsafe { ffi::CiA402Modes_ExpandSupportedMask(mask, buf.as_mut_ptr(), buf.len() as i32) };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    pub fn cia402_mode_name(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeName(mode), "未知模式") }
    }

    pub fn cia402_mode_name_en(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeNameEn(mode), "Unknown") }
    }

    pub fn cia402_mode_description(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeDescription(mode), "无描述") }
    }

    pub fn cia402_is_cyclic_sync_mode(mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsCyclicSyncMode(mode) != 0 }
    }

    pub fn cia402_requires_dc(mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_RequiresDC(mode) != 0 }
    }

    pub fn cia402_is_standard_homing_method(method: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsStandardHomingMethod(method) != 0 }
    }

    pub fn cia402_homing_method_name(method: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetHomingMethodName(method), "厂商自定义") }
    }

    pub fn cia402_homing_trigger(method: i8) -> HomingTrigger {
        unsafe { HomingTrigger::from_i32(ffi::CiA402Modes_GetHomingTrigger(method)) }
    }

    pub fn cia402_homing_direction(method: i8) -> i32 {
        unsafe { ffi::CiA402Modes_GetHomingDirection(method) }
    }

    pub fn cia402_list_standard_homing_methods() -> Vec<i8> {
        let mut buf = [0i8; 64];
        let n = unsafe {
            ffi::CiA402Modes_ListStandardHomingMethods(buf.as_mut_ptr(), buf.len() as i32)
        };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    pub fn mailbox_type_name(mbx_type: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetTypeName(mbx_type), "未知") }
    }

    pub fn mailbox_type_name_en(mbx_type: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetTypeNameEn(mbx_type), "Unknown") }
    }

    pub fn mailbox_error_description(mbx_error_code: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetErrorDescription(mbx_error_code), "未知错误") }
    }

    pub fn mailbox_next_counter(current: u8) -> u8 {
        unsafe { ffi::EcMailbox_NextCounter(current) }
    }

    pub fn soe_error_description(soe_error: u16) -> String {
        unsafe { cstr_to_string(ffi::EcSoE_GetErrorDescription(soe_error), "未知 SoE 错误") }
    }

    pub fn sii_find_category(sii_data: &[u8], cat_type: u16) -> Option<(i32, i32)> {
        let mut out_size: i32 = 0;
        let off = unsafe {
            ffi::EcSii_FindCategory(sii_data.as_ptr(), sii_data.len() as i32, cat_type, &mut out_size)
        };
        if off < 0 {
            None
        } else {
            Some((off, out_size))
        }
    }

    pub fn sii_enumerate_categories(sii_data: &[u8]) -> Vec<u16> {
        let mut buf = vec![0u16; 32];
        let n = unsafe {
            ffi::EcSii_EnumerateCategories(
                sii_data.as_ptr(),
                sii_data.len() as i32,
                buf.as_mut_ptr(),
                buf.len() as i32,
            )
        };
        if n <= 0 {
            return Vec::new();
        }
        buf.truncate((n as usize).min(buf.len()));
        buf
    }

    pub fn sii_get_string_by_index(cat_data: &[u8], idx: i32) -> Option<String> {
        let mut buf = vec![0u8; 256];
        let n = unsafe {
            ffi::EcSii_GetStringByIndex(
                cat_data.as_ptr(),
                cat_data.len() as i32,
                idx,
                buf.as_mut_ptr(),
                buf.len() as i32,
            )
        };
        if n < 0 {
            return None;
        }
        let n = (n as usize).min(buf.len());
        Some(String::from_utf8_lossy(&buf[..n]).into_owned())
    }

    pub fn sii_get_string_count(cat_data: &[u8]) -> i32 {
        unsafe { ffi::EcSii_GetStringCount(cat_data.as_ptr(), cat_data.len() as i32) }
    }

    pub fn sii_get_vendor_id(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetVendorId(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    pub fn sii_get_product_code(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetProductCode(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    pub fn sii_get_revision(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetRevision(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    pub fn sii_get_serial_number(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetSerialNumber(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    pub fn sii_get_configured_alias(sii_data: &[u8]) -> u16 {
        unsafe { ffi::EcSii_GetConfiguredAlias(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    pub fn sii_coe_enabled(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeEnabled(coe_details) != 0 }
    }

    pub fn sii_coe_sdo_info(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeSdoInfo(coe_details) != 0 }
    }

    pub fn sii_coe_pdo_assign(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoePdoAssign(coe_details) != 0 }
    }

    pub fn sii_coe_pdo_config(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoePdoConfig(coe_details) != 0 }
    }

    pub fn sii_coe_upload_at_startup(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeUploadAtStartup(coe_details) != 0 }
    }

    pub fn sii_coe_complete_access(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeCompleteAccess(coe_details) != 0 }
    }

    pub fn sii_foe_enabled(foe_details: u8) -> bool {
        unsafe { ffi::EcSii_FoeEnabled(foe_details) != 0 }
    }

    pub fn sii_eoe_enabled(eoe_details: u8) -> bool {
        unsafe { ffi::EcSii_EoeEnabled(eoe_details) != 0 }
    }

    pub fn coupler_detect_device_type(vendor_id: u32, product_code: u32) -> EcDeviceType {
        unsafe {
            EcDeviceType::from_i32(ffi::EcCouplerId_DetectDeviceType(vendor_id, product_code))
        }
    }

    pub fn coupler_is_coupler(vendor_id: u32, product_code: u32) -> bool {
        unsafe { ffi::EcCouplerId_IsCoupler(vendor_id, product_code) != 0 }
    }

    pub fn coupler_is_terminal(vendor_id: u32, product_code: u32) -> bool {
        unsafe { ffi::EcCouplerId_IsTerminal(vendor_id, product_code) != 0 }
    }

    pub fn coupler_vendor_name(vendor_id: u32) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorName(vendor_id), "未知厂商") }
    }

    pub fn coupler_vendor_name_en(vendor_id: u32) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorNameEn(vendor_id), "Unknown") }
    }

    pub fn coupler_device_type_name(dtype: EcDeviceType) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetDeviceTypeName(dtype as i32), "未知") }
    }

    pub fn diag_topology_description(topo: u8) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_TopologyDescription(topo), "未知") }
    }

    pub fn diag_timing_mode(mode: u32) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_TimingMode(mode), "未知") }
    }

    pub fn diag_breakpoint_type(bp: u8) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_BreakpointType(bp), "未知故障") }
    }

    pub fn diag_format_breakpoint(slave_idx: u16, port: u8, bp: u8) -> String {
        let mut buf = vec![0u8; 128];
        unsafe {
            ffi::EcDiagStrings_FormatBreakpoint(
                slave_idx,
                port,
                bp,
                buf.as_mut_ptr(),
                buf.len() as i32,
            );
        }
        let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
        String::from_utf8_lossy(&buf[..len]).into_owned()
    }
}
