//! Transport layer abstraction.

use self::pool::NonBlockingStream;
pub use super::event::{GenericSource, IONotifier, IOSource};
use async_trait::async_trait;
use bytes::Bytes;
use std::io;
use std::net::Shutdown;
use std::net::SocketAddr;

pub mod pool;
pub mod stream;

#[cfg(not(target_arch = "wasm32"))] mod tcp;
#[cfg(not(target_arch = "wasm32"))] mod udp;
#[cfg(target_arch = "wasm32")] mod web;
#[cfg(not(target_arch = "wasm32"))] mod websocket;

#[cfg(not(target_arch = "wasm32"))]
mod arch {
    use super::*;
    pub use tcp::Factory as TcpFactory;
    pub use udp::Factory as UdpFactory;
    pub use websocket::Factory as WSFactory;
}

#[cfg(target_arch = "wasm32")]
mod arch {
    use super::*;
    pub use web::websocket::Factory as WSFactory;
}

pub use arch::*;

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Address {
    IP(SocketAddr),
    WSUrl(String),
    PhoneNumber,
}

#[derive(Debug)]
pub enum TransportError {
    /// Both directions are closed. The endpoint will attempt to [renew](Transport::renew) the
    /// transport.
    BothTerminated,
    /// The network transport is closed in one direction ([Transport::try_send] and [Transport::try_recv]
    /// define each direction). When both directions are terminated, the endpoint will
    /// attempt to [renew](Transport::renew) the transport.
    HalfTerminated,
    /// The requested direction (send/recv) is not ready for data yet. The endpoint need to come
    /// back later to check.
    NotReady,
}

/// Describes an opaque object that could be used by an endpoint to transmite data to/from
/// the network.
///
/// When data is transmitted, [Transport::try_send] and [Transport::try_recv] are used by the endpoint
/// worker in a non-blocking manner so that they should return immediately if the direction is not ready
/// ([TransportError::NotReady]).
///
/// Whenever both directions are closed (endpoint gets
/// [TransportError::HalfTerminated] in both `try_send` and `try_recv`, or [TransportError::BothTerminated] in either call), the
/// endpoint will attempt
/// to *renew* the transport by transferring the ownership of the transport object back into the
/// [renew](Transport::renew) function and using the returned new object (`Box<dyn Transport>`, which could still be
/// the original one, depending on the implementation of the transport) as the new transport. It
/// could also intentionally used by the transport implementation to replace/update its internal
/// data stream/socket. This semantic ensures that a transport needs to be drained in both
/// directions in order to be reconfigured/replaced so there is no data loss by the endpoint.
#[async_trait]
pub trait Transport: Send {
    fn try_send(&mut self, data: Option<Bytes>) -> Result<bool, TransportError>;
    fn try_recv(&mut self) -> Result<Bytes, TransportError>;
    fn source(&mut self) -> IOSource;
    fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
    /// Renew a transport object. The `closer` is given to the tranport implementation to be used
    /// as a handle to shutdown the returned transport. This is because after `renew` returns, the
    /// renewed transport object is owned by the endpoint (i.e, the ownership of a transport object
    /// transfers briefly back to the transport implementation with the help of this function).
    /// This is useful when a working transport needs to be updated or replaced.
    /// The implementation can invoke `closer.shutdown()` to trigger [Transport::shutdown] inside the
    /// endpoint and eventually shut down both directions to trigger `renew` again so no data is lost by the endpoint.
    /// The `closer` only shuts down the returned transport object from this function, so each invocation
    /// of `renew` gives a different `closer`.
    async fn renew(
        self: Box<Self>, closer: super::endpoint::TransportCloser,
    ) -> (Box<dyn Transport>, Result<Vec<Bytes>, TransportError>);

    fn take_stream(self: Box<Self>) -> Result<Box<dyn NonBlockingStream>, TransportError>;
}
