#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "test", feature(async_closure))]
//! ## Example
//!
//! ```rust
//! use hive_pubsub::{Hive, PubSub};
//! 
//! let mut hive = Hive::new(
//!     |users, data| {
//!         println!("Received data! [{:?}]: {}.", users, data);
//!     }
//! );
//! 
//! hive.subscribe_multiple(1, vec! [ 2, 5 ]).unwrap();
//! hive.subscribe_multiple(3, vec! [ 2, 6 ]).unwrap();
//! hive.subscribe_multiple(4, vec! [ 2, 5 ]).unwrap();
//! 
//! hive.drop_topic(&6).unwrap();
//! hive.publish(&6, "This will not appear.".to_string()).unwrap();
//! hive.publish(&2, "This will appear for all three clients.".to_string()).unwrap();
//! 
//! hive.drop_client(&3).unwrap();
//! hive.publish(&2, "This will appear for two of the clients.".to_string()).unwrap();
//! hive.publish(&5, "This will also appear for two of the clients.".to_string()).unwrap();
//! ```
//!
//! ## Example, for use with MongoDB.
//! 
//! This example only works with MongoDB and the sync feature enabled. This is for compatibilty reasons down the line, feel free to fork and change a few links to add async compat.
//!
//! ```no_run
//! // test
//! ```
//!
//! ## Design
//!
//! Hive is designed to be slotted into any server application to act as a middle-man between you and your clients, it will automatically distribute any notifications you give it to all relevant connected clients and other nodes.
//!
//! <p align="center">
//!   <img src="https://gitlab.insrt.uk/insert/hive/-/raw/master/assets/concept.png" />
//! </p>
//!
//! <script>console.log("hive-pubsub, crated by insrt.uk")</script>

mod hive;
mod types;
pub mod backend;

pub use hive::Hive;
pub use types::PubSub;

#[cfg(test)]
mod tests {
    use crate::{Hive, PubSub};

    #[test]
    fn simple() {
        let hive = Hive::new(
            |users, data| {
                println!("We received data! For {:?}, data: {}.", users, data);
            }
        );

        hive.subscribe_multiple("0001".to_string(), vec! [ "0002".to_string(), "0005".to_string() ]).unwrap();
        hive.subscribe_multiple("0003".to_string(), vec! [ "0002".to_string(), "0006".to_string() ]).unwrap();
        hive.subscribe_multiple("0004".to_string(), vec! [ "0002".to_string(), "0005".to_string() ]).unwrap();

        hive.drop_topic(&"0006".to_string()).unwrap();
        hive.publish(&"0006".to_string(), "This will not appear.".to_string()).unwrap();
        hive.publish(&"0002".to_string(), "This will appear for all three clients.".to_string()).unwrap();

        hive.drop_client(&"0003".to_string()).unwrap();
        hive.publish(&"0002".to_string(), "This will appear for two of the clients.".to_string()).unwrap();
        hive.publish(&"0005".to_string(), "This will also appear for two of the clients.".to_string()).unwrap();
    }

    #[test]
    fn generics() {
        let hive: Hive<i32, u32, String> = Hive::new(
            |users, data| {
                println!("We received data! For {:?}, data: {}.", users, data);
            }
        );

        hive.subscribe_multiple(5, vec! [ 2, 3 ]).unwrap();
        hive.publish(&2, "This will appear for one of the clients.".to_string()).unwrap();
    }

    #[tokio::test]
    #[cfg(feature = "test")]
    async fn mongo() {
        dotenv::dotenv().ok();

        use std::env::var;
        use mongodb::Client;
        use futures::FutureExt;
        use tokio::sync::mpsc::channel;

        use crate::{PubSub};
        use crate::backend::mongo::{MongodbPubSub, publish};
        
        // You'll want to have some sort of worker which can
        // handle incoming messages and deal with them accordingly.
        // Here we are just using a channel with predictable data
        // so we can just use assert_eq!().
        let ( sender, mut receiver ) = channel(100);

        let client = Client::with_uri_str(&var("MONGODB_URI").expect("Specify MONGODB_URI in .env or pass in."))
            .await
            .unwrap();

        let mut hive = MongodbPubSub::<i32, i32, String>::new(
            move |_ids, data| {
                let mut s = sender.clone();
                // We just send the data into the channel.
                tokio::spawn(async move {
                    s.send(data).await.unwrap();
                });
            },
            client.database("hive").collection("pubsub")
        );

        // We need to subscribe to the topic to get any data.
        hive.subscribe(0, 0).unwrap();

        // Listen to MongoDB collection.
        let listener = hive.clone();
        let fut = listener.listen().fuse();

        // We are setting source here to make the hive instance
        // accept incoming data. Since `source` is just a String,
        // it means we just cloned the original value above and
        // are now replacing it.
        hive.set_source("1234".to_string());

        let op = async {
            // Delay so that the other thread can send a message.
            tokio::time::delay_for(tokio::time::Duration::from_secs(2)).await;

            // Hence, we publish our data.
            publish(&hive, &0, "The data.".to_string()).await.unwrap();

            // And we should receive it back twice.
            assert_eq!(receiver.recv().await.unwrap(), "The data.");
            assert_eq!(receiver.recv().await.unwrap(), "The data.");
        }.fuse();

        futures::pin_mut!(fut, op);

        futures::select! {
            _ = fut => println!("Listener exited early."),
            _ = op  => println!("Successfully received data back."),
        };
    }
}
