use std::any::{Any, TypeId};
use std::fmt;

pub use mm1_proto::Message;

pub struct AnyMessage {
    message: Box<dyn Any + Send + 'static>,
    info:    TypeInfo,
}

struct TypeInfo {
    id:   TypeId,
    name: &'static str,
}

impl AnyMessage {
    pub fn new<T>(value: T) -> Self
    where
        T: Message,
    {
        let info = TypeInfo {
            id:   std::any::TypeId::of::<T>(),
            name: std::any::type_name::<T>(),
        };
        let message = Box::new(value);
        Self { info, message }
    }

    pub fn peek<T>(&self) -> Option<&T>
    where
        T: Message,
    {
        self.message.downcast_ref()
    }

    pub fn cast<T>(self) -> Result<T, Self>
    where
        T: Message,
    {
        let Self { message, info } = self;
        message
            .downcast()
            .map(|b| *b)
            .map_err(move |message| Self { info, message })
    }

    pub fn is<T>(&self) -> bool
    where
        T: Message,
    {
        self.message.is::<T>()
    }

    pub fn tid(&self) -> TypeId {
        self.info.id
    }

    pub fn type_name(&self) -> &'static str {
        self.info.name
    }
}

impl<M> From<M> for AnyMessage
where
    M: Message,
{
    fn from(message: M) -> Self {
        Self::new(message)
    }
}

impl fmt::Debug for AnyMessage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AnyMessage")
            .field("type_name", &self.type_name())
            .field("type_id", &self.tid())
            .finish()
    }
}
