
use crate::utils::ffi;

use std::ffi::CString;
use std::os::raw::{c_void, c_int};
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;

const DEFAULT_TIMEOUT: c_int = 5_000_000;

pub const DEFAULT_PORT: u16 = 0x88A4;

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

    pub rx_packets: u64,

    pub tx_packets: u64,

    pub rx_bytes: u64,

    pub tx_bytes: u64,

    pub master_od_requests: u64,

    pub slave_requests: u64,

    pub drop_malformed: u64,

    pub drop_timeout: u64,
}

#[derive(Default)]
struct AtomicStats {
    rx_packets: AtomicU64,
    tx_packets: AtomicU64,
    rx_bytes: AtomicU64,
    tx_bytes: AtomicU64,
    master_od_requests: AtomicU64,
    slave_requests: AtomicU64,
    drop_malformed: AtomicU64,
    drop_timeout: AtomicU64,
}

impl AtomicStats {
    fn snapshot(&self) -> MailboxGatewayStats {
        MailboxGatewayStats {
            rx_packets: self.rx_packets.load(Ordering::Relaxed),
            tx_packets: self.tx_packets.load(Ordering::Relaxed),
            rx_bytes: self.rx_bytes.load(Ordering::Relaxed),
            tx_bytes: self.tx_bytes.load(Ordering::Relaxed),
            master_od_requests: self.master_od_requests.load(Ordering::Relaxed),
            slave_requests: self.slave_requests.load(Ordering::Relaxed),
            drop_malformed: self.drop_malformed.load(Ordering::Relaxed),
            drop_timeout: self.drop_timeout.load(Ordering::Relaxed),
        }
    }

    fn reset(&self) {
        self.rx_packets.store(0, Ordering::Relaxed);
        self.tx_packets.store(0, Ordering::Relaxed);
        self.rx_bytes.store(0, Ordering::Relaxed);
        self.tx_bytes.store(0, Ordering::Relaxed);
        self.master_od_requests.store(0, Ordering::Relaxed);
        self.slave_requests.store(0, Ordering::Relaxed);
        self.drop_malformed.store(0, Ordering::Relaxed);
        self.drop_timeout.store(0, Ordering::Relaxed);
    }
}

pub struct MailboxGatewayService {

    master_index: u16,

    port: u16,

    running: Arc<AtomicBool>,

    listener_thread: Option<thread::JoinHandle<()>>,

    stop_socket: Option<Arc<UdpSocket>>,

    _mailbox_counter: std::sync::atomic::AtomicU8,

    stats: Arc<AtomicStats>,
}

impl MailboxGatewayService {

    pub fn new(master_index: u16) -> Self {
        Self {
            master_index,
            port: DEFAULT_PORT + master_index.saturating_sub(1),
            running: Arc::new(AtomicBool::new(false)),
            listener_thread: None,
            stop_socket: None,
            _mailbox_counter: std::sync::atomic::AtomicU8::new(0),
            stats: Arc::new(AtomicStats::default()),
        }
    }

    pub fn get_statistics(&self) -> MailboxGatewayStats {
        self.stats.snapshot()
    }

    pub fn reset_statistics(&self) {
        self.stats.reset();
    }

    pub fn start(&mut self) -> Result<(), String> {
        if self.running.load(Ordering::SeqCst) {
            return Ok(());
        }

        self.stop_internal();

        let socket = self.bind_udp_port(self.port)?;
        let actual_port = socket.local_addr()
            .map(|a| a.port())
            .unwrap_or(self.port);
        self.port = actual_port;

        let socket = Arc::new(socket);
        self.stop_socket = Some(socket.clone());
        self.running.store(true, Ordering::SeqCst);

        let running = self.running.clone();
        let master_index = self.master_index;
        let stats = self.stats.clone();

        self.listener_thread = Some(thread::spawn(move || {
            listen_loop(socket, running, master_index, stats);
        }));

        Ok(())
    }

    pub fn stop(&mut self) {
        self.stop_internal();
    }

    pub fn is_running(&self) -> bool {
        self.running.load(Ordering::SeqCst)
    }

