//! Relayers
//!
//! This is the main entry point to the client library.
use crate::{Error, Relayer};
use futures::Future;
use nostr_rs_types::{Request, Response};
use std::{collections::HashMap, pin::Pin};
use tokio::sync::mpsc;

/// Clients
///
/// This is a set of outgoing connections to relayers. This struct can connect
/// async to N relayers offering a simple API to talk to all of them at the same
/// time, and to receive messages
#[derive(Debug)]
pub struct Pool {
    clients: HashMap<String, Relayer>,
    sender: mpsc::Sender<(Event, String)>,
    receiver: mpsc::Receiver<(Event, String)>,
}

impl Default for Pool {
    fn default() -> Self {
        Self::new()
    }
}

/// Client event
///
/// This type wraps a response a disconnected event. The disconnected will be
/// the last event to be sent.
#[derive(Debug, Clone)]
pub enum Event {
    /// A response
    Response(Box<Response>),
    /// A disconnection event
    Disconnected,
}

const DEFAULT_CHANNEL_BUFFER_SIZE: usize = 10_000;

impl Pool {
    /// Creates a new Relayers object
    pub fn new() -> Self {
        let (sender, receiver) = mpsc::channel(DEFAULT_CHANNEL_BUFFER_SIZE);
        Self {
            clients: HashMap::new(),
            receiver,
            sender,
        }
    }

    /// Tries to receive a message from any of the connected relayers
    pub fn try_recv(&mut self) -> Option<(Event, String)> {
        self.receiver.try_recv().ok()
    }

    /// Receives a message from any of the connected relayers
    pub async fn recv(&mut self) -> Option<(Event, String)> {
        self.receiver.recv().await
    }

    /// Sends a request to all the connected relayers
    pub async fn send(&self, request: Request) {
        for (_, sender) in self.clients.iter() {
            let _ = sender.send(request.clone()).await;
        }
    }

    /// Returns a vector to all outgoing connections
    pub fn get_connections(&self) -> Vec<&Relayer> {
        self.clients.values().collect::<Vec<&Relayer>>()
    }

    /// Returns the number of active connections.
    pub fn check_active_connections(&self) -> usize {
        self.clients
            .iter()
            .filter(|(_, relayer)| relayer.is_connected())
            .collect::<Vec<_>>()
            .len()
    }

    /// Creates a connection to a new relayer. If the connection is successful a
    /// Callback will be called, with a list of previously sent requests, and a
    /// Sender to send new requests to this relayer alone.
    ///
    /// The same callback will be called for every reconnection to the same
    /// relayer, also the callback will be called, giving the chance to re-send
    /// sent requests to the new connections
    ///
    /// This function will open a connection at most once, if a connection
    /// already exists false will be returned
    pub fn connect_to<F>(mut self, url: &str, on_connection: Option<F>) -> Result<Self, Error>
    where
        F: (Fn(&str, mpsc::Sender<Request>) -> Pin<Box<dyn Future<Output = ()> + Send>>)
            + Send
            + Sync
            + 'static,
    {
        if !self.clients.contains_key(url) {
            log::warn!("Connecting to {}", url);
            self.clients.insert(
                url.to_owned(),
                Relayer::new(self.sender.clone(), url, on_connection)?,
            );
        }

        Ok(self)
    }
}
