/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * nautilus-progress-persistence-handler.c: file operation progress systray icon and notification handler.
 *
 * Copyright (C) 2007, 2011, 2015 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Alexander Larsson <alexl@redhat.com>
 *          Cosimo Cecchi <cosimoc@redhat.com>
 *          Carlos Soriano <csoriano@gnome.com>
 *
 */

#include <config.h>

#include "nautilus-progress-persistence-handler.h"

#include "nautilus-application.h"
#include "nautilus-progress-info-widget.h"

#include <glib/gi18n.h>

#include <libnautilus-private/nautilus-progress-info.h>
#include <libnautilus-private/nautilus-progress-info-manager.h>

struct _NautilusProgressPersistenceHandlerPriv {
	NautilusProgressInfoManager *manager;

        NautilusApplication *app;
	guint active_infos;

	GtkStatusIcon *status_icon;
};

G_DEFINE_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, G_TYPE_OBJECT);

/* Our policy for showing progress notification is the following:
 * - file operations that end within two seconds do not get notified in any way
 * - if no file operations are running, and one passes the two seconds
 *   timeout, a window is displayed with the progress
 * - if the window is closed, we show a resident notification, or a status icon, depending on
 *   the capabilities of the notification daemon running in the session
 * - if some file operations are running, and another one passes the two seconds
 *   timeout, and the window is showing, we add it to the window directly
 * - in the same case, but when the window is not showing, we update the resident
 *   notification, changing its message, or the status icon's tooltip
 * - when one file operation finishes, if it's not the last one, we only update the
 *   resident notification's message, or the status icon's tooltip
 * - in the same case, if it's the last one, we close the resident notification,
 *   or the status icon, and trigger a transient one
 * - in the same case, but the window was showing, we just hide the window
 */

static gboolean server_has_persistence (void);

static void
show_file_transfers (NautilusProgressPersistenceHandler *self)
{
        GFile *home;

        home = g_file_new_for_path (g_get_home_dir ());
        nautilus_application_open_location (self->priv->app, home, NULL, NULL);

        g_object_unref (home);
}

static void
action_show_file_transfers (GSimpleAction *action,
			    GVariant *parameter,
			    gpointer user_data)
{
        NautilusProgressPersistenceHandler *self;

        self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (user_data);
        show_file_transfers (self);
}

static GActionEntry progress_persistence_entries[] = {
	{ "show-file-transfers", action_show_file_transfers, NULL, NULL, NULL }
};

static void
status_icon_activate_cb (GtkStatusIcon                      *icon,
                         NautilusProgressPersistenceHandler *self)
{
	gtk_status_icon_set_visible (icon, FALSE);
        show_file_transfers (self);
}

static void
progress_persistence_handler_ensure_status_icon (NautilusProgressPersistenceHandler *self)
{
	GIcon *icon;
	GtkStatusIcon *status_icon;

	if (self->priv->status_icon != NULL) {
		return;
	}

	icon = g_themed_icon_new_with_default_fallbacks ("system-file-manager-symbolic");
	status_icon = gtk_status_icon_new_from_gicon (icon);
	g_signal_connect (status_icon, "activate",
			  (GCallback) status_icon_activate_cb,
			  self);

	gtk_status_icon_set_visible (status_icon, FALSE);
	g_object_unref (icon);

	self->priv->status_icon = status_icon;
}

static void
progress_persistence_handler_update_notification (NautilusProgressPersistenceHandler *self)
{
        GNotification *notification;
	gchar *body;

        notification = g_notification_new (_("File Operations"));
	g_notification_set_default_action (notification, "app.show-file-transfers");
        g_notification_add_button (notification, _("Show Details"),
                                   "app.show-file-transfers");

	body = g_strdup_printf (ngettext ("%'d file operation active",
					  "%'d file operations active",
					  self->priv->active_infos),
				self->priv->active_infos);
        g_notification_set_body (notification, body);

        g_application_send_notification (g_application_get_default (),
                                         "progress", notification);

        g_object_unref (notification);
	g_free (body);
}