    pub fn port(&self) -> u16 {
        self.port
    }

    pub fn set_port(&mut self, port: u16) {
        if !self.running.load(Ordering::SeqCst) {
            self.port = port;
        }
    }

    fn stop_internal(&mut self) {
        if !self.running.load(Ordering::SeqCst) && self.listener_thread.is_none() {
            return;
        }

        self.running.store(false, Ordering::SeqCst);

        if let Some(ref socket) = self.stop_socket {

            let _ = UdpSocket::bind("0.0.0.0:0")
                .and_then(|s| s.send_to(&[0], socket.local_addr().unwrap_or_else(|_| {
                    std::net::SocketAddr::from(([127, 0, 0, 1], self.port))
                })));
        }
        self.stop_socket = None;

        if let Some(handle) = self.listener_thread.take() {
            let _ = handle.join();
        }
    }

    fn bind_udp_port(&self, preferred_port: u16) -> Result<UdpSocket, String> {

        for i in 0..10u16 {
            let port = preferred_port.wrapping_add(i);
            match UdpSocket::bind(format!("0.0.0.0:{}", port)) {
                Ok(socket) => return Ok(socket),
                Err(_) => continue,
            }
        }

        UdpSocket::bind("0.0.0.0:0")
            .map_err(|e| format!("无法绑定 UDP 端口: {}", e))
    }
}

impl Drop for MailboxGatewayService {
    fn drop(&mut self) {
        self.stop_internal();
    }
}

fn listen_loop(
    socket: Arc<UdpSocket>,
    running: Arc<AtomicBool>,
    master_index: u16,
    stats: Arc<AtomicStats>,
) {
    let mut buf = [0u8; 2048];

    let _ = socket.set_read_timeout(Some(std::time::Duration::from_millis(500)));

    while running.load(Ordering::SeqCst) {
        match socket.recv_from(&mut buf) {
            Ok((len, remote_addr)) => {
                if !running.load(Ordering::SeqCst) { break; }
                stats.rx_packets.fetch_add(1, Ordering::Relaxed);
                stats.rx_bytes.fetch_add(len as u64, Ordering::Relaxed);

                if len < 9 {

                    stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
                    continue;
                }

                if let Some(response) = process_request(&buf[..len], master_index, &stats) {
                    match socket.send_to(&response, remote_addr) {
                        Ok(sent) => {
                            stats.tx_packets.fetch_add(1, Ordering::Relaxed);
                            stats.tx_bytes.fetch_add(sent as u64, Ordering::Relaxed);
                        }
                        Err(_) => {
                            stats.drop_timeout.fetch_add(1, Ordering::Relaxed);
                        }
                    }
                }
            }
            Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut
                || e.kind() == std::io::ErrorKind::WouldBlock => {
                continue;
            }
            Err(_) => {
                if running.load(Ordering::SeqCst) {

                    thread::sleep(std::time::Duration::from_millis(10));
                } else {
                    break;
                }
            }
        }
    }
}

fn process_request(data: &[u8], master_index: u16, stats: &Arc<AtomicStats>) -> Option<Vec<u8>> {

    if data.len() < 8 {
        stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
        return None;
    }

    let ecat_header = u16::from_le_bytes([data[0], data[1]]);
    let ecat_data_type = ((ecat_header >> 12) & 0x0F) as u8;

    if ecat_data_type != 0x05 {
        stats.drop_malformed.fetch_add(1, Ordering::Relaxed);
        return None;
    }

    let mb_length = u16::from_le_bytes([data[2], data[3]]) as usize;
    let address = u16::from_le_bytes([data[4], data[5]]);
    let _channel_prio = data[6];
    let type_cnt_res = data[7];

    let mailbox_type = (type_cnt_res >> 4) & 0x0F;
    let _mailbox_cnt = (type_cnt_res >> 1) & 0x07;

    let mb_data_start = 8;
    let avail = data.len().saturating_sub(mb_data_start);
    let actual_len = mb_length.min(avail);
    let mailbox_data = &data[mb_data_start..mb_data_start + actual_len];

    let response_data = if address == 0x0000 {

        stats.master_od_requests.fetch_add(1, Ordering::Relaxed);
        process_master_request(master_index, mailbox_type, mailbox_data)
    } else {

        stats.slave_requests.fetch_add(1, Ordering::Relaxed);
        process_slave_request(master_index, address, mailbox_type, mailbox_data)
    };

    response_data.map(|resp| build_response_frame(address, mailbox_type, &resp))
}

