/*
 * This file Copyright (C) 2008-2014 Mnemosyne LLC
 *
 * It may be used under the GNU GPL versions 2 or 3
 * or any future license endorsed by Mnemosyne LLC.
 *
 */

#include <gio/gio.h>

#include <glib/gi18n.h>
#include "conf.h"
#include "notify.h"
#include "tr-prefs.h"
#include "util.h"

#define NOTIFICATIONS_DBUS_NAME "org.freedesktop.Notifications"
#define NOTIFICATIONS_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
#define NOTIFICATIONS_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"

static GDBusProxy* proxy = NULL;
static GHashTable* active_notifications = NULL;
static gboolean server_supports_actions = FALSE;

typedef struct TrNotification
{
    TrCore* core;
    int torrent_id;
} TrNotification;

static void tr_notification_free(gpointer data)
{
    TrNotification* n = data;

    if (n->core != NULL)
    {
        g_object_unref(G_OBJECT(n->core));
    }

    g_free(n);
}

static void get_capabilities_callback(GObject* source, GAsyncResult* res, gpointer user_data)
{
    TR_UNUSED(user_data);

    char** caps;
    GVariant* result;

    result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, NULL);

    if (result == NULL || !g_variant_is_of_type(result, G_VARIANT_TYPE("(as)")))
    {
        if (result != NULL)
        {
            g_variant_unref(result);
        }

        return;
    }

    g_variant_get(result, "(^a&s)", &caps);

    for (int i = 0; caps[i] != NULL; i++)
    {
        if (g_strcmp0(caps[i], "actions") == 0)
        {
            server_supports_actions = TRUE;
            break;
        }
    }

    g_free(caps);
    g_variant_unref(result);
}

static void g_signal_callback(
    GDBusProxy const* dbus_proxy,
    char const* sender_name,
    char const* signal_name,
    GVariant* params,
    gconstpointer user_data)
{
    TR_UNUSED(dbus_proxy);
    TR_UNUSED(sender_name);
    TR_UNUSED(user_data);

    guint id;
    TrNotification* n;

    g_return_if_fail(g_variant_is_of_type(params, G_VARIANT_TYPE("(u*)")));

    g_variant_get(params, "(u*)", &id, NULL);
    n = g_hash_table_lookup(active_notifications, GUINT_TO_POINTER(id));

    if (n == NULL)
    {
        return;
    }

    if (g_strcmp0(signal_name, "NotificationClosed") == 0)
    {
        g_hash_table_remove(active_notifications, GUINT_TO_POINTER(id));
    }
    else if (g_strcmp0(signal_name, "ActionInvoked") == 0 && g_variant_is_of_type(params, G_VARIANT_TYPE("(us)")))
    {
        tr_torrent const* tor = gtr_core_find_torrent(n->core, n->torrent_id);
        if (tor == NULL)
        {
            return;
        }

        char* action = NULL;
        g_variant_get(params, "(u&s)", NULL, &action);

        if (g_strcmp0(action, "folder") == 0)
        {
            gtr_core_open_folder(n->core, n->torrent_id);
        }
        else if (g_strcmp0(action, "file") == 0)
        {
            tr_info const* inf = tr_torrentInfo(tor);
            char const* dir = tr_torrentGetDownloadDir(tor);
            char* path = g_build_filename(dir, inf->files[0].name, NULL);
            gtr_open_file(path);
            g_free(path);
        }
    }
}

static void dbus_proxy_ready_callback(GObject* source, GAsyncResult* res, gpointer user_data)
{
    TR_UNUSED(source);
    TR_UNUSED(user_data);

    proxy = g_dbus_proxy_new_for_bus_finish(res, NULL);

    if (proxy == NULL)
    {
        g_warning("Failed to create proxy for %s", NOTIFICATIONS_DBUS_NAME);
        return;
    }

    g_signal_connect(proxy, "g-signal", G_CALLBACK(g_signal_callback), NULL);
    g_dbus_proxy_call(
        proxy,
        "GetCapabilities",
        g_variant_new("()"),
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        get_capabilities_callback,
        NULL);
}

