#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <libnotify/notify.h>
#include <gio/gio.h>

#include "update-notifier.h"
#include "update.h"
#include "trayappletui.h"

#define DELAYED_NOTIFICATION_SOURCE "delayed-notification-source"

#define REBOOT_NOTIFICATION_FIRST_DELAY 24*60*60
#define REBOOT_NOTIFICATION_REMIND_DELAY 24*60*60

static void ask_reboot_required (TrayApplet *ta, gboolean focus_on_map);
static void show_notification (TrayApplet *ta);

static guint
delay_remaining (gint64 deadline)
{
   gint64 remaining = deadline - time (NULL);

   if (remaining <= 0)
      return 0;
   return remaining > G_MAXUINT ? G_MAXUINT : (guint) remaining;
}

static void
on_show_notification_timeout (gpointer user_data)
{
	TrayApplet *ta = user_data;

	tray_applet_ui_set_data (ta, DELAYED_NOTIFICATION_SOURCE, NULL);
	show_notification (ta);
}

static void
unschedule_delayed_notification (TrayApplet *ta)
{
	guint source_tag;

	source_tag = GPOINTER_TO_UINT (tray_applet_ui_get_data (ta, DELAYED_NOTIFICATION_SOURCE));
	if (source_tag > 0) {
		g_source_remove (source_tag);
		tray_applet_ui_set_data (ta, DELAYED_NOTIFICATION_SOURCE, NULL);
	}
}

static void
schedule_delayed_notification (TrayApplet *ta, guint delay)
{
	guint source_tag;

	unschedule_delayed_notification (ta);

	source_tag = g_timeout_add_seconds_once (delay, on_show_notification_timeout, ta);
	tray_applet_ui_set_data (ta, DELAYED_NOTIFICATION_SOURCE, GUINT_TO_POINTER (source_tag));
}

static void
on_notification_remind_action (NotifyNotification *notification, char *action, gpointer user_data)
{
	TrayApplet *ta = user_data;
	gint64 reminder;

	reminder = time (NULL) + REBOOT_NOTIFICATION_REMIND_DELAY;
	g_settings_set_int64 (ta->un->settings,
	                      SETTINGS_KEY_REBOOT_NOTIFICATION_REMINDER,
	                      reminder);
	schedule_delayed_notification (ta, REBOOT_NOTIFICATION_REMIND_DELAY);
}

static void
on_notification_reboot_action (NotifyNotification *notification, char *action, gpointer user_data)
{
	TrayApplet *ta = user_data;

	ask_reboot_required (ta, TRUE);
}

static void
show_notification (TrayApplet *ta)
{
	NotifyNotification *n;
	struct stat statbuf;
	gboolean has_deferred_install = FALSE;
	const char *body;

	n = tray_applet_ui_get_data (ta, "notification");
	if (n) {
		notify_notification_close (n, NULL);
		g_object_unref (n);
	}
	tray_applet_ui_set_data (ta, "notification", NULL);

	/* Create and show the notification */
	body = !has_deferred_install ?
		_("Restart to finish updating your system") :
		_("Driver updates can only be installed on restart.\n"
		  "This may take a few minutes.");
	n = notify_notification_new(
				     _("System restart required"),
				     body,
				     "system-reboot");
	notify_notification_set_hint (n, "desktop-entry", g_variant_new_string ("update-manager"));
	notify_notification_add_action (n, "default", "default", on_notification_reboot_action, ta, NULL);
	notify_notification_add_action (n, "reboot", _("Restart…"), on_notification_reboot_action, ta, NULL);
	notify_notification_add_action (n, "remind", _("Remind me tomorrow"), on_notification_remind_action, ta, NULL);
	notify_notification_show (n, NULL);
	tray_applet_ui_set_data (ta, "notification", n);

	if (stat (REBOOT_FILE, &statbuf) == 0)
		g_settings_set_int64 (ta->un->settings,
		                      SETTINGS_KEY_REBOOT_NOTIFICATION_LAST_UPDATE,
		                      statbuf.st_mtime);
	g_settings_set_int64 (ta->un->settings,
	                      SETTINGS_KEY_REBOOT_NOTIFICATION_REMINDER, 0);
}