fn process_master_request(master_index: u16, mailbox_type: u8, mailbox_data: &[u8]) -> Option<Vec<u8>> {

    if mailbox_type != 0x03 {
        return Some(build_coe_abort_response(0x06010000));
    }

    if mailbox_data.len() < 6 { return None; }

    let coe_type = (mailbox_data[1] >> 4) & 0x0F;
    if coe_type != 0x02 { return None; }

    let sdo_command = mailbox_data[2];
    let index = u16::from_le_bytes([mailbox_data[3], mailbox_data[4]]);
    let subindex = mailbox_data[5];

    let is_upload = (sdo_command & 0x40) != 0;
    let is_download = (sdo_command & 0x20) != 0 && (sdo_command & 0x40) == 0;

    if is_upload {

        let data = read_master_od_object(master_index, index, subindex);
        match data {
            Some(d) => Some(build_coe_upload_response(index, subindex, &d)),
            None => Some(build_coe_abort_response(0x06020000)),
        }
    } else if is_download {

        let data_offset = 6;
        if mailbox_data.len() > data_offset {
            let write_data = &mailbox_data[data_offset..];
            write_master_od_object(master_index, index, subindex, write_data)
        } else {
            Some(build_coe_abort_response(0x05040001))
        }
    } else {
        Some(build_coe_abort_response(0x05040001))
    }
}

fn read_master_od_object(master_index: u16, index: u16, subindex: u8) -> Option<Vec<u8>> {
    match index {
        0x1000 => Some(0x000011A4u32.to_le_bytes().to_vec()),
        0x1008 => Some(b"Darra EtherCAT Master".to_vec()),
        0x1009 => Some(b"1.0".to_vec()),
        0x100A => {
            let ptr = unsafe { ffi::GetDllVersionInfo() };
            if ptr.is_null() { return Some(b"0.0.0.0".to_vec()); }
            let info = unsafe { std::ptr::read_unaligned(ptr) };

            let ver = format!("{}.{}.{}.{}", {info.major}, {info.minor}, {info.patch}, {info.build});
            Some(ver.into_bytes())
        }
        0x1018 => {
            match subindex {
                0 => Some(vec![4]),
                1 => Some(0x00001164u32.to_le_bytes().to_vec()),
                2 => Some(0x00000001u32.to_le_bytes().to_vec()),
                3 => {
                    let ptr = unsafe { ffi::GetDllVersionInfo() };
                    if ptr.is_null() { return Some(vec![0; 4]); }
                    let info = unsafe { std::ptr::read_unaligned(ptr) };
                    let rev = ((info.major as u32) << 16) | (info.minor as u32);
                    Some(rev.to_le_bytes().to_vec())
                }
                4 => Some(0u32.to_le_bytes().to_vec()),
                _ => None,
            }
        }
        0xF120 => {

            let mut diag = ffi::MasterDiagData {
                cyclic_lost_frames: 0, acyclic_lost_frames: 0,
                cyclic_frames_per_sec: 0, acyclic_frames_per_sec: 0,
                master_state: 0,
            };
            unsafe { ffi::GetMasterDiagData(master_index, &mut diag) };
            match subindex {
                0 => Some(vec![16]),
                1 => Some(diag.cyclic_lost_frames.to_le_bytes().to_vec()),
                2 => Some(diag.acyclic_lost_frames.to_le_bytes().to_vec()),
                3 => Some(diag.cyclic_frames_per_sec.to_le_bytes().to_vec()),
                4 => Some(diag.acyclic_frames_per_sec.to_le_bytes().to_vec()),
                5 => Some(diag.master_state.to_le_bytes().to_vec()),
                _ => Some(vec![0; 4]),
            }
        }
        _ => None,
    }
}