static void
progress_persistence_handler_update_status_icon (NautilusProgressPersistenceHandler *self)
{
	gchar *tooltip;

	progress_persistence_handler_ensure_status_icon (self);

	tooltip = g_strdup_printf (ngettext ("%'d file operation active",
					     "%'d file operations active",
					     self->priv->active_infos),
				   self->priv->active_infos);
	gtk_status_icon_set_tooltip_text (self->priv->status_icon, tooltip);
	g_free (tooltip);

	gtk_status_icon_set_visible (self->priv->status_icon, TRUE);
}

void
nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self)
{
        GList *windows;

        windows = nautilus_application_get_windows (self->priv->app);
        if (self->priv->active_infos > 0 &&
            g_list_length (windows) == 0) {
	        if (server_has_persistence ()) {
		        progress_persistence_handler_update_notification (self);
	        } else {
		        progress_persistence_handler_update_status_icon (self);
	        }
        }
}

static void
progress_persistence_handler_update_notification_or_status (NautilusProgressPersistenceHandler *self)
{
	if (server_has_persistence ()) {
		progress_persistence_handler_update_notification (self);
	} else {
		progress_persistence_handler_update_status_icon (self);
	}
}

static void
progress_persistence_handler_show_complete_notification (NautilusProgressPersistenceHandler *self)
{
	GNotification *complete_notification;

	/* don't display the notification if we'd be using a status icon */
	if (!server_has_persistence ()) {
		return;
	}

	complete_notification = g_notification_new (_("File Operations"));
        g_notification_set_body (complete_notification,
                                 _("All file operations have been successfully completed"));
	g_application_send_notification (g_application_get_default (),
                                         "transfer-complete",
                                         complete_notification);

	g_object_unref (complete_notification);
}

static void
progress_persistence_handler_hide_notification_or_status (NautilusProgressPersistenceHandler *self)
{
	if (self->priv->status_icon != NULL) {
		gtk_status_icon_set_visible (self->priv->status_icon, FALSE);
	}

        g_application_withdraw_notification (g_application_get_default (),
                                             "progress");
}

static void
progress_info_finished_cb (NautilusProgressInfo               *info,
                           NautilusProgressPersistenceHandler *self)
{
        GList *windows;

	self->priv->active_infos--;

        windows = nautilus_application_get_windows (self->priv->app);
        if (self->priv->active_infos > 0) {
	        if (g_list_length (windows) == 0) {
		        progress_persistence_handler_update_notification_or_status (self);
	        }
        } else if (g_list_length (windows)  == 0) {
	        progress_persistence_handler_hide_notification_or_status (self);
	        progress_persistence_handler_show_complete_notification (self);
        }

}

static void
handle_new_progress_info (NautilusProgressPersistenceHandler *self,
                          NautilusProgressInfo               *info)
{
        GList *windows;
	g_signal_connect (info, "finished",
			  G_CALLBACK (progress_info_finished_cb), self);

	self->priv->active_infos++;
        windows = nautilus_application_get_windows (self->priv->app);

	if (g_list_length (windows) == 0) {
		progress_persistence_handler_update_notification_or_status (self);
        }
}

typedef struct {
	NautilusProgressInfo *info;
	NautilusProgressPersistenceHandler *self;
} TimeoutData;

static void
timeout_data_free (TimeoutData *data)
{
	g_clear_object (&data->self);
	g_clear_object (&data->info);

	g_slice_free (TimeoutData, data);
}

static TimeoutData *
timeout_data_new (NautilusProgressPersistenceHandler *self,
                  NautilusProgressInfo               *info)
{
	TimeoutData *retval;

	retval = g_slice_new0 (TimeoutData);
	retval->self = g_object_ref (self);
	retval->info = g_object_ref (info);

	return retval;
}

