///////////////////////////////////////////////////////////////////////////////
// Name:        src/gtk/notifmsg.cpp
// Purpose:     wxNotificationMessage for wxGTK using libnotify.
// Author:      Vadim Zeitlin
// Created:     2012-07-25
// Copyright:   (c) 2012 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_NOTIFICATION_MESSAGE && wxUSE_LIBNOTIFY

#include "wx/notifmsg.h"

#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/icon.h"
#endif // WX_PRECOMP

#include <libnotify/notify.h>

#include "wx/module.h"
#include "wx/private/notifmsg.h"
#include <wx/stockitem.h>

// General note about error handling: as notifications are meant to be
// non-intrusive, we use wxLogDebug() and not wxLogError() if anything goes
// wrong here to avoid spamming the user with message boxes. As all methods
// return boolean indicating success or failure, the caller could show the
// notification in some other way or notify about the error itself if needed.
#include "wx/gtk/private/error.h"

// ----------------------------------------------------------------------------
// A module for cleaning up libnotify on exit.
// ----------------------------------------------------------------------------

class wxLibnotifyModule : public wxModule
{
public:
    virtual bool OnInit() override
    {
        // We're initialized on demand.
        return true;
    }

    virtual void OnExit() override
    {
        if ( notify_is_initted() )
            notify_uninit();
    }

    // Do initialize the library.
    static bool Initialize()
    {
        if ( !notify_is_initted() )
        {
            if ( !notify_init(wxTheApp->GetAppName().utf8_str()) )
                return false;
        }

        return true;
    }

private:
    wxDECLARE_DYNAMIC_CLASS(wxLibnotifyModule);
};

wxIMPLEMENT_DYNAMIC_CLASS(wxLibnotifyModule, wxModule);

// ============================================================================
// wxNotificationMessage implementation
// ============================================================================

class wxLibNotifyMsgImpl;

extern "C" {
static
void wxLibNotifyMsgImplActionCallback(NotifyNotification *notification,
                                                         char *action,
                                                         gpointer user_data);

static void closed_notification(NotifyNotification* notification, void* user_data);
}

class wxLibNotifyMsgImpl : public wxNotificationMessageImpl
{
public:
    wxLibNotifyMsgImpl(wxNotificationMessageBase* notification) :
        wxNotificationMessageImpl(notification),
        m_notification(nullptr),
        m_flags(wxICON_INFORMATION)
    {
        if ( !wxLibnotifyModule::Initialize() )
            wxLogDebug("Could not initialize libnotify");

    }

    virtual ~wxLibNotifyMsgImpl()
    {
        if ( m_notification )
            g_object_unref(m_notification);
    }

    bool CreateOrUpdateNotification()
    {
        if ( !wxLibnotifyModule::Initialize() )
            return false;

        // Determine the GTK+ icon to use from flags and also set the urgency
        // appropriately.
        const char* icon;
        switch ( m_flags )
        {
            case wxICON_INFORMATION:
                icon = "dialog-information";
                break;

            case wxICON_WARNING:
                icon = "dialog-warning";
                break;

            case wxICON_ERROR:
                icon = "dialog-error";
                break;

            default:
                wxFAIL_MSG( "Unknown notification message flags." );
                return false;
        }

        // Create the notification or update an existing one if we had already been
        // shown before.
        if ( !m_notification )
        {
            m_notification = notify_notification_new
                             (
                                m_title.utf8_str(),
                                m_message.utf8_str(),
                                icon
#if !wxUSE_LIBNOTIFY_0_7
                                // There used to be an "associated window"
                                // parameter in this function but it has
                                // disappeared by 0.7, so use it for previous
                                // versions only.
                                , 0
#endif // libnotify < 0.7
                             );
            if ( !m_notification )
            {
                wxLogDebug("Failed to creation notification.");

                return false;
            }


            g_signal_connect(m_notification, "closed", G_CALLBACK(closed_notification), this);

        }
        else
        {
            if ( !notify_notification_update
                  (
                    m_notification,
                    m_title.utf8_str(),
                    m_message.utf8_str(),
                    icon
                  ) )
            {
                wxLogDebug(wxS("notify_notification_update() unexpectedly failed."));
            }
        }

#ifdef __WXGTK3__
        // Explicitly specified icon name overrides the implicit one determined by
        // the flags.
        if ( m_icon.IsOk() )
        {
            notify_notification_set_image_from_pixbuf(
                m_notification,
                m_icon.GetPixbufNoMask()
            );
        }
#endif

        return true;
    }

