//! 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 parking_lot::RwLock;
use std::{collections::HashMap, pin::Pin, sync::Arc};
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 Relayers {
    clients: HashMap<String, Relayer>,
    messages: Arc<RwLock<Vec<Request>>>,
    sender: mpsc::Sender<(Response, String)>,
}

impl Relayers {
    /// Creates a new Relayers object
    pub fn new() -> (Self, mpsc::Receiver<(Response, String)>) {
        let (sender, receiver) = mpsc::channel(100_000);
        (
            Self {
                clients: HashMap::new(),
                messages: Arc::new(RwLock::new(vec![])),
                sender,
            },
            receiver,
        )
    }

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

    /// Returns the number of active connections. If a connection to a relayer
    /// is not active it will be removed from the list
    pub fn check_active_connections(&mut self) -> usize {
        let mut to_remove = vec![];
        for (url, client) in self.clients.iter() {
            if !client.is_running() {
                to_remove.push(url.to_owned());
            }
        }

        for url in to_remove.iter() {
            self.clients.remove(url);
        }

        self.clients.len()
    }

    /// 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;
        }

        // Add a copy of the request for incoming connections.
        self.messages.write().push(request);
    }

    /// 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 async fn connect_to<F>(
        &mut self,
        url: &str,
        max_connections_attempts: u16,
        on_connection: Option<F>,
    ) -> Result<bool, Error>
    where
        F: (Fn(Vec<Request>, mpsc::Sender<Request>) -> Pin<Box<dyn Future<Output = ()> + Send>>)
            + Send
            + Sync
            + 'static,
    {
        Ok(if self.clients.get(url).is_some() {
            false
        } else {
            log::warn!("Connecting to {}", url);
            self.clients.insert(
                url.to_owned(),
                Relayer::new(
                    self.sender.clone(),
                    self.messages.clone(),
                    max_connections_attempts,
                    url,
                    on_connection,
                )?,
            );
            true
        })
    }

    /// Disconnects from all relayers
    pub async fn stop(&mut self) {
        let keys = self.clients.keys().cloned().collect::<Vec<_>>();

        for key in keys.iter() {
            if let Some(client) = self.clients.remove(key) {
                client.stop().await;
            }
        }
    }
}