static gboolean
gdm_action_reboot(void)
{
  g_autoptr (GVariant) answer= NULL;
  g_autoptr (GDBusProxy) proxy = NULL;
  g_autoptr (GError) error = NULL;

  proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                         G_DBUS_PROXY_FLAGS_NONE,
                                         NULL, /* GDBusInterfaceInfo */
                                         "org.gnome.SessionManager",
                                         "/org/gnome/SessionManager",
                                         "org.gnome.SessionManager",
                                         NULL, /* GCancellable */
                                         NULL /* GError */);
  if (proxy == NULL)
     return FALSE;

  answer = g_dbus_proxy_call_sync (proxy, "Reboot", NULL,
                                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);

  if (answer == NULL) {
    g_autofree char *remote_error = g_dbus_error_get_remote_error (error);
    g_autoptr (GError) native_err = g_dbus_error_new_for_dbus_error (remote_error, error->message);

    if (!g_error_matches(native_err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
      return FALSE;
  }

  return TRUE;
}

static gboolean
logind_action_reboot(void)
{
  GVariant *answer;
  GDBusProxy *proxy;

  proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
                                         G_DBUS_PROXY_FLAGS_NONE,
                                         NULL, /* GDBusInterfaceInfo */
                                         "org.freedesktop.login1",
                                         "/org/freedesktop/login1",
                                         "org.freedesktop.login1.Manager",
                                         NULL, /* GCancellable */
                                         NULL /* GError */);
  if (proxy == NULL)
     return FALSE;

  answer = g_dbus_proxy_call_sync (proxy, "Reboot",
                                   g_variant_new ("(b)", FALSE),
                                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
  g_object_unref (proxy);

  if (answer == NULL)
    return FALSE;

  g_variant_unref (answer);
  return TRUE;

}

static void
request_reboot (void)
{
   if(!logind_action_reboot()) {
      const char *fmt, *msg, *details;
      fmt = "<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s\n";
      msg = _("Reboot failed");
      details = _("Failed to request reboot, please shutdown manually");
      GtkWidget *dlg = gtk_message_dialog_new_with_markup(NULL, 0,
							  GTK_MESSAGE_ERROR,
							  GTK_BUTTONS_CLOSE,
							  fmt, msg, details);
      gtk_dialog_run(GTK_DIALOG(dlg));
      gtk_widget_destroy(dlg);
   }
}

static void
ask_reboot_required(TrayApplet *ta, gboolean focus_on_map)
{
   GtkBuilder *builder;
   GError *error = NULL;
   GtkWidget *image, *dia;

   if (gdm_action_reboot ()) {
      // if gdm can handle the reboot, we don't need to ask the user again.
      return;
   }

   builder = gtk_builder_new ();
   if (!gtk_builder_add_from_file (builder, UIDIR"reboot-dialog.ui", &error)) {
      g_warning ("Couldn't load builder file: %s", error->message);
      g_error_free (error);
   }

   image = GTK_WIDGET (gtk_builder_get_object (builder, "image"));
   gtk_image_set_from_icon_name(GTK_IMAGE (image), "un-reboot", GTK_ICON_SIZE_DIALOG);

   dia = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_reboot"));

   g_object_unref (builder);

   gtk_window_set_focus_on_map(GTK_WINDOW(dia), focus_on_map);
   if (gtk_dialog_run (GTK_DIALOG(dia)) == GTK_RESPONSE_OK)
      request_reboot ();
   gtk_widget_destroy (dia);
}

static gboolean
button_release_cb (GtkWidget *widget,
		   TrayApplet *ta)
{
   ask_reboot_required(ta, TRUE);

   return TRUE;
}

static gboolean
aptdaemon_pending_transactions (void)
{
  GError *error;
  GVariant *answer;
  GDBusProxy *proxy;
  char *owner = NULL;
  const char *current = NULL;
  char **pending = NULL;

  error = NULL;
  proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
                                         G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
                                         NULL, /* GDBusInterfaceInfo */
                                         "org.debian.apt",
                                         "/org/debian/apt",
                                         "org.debian.apt",
                                         NULL, /* GCancellable */
                                         &error);
  if (proxy == NULL) {
    g_debug ("Failed to open connection to bus: %s", error->message);
    g_error_free (error);
    return FALSE;
  }

  owner = g_dbus_proxy_get_name_owner (proxy);
  g_debug("aptdaemon on bus: %i", (owner != NULL));
  if (owner == NULL) {
    g_object_unref (proxy);
    g_free (owner);
    return FALSE;
  }
  g_free (owner);

  error = NULL;
  answer = g_dbus_proxy_call_sync (proxy, "GetActiveTransactions", NULL,
                                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
  g_object_unref (proxy);

  if (answer == NULL) {
    g_debug ("error during dbus call: %s", error->message);
    g_error_free (error);
    return FALSE;
  }

  if (g_strcmp0 (g_variant_get_type_string (answer), "(sas)") != 0) {
    g_debug ("aptd answer in unexpected format: %s",
             g_variant_get_type_string (answer));
    g_variant_unref (answer);
    return FALSE;
  }

  g_variant_get (answer, "(&s^a&s)", &current, &pending);

  gboolean has_pending = FALSE;
  if ((current && strcmp(current,"") != 0) || g_strv_length(pending) > 0)
     has_pending = TRUE;

  g_free (pending);
  g_variant_unref (answer);

  return has_pending;
}

static void
do_reboot_check (TrayApplet *ta)
{
	struct stat statbuf;
	gint64 last_notified_update;
	gint64 reminder;
	gint64 notification_deadline;
	guint notification_delay;
	gboolean wants_reboot;

	// if we are not supposed to show the reboot notification
	// just skip it
	if(g_settings_get_boolean(ta->un->settings, SETTINGS_KEY_HIDE_REBOOT))
	   return;

	/* If the file doesn't exist, we don't need to reboot */
	wants_reboot = (stat (REBOOT_FILE, &statbuf) == 0);

	if (!wants_reboot) {
		g_settings_set_int64 (ta->un->settings,
		                      SETTINGS_KEY_REBOOT_NOTIFICATION_REMINDER, 0);

		if (tray_applet_ui_get_visible (ta)) {
			NotifyNotification *n;

			unschedule_delayed_notification (ta);

			n = tray_applet_ui_get_data (ta, "notification");
			notify_notification_close (n, NULL);
			g_object_unref (n);

			tray_applet_ui_destroy (ta);
		}
		return;
	}

	if (!tray_applet_ui_get_visible (ta)) {
		tray_applet_ui_ensure (ta);
		tray_applet_ui_set_icon(ta, "system-reboot-symbolic", _("System restart required"));
		tray_applet_ui_set_single_action(ta, _("System restart required"),
						 G_CALLBACK (button_release_cb), ta);
		tray_applet_ui_set_visible (ta, TRUE);
	}

	/* Check whether the user doesn't like notifications */
	if (g_settings_get_boolean (ta->un->settings,
	                            SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS))
		return;

	reminder = g_settings_get_int64 (ta->un->settings,
	                                 SETTINGS_KEY_REBOOT_NOTIFICATION_REMINDER);
	if (reminder > 0) {
		notification_delay = delay_remaining (reminder);
	} else {
		last_notified_update = g_settings_get_int64 (
			ta->un->settings, SETTINGS_KEY_REBOOT_NOTIFICATION_LAST_UPDATE);
		if (statbuf.st_mtime <= last_notified_update)
			return;

		notification_deadline = statbuf.st_mtime + REBOOT_NOTIFICATION_FIRST_DELAY;
		notification_delay = delay_remaining (notification_deadline);
	}

	schedule_delayed_notification (ta, notification_delay);
}


gboolean
unattended_upgrades_running (void) {
   // wait for 10min after unattended-upgrades ran
   const int UU_PROGRESS_DELAY = 60 * 10;

   struct stat buf;
   if (stat (UNATTENDED_UPGRADES_PROGRESS_FILE, &buf) != 0)
      return FALSE;
   return (buf.st_mtime + UU_PROGRESS_DELAY > time(NULL));
}

gboolean
reboot_check (TrayApplet *ta)
{
   time_t timeout = 0;

   if (aptdaemon_pending_transactions() || unattended_upgrades_running())
      timeout = 5;

   if (timeout > 0)
      g_timeout_add_seconds (timeout, (GSourceFunc)reboot_check, ta);
   else
      do_reboot_check(ta);
   return FALSE;
}


void
reboot_tray_icon_init (TrayApplet *ta)
{
	/* Check for updates for the first time */
	reboot_check (ta);
}