fn write_master_od_object(master_index: u16, index: u16, subindex: u8, data: &[u8]) -> Option<Vec<u8>> {
    match index {
        0xF120 => {

            match subindex {

                5 => {
                    if data.len() >= 4 {
                        let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
                        unsafe { ffi::SetDiagnosticsEnabled(master_index, val as i32) };
                        Some(build_coe_download_response(index, subindex))
                    } else {
                        Some(build_coe_abort_response(0x06070010))
                    }
                }
                _ => Some(build_coe_abort_response(0x06010002)),
            }
        }

        0x1000 | 0x1008 | 0x1009 | 0x100A | 0x1018 => {
            Some(build_coe_abort_response(0x06010002))
        }
        _ => Some(build_coe_abort_response(0x06010001)),
    }
}

fn process_slave_request(
    master_index: u16,
    address: u16,
    mailbox_type: u8,
    mailbox_data: &[u8],
) -> Option<Vec<u8>> {
    match mailbox_type {
        0x03 => process_slave_coe_request(master_index, address, mailbox_data),
        0x04 => process_slave_foe_request(master_index, address, mailbox_data),
        0x05 => process_slave_soe_request(master_index, address, mailbox_data),
        0x0F => process_slave_voe_request(master_index, address, mailbox_data),
        _ => Some(build_coe_abort_response(0x06010000)),
    }
}

fn process_slave_coe_request(
    master_index: u16,
    address: u16,
    mailbox_data: &[u8],
) -> Option<Vec<u8>> {
    if mailbox_data.len() < 6 { return None; }

    let sdo_command = mailbox_data[2];
    let index = u16::from_le_bytes([mailbox_data[3], mailbox_data[4]]);
    let subindex = mailbox_data[5];

    let is_upload = (sdo_command & 0x40) != 0;

    if is_upload {

        let slave = crate::slave::Slave::new(master_index, address);
        match slave.sdo_read(index, subindex, false) {
            Ok(data) => Some(build_coe_upload_response(index, subindex, &data)),
            Err(_) => Some(build_coe_abort_response(0x06090011)),
        }
    } else {

        let data_offset = 6;
        if mailbox_data.len() > data_offset {
            let write_data = &mailbox_data[data_offset..];
            let slave = crate::slave::Slave::new(master_index, address);
            match slave.sdo_write(index, subindex, false, write_data) {
                Ok(_) => Some(build_coe_download_response(index, subindex)),
                Err(_) => Some(build_coe_abort_response(0x06010002)),
            }
        } else {
            Some(build_coe_abort_response(0x05040001))
        }
    }
}

fn process_slave_soe_request(
    master_index: u16,
    address: u16,
    mailbox_data: &[u8],
) -> Option<Vec<u8>> {
    if mailbox_data.len() < 4 { return Some(build_soe_error_response(0, 0, 0x7001)); }

    let op_code = mailbox_data[0] & 0x07;
    let drive_no = (mailbox_data[1] >> 4) & 0x0F;
    let element_flags = mailbox_data[1] & 0x0F;
    let idn = u16::from_le_bytes([mailbox_data[2], mailbox_data[3]]);

    match op_code {
        1 => {

            let mut data_ptr: *mut c_void = std::ptr::null_mut();
            let mut data_size: c_int = 0;

            let ret = unsafe {
                ffi::SoERead(
                    master_index, address, drive_no, element_flags, idn,
                    &mut data_ptr, &mut data_size, DEFAULT_TIMEOUT,
                )
            };

            if ret == 0 && !data_ptr.is_null() && data_size > 0 {

                let data = unsafe {
                    std::slice::from_raw_parts(data_ptr as *const u8, data_size as usize)
                }.to_vec();
                unsafe { ffi::FreeMemory(data_ptr); }
                Some(build_soe_response(drive_no, element_flags, idn, &data))
            } else {

                if !data_ptr.is_null() {
                    unsafe { ffi::FreeMemory(data_ptr); }
                }
                Some(build_soe_error_response(drive_no, idn, 0x7002))
            }
        }
        2 => {

            if mailbox_data.len() <= 4 {
                return Some(build_soe_error_response(drive_no, idn, 0x7001));
            }
            let write_data = &mailbox_data[4..];

            let ret = unsafe {
                ffi::SoEWrite(
                    master_index, address, drive_no, element_flags, idn,
                    write_data.as_ptr(), write_data.len() as c_int, DEFAULT_TIMEOUT,
                )
            };

            if ret == 0 {

                Some(build_soe_response(drive_no, element_flags, idn, &[]))
            } else {
                Some(build_soe_error_response(drive_no, idn, 0x7002))
            }
        }
        _ => Some(build_soe_error_response(drive_no, idn, 0x7001)),
    }
}

