use crate::{
    Multiaddr,
    Transport,
    StreamMuxer,
    connection::{
        Connected,
        ConnectedPoint,
        ConnectionHandler,
        Connection,
        ConnectionId,
        ConnectionLimit,
        EstablishedConnection,
        EstablishedConnectionIter,
        IntoConnectionHandler,
        PendingConnection,
        Substream,
        pool::Pool,
    },
    PeerId
};
use fnv::FnvHashMap;
use smallvec::SmallVec;
use std::{
    collections::hash_map,
    error,
    fmt,
};
use super::{Network, DialingOpts};
pub enum Peer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler
{
    
    Connected(ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>),
    
    
    
    Dialing(DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>),
    
    
    
    Disconnected(DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>),
    
    Local,
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler> fmt::Debug for
    Peer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        match self {
            Peer::Connected(p) => {
                f.debug_struct("Connected")
                    .field("peer", &p)
                    .finish()
            }
            Peer::Dialing(p) => {
                f.debug_struct("Dialing")
                    .field("peer", &p)
                    .finish()
            }
            Peer::Disconnected(p) => {
                f.debug_struct("Disconnected")
                    .field("peer", &p)
                    .finish()
            }
            Peer::Local => {
                f.debug_struct("Local")
                    .finish()
            }
        }
    }
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler>
    Peer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    pub(super) fn new(
        network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
        peer_id: PeerId
    ) -> Self {
        if peer_id == network.local_peer_id {
            return Peer::Local;
        }
        if network.pool.is_connected(&peer_id) {
            return Self::connected(network, peer_id)
        }
        if network.dialing.get_mut(&peer_id).is_some() {
            return Self::dialing(network, peer_id);
        }
        Self::disconnected(network, peer_id)
    }
    fn disconnected(
        network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
        peer_id: PeerId
    ) -> Self {
        Peer::Disconnected(DisconnectedPeer { network, peer_id })
    }
    fn connected(
        network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
        peer_id: PeerId
    ) -> Self {
        Peer::Connected(ConnectedPeer { network, peer_id })
    }
    fn dialing(
        network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
        peer_id: PeerId
    ) -> Self {
        Peer::Dialing(DialingPeer { network, peer_id })
    }
}
impl<'a, TTrans, TMuxer, TInEvent, TOutEvent, THandler>
    Peer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport<Output = (PeerId, TMuxer)> + Clone,
    TTrans::Error: Send + 'static,
    TTrans::Dial: Send + 'static,
    TMuxer: StreamMuxer + Send + Sync + 'static,
    TMuxer::OutboundSubstream: Send,
    TInEvent: Send + 'static,
    TOutEvent: Send + 'static,
    THandler: IntoConnectionHandler + Send + 'static,
    THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
    <THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
    <THandler::Handler as ConnectionHandler>::Error: error::Error + Send + 'static,
{
    
    
    
    pub fn is_connected(&self) -> bool {
        match self {
            Peer::Connected(..) => true,
            Peer::Dialing(peer) => peer.is_connected(),
            Peer::Disconnected(..) => false,
            Peer::Local => false
        }
    }
    
    
    
    pub fn is_dialing(&self) -> bool {
        match self {
            Peer::Dialing(_) => true,
            Peer::Connected(peer) => peer.is_dialing(),
            Peer::Disconnected(..) => false,
            Peer::Local => false
        }
    }
    
    
    
    pub fn is_disconnected(&self) -> bool {
        matches!(self, Peer::Disconnected(..))
    }
    
    
    
    
    
    
    
    pub fn dial<I>(self, address: Multiaddr, remaining: I, handler: THandler)
        -> Result<
            (ConnectionId, DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>),
            ConnectionLimit
        >
    where
        I: IntoIterator<Item = Multiaddr>,
    {
        let (peer_id, network) = match self {
            Peer::Connected(p) => (p.peer_id, p.network),
            Peer::Dialing(p) => (p.peer_id, p.network),
            Peer::Disconnected(p) => (p.peer_id, p.network),
            Peer::Local => return Err(ConnectionLimit { current: 0, limit: 0 })
        };
        let id = network.dial_peer(DialingOpts {
            peer: peer_id.clone(),
            handler,
            address,
            remaining: remaining.into_iter().collect(),
        })?;
        Ok((id, DialingPeer { network, peer_id }))
    }
    
    
    
    pub fn into_connected(self) -> Option<
        ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    > {
        match self {
            Peer::Connected(peer) => Some(peer),
            Peer::Dialing(peer) => peer.into_connected(),
            Peer::Disconnected(..) => None,
            Peer::Local => None,
        }
    }
    
    
    
    pub fn into_dialing(self) -> Option<
        DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    > {
        match self {
            Peer::Dialing(peer) => Some(peer),
            Peer::Connected(peer) => peer.into_dialing(),
            Peer::Disconnected(..) => None,
            Peer::Local => None
        }
    }
    
    
    pub fn into_disconnected(self) -> Option<
        DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    > {
        match self {
            Peer::Disconnected(peer) => Some(peer),
            _ => None,
        }
    }
}
pub struct ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
    peer_id: PeerId,
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler>
    ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    pub fn id(&self) -> &PeerId {
        &self.peer_id
    }
    
    pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler> {
        Peer::Connected(self)
    }
    
    pub fn connection(&mut self, id: ConnectionId)
        -> Option<EstablishedConnection<TInEvent>>
    {
        self.network.pool.get_established(id)
    }
    
    pub fn num_connections(&self) -> u32 {
        self.network.pool.num_peer_established(&self.peer_id)
    }
    
    
    
    pub fn is_dialing(&self) -> bool {
        self.network.dialing.contains_key(&self.peer_id)
    }
    
    
    pub fn into_dialing(self) -> Option<
        DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    > {
        if self.network.dialing.contains_key(&self.peer_id) {
            Some(DialingPeer { network: self.network, peer_id: self.peer_id })
        } else {
            None
        }
    }
    
    pub fn connections(&mut self) ->
        EstablishedConnectionIter<
            impl Iterator<Item = ConnectionId>,
            TInEvent,
            TOutEvent,
            THandler,
            TTrans::Error,
            <THandler::Handler as ConnectionHandler>::Error>
    {
        self.network.pool.iter_peer_established(&self.peer_id)
    }
    
    pub fn some_connection(&mut self)
        -> EstablishedConnection<TInEvent>
    {
        self.connections()
            .into_first()
            .expect("By `Peer::new` and the definition of `ConnectedPeer`.")
    }
    
    pub fn disconnect(self)
        -> DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    {
        self.network.disconnect(&self.peer_id);
        DisconnectedPeer { network: self.network, peer_id: self.peer_id }
    }
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler> fmt::Debug for
    ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.debug_struct("ConnectedPeer")
            .field("peer_id", &self.peer_id)
            .field("established", &self.network.pool.iter_peer_established_info(&self.peer_id))
            .field("attempts", &self.network.dialing.get(&self.peer_id))
            .finish()
    }
}
pub struct DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
    peer_id: PeerId,
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler>
    DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    pub fn id(&self) -> &PeerId {
        &self.peer_id
    }
    
    pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler> {
        Peer::Dialing(self)
    }
    
    
    pub fn disconnect(self)
        -> DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
    {
        self.network.disconnect(&self.peer_id);
        DisconnectedPeer { network: self.network, peer_id: self.peer_id }
    }
    
    
    
    pub fn is_connected(&self) -> bool {
        self.network.pool.is_connected(&self.peer_id)
    }
    
    pub fn into_connected(self)
        -> Option<ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>>
    {
        if self.is_connected() {
            Some(ConnectedPeer { peer_id: self.peer_id, network: self.network })
        } else {
            None
        }
    }
    
    
    pub fn attempt(&mut self, id: ConnectionId)
        -> Option<DialingAttempt<'_, TInEvent>>
    {
        if let hash_map::Entry::Occupied(attempts) = self.network.dialing.entry(self.peer_id.clone()) {
            if let Some(pos) = attempts.get().iter().position(|s| s.current.0 == id) {
                if let Some(inner) = self.network.pool.get_outgoing(id) {
                    return Some(DialingAttempt { pos, inner, attempts })
                }
            }
        }
        None
    }
    
    pub fn attempts(&mut self)
        -> DialingAttemptIter<'_,
            TInEvent,
            TOutEvent,
            THandler,
            TTrans::Error,
            <THandler::Handler as ConnectionHandler>::Error>
    {
        DialingAttemptIter::new(&self.peer_id, &mut self.network.pool, &mut self.network.dialing)
    }
    
    
    
    pub fn some_attempt(&mut self)
        -> DialingAttempt<'_, TInEvent>
    {
        self.attempts()
            .into_first()
            .expect("By `Peer::new` and the definition of `DialingPeer`.")
    }
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler> fmt::Debug for
    DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.debug_struct("DialingPeer")
            .field("peer_id", &self.peer_id)
            .field("established", &self.network.pool.iter_peer_established_info(&self.peer_id))
            .field("attempts", &self.network.dialing.get(&self.peer_id))
            .finish()
    }
}
pub struct DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    peer_id: PeerId,
    network: &'a mut Network<TTrans, TInEvent, TOutEvent, THandler>,
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler> fmt::Debug for
    DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.debug_struct("DisconnectedPeer")
            .field("peer_id", &self.peer_id)
            .finish()
    }
}
impl<'a, TTrans, TInEvent, TOutEvent, THandler>
    DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>
