use crate::actor::Actor;
use crate::error::TheatreError;
use crate::handler::Handler;
use crate::mailbox::{common::NotifyCounter, Envelope, Message, Recipient};
use async_trait::async_trait;
use futures::channel::mpsc::Sender;
use futures::SinkExt;
use std::fmt::{Debug, Formatter};

/// An Address to an Actor. Actor addresses allow for messages to be sent to actors.
///
/// A message must implement `Message`and the actor that should receive it needs to implement
/// `Handler<M>` where `M` is the message type.
///
/// Addresses are parametrized over the Actor they point to. To obtain an address that is
/// parametrized over the message type call `recipient()`.
///
/// Addresses keep actors running. Once all addresses are dropped the actor will stop. To obtain an
/// address to an actor without keeping it alive call `downgrade()` to get a `WeakAddr` (similar to
/// Arc & WeakArc).
pub struct Addr<A>
where
    A: 'static + Actor,
{
    tx: Sender<Envelope<A>>,
    leases: NotifyCounter,
}

impl<A> Addr<A>
where
    A: Actor + Send,
{
    pub(crate) fn new(tx: Sender<Envelope<A>>, leases: NotifyCounter) -> Self {
        Self { tx, leases }
    }

    /// Send a message and wait for the response. If the mailbox is full wait until there is capacity.
    pub async fn send<M>(&mut self, msg: M) -> Result<M::Return, TheatreError>
    where
        A: Handler<M>,
        M: Message + 'static,
    {
        let (env, return_result) = Envelope::pack(msg);

        self.tx.send(env).await.unwrap();

        return_result.await.map_err(|_| TheatreError::SendError)
    }

    /// Try send a message and wait for the response. If the mailbox is full or dropped return an error.
    pub async fn try_send<M>(&mut self, msg: M) -> Result<M::Return, TheatreError>
    where
        A: Handler<M>,
        M: Message + 'static,
    {
        let (env, return_result) = Envelope::pack(msg);

        self.tx.try_send(env).map_err(|_| TheatreError::SendError)?;

        Ok(return_result.await.unwrap())
    }

    /// Try send a message and discard the result. If the mailbox is full or dropped return an error.
    pub fn try_send_and_forget<M>(&mut self, msg: M) -> Result<(), TheatreError>
    where
        A: Handler<M>,
        M: Message + 'static,
    {
        let (env, _) = Envelope::pack(msg);

        self.tx.try_send(env).map_err(|_| TheatreError::SendError)?;
        Ok(())
    }

    /// Obtain a recipient to the actor.
    pub fn recipient<M>(self) -> Recipient<M>
    where
        A: Handler<M>,
        M: 'static + Message,
    {
        Recipient::new(Box::new(self))
    }

    /// Downgrade the address into a WeakAddr
    pub fn downgrade(&self) -> WeakAddr<A> {
        WeakAddr::new(self.tx.clone(), self.leases.clone())
    }
}

impl<A> Clone for Addr<A>
where
    A: Actor,
{
    fn clone(&self) -> Self {
        self.leases.inc();
        Addr::new(self.tx.clone(), self.leases.clone())
    }
}

impl<A> Drop for Addr<A>
where
    A: Actor,
{
    fn drop(&mut self) {
        self.leases.dec();
    }
}

impl<A> Debug for Addr<A>
where
    A: Actor,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Addr")
            .field("to", &std::any::type_name::<A>())
            .finish()
    }
}

/// A WeakAddr does not prevent the address from stopping when all Addr's are dropped.
/// WeakAddr can be upgraded to a Strong Addr by calling `upgrade()`.
#[derive(Debug)]
pub struct WeakAddr<A>
where
    A: 'static + Actor,
{
    tx: Sender<Envelope<A>>,
    leases: NotifyCounter,
}

impl<A> WeakAddr<A>
where
    A: Actor,
{
    pub(crate) fn new(tx: Sender<Envelope<A>>, leases: NotifyCounter) -> Self {
        Self { tx, leases }
    }

    /// Upgrade this `WeakAddr<A>` to an `Addr<A>`. Returns None if the mailbox is closed
    pub fn upgrade(&mut self) -> Option<Addr<A>> {
        if !self.tx.is_closed() {
            self.leases.inc();
            Some(Addr::new(self.tx.clone(), self.leases.clone()))
        } else {
            None
        }
    }
}

impl<A> Clone for WeakAddr<A>
where
    A: Actor,
{
    fn clone(&self) -> Self {
        Self {
            tx: self.tx.clone(),
            leases: self.leases.clone(),
        }
    }
}

#[async_trait]
pub(crate) trait MessageSender<M>: Send + Sync
where
    M: Message,
{
    async fn send(&mut self, msg: M) -> Result<M::Return, TheatreError>;
    async fn try_send(&mut self, msg: M) -> Result<M::Return, TheatreError>;
    fn try_send_and_forget(&mut self, msg: M) -> Result<(), TheatreError>;
    fn clone_recipient(&self) -> Recipient<M>;
}

#[async_trait]
impl<A, M> MessageSender<M> for Addr<A>
where
    A: Actor + Handler<M> + Send,
    M: 'static + Message,
{
    async fn send(&mut self, msg: M) -> Result<<M as Message>::Return, TheatreError> {
        self.send(msg).await
    }

    async fn try_send(&mut self, msg: M) -> Result<<M as Message>::Return, TheatreError> {
        self.try_send(msg).await
    }

    fn try_send_and_forget(&mut self, msg: M) -> Result<(), TheatreError> {
        self.try_send_and_forget(msg)
    }

    fn clone_recipient(&self) -> Recipient<M> {
        Addr::clone(&self).recipient()
    }
}

#[cfg(test)]
mod test {
    use crate::prelude::*;
    struct A;
    impl Actor for A {}

    struct Msg;
    impl Message for Msg {
        type Return = bool;
    }

    #[async_trait]
    impl Handler<Msg> for A {
        async fn handle(&mut self, _: Msg, _: &mut Context<Self>) -> <Msg as Message>::Return {
            true
        }
    }

    #[tokio::test]
    async fn send_msg() {
        let mut addr = A.spawn().await;
        assert!(addr.send(Msg).await.unwrap())
    }

    #[tokio::test]
    async fn try_send_msg() {
        let mut addr = A.spawn().await;
        assert!(addr.try_send(Msg).await.unwrap())
    }

    #[tokio::test]
    async fn try_send_and_forget_msg() {
        let (mut addr, mut handle) = A.start().await;
        addr.try_send_and_forget(Msg).unwrap();
        assert!(handle.heartbeat().await)
    }

    #[tokio::test]
    async fn clone_addr() {
        let (addr, mut handle) = A.start().await;
        let mut addr2 = Addr::clone(&addr);

        assert!(addr2.send(Msg).await.unwrap());
        assert!(handle.heartbeat().await)
    }
}
