
use crate::utils::ffi;

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

impl SlavePdo {

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

    pub fn read_input_u8(&self, offset: u32) -> u8 {
        unsafe { ffi::PDOReadInputU8(self.master_index, self.slave_index, offset) }
    }

    pub fn read_input_i16(&self, offset: u32) -> i16 {
        unsafe { ffi::PDOReadInputI16(self.master_index, self.slave_index, offset) }
    }

    pub fn read_input_u16(&self, offset: u32) -> u16 {
        unsafe { ffi::PDOReadInputU16(self.master_index, self.slave_index, offset) }
    }

    pub fn read_input_i32(&self, offset: u32) -> i32 {
        unsafe { ffi::PDOReadInputI32(self.master_index, self.slave_index, offset) }
    }

    pub fn read_input_u32(&self, offset: u32) -> u32 {
        unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset) }
    }

    pub fn read_input_f32(&self, offset: u32) -> f32 {
        unsafe { ffi::PDOReadInputF32(self.master_index, self.slave_index, offset) }
    }

    pub fn write_output_u8(&self, offset: u32, value: u8) -> bool {
        unsafe { ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn write_output_i16(&self, offset: u32, value: i16) -> bool {
        unsafe { ffi::PDOWriteOutputI16(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn write_output_u16(&self, offset: u32, value: u16) -> bool {
        unsafe { ffi::PDOWriteOutputU16(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn write_output_i32(&self, offset: u32, value: i32) -> bool {
        unsafe { ffi::PDOWriteOutputI32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn write_output_u32(&self, offset: u32, value: u32) -> bool {
        unsafe { ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn write_output_f32(&self, offset: u32, value: f32) -> bool {
        unsafe { ffi::PDOWriteOutputF32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    pub fn stats(&self) -> Option<*const std::os::raw::c_void> {
        unsafe {
            let ptr = ffi::GetPDOStats(self.master_index, self.slave_index);
            if ptr.is_null() { None } else { Some(ptr) }
        }
    }

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

    pub fn get_input_data_pointer(&self) -> *mut u8 {
        super::pdo::get_input_data_pointer(self.master_index, self.slave_index)
    }

    pub fn get_output_data_pointer(&self) -> *mut u8 {
        super::pdo::get_output_data_pointer(self.master_index, self.slave_index)
    }

    pub fn inputs(&self) -> Vec<u8> {
        let (ptr, size) = self.inputs_mapping_info();
        if ptr.is_null() || size == 0 {
            return Vec::new();
        }
        let mut buf = vec![0u8; size];
        unsafe { std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), size); }
        buf
    }

    pub fn outputs(&self) -> Vec<u8> {
        let (ptr, size) = self.outputs_mapping_info();
        if ptr.is_null() || size == 0 {
            return Vec::new();
        }
        let mut buf = vec![0u8; size];
        unsafe { std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), size); }
        buf
    }

    pub fn write_outputs(&self, data: &[u8]) {
        let (ptr, size) = self.outputs_mapping_info();
        if ptr.is_null() || size == 0 { return; }
        let len = data.len().min(size);
        unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, len); }
    }

    pub fn read_input_data_direct(&self, buffer: &mut [u8], offset: usize, length: usize) -> usize {
        super::pdo::read_input_data_direct(self.master_index, self.slave_index, buffer, offset, length)
    }

    pub fn write_output_data_direct(&self, data: &[u8], offset: usize, length: usize) -> usize {
        super::pdo::write_output_data_direct(self.master_index, self.slave_index, data, offset, length)
    }

    pub unsafe fn input_slice(&self) -> &[u8] {
        super::pdo::input_slice(self.master_index, self.slave_index)
    }

    pub unsafe fn output_slice(&self) -> &mut [u8] {
        super::pdo::output_slice(self.master_index, self.slave_index)
    }

    pub fn inputs_mapping_info(&self) -> (*mut u8, usize) {
        let mut out_size: i32 = 0;
        let mut out_ptr: *mut u8 = std::ptr::null_mut();
        let mut in_size: i32 = 0;
        let mut in_ptr: *mut u8 = std::ptr::null_mut();
        unsafe {
            ffi::GetIO(self.master_index, self.slave_index,
                       &mut out_size, &mut out_ptr,
                       &mut in_size, &mut in_ptr);
        }
        (in_ptr, in_size.max(0) as usize)
    }

    pub fn outputs_mapping_info(&self) -> (*mut u8, usize) {
        let mut out_size: i32 = 0;
        let mut out_ptr: *mut u8 = std::ptr::null_mut();
        let mut in_size: i32 = 0;
        let mut in_ptr: *mut u8 = std::ptr::null_mut();
        unsafe {
            ffi::GetIO(self.master_index, self.slave_index,
                       &mut out_size, &mut out_ptr,
                       &mut in_size, &mut in_ptr);
        }
        (out_ptr, out_size.max(0) as usize)
    }

    pub unsafe fn inputs_slice_mapping(&self, offset: usize, size: usize) -> &[u8] {
        let (ptr, total) = self.inputs_mapping_info();
        if ptr.is_null() || offset + size > total {
            return &[];
        }
        std::slice::from_raw_parts(ptr.add(offset), size)
    }

    pub unsafe fn outputs_slice_mapping(&self, offset: usize, size: usize) -> &mut [u8] {
        let (ptr, total) = self.outputs_mapping_info();
        if ptr.is_null() || offset + size > total {
            return &mut [];
        }
        std::slice::from_raw_parts_mut(ptr.add(offset), size)
    }

    pub unsafe fn bind_pdo_struct<T>(&self, is_input: bool) -> Option<*mut T> {
        let (ptr, size) = if is_input {
            self.inputs_mapping_info()
        } else {
            self.outputs_mapping_info()
        };
        if ptr.is_null() || size < std::mem::size_of::<T>() {
            return None;
        }
        Some(ptr as *mut T)
    }

    pub fn start_monitoring(&self) {
        unsafe { ffi::EnablePDOMonitoring(1); }
    }

    pub fn stop_monitoring(&self) {
        unsafe { ffi::EnablePDOMonitoring(0); }
    }

    pub fn is_monitoring_enabled(&self) -> bool {
        unsafe { ffi::IsPDOMonitoringEnabled() != 0 }
    }

    pub fn get_pdo_mapping(&self, sm_index: u16) -> Option<(Vec<u8>, u32)> {
        let mut buffer = vec![0u8; 512];
        let mut mapping_count: u32 = 0;
        let ret = unsafe {
            ffi::GetPDOMapping(
                self.master_index, self.slave_index, sm_index,
                buffer.as_mut_ptr() as *mut std::os::raw::c_void,
                buffer.len() as u32, &mut mapping_count,
            )
        };
        if ret != 0 && mapping_count > 0 {
            buffer.truncate((mapping_count * 8) as usize);
            Some((buffer, mapping_count))
        } else {
            None
        }
    }

    pub fn lock_iomap(&self) {
        unsafe { ffi::LockIOmap(self.master_index); }
    }

    pub fn unlock_iomap(&self) {
        unsafe { ffi::UnlockIOmap(self.master_index); }
    }

    pub fn set_mutex_protection(&self, enabled: bool) {
        unsafe { ffi::SetMutexProtection(self.master_index, if enabled { 1 } else { 0 }); }
    }

    pub fn get_mutex_protection(&self) -> bool {
        unsafe { ffi::GetMutexProtection(self.master_index) != 0 }
    }

    pub fn read_direct(&self, pdo_index: u16, size: u32) -> Option<Vec<u8>> {
        let mut buffer = vec![0u8; size as usize];
        let mut bytes_read: u32 = 0;
        let ret = unsafe {
            ffi::PDOReadDirect(
                self.master_index, self.slave_index, pdo_index,
                buffer.as_mut_ptr() as *mut std::os::raw::c_void,
                size, &mut bytes_read,
            )
        };
        if ret != 0 && bytes_read > 0 {
            buffer.truncate(bytes_read as usize);
            Some(buffer)
        } else {
            None
        }
    }

    pub fn write_direct(&self, pdo_index: u16, data: &[u8]) -> bool {
        let ret = unsafe {
            ffi::PDOWriteDirect(
                self.master_index, self.slave_index, pdo_index,
                data.as_ptr() as *const std::os::raw::c_void,
                data.len() as u32,
            )
        };
        ret != 0
    }

    pub fn read_string(&self, offset: u32, max_len: u32) -> String {

        let mut buf = Vec::with_capacity(max_len as usize);
        for i in 0..max_len {
            let b = unsafe { ffi::PDOReadInputU8(self.master_index, self.slave_index, offset + i) };
            if b == 0 { break; }
            buf.push(b);
        }

        crate::utils::help::decode_ethercat_string(&buf)
    }

    pub fn write_string(&self, offset: u32, value: &str, max_len: u32) -> bool {
        let bytes = value.as_bytes();
        let write_len = bytes.len().min(max_len as usize);
        for i in 0..write_len {
            let ret = unsafe {
                ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset + i as u32, bytes[i])
            };
            if ret == 0 { return false; }
        }

        if write_len < max_len as usize {
            unsafe {
                ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset + write_len as u32, 0);
            };
        }
        true
    }

    pub fn read_input_i64(&self, offset: u32) -> i64 {
        let low = unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset) };
        let high = unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset + 4) };
        ((high as i64) << 32) | (low as i64)
    }

    pub fn write_output_i64(&self, offset: u32, value: i64) -> bool {
        let low = value as u32;
        let high = (value >> 32) as u32;
        unsafe {
            ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset, low);
            ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset + 4, high);
        }
        true
    }

    pub fn group_cycle_divider(&self, group: u8) -> u8 {
        unsafe { ffi::GetGroupCycleDivider(self.master_index, group) }
    }

    pub fn set_group_cycle_divider(&self, group: u8, divider: u8) -> bool {
        unsafe { ffi::SetGroupCycleDivider(self.master_index, group, divider) != 0 }
    }

    pub fn frame_loss_stats(&self, group: u8) -> (u32, u32, u32) {
        let mut total_lost: u32 = 0;
        let mut consecutive_lost: u32 = 0;
        let mut max_consecutive_lost: u32 = 0;
        unsafe {
            ffi::GetPDOFrameLossStats(
                self.master_index, group,
                &mut total_lost, &mut consecutive_lost, &mut max_consecutive_lost,
            );
        }
        (total_lost, consecutive_lost, max_consecutive_lost)
    }

    pub fn reset_frame_loss_stats(&self, group: u8) {
        unsafe { ffi::ResetPDOFrameLossStats(self.master_index, group); }
    }
}
