use std::{
    any::{Any, TypeId},
    collections::HashMap,
    vec,
};

use async_trait::async_trait;
use futures::future::join_all;

/// Event bus
/// # Using example
/// ```rust
/// use eventbus_rs::{Subscriber, EventBus};
/// use async_trait::async_trait;
/// use std::any::Any;
/// // Create new topic
/// type Topic = ();
///
/// // Impl our subscriber
/// struct SimpleSubscriber;
/// #[async_trait]
/// impl Subscriber for SimpleSubscriber {
///     async fn handle(&self, message: &(dyn Any + Send + Sync)) {}
/// }
/// async fn subscribe_and_publish() {
///     // Create event bus
///     let mut event_bus = EventBus::new().await;
///     // Subscribe on our topic
///     event_bus.subscribe::<Topic>(Box::new(SimpleSubscriber {})).await;
///     // Publish event
///     event_bus.publish::<Topic>(&"Hello".to_string()).await;
/// }
/// ```
pub struct EventBus(HashMap<TypeId, Topic>);

impl EventBus {
    /// Creates new event bus.
    pub async fn new() -> EventBus {
        EventBus { 0: HashMap::new() }
    }
    async fn register_topic<T: 'static>(&mut self, topic: Topic) {
        self.0.insert(TypeId::of::<T>(), topic);
    }

    /// Subscribes on topic.
    pub async fn subscribe<T: 'static>(
        &mut self,
        subscriber: Box<dyn Subscriber>,
    ) {
        match self.0.get_mut(&TypeId::of::<T>()) {
            Some(topic) => topic.0.push(subscriber),
            None => {
                let topic = Topic(vec![subscriber]);
                self.register_topic::<T>(topic).await;
            }
        }
    }

    /// Publishes event.
    pub async fn publish<T: 'static>(&self, message: &(dyn Any + Send + Sync)) {
        match self.0.get(&TypeId::of::<T>()) {
            Some(topic) => topic.publish(message).await,
            None => (),
        }
    }

    /// Creates new topic.
    pub async fn new_topic<T: 'static>(&mut self) {
        if !self.0.contains_key(&TypeId::of::<T>()) {
            let topic = Topic(Vec::new());
            self.register_topic::<T>(topic).await;
        }
    }
}

/// Topic in event bus
struct Topic(Vec<Box<dyn Subscriber>>);
impl Topic {
    /// Publishes event.
    pub async fn publish(&self, message: &(dyn Any + Send + Sync)) {
        let mut futures = vec![];
        for i in 0..self.0.len() {
            if let Some(subscriber) = self.0.get(i) {
                futures.push(subscriber.handle(message));
            }
        }

        join_all(futures).await;
    }
}

/// Subscriber trait
/// # Example
///
/// ```rust
/// use eventbus_rs::Subscriber;
/// use std::any::Any;
/// use async_trait::async_trait;
///
/// struct SimpleSubscriber;
/// #[async_trait]
/// impl Subscriber for SimpleSubscriber {
///         async fn handle(&self, message: &(dyn Any + Send + Sync)) {
///             match message.downcast_ref::<String>() {
///                 Some(str) => {
///                     println!("{}", str);
///                 }
///                 None => ()
///             }
///         }
///}
/// ```
#[async_trait]
pub trait Subscriber {
    async fn handle(&self, message: &(dyn Any + Send + Sync));
}

#[cfg(test)]
mod tests {

    use futures::executor::block_on;

    use crate::EventBus;

    mod subscribers {
        use std::any::Any;

        use async_trait::async_trait;

        use crate::Subscriber;

        pub type Topic = ();

        pub struct SimpleSubscriber;

        #[async_trait]
        impl Subscriber for SimpleSubscriber {
            async fn handle(&self, message: &(dyn Any + Send + Sync)) {
                check_message(message);
            }
        }

        pub fn check_message(message: &(dyn Any + Send + Sync)) {
            match message.downcast_ref::<String>() {
                Some(message) => {
                    if message != "Hello" {
                        panic!("Message != Hello")
                    }
                }
                None => panic!("No message"),
            }
        }
    }

    #[test]
    fn test_event_bus() {
        block_on(async {
            let event_bus = create_eventbus_and_topic().await;
            event_bus
                .publish::<subscribers::Topic>(&"Hello".to_string())
                .await;
        });
    }

    async fn create_eventbus_and_topic() -> EventBus {
        let mut event_bus = EventBus::new().await;
        event_bus
            .subscribe::<subscribers::Topic>(Box::new(subscribers::SimpleSubscriber {}))
            .await;
        event_bus
    }
    #[test]
    #[should_panic]
    fn test_event_bus2() {
        block_on(async {
            let event_bus = create_eventbus_and_topic().await;
            event_bus
                .publish::<subscribers::Topic>(&"Not a hello".to_string())
                .await;
        });
    }
}
