
use std::fmt;

#[derive(Debug, Clone)]
pub enum DarraError {

    AlreadyInitialized,

    NotInitialized,

    Timeout,

    NoSlaves,

    NetworkFailed(i32),

    StateChangeFailed(u8),

    SdoReadFailed { index: u16, subindex: u8, abort_code: Option<crate::data::types::SDOError> },

    SdoWriteFailed { index: u16, subindex: u8, abort_code: Option<crate::data::types::SDOError> },

    SoeFailed(u16),

    FoeFailed(String),

    EoeFailed,

    AoeFailed,

    VoeFailed,

    InvalidParameter(String),

    ConfigLoadFailed(i32),

    NullPointer,

    CiA402Failed(String),

    EmcyFailed,

    PdoFailed(String),

    TopologyFailed,

    RedundancyFailed,

    RegisterFailed,

    DiagnosticsFailed,

    WdkFailed(String),

    UdpFailed,

    Cancelled(String),

    Other(String),
}

impl fmt::Display for DarraError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::AlreadyInitialized => write!(f, "主站已初始化"),
            Self::NotInitialized => write!(f, "主站未初始化"),
            Self::Timeout => write!(f, "操作超时"),
            Self::NoSlaves => write!(f, "未发现从站"),
            Self::NetworkFailed(code) => write!(f, "网络配置失败 (返回码: {})", code),
            Self::StateChangeFailed(state) => write!(f, "状态切换失败 (目标: 0x{:02X})", state),
            Self::SdoReadFailed { index, subindex, abort_code } => {
                match abort_code {
                    Some(ac) => write!(f, "SDO 读取失败 (0x{:04X}:{:02X}, abort=0x{:08X})", index, subindex, *ac as u32),
                    None => write!(f, "SDO 读取失败 (0x{:04X}:{:02X})", index, subindex),
                }
            }
            Self::SdoWriteFailed { index, subindex, abort_code } => {
                match abort_code {
                    Some(ac) => write!(f, "SDO 写入失败 (0x{:04X}:{:02X}, abort=0x{:08X})", index, subindex, *ac as u32),
                    None => write!(f, "SDO 写入失败 (0x{:04X}:{:02X})", index, subindex),
                }
            }
            Self::SoeFailed(idn) => write!(f, "SoE 操作失败 (IDN: 0x{:04X})", idn),
            Self::FoeFailed(name) => write!(f, "FoE 操作失败 (文件: {})", name),
            Self::EoeFailed => write!(f, "EoE 操作失败"),
            Self::AoeFailed => write!(f, "AoE 操作失败"),
            Self::VoeFailed => write!(f, "VoE 操作失败"),
            Self::InvalidParameter(msg) => write!(f, "无效参数: {}", msg),
            Self::ConfigLoadFailed(code) => write!(f, "配置加载失败 (返回码: {})", code),
            Self::NullPointer => write!(f, "DLL 返回空指针"),
            Self::CiA402Failed(msg) => write!(f, "CiA 402 操作失败: {}", msg),
            Self::EmcyFailed => write!(f, "EMCY 操作失败"),
            Self::PdoFailed(msg) => write!(f, "PDO 操作失败: {}", msg),
            Self::TopologyFailed => write!(f, "拓扑操作失败"),
            Self::RedundancyFailed => write!(f, "冗余操作失败"),
            Self::RegisterFailed => write!(f, "寄存器操作失败"),
            Self::DiagnosticsFailed => write!(f, "诊断操作失败"),
            Self::WdkFailed(msg) => write!(f, "WDK 操作失败: {}", msg),
            Self::UdpFailed => write!(f, "UDP 操作失败"),
            Self::Cancelled(reason) => write!(f, "异步操作被取消: {}", reason),
            Self::Other(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for DarraError {}

pub type Result<T> = std::result::Result<T, DarraError>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum EcState {

    None = 0x00,

    Init = 0x01,

    PreOp = 0x02,

    Boot = 0x03,

    SafeOp = 0x04,

    Operational = 0x08,
}

impl EcState {

    pub fn from_raw(val: u8) -> Option<Self> {

        match val & 0x0F {
            0x00 => Some(Self::None),
            0x01 => Some(Self::Init),
            0x02 => Some(Self::PreOp),
            0x03 => Some(Self::Boot),
            0x04 => Some(Self::SafeOp),
            0x08 => Some(Self::Operational),
            _ => None,
        }
    }

    pub fn has_error(raw: u8) -> bool {
        (raw & 0x10) != 0
    }

    pub fn format(raw: u8) -> String {
        let base = match raw & 0x0F {
            0x00 => "None",
            0x01 => "Init",
            0x02 => "PreOp",
            0x03 => "Boot",
            0x04 => "SafeOp",
            0x08 => "OP",
            _ => "Unknown",
        };
        if (raw & 0x10) != 0 {
            format!("{}+Error", base)
        } else {
            base.to_string()
        }
    }
}

impl fmt::Display for EcState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Self::None => "None",
            Self::Init => "Init",
            Self::PreOp => "PreOp",
            Self::Boot => "Boot",
            Self::SafeOp => "SafeOp",
            Self::Operational => "OP",
        };
        write!(f, "{}", name)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum EcSlaveStatus {

    None = 0x00,

    Init = 0x01,

    PreOp = 0x02,

    Boot = 0x03,

    SafeOp = 0x04,

    OP = 0x08,

    ErrorFlag = 0x10,

    InitFault = 0x11,

    PreOpFault = 0x12,

    BootFault = 0x13,

    SafeOpFault = 0x14,

    OpFault = 0x18,
}

