
use crate::data::error::SyncWindowStatus;
use crate::utils::ffi;

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

    pub rx_error: [u8; 4],

    pub invalid_frame: [u8; 4],

    pub lost_link: [u8; 4],

    pub fwd_rx_error: [u8; 4],
}

impl EscPortErrors {

    pub fn has_errors(&self) -> bool {
        self.rx_error.iter().any(|&b| b > 0)
            || self.invalid_frame.iter().any(|&b| b > 0)
            || self.lost_link.iter().any(|&b| b > 0)
            || self.fwd_rx_error.iter().any(|&b| b > 0)
    }
}

pub struct SlaveDCDiagnostics {
    master_index: u16,
    slave_index: u16,
}

impl SlaveDCDiagnostics {

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

    pub fn is_in_sync(&self) -> bool {
        let mut diff_ns: i32 = 0;
        let mut max_diff_ns: i32 = 0;
        let mut min_diff_ns: i32 = 0;
        let mut in_sync: i32 = 0;
        let mut out_of_sync_count: u32 = 0;
        let ret = unsafe {
            ffi::GetSlaveSyncWindowStatus(
                self.master_index, self.slave_index,
                &mut diff_ns, &mut max_diff_ns, &mut min_diff_ns,
                &mut in_sync, &mut out_of_sync_count,
            )
        };
        ret != 0 && in_sync != 0
    }

    pub fn sync_time_difference(&self) -> i32 {
        let mut diff_ns: i32 = 0;
        let mut max_diff_ns: i32 = 0;
        let mut min_diff_ns: i32 = 0;
        let mut in_sync: i32 = 0;
        let mut out_of_sync_count: u32 = 0;
        let ret = unsafe {
            ffi::GetSlaveSyncWindowStatus(
                self.master_index, self.slave_index,
                &mut diff_ns, &mut max_diff_ns, &mut min_diff_ns,
                &mut in_sync, &mut out_of_sync_count,
            )
        };
        if ret != 0 { diff_ns } else { 0 }
    }

    pub fn sync_window_status(&self) -> Option<SyncWindowStatus> {
        let mut diff_ns: i32 = 0;
        let mut max_diff_ns: i32 = 0;
        let mut min_diff_ns: i32 = 0;
        let mut in_sync: i32 = 0;
        let mut out_of_sync_count: u32 = 0;
        let ret = unsafe {
            ffi::GetSlaveSyncWindowStatus(
                self.master_index, self.slave_index,
                &mut diff_ns, &mut max_diff_ns, &mut min_diff_ns,
                &mut in_sync, &mut out_of_sync_count,
            )
        };
        if ret != 0 {
            Some(SyncWindowStatus {
                diff_ns,
                max_diff_ns,
                min_diff_ns,
                in_sync: in_sync != 0,
                out_of_sync_count,
            })
        } else {
            None
        }
    }

    pub fn reset_stats(&self) {
        unsafe { ffi::ResetSlaveSyncWindowStats(self.master_index, self.slave_index) };
    }
}

pub struct SlaveDiagnostics {
    master_index: u16,
    slave_index: u16,
}

impl SlaveDiagnostics {

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

    pub fn dc(&self) -> SlaveDCDiagnostics {
        SlaveDCDiagnostics::new(self.master_index, self.slave_index)
    }

    pub fn read_port_errors(&self) -> Option<EscPortErrors> {
        let mut rx_error = [0u8; 4];
        let mut invalid_frame = [0u8; 4];
        let mut lost_link = [0u8; 4];
        let ret = unsafe {
            ffi::ReadSlavePortErrorCounters(
                self.master_index, self.slave_index,
                rx_error.as_mut_ptr(), invalid_frame.as_mut_ptr(), lost_link.as_mut_ptr(),
            )
        };
        if ret != 0 {
            Some(EscPortErrors { rx_error, invalid_frame, lost_link, fwd_rx_error: [0; 4] })
        } else {
            None
        }
    }

    pub fn reset_port_errors(&self) -> bool {
        unsafe { ffi::ResetSlavePortErrorCounters(self.master_index, self.slave_index) != 0 }
    }

    pub fn link_quality(&self) -> i16 {
        unsafe { ffi::GetSlaveLinkQuality(self.master_index, self.slave_index) }
    }

    pub fn redundancy_activated(&self) -> bool {

        let mode = unsafe { ffi::GetRingMode(self.master_index) };
        mode == 1
    }

    pub fn primary_link_broken(&self) -> bool {
        if let Some(errors) = self.read_port_errors() {

            errors.lost_link[0] > 0
        } else {
            false
        }
    }

    pub fn secondary_link_broken(&self) -> bool {
        if let Some(errors) = self.read_port_errors() {

            errors.lost_link[1] > 0
        } else {
            false
        }
    }
}