fn process_slave_foe_request(
    master_index: u16,
    address: u16,
    mailbox_data: &[u8],
) -> Option<Vec<u8>> {
    if mailbox_data.len() < 6 { return Some(build_foe_error_response(0x8001)); }

    let op_code = mailbox_data[0];
    let password = u32::from_le_bytes([
        mailbox_data[2], mailbox_data[3], mailbox_data[4], mailbox_data[5],
    ]);

    match op_code {
        1 => {

            let filename_bytes = &mailbox_data[6..];

            let name_end = filename_bytes.iter().position(|&b| b == 0)
                .unwrap_or(filename_bytes.len());
            let filename_str = std::str::from_utf8(&filename_bytes[..name_end])
                .unwrap_or("");
            let c_filename = match CString::new(filename_str) {
                Ok(s) => s,
                Err(_) => return Some(build_foe_error_response(0x8002)),
            };

            let mut file_data: *mut c_void = std::ptr::null_mut();
            let mut file_size: c_int = 0;

            let ret = unsafe {
                ffi::FOERead(
                    master_index, address, c_filename.as_ptr(),
                    password, &mut file_data, &mut file_size, DEFAULT_TIMEOUT,
                )
            };

            if ret == 0 && !file_data.is_null() && file_size > 0 {
                let data = unsafe {
                    std::slice::from_raw_parts(file_data as *const u8, file_size as usize)
                }.to_vec();
                unsafe { ffi::FreeMemory(file_data); }
                Some(build_foe_data_response(&data))
            } else {
                if !file_data.is_null() {
                    unsafe { ffi::FreeMemory(file_data); }
                }
                Some(build_foe_error_response(0x8001))
            }
        }
        2 => {

            let payload = &mailbox_data[6..];
            let name_end = payload.iter().position(|&b| b == 0)
                .unwrap_or(0);

            let filename_str = std::str::from_utf8(&payload[..name_end])
                .unwrap_or("");
            let c_filename = match CString::new(filename_str) {
                Ok(s) => s,
                Err(_) => return Some(build_foe_error_response(0x8002)),
            };

            let data_start = if name_end < payload.len() { name_end + 1 } else { payload.len() };
            let file_data = &payload[data_start..];

            let ret = unsafe {
                ffi::FOEWrite(
                    master_index, address, c_filename.as_ptr(),
                    password, file_data.as_ptr() as *const c_void,
                    file_data.len() as c_int, DEFAULT_TIMEOUT,
                )
            };

            if ret == 0 {
                Some(build_foe_ack_response())
            } else {
                Some(build_foe_error_response(0x8001))
            }
        }
        _ => Some(build_foe_error_response(0x8003)),
    }
}

