//! Module defining the abstraction over IO usage.
//!
//! As a library user, you just need to read the documentation of the [`IoImpl`] trait but you can
//! ignore all the other items in this module.
use core::{net::{Ipv4Addr, Ipv6Addr, SocketAddr}, task::{Poll, Context}, time::Duration};

use rustls::{ClientConfig, CommonState, ServerConfig, pki_types::ServerName};

#[cfg(feature = "std")]
use std::sync::Arc;

#[cfg(not(feature = "std"))]
use alloc::sync::Arc;

#[cfg(feature = "tokio")]
mod tokio;

#[cfg(feature = "tokio")]
pub use tokio::TokioIoImpl;

pub use rustls;

use crate::{error::Result, device::Device};

/// An abstract representation of a function of the public API of `kdeconnect_proto` that can be
/// launched as a standalone asynchronous task.
#[derive(Debug)]
pub enum KnownFunctionName<TcpStream: TcpStreamImpl> {
    /// This variant represents the [`kdeconnect_proto::transport::tcp::setup_tcp`](`crate::transport::tcp::setup_tcp`) function.
    SetupUdp,

    /// This variant represents the [`kdeconnect_proto::transport::mdns::setup_mdns`](`crate::transport::mdns::setup_mdns`) function.
    SetupMdns,

    /// This variant represents the [`kdeconnect_proto::transport::tcp::per_tcp_stream`](`crate::transport::tcp::per_tcp_stream`) function.
    PerTcpStream(TcpStream),
}

/// An abstract representation of a UDP socket with all the methods on it that are needed by
/// `kdeconnect_proto`.
pub trait UdpSocketImpl {
    /// Set the broadcast flag on the UDP socket.
    fn set_broadcast(&self, on: bool) -> Result<()>;

    /// Poll the inner UDP socket for read data.
    fn poll_recv(&self, cx: &mut Context, buf: &mut [u8]) -> Poll<Result<()>>;

    /// Read data from the UDP socket into the provided buffer.
    fn recv_from(&self, buf: &mut [u8]) -> impl Future<Output = Result<(usize, SocketAddr)>>;

    /// Send data to the provided address using this UDP socket.
    fn send_to(&mut self, buf: &[u8], addr: SocketAddr) -> impl Future<Output = Result<usize>>;
}

/// An abstract representation of a TCP stream with all the methods on it that are needed by
/// `kdeconnect_proto`.
pub trait TcpStreamImpl {
    /// Read data from this TCP stream into the provided buffer.
    fn read(&mut self, buf: &mut [u8]) -> impl Future<Output = Result<usize>>;

    /// Wait until the stream becomes writable.
    fn writable(&self) -> impl Future<Output = Result<()>>;

    /// Write the full provided buffer into the TCP stream.
    fn write_all(&mut self, src: &[u8]) -> impl Future<Output = Result<()>>;
}

/// An abstract representation of a TCP listener with all the methods on it that are needed by
/// `kdeconnect_proto`.
pub trait TcpListenerImpl<TcpStream: TcpStreamImpl> {
    /// Wait and accept an incoming TCP connection.
    fn accept(&self) -> impl Future<Output = Result<TcpStream>>;
}

/// An abstract representation of a TLS stream with all the methods on it that are needed by
/// `kdeconnect_proto`.
pub trait TlsStreamImpl {
    /// Get the [`rustls::CommonState`] structure associated to the TLS stream.
    ///
    /// This structure gives access to the SSL certificate used by the peer.
    fn get_common_state(&self) -> &CommonState;

    /// Read and decrypt data from the TLS stream.
    fn read(&mut self, buf: &mut [u8]) -> impl Future<Output = Result<usize>>;

    /// Encrypt and write data to the TLS stream.
    fn write_all(&mut self, src: &[u8]) -> impl Future<Output = Result<()>>;
}

/// A trait representing all IO operations that `kdeconnect_proto` need to do.
///
/// It allows extreme portability by abstracting all IO usage.
///
/// As a library user, you don't have to implement this trait yourself but instead choose an
/// implementation for the platform you target and give it as fourth argument to [`Device::new`].
///
/// The [`TokioIoImpl`](`tokio::TokioIoImpl`) implementation of this trait uses `tokio` to do all
/// IO operations asynchronously. Another implementation for embedded devices using `embassy`
/// is available in the `kdeconnect_embassy` crate.
pub trait IoImpl<UdpSocket: UdpSocketImpl, TcpStream: TcpStreamImpl, TcpListener: TcpListenerImpl<TcpStream>, TlsStream: TlsStreamImpl> where Self: Sized {
    /// Bind a UDP socket to the provided address.
    fn bind_udp(&self, addr: SocketAddr) -> impl Future<Output = Result<UdpSocket>>;

    /// Bind an UDP socket to an IPv6 address while setting the `reuse` flag.
    fn bind_udp_reuse_v6(&self, addr: SocketAddr) -> impl Future<Output = Result<UdpSocket>>;

    /// Bind an UDP socket to an IPv4 address while setting the `reuse` flag and joining the
    /// provided multicast group.
    fn bind_udp_reuse_multicast_v4(&self, addr: SocketAddr, multicast_addr: (Ipv4Addr, Ipv4Addr)) -> impl Future<Output = Result<UdpSocket>>;

    /// Start a TCP server listening on the provided address.
    fn listen_tcp(&self, addr: SocketAddr) -> impl Future<Output = Result<TcpListener>>;

    /// Make a TCP socket connected connected to the provided peer address.
    fn connect_tcp(&self, addr: SocketAddr) -> impl Future<Output = Result<TcpStream>>;

    /// Wait and accept an incoming TLS connection from the provided TCP stream.
    fn accept_server_tls(&self, config: ServerConfig, stream: TcpStream) -> impl Future<Output = Result<TlsStream>>;

    /// Upgrade the TCP stream to a TLS stream.
    fn connect_client_tls(&self, config: ClientConfig, server_name: ServerName<'static>, stream: TcpStream) -> impl Future<Output = Result<TlsStream>>;

    /// Get the host IPv4 and IPv6 addresses.
    fn get_host_addresses(&self) -> impl Future<Output = (Option<Ipv4Addr>, Option<Ipv6Addr>)>;

    /// Sleep for the provided duration.
    fn sleep(&self, duration: Duration) -> impl Future<Output = ()>;

    /// Spawn a task executing the public function provided by the [`KnownFunctionName`]
    /// enumeration variant. Each of the variants corresponds to a function of the public API of
    /// `kdeconnect_proto`.
    fn spawn(&self, name: KnownFunctionName<TcpStream>, device: Arc<Device<Self, UdpSocket, TcpStream, TcpListener, TlsStream>>);

    /// Start the device host client by launching the TCP server and discovery mechanisms.
    fn start(&self, device: Arc<Device<Self, UdpSocket, TcpStream, TcpListener, TlsStream>>);

    /// Get the current timestamp since the Unix epoch.
    fn get_current_timestamp(&self) -> impl Future<Output = u64>;
}
