// License: see LICENSE file at root directory of `master` branch

//! # Kit for notifications

use {
    core::time::Duration,
    std::{
        collections::HashMap,
        io::{Error, ErrorKind},
    },

    dbus::{
        arg::{RefArg, Variant},
        blocking::Connection,
    },
};

/// # Message
enum Msg<S> where S: AsRef<str> {

    /// # Success
    Success(S),

    /// # Failure
    Failure(S),

}

impl<S> AsRef<str> for Msg<S> where S: AsRef<str> {

    fn as_ref(&self) -> &str {
        match self {
            Msg::Success(s) | Msg::Failure(s) => s.as_ref(),
        }
    }

}

/// # Notification
#[derive(Debug)]
pub struct Notification {

    /// # App name
    pub app_name: Option<String>,

    /// # App icon
    pub app_icon: Option<String>,

}

impl Notification {

    /// # Calls D-Bus, if available
    ///
    /// If there is any error, it will be printed to stderr.
    fn call_dbus<S>(&self, msg: Msg<S>) where S: AsRef<str> {
        let job = || -> core::result::Result<(), dbus::Error> {
            // Specifications: https://developer.gnome.org/notification-spec/
            let conn = Connection::new_session()?;
            let proxy = conn.with_proxy("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Duration::from_millis(1_000));

            let app_name = self.app_name.as_deref().unwrap_or("[Unknown]");
            let actions: Vec<&str> = vec![];
            let hints: HashMap<String, Variant<Box<dyn RefArg + 'static>>> = HashMap::new();
            proxy.method_call("org.freedesktop.Notifications", "Notify", (
                app_name,
                0_u32,
                match self.app_icon.as_deref() {
                    Some(app_icon) => app_icon,
                    None => match msg {
                        Msg::Success(_) => "dialog-information",
                        Msg::Failure(_) => "dialog-error",
                    },
                },
                app_name,
                msg.as_ref(),
                actions,
                hints,
                -1_i32,
            ))
        };

        if let Err(err) = job() {
            eprintln!("Failed calling D-Bus: {err:?}", err=err);
        }
    }

    /// # Shows a successful message (if available)
    ///
    /// If there is any error, it will be printed to stderr.
    pub fn show_success<S>(&self, msg: S) where S: AsRef<str> {
        self.call_dbus(Msg::Success(msg));
    }

    /// # Shows a message of failure (if available)
    ///
    /// - If there is any error, it will be printed to stderr.
    /// - Returning error is a simple wrapper of your message.
    pub fn show_failure<S>(&self, msg: S) -> Error where S: AsRef<str> {
        self.call_dbus(Msg::Failure(&msg));
        Error::new(ErrorKind::Other, msg.as_ref())
    }

}