fn process_slave_voe_request(
    master_index: u16,
    address: u16,
    mailbox_data: &[u8],
) -> Option<Vec<u8>> {
    if mailbox_data.len() < 6 { return Some(build_voe_error_response()); }

    let _vendor_id = u32::from_le_bytes([
        mailbox_data[0], mailbox_data[1], mailbox_data[2], mailbox_data[3],
    ]);
    let _vendor_type = u16::from_le_bytes([mailbox_data[4], mailbox_data[5]]);

    let ret_send = unsafe {
        ffi::VOESendRaw(
            master_index, address,
            mailbox_data.as_ptr(), mailbox_data.len() as c_int,
            DEFAULT_TIMEOUT,
        )
    };

    if ret_send != 0 {
        return Some(build_voe_error_response());
    }

    let mut recv_data: *mut c_void = std::ptr::null_mut();
    let mut recv_size: c_int = 0;

    let ret_recv = unsafe {
        ffi::VOEReceiveRaw(
            master_index, address,
            &mut recv_data, &mut recv_size,
            DEFAULT_TIMEOUT,
        )
    };

    if ret_recv == 0 && !recv_data.is_null() && recv_size > 0 {
        let data = unsafe {
            std::slice::from_raw_parts(recv_data as *const u8, recv_size as usize)
        }.to_vec();
        unsafe { ffi::FreeMemory(recv_data); }

        Some(data)
    } else {
        if !recv_data.is_null() {
            unsafe { ffi::FreeMemory(recv_data); }
        }
        Some(build_voe_error_response())
    }
}

fn build_response_frame(address: u16, mailbox_type: u8, data: &[u8]) -> Vec<u8> {
    let mb_length = data.len() as u16;

    let ecat_header: u16 = (mb_length + 6) & 0x07FF | (0x05 << 12);

    let mut frame = Vec::with_capacity(8 + data.len());

    frame.extend_from_slice(&ecat_header.to_le_bytes());

    frame.extend_from_slice(&mb_length.to_le_bytes());
    frame.extend_from_slice(&address.to_le_bytes());
    frame.push(0x00);
    frame.push((mailbox_type << 4) | 0x02);

    frame.extend_from_slice(data);

    frame
}

fn build_coe_upload_response(index: u16, subindex: u8, data: &[u8]) -> Vec<u8> {
    let mut resp = Vec::with_capacity(6 + data.len());

    resp.push(0x00);
    resp.push(0x20);

    if data.len() <= 4 {

        let size_indicator = (4 - data.len()) as u8;
        resp.push(0x43 | (size_indicator << 2));
    } else {

        resp.push(0x41);
    }
    resp.extend_from_slice(&index.to_le_bytes());
    resp.push(subindex);
    if data.len() <= 4 {

        resp.extend_from_slice(data);
        for _ in data.len()..4 {
            resp.push(0x00);
        }
    } else {

        resp.extend_from_slice(&(data.len() as u32).to_le_bytes());
        resp.extend_from_slice(data);
    }
    resp
}

fn build_coe_download_response(index: u16, subindex: u8) -> Vec<u8> {
    let mut resp = Vec::with_capacity(6);

    resp.push(0x00);
    resp.push(0x20);

    resp.push(0x60);
    resp.extend_from_slice(&index.to_le_bytes());
    resp.push(subindex);
    resp
}

fn build_coe_abort_response(abort_code: u32) -> Vec<u8> {
    let mut resp = Vec::with_capacity(10);

    resp.push(0x00);
    resp.push(0x20);

    resp.push(0x80);
    resp.push(0x00);
    resp.push(0x00);
    resp.push(0x00);
    resp.extend_from_slice(&abort_code.to_le_bytes());
    resp
}

fn build_soe_response(drive_no: u8, element_flags: u8, idn: u16, data: &[u8]) -> Vec<u8> {
    let op_code: u8 = if data.is_empty() { 0x00 } else { 0x03 };
    let mut resp = Vec::with_capacity(4 + data.len());
    resp.push(op_code);
    resp.push((drive_no << 4) | (element_flags & 0x0F));
    resp.extend_from_slice(&idn.to_le_bytes());
    if !data.is_empty() {
        resp.extend_from_slice(data);
    }
    resp
}

fn build_soe_error_response(drive_no: u8, idn: u16, error_code: u16) -> Vec<u8> {
    let mut resp = Vec::with_capacity(6);
    resp.push(0x04);
    resp.push(drive_no << 4);
    resp.extend_from_slice(&idn.to_le_bytes());
    resp.extend_from_slice(&error_code.to_le_bytes());
    resp
}