impl EcSlaveStatus {

    pub fn from_raw(raw: u8) -> Option<Self> {
        match raw & 0x1F {
            0x00 => Some(Self::None),
            0x01 => Some(Self::Init),
            0x02 => Some(Self::PreOp),
            0x03 => Some(Self::Boot),
            0x04 => Some(Self::SafeOp),
            0x08 => Some(Self::OP),
            0x10 => Some(Self::ErrorFlag),
            0x11 => Some(Self::InitFault),
            0x12 => Some(Self::PreOpFault),
            0x13 => Some(Self::BootFault),
            0x14 => Some(Self::SafeOpFault),
            0x18 => Some(Self::OpFault),
            _ => None,
        }
    }

    pub fn from_raw_or_none(raw: u8) -> Self {
        Self::from_raw(raw).unwrap_or(Self::None)
    }

    pub fn base_state(self) -> EcState {
        let raw = (self as u8) & 0x0F;
        EcState::from_raw(raw).unwrap_or(EcState::None)
    }

    pub fn has_fault(self) -> bool {
        ((self as u8) & 0x10) != 0
    }

    pub fn is_running(self) -> bool {
        matches!(self, Self::SafeOp | Self::OP)
    }

    pub fn is_op(self) -> bool {
        matches!(self, Self::OP)
    }

    pub fn pretty(self) -> &'static str {
        match self {
            Self::None => "None",
            Self::Init => "Init",
            Self::PreOp => "PreOp",
            Self::Boot => "Boot",
            Self::SafeOp => "SafeOp",
            Self::OP => "OP",
            Self::ErrorFlag => "ErrorFlag",
            Self::InitFault => "InitFault (Init+Error)",
            Self::PreOpFault => "PreOpFault (PreOp+Error)",
            Self::BootFault => "BootFault (Boot+Error)",
            Self::SafeOpFault => "SafeOpFault (SafeOp+Error)",
            Self::OpFault => "OpFault (OP+Error)",
        }
    }
}

impl fmt::Display for EcSlaveStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.pretty())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum WcContribution {

    NotContributed = 0,

    Contributed = 1,

    Unknown = 0xFF,
}

impl WcContribution {

    pub fn from_raw(raw: u8) -> Self {
        match raw {
            1 => Self::Contributed,
            0 => Self::NotContributed,
            _ => Self::Unknown,
        }
    }

    pub fn is_contributed(self) -> bool {
        matches!(self, Self::Contributed)
    }
}

impl fmt::Display for WcContribution {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Contributed => "Contributed",
            Self::NotContributed => "NotContributed",
            Self::Unknown => "Unknown",
        };
        write!(f, "{}", s)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlaveAlMirror {

    pub al_state: EcSlaveStatus,

    pub al_status_code: u16,

    pub raw: u16,
}

impl SlaveAlMirror {

    pub fn from_raw(raw: u16) -> Self {
        let state_byte = (raw & 0xFF) as u8;
        let code = (raw >> 8) & 0xFF;
        Self {
            al_state: EcSlaveStatus::from_raw_or_none(state_byte),
            al_status_code: code,
            raw,
        }
    }

    pub fn has_fault(self) -> bool {
        self.al_state.has_fault()
    }

    pub fn is_unknown(self) -> bool {
        self.raw == 0
    }
}