where
    TTrans: Transport,
    THandler: IntoConnectionHandler,
{
    pub fn id(&self) -> &PeerId {
        &self.peer_id
    }
    
    pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler> {
        Peer::Disconnected(self)
    }
    
    
    
    
    
    
    
    
    pub fn set_connected<TMuxer>(
        self,
        connected: Connected,
        connection: Connection<TMuxer, THandler::Handler>,
    ) -> Result<
        ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler>,
        ConnectionLimit
    > where
        TInEvent: Send + 'static,
        TOutEvent: Send + 'static,
        THandler: Send + 'static,
        TTrans::Error: Send + 'static,
        THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
        <THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
        <THandler::Handler as ConnectionHandler>::Error: error::Error + Send + 'static,
        TMuxer: StreamMuxer + Send + Sync + 'static,
        TMuxer::OutboundSubstream: Send,
    {
        if connected.peer_id != self.peer_id {
            panic!("Invalid peer ID given: {:?}. Expected: {:?}", connected.peer_id, self.peer_id)
        }
        self.network.pool.add(connection, connected)
            .map(move |_id| ConnectedPeer {
                network: self.network,
                peer_id: self.peer_id,
            })
    }
}
#[derive(Debug, Clone)]
pub(super) struct DialingState {
    
    pub(super) current: (ConnectionId, Multiaddr),
    
