//! UDP transport.
//!
//! Single-process MAVLink links over UDP behave like a connected datagram pair:
//! the autopilot binds a local port and exchanges datagrams with the peer.
//! Here we bind to the OS-chosen local port `0.0.0.0:0` and `connect` to the
//! configured peer so the socket can be used like a stream.

use async_trait::async_trait;
use tokio::net::UdpSocket;

use shared::error::{AutopilotError, Result};

use super::Transport;

#[derive(Debug)]
pub struct UdpTransport {
    socket: UdpSocket,
}

impl UdpTransport {
    /// Bind a local UDP socket and `connect` it to `peer`, so subsequent
    /// `send` / `recv` calls behave like a stream.
    pub async fn connect(peer: &str) -> Result<Self> {
        let socket = UdpSocket::bind("0.0.0.0:0")
            .await
            .map_err(|e| AutopilotError::Network(format!("udp bind failed: {e}")))?;
        socket
            .connect(peer)
            .await
            .map_err(|e| AutopilotError::Network(format!("udp connect failed: {e}")))?;
        Ok(Self { socket })
    }

    #[allow(dead_code)] // Used by the AZ-641 UDP integration tests in `tests/`.
    pub fn local_addr(&self) -> Result<std::net::SocketAddr> {
        self.socket
            .local_addr()
            .map_err(|e| AutopilotError::Network(format!("udp local_addr failed: {e}")))
    }
}

#[async_trait]
impl Transport for UdpTransport {
    async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        self.socket
            .recv(buf)
            .await
            .map_err(|e| AutopilotError::Network(format!("udp recv: {e}")))
    }

    async fn write_all(&mut self, buf: &[u8]) -> Result<()> {
        self.socket
            .send(buf)
            .await
            .map_err(|e| AutopilotError::Network(format!("udp send: {e}")))?;
        Ok(())
    }
}
