//! Relayers
//!
//! This is the main entry point to the client library.
use crate::{client::ActiveSubscription, Client, Error};
use futures::future::join_all;
use nostr_rs_types::{
    client::{self, subscribe},
    types::SubscriptionId,
    Response,
};
use std::collections::HashMap;
use tokio::sync::mpsc;
use url::Url;

/// 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<Url, Client>,
    sender: mpsc::Sender<(Event, Url)>,
    receiver: mpsc::Receiver<(Event, Url)>,
    subscriptions: HashMap<SubscriptionId, Vec<ActiveSubscription>>,
}

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,
            subscriptions: Default::default(),
            sender,
        }
    }

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

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

    /// Subscribes to all the connected relayers
    pub async fn subscribe(&mut self, subscription: subscribe::Subscribe) -> Result<(), Error> {
        let wait_all = self
            .clients
            .values()
            .map(|sender| sender.subscribe(subscription.clone()))
            .collect::<Vec<_>>();

        self.subscriptions.insert(
            subscription.subscription_id,
            join_all(wait_all)
                .await
                .into_iter()
                .collect::<Result<Vec<_>, _>>()?,
        );

        Ok(())
    }

    /// Sends a request to all the connected relayers
    pub async fn post(&self, request: client::Event) {
        let wait_all = self
            .clients
            .values()
            .map(|sender| sender.post(request.clone()))
            .collect::<Vec<_>>();

        join_all(wait_all).await;
    }

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

    /// 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.
    ///
    /// This function will open a connection at most once, if a connection
    /// already exists false will be returned
    pub fn connect_to(mut self, url: Url) -> Self {
        if !self.clients.contains_key(&url) {
            log::warn!("Connecting to {}", url);
            self.clients
                .insert(url.clone(), Client::new(self.sender.clone(), url));
        }

        self
    }
}