void gtr_notify_init(void)
{
    active_notifications = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, tr_notification_free);
    g_dbus_proxy_new_for_bus(
        G_BUS_TYPE_SESSION,
        G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
        NULL,
        NOTIFICATIONS_DBUS_NAME,
        NOTIFICATIONS_DBUS_CORE_OBJECT,
        NOTIFICATIONS_DBUS_CORE_INTERFACE,
        NULL,
        dbus_proxy_ready_callback,
        NULL);
}

static void notify_callback(GObject* source, GAsyncResult* res, gpointer user_data)
{
    GVariant* result;
    TrNotification* n = user_data;

    result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, NULL);

    if (result == NULL || !g_variant_is_of_type(result, G_VARIANT_TYPE("(u)")))
    {
        if (result != NULL)
        {
            g_variant_unref(result);
        }

        tr_notification_free(n);
        return;
    }

    guint id;
    g_variant_get(result, "(u)", &id);
    g_hash_table_insert(active_notifications, GUINT_TO_POINTER(id), n);

    g_variant_unref(result);
}

void gtr_notify_torrent_completed(TrCore* core, int torrent_id)
{
    if (gtr_pref_flag_get(TR_KEY_torrent_complete_sound_enabled))
    {
        char** argv = gtr_pref_strv_get(TR_KEY_torrent_complete_sound_command);
        g_spawn_async(
            NULL /*cwd*/,
            argv,
            NULL /*envp*/,
            G_SPAWN_SEARCH_PATH,
            NULL /*GSpawnChildSetupFunc*/,
            NULL /*user_data*/,
            NULL /*child_pid*/,
            NULL);
        g_strfreev(argv);
    }

    if (!gtr_pref_flag_get(TR_KEY_torrent_complete_notification_enabled))
    {
        return;
    }

    g_return_if_fail(G_IS_DBUS_PROXY(proxy));

    tr_torrent const* const tor = gtr_core_find_torrent(core, torrent_id);

    TrNotification* const n = g_new0(TrNotification, 1);
    g_object_ref(G_OBJECT(core));
    n->core = core;
    n->torrent_id = torrent_id;

    GVariantBuilder actions_builder;
    g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as"));
    if (server_supports_actions)
    {
        tr_info const* inf = tr_torrentInfo(tor);

        if (inf->fileCount == 1)
        {
            g_variant_builder_add(&actions_builder, "s", "file");
            g_variant_builder_add(&actions_builder, "s", _("Open File"));
        }
        else
        {
            g_variant_builder_add(&actions_builder, "s", "folder");
            g_variant_builder_add(&actions_builder, "s", _("Open Folder"));
        }
    }

    GVariantBuilder hints_builder;
    g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(&hints_builder, "{sv}", "category", g_variant_new_string("transfer.complete"));

    g_dbus_proxy_call(
        proxy,
        "Notify",
        g_variant_new(
            "(susssasa{sv}i)",
            "Transmission",
            0,
            "transmission",
            _("Torrent Complete"),
            tr_torrentName(tor),
            &actions_builder,
            &hints_builder,
            -1),
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        notify_callback,
        n);
}

void gtr_notify_torrent_added(char const* name)
{
    TrNotification* n;

    g_return_if_fail(G_IS_DBUS_PROXY(proxy));

    if (!gtr_pref_flag_get(TR_KEY_torrent_added_notification_enabled))
    {
        return;
    }

    n = g_new0(TrNotification, 1);
    g_dbus_proxy_call(
        proxy,
        "Notify",
        g_variant_new("(susssasa{sv}i)", "Transmission", 0, "transmission", _("Torrent Added"), name, NULL, NULL, -1),
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        notify_callback,
        n);
}