    virtual bool Show(int timeout) override
    {
        if ( !CreateOrUpdateNotification() )
            return false;

        // Set the notification parameters not specified during creation.
        notify_notification_set_timeout
        (
            m_notification,
            timeout == wxNotificationMessage::Timeout_Auto ? NOTIFY_EXPIRES_DEFAULT
                                    : timeout == wxNotificationMessage::Timeout_Never ? NOTIFY_EXPIRES_NEVER
                                                               : 1000*timeout
        );

        NotifyUrgency urgency;
        switch ( m_flags )
        {
            case wxICON_INFORMATION:
                urgency = NOTIFY_URGENCY_LOW;
                break;

            case wxICON_WARNING:
                urgency = NOTIFY_URGENCY_NORMAL;
                break;

            case wxICON_ERROR:
                urgency = NOTIFY_URGENCY_CRITICAL;
                break;

            default:
                wxFAIL_MSG( "Unknown notification message flags." );
                return false;
        }
        notify_notification_set_urgency(m_notification, urgency);


        // Finally do show the notification.
        wxGtkError error;
        if ( !notify_notification_show(m_notification, error.Out()) )
        {
            wxLogDebug("Failed to shown notification: %s", error.GetMessage());

            return false;
        }

        return true;
    }

    virtual bool Close() override
    {
        wxCHECK_MSG( m_notification, false,
                     wxS("Can't close not shown notification.") );

        wxGtkError error;
        if ( !notify_notification_close(m_notification, error.Out()) )
        {
            wxLogDebug("Failed to hide notification: %s", error.GetMessage());

            return false;
        }

        return true;
    }

    virtual void SetTitle(const wxString& title) override
    {
        m_title = title;
    }

    virtual void SetMessage(const wxString& message) override
    {
        m_message = message;
    }

    virtual void SetParent(wxWindow *WXUNUSED(parent)) override
    {
    }

    virtual void SetFlags(int flags) override
    {
        m_flags = flags;
    }

    virtual void SetIcon(const wxIcon& icon) override
    {
        m_icon = icon;
        CreateOrUpdateNotification();
    }

    virtual bool AddAction(wxWindowID actionid, const wxString &label) override
    {
        if ( !CreateOrUpdateNotification() )
            return false;

        wxString labelStr = label;
        if ( labelStr.empty() )
            labelStr = wxGetStockLabel(actionid, wxSTOCK_NOFLAGS);

        notify_notification_add_action
            (
                m_notification,
                wxString::Format("%d", actionid).utf8_str(),
                labelStr.utf8_str(),
                &wxLibNotifyMsgImplActionCallback,
                this,
                nullptr
            );

        return true;
    }

    void NotifyClose(int closeReason)
    {
        // Values according to the OpenDesktop specification at:
        // https://developer.gnome.org/notification-spec/

        auto reason = wxNotificationMessage::DismissalReason::Unknown;
        switch (closeReason)
        {
            case 1: // "The notification expired."
                reason = wxNotificationMessage::DismissalReason::TimedOut;
                break;
            case 2: // "The notification was dismissed by the user."
                reason = wxNotificationMessage::DismissalReason::ByUser;
                break;
            case 3: // "The notification was closed by a call to CloseNotification."
                reason = wxNotificationMessage::DismissalReason::ByApp;
                break;
        }

        wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_DISMISSED);
        evt.SetInt(static_cast<int>(reason));
        ProcessNotificationEvent(evt);

    }

    void NotifyAction(wxWindowID actionid)
    {
        wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_ACTION, actionid);
        ProcessNotificationEvent(evt);
    }

private:
    NotifyNotification* m_notification;
    wxString m_title;
    wxString m_message;
    wxIcon m_icon;
    int m_flags;
};

extern "C" {
static
void wxLibNotifyMsgImplActionCallback(NotifyNotification *WXUNUSED(notification),
                                                         char *action,
                                                         gpointer user_data)
{
    wxLibNotifyMsgImpl* impl = (wxLibNotifyMsgImpl*) user_data;

    impl->NotifyAction(wxAtoi(action));
}

static void closed_notification(NotifyNotification* notification, void* user_data)
{
    wxLibNotifyMsgImpl* impl = (wxLibNotifyMsgImpl*) user_data;
    gint closeReason = notify_notification_get_closed_reason(notification);
    impl->NotifyClose(closeReason);
}
}

// ----------------------------------------------------------------------------
// wxNotificationMessage
// ----------------------------------------------------------------------------

void wxNotificationMessage::Init()
{
    m_impl = new wxLibNotifyMsgImpl(this);
}

#endif // wxUSE_NOTIFICATION_MESSAGE && wxUSE_LIBNOTIFY