    pub(super) remaining: Vec<Multiaddr>,
}
pub struct DialingAttempt<'a, TInEvent> {
    
    inner: PendingConnection<'a, TInEvent>,
    
    attempts: hash_map::OccupiedEntry<'a, PeerId, SmallVec<[DialingState; 10]>>,
    
    pos: usize,
}
impl<'a, TInEvent>
    DialingAttempt<'a, TInEvent>
{
    
    pub fn id(&self) -> ConnectionId {
        self.inner.id()
    }
    
    pub fn peer_id(&self) -> &PeerId {
        self.attempts.key()
    }
    
    pub fn address(&self) -> &Multiaddr {
        match self.inner.endpoint() {
            ConnectedPoint::Dialer { address } => address,
            ConnectedPoint::Listener { .. } => unreachable!("by definition of a `DialingAttempt`.")
        }
    }
    
    
    
    
    
    pub fn abort(mut self) {
        self.attempts.get_mut().remove(self.pos);
        if self.attempts.get().is_empty() {
            self.attempts.remove();
        }
        self.inner.abort();
    }
    
    
    pub fn add_address(&mut self, addr: Multiaddr) {
        let remaining = &mut self.attempts.get_mut()[self.pos].remaining;
        if remaining.iter().all(|a| a != &addr) {
            remaining.push(addr);
        }
    }
}
pub struct DialingAttemptIter<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr> {
    
    peer_id: &'a PeerId,
    
    pool: &'a mut Pool<TInEvent, TOutEvent, THandler, TTransErr, THandlerErr>,
    
    
    
    
    
    dialing: &'a mut FnvHashMap<PeerId, SmallVec<[DialingState; 10]>>,
    
    pos: usize,
    
    end: usize,
}
impl<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr>
    DialingAttemptIter<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr>
{
    fn new(
        peer_id: &'a PeerId,
        pool: &'a mut Pool<TInEvent, TOutEvent, THandler, TTransErr, THandlerErr>,
        dialing: &'a mut FnvHashMap<PeerId, SmallVec<[DialingState; 10]>>,
    ) -> Self {
        let end = dialing.get(peer_id).map_or(0, |conns| conns.len());
        Self { pos: 0, end, pool, dialing, peer_id }
    }
    
    pub fn next<'b>(&'b mut self) -> Option<DialingAttempt<'b, TInEvent>> {
        
        
        
        let end = self.dialing.get(self.peer_id).map_or(0, |conns| conns.len());
        if self.end > end {
            self.end = end;
            self.pos -= 1;
        }
        if self.pos == self.end {
            return None
        }
        if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(self.peer_id.clone()) {
            let id = attempts.get()[self.pos].current.0;
            if let Some(inner) = self.pool.get_outgoing(id) {
                let conn = DialingAttempt { pos: self.pos, inner, attempts };
                self.pos += 1;
                return Some(conn)
            }
        }
        None
    }
    
    pub fn into_first<'b>(self)
        -> Option<DialingAttempt<'b, TInEvent>>
    where 'a: 'b
    {
        if self.pos == self.end {
            return None
        }
        if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(self.peer_id.clone()) {
            let id = attempts.get()[self.pos].current.0;
            if let Some(inner) = self.pool.get_outgoing(id) {
                return Some(DialingAttempt { pos: self.pos, inner, attempts })
            }
        }
        None
    }
}