fn build_foe_data_response(data: &[u8]) -> Vec<u8> {
    let mut resp = Vec::with_capacity(6 + data.len());
    resp.push(0x03);
    resp.push(0x00);
    resp.extend_from_slice(&1u32.to_le_bytes());
    resp.extend_from_slice(data);
    resp
}

fn build_foe_ack_response() -> Vec<u8> {
    let mut resp = Vec::with_capacity(6);
    resp.push(0x04);
    resp.push(0x00);
    resp.extend_from_slice(&1u32.to_le_bytes());
    resp
}

fn build_foe_error_response(error_code: u32) -> Vec<u8> {
    let mut resp = Vec::with_capacity(6);
    resp.push(0x05);
    resp.push(0x00);
    resp.extend_from_slice(&error_code.to_le_bytes());
    resp
}

fn build_voe_error_response() -> Vec<u8> {
    let mut resp = Vec::with_capacity(6);
    resp.extend_from_slice(&0u32.to_le_bytes());
    resp.extend_from_slice(&0xFFFFu16.to_le_bytes());
    resp
}

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

    pub rx_packets: u64,

    pub rx_bytes: u64,

    pub forwarded_ok: u64,

    pub forwarded_failed: u64,

    pub timeout_count: u64,

    pub invalid_frame: u64,

    pub unknown_target: u64,

    pub unsupported_type: u64,

    pub tx_packets: u64,

    pub tx_bytes: u64,

    pub master_requests: u64,

    pub udp_port: u16,

    pub is_running: bool,

    pub discovery_requests: u64,

    pub tcp_connections: u64,

    pub tcp_active: u64,

    pub tcp_port: u16,
}

impl From<ffi::MbgStatsC> for MbgNativeStats {
    fn from(c: ffi::MbgStatsC) -> Self {
        Self {
            rx_packets: c.rx_packets,
            rx_bytes: c.rx_bytes,
            forwarded_ok: c.forwarded_ok,
            forwarded_failed: c.forwarded_failed,
            timeout_count: c.timeout_count,
            invalid_frame: c.invalid_frame,
            unknown_target: c.unknown_target,
            unsupported_type: c.unsupported_type,
            tx_packets: c.tx_packets,
            tx_bytes: c.tx_bytes,
            master_requests: c.master_requests,
            udp_port: c.udp_port,
            is_running: c.is_running != 0,
            discovery_requests: c.discovery_requests,
            tcp_connections: c.tcp_connections,
            tcp_active: c.tcp_active,
            tcp_port: c.tcp_port,
        }
    }
}

pub struct MailboxGateway {
    master_index: u16,
}

impl MailboxGateway {

    pub const DEFAULT_PORT: u16 = 0x88A4;

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

    pub fn start(&self, udp_port: u16) -> bool {
        unsafe { ffi::MBGStart(self.master_index, udp_port) != 0 }
    }

    pub fn start_default(&self) -> bool {
        self.start(Self::DEFAULT_PORT)
    }

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

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

    pub fn statistics(&self) -> Option<MbgNativeStats> {
        let mut c = ffi::MbgStatsC::default();
        let ok = unsafe { ffi::MBGGetStats(self.master_index, &mut c) };
        if ok != 0 { Some(c.into()) } else { None }
    }

    pub fn reset_statistics(&self) {
        unsafe { ffi::MBGResetStats(self.master_index) };
    }

    pub fn port(&self) -> u16 {
        unsafe { ffi::MBGGetPort(self.master_index) }
    }

    pub fn start_tcp(&self, tcp_port: u16) -> bool {
        unsafe { ffi::MBGStartTcp(self.master_index, tcp_port) != 0 }
    }

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

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

    pub fn tcp_port(&self) -> u16 {
        unsafe { ffi::MBGGetTcpPort(self.master_index) }
    }

    pub fn selected_master(&self) -> u16 {
        unsafe { ffi::MBGGetSelectedMaster(self.master_index) }
    }

    pub fn set_selected_master(&self, selected: u16) -> bool {
        unsafe { ffi::MBGSetSelectedMaster(self.master_index, selected) != 0 }
    }
}
