//! Structures related to the actor mailbox
mod address;
mod common;
mod envelope;
mod message;
mod recipient;

use futures::channel::mpsc::{channel, Receiver, Sender};
use futures::future::FutureExt;
use futures::select;
use futures::StreamExt;
use thiserror::Error;

pub use address::{Addr, WeakAddr};
pub(self) use envelope::Envelope;
pub use message::Message;
pub use recipient::Recipient;

use crate::actor::Actor;
use crate::mailbox::common::NotifyCounter;

#[derive(Debug)]
struct AddressLeaseControl<A>
where
    A: 'static + Actor,
{
    sender: Sender<Envelope<A>>,
    leases: NotifyCounter,
}

impl<A> AddressLeaseControl<A>
where
    A: 'static + Actor,
{
    pub async fn recv(&mut self) {
        self.leases.notified().await;
    }

    pub fn new_addr(&self) -> Addr<A> {
        self.leases.inc();
        Addr::new(self.sender.clone(), self.leases.clone())
    }

    pub fn should_close(&self) -> bool {
        self.leases.equals(0)
    }
}

#[derive(Debug)]
pub(crate) struct Mailbox<A>
where
    A: 'static + Actor,
{
    receiver: Receiver<Envelope<A>>,
    address_ctrl: AddressLeaseControl<A>,
    open: bool,
}

impl<A> Mailbox<A>
where
    A: Actor,
{
    pub fn new(size: usize) -> Self {
        let (tx, rx) = channel(size);

        Self {
            receiver: rx,
            open: true,
            address_ctrl: AddressLeaseControl {
                sender: tx,
                leases: NotifyCounter::default(),
            },
        }
    }

    pub async fn recv(&mut self) -> Result<Envelope<A>, MailboxError> {
        while self.open {
            select! {
                msg = self.receiver.next() => return msg.ok_or(MailboxError::Closed),
                _ = self.address_ctrl.recv().fuse() => if self.address_ctrl.should_close() {
                    self.open = false;
                }
            }
        }
        Err(MailboxError::Closed)
    }

    pub fn close(&mut self) {
        self.open = false;
        self.receiver.close();
    }

    pub fn new_addr(&self) -> Addr<A> {
        self.address_ctrl.new_addr()
    }
}

#[derive(Error, Debug)]
pub enum MailboxError {
    #[error("Mailbox is closed")]
    Closed,
}