impl fmt::Display for SlaveAlMirror {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.is_unknown() {
            write!(f, "Unknown (no mirror)")
        } else {
            write!(f, "{} / AL Code 0x{:04X}", self.al_state, self.al_status_code)
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CiA402State {

    NotReady = 0,

    SwitchOnDisabled = 1,

    ReadyToSwitchOn = 2,

    SwitchedOn = 3,

    OperationEnabled = 4,

    QuickStopActive = 5,

    FaultReaction = 6,

    Fault = 7,

    Unknown = 99,
}

impl CiA402State {

    pub fn from_raw(val: i32) -> Self {
        match val {
            0 => Self::NotReady,
            1 => Self::SwitchOnDisabled,
            2 => Self::ReadyToSwitchOn,
            3 => Self::SwitchedOn,
            4 => Self::OperationEnabled,
            5 => Self::QuickStopActive,
            6 => Self::FaultReaction,
            7 => Self::Fault,
            _ => Self::Unknown,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i8)]
pub enum CiA402Mode {

    PP = 1,

    VL = 2,

    PV = 3,

    PT = 4,

    HM = 6,

    IP = 7,

    CSP = 8,

    CSV = 9,

    CST = 10,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum LinkState {

    Disconnected = 0,

    Connected = 1,

    Redundancy = 2,

    PrimaryOnly = 3,

    SecondaryOnly = 4,
}

impl LinkState {

    pub fn from_raw(val: u8) -> Self {
        match val {
            0 => Self::Disconnected,
            1 => Self::Connected,
            2 => Self::Redundancy,
            3 => Self::PrimaryOnly,
            4 => Self::SecondaryOnly,
            _ => Self::Disconnected,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum RedundancyState {

    None = 0,

    Primary = 1,

    Secondary = 2,

    Both = 3,
}

impl RedundancyState {

    pub fn from_raw(val: i32) -> Self {
        match val {
            0 => Self::None,
            1 => Self::Primary,
            2 => Self::Secondary,
            3 => Self::Both,
            _ => Self::None,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum FoEErrorCode {

    NotDefined       = 0x8000,

    NotFound         = 0x8001,

    AccessDenied     = 0x8002,

    DiskFull         = 0x8003,

    Illegal          = 0x8004,

    PacketNumberWrong = 0x8005,

    AlreadyExists    = 0x8006,

    NoUser           = 0x8007,

    BootstrapOnly    = 0x8008,

    NotBootstrap     = 0x8009,

    NoRights         = 0x800A,

    ProgramError     = 0x800B,
}

impl FoEErrorCode {

    pub fn from_dll_error(dll_error_code: i32) -> Self {
        let abs_code = dll_error_code.unsigned_abs();
        match abs_code {
            7  => FoEErrorCode::PacketNumberWrong,
            10 => FoEErrorCode::NotFound,
            12 => FoEErrorCode::AccessDenied,
            13 => FoEErrorCode::DiskFull,
            14 => FoEErrorCode::Illegal,
            15 => FoEErrorCode::PacketNumberWrong,
            16 => FoEErrorCode::AlreadyExists,
            17 => FoEErrorCode::NoUser,
            18 => FoEErrorCode::BootstrapOnly,
            19 => FoEErrorCode::NotBootstrap,
            20 => FoEErrorCode::NoRights,
            21 => FoEErrorCode::ProgramError,
            _ => FoEErrorCode::NotDefined,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum SoEErrorCode {

    NoError = 0x0000,

    NoIDN = 0x1001,

    InvalidAccessToElement = 0x1009,

    NoName = 0x2001,

    NameTransmissionTooShort = 0x2002,

    NameTransmissionTooLong = 0x2003,

    NameCannotBeChanged = 0x2004,

    NameWriteProtected = 0x2005,

    AttributeTransmissionTooShort = 0x3002,

    AttributeTransmissionTooLong = 0x3003,

    AttributeCannotBeChanged = 0x3004,

    AttributeWriteProtected = 0x3005,

    NoUnits = 0x4001,

    UnitTransmissionTooShort = 0x4002,

    UnitTransmissionTooLong = 0x4003,

    UnitCannotBeChanged = 0x4004,

    UnitWriteProtected = 0x4005,

    NoMinimumInputValue = 0x5001,

    MinValueTooShort = 0x5002,

    MinValueTooLong = 0x5003,

    MinValueCannotBeChanged = 0x5004,

    MinValueWriteProtected = 0x5005,

    NoMaximumInputValue = 0x6001,

    MaxValueTooShort = 0x6002,

    MaxValueTooLong = 0x6003,

    MaxValueCannotBeChanged = 0x6004,

    MaxValueWriteProtected = 0x6005,

    NoOperationData = 0x7001,

    OpDataTooShort = 0x7002,

    OpDataTooLong = 0x7003,

    OpDataCannotBeChanged = 0x7004,

    OpDataWriteProtected = 0x7005,

    OpDataSmallerThanMin = 0x7006,

    OpDataGreaterThanMax = 0x7007,

    InvalidDriveNumber = 0x800A,

    GeneralError = 0x800B,

    NoElementAddressed = 0x800C,

    Unknown = 0xFFFF,
}

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

    pub diff_ns: i32,

    pub max_diff_ns: i32,

    pub min_diff_ns: i32,

    pub in_sync: bool,

    pub out_of_sync_count: u32,
}