static gboolean
new_op_started_timeout (TimeoutData *data)
{
	NautilusProgressInfo *info = data->info;
	NautilusProgressPersistenceHandler *self = data->self;

	if (nautilus_progress_info_get_is_paused (info)) {
		return TRUE;
	}

	if (!nautilus_progress_info_get_is_finished (info)) {
		handle_new_progress_info (self, info);
	}

	timeout_data_free (data);

	return FALSE;
}

static void
release_application (NautilusProgressInfo               *info,
                     NautilusProgressPersistenceHandler *self)
{
	/* release the GApplication hold we acquired */
	g_application_release (g_application_get_default ());
}

static void
progress_info_started_cb (NautilusProgressInfo               *info,
                          NautilusProgressPersistenceHandler *self)
{
	TimeoutData *data;

	/* hold GApplication so we never quit while there's an operation pending */
	g_application_hold (g_application_get_default ());

	g_signal_connect (info, "finished",
			  G_CALLBACK (release_application), self);

	data = timeout_data_new (self, info);

	/* timeout for the progress window to appear */
	g_timeout_add_seconds (2,
			       (GSourceFunc) new_op_started_timeout,
			       data);
}

static void
new_progress_info_cb (NautilusProgressInfoManager        *manager,
                      NautilusProgressInfo               *info,
                      NautilusProgressPersistenceHandler *self)
{
	g_signal_connect (info, "started",
			  G_CALLBACK (progress_info_started_cb), self);
}

static void
nautilus_progress_persistence_handler_dispose (GObject *obj)
{
	NautilusProgressPersistenceHandler *self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (obj);

	g_clear_object (&self->priv->manager);

	G_OBJECT_CLASS (nautilus_progress_persistence_handler_parent_class)->dispose (obj);
}

static gboolean
server_has_persistence (void)
{
        static gboolean retval = FALSE;
        GDBusConnection *conn;
        GVariant *result;
        char **cap, **caps;
        static gboolean initialized = FALSE;

        if (initialized) {
                return retval;
        }
        initialized = TRUE;

        conn = g_application_get_dbus_connection (g_application_get_default ());
        result = g_dbus_connection_call_sync (conn,
                                              "org.freedesktop.Notifications",
                                              "/org/freedesktop/Notifications",
                                              "org.freedesktop.Notifications",
                                              "GetCapabilities",
                                              g_variant_new ("()"),
                                              G_VARIANT_TYPE ("(as)"),
                                              G_DBUS_CALL_FLAGS_NONE,
                                              -1, NULL, NULL);

        if (result == NULL)
                return FALSE;

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

        for (cap = caps; *cap != NULL; cap++)
                if (g_strcmp0 ("persistence", *cap) == 0)
                        retval = TRUE;

        g_free (caps);
        g_variant_unref (result);

        return retval;
}

static void
nautilus_progress_persistence_handler_init (NautilusProgressPersistenceHandler *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER,
						  NautilusProgressPersistenceHandlerPriv);

	self->priv->manager = nautilus_progress_info_manager_dup_singleton ();
	g_signal_connect (self->priv->manager, "new-progress-info",
			  G_CALLBACK (new_progress_info_cb), self);
}

static void
nautilus_progress_persistence_handler_class_init (NautilusProgressPersistenceHandlerClass *klass)
{
	GObjectClass *oclass;

	oclass = G_OBJECT_CLASS (klass);
	oclass->dispose = nautilus_progress_persistence_handler_dispose;

	g_type_class_add_private (klass, sizeof (NautilusProgressPersistenceHandlerPriv));
}

NautilusProgressPersistenceHandler *
nautilus_progress_persistence_handler_new (GObject *app)
{
        NautilusProgressPersistenceHandler *self;

        self = g_object_new (NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER, NULL);
        self->priv->app = NAUTILUS_APPLICATION (app);

        g_action_map_add_action_entries (G_ACTION_MAP (self->priv->app),
                                         progress_persistence_entries, G_N_ELEMENTS (progress_persistence_entries),
                                         self);
	return self;
}
