/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 *
 * Copyright 2024 GNOME Foundation, Inc.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors:
 *  - Philip Withnall <pwithnall@gnome.org>
 */

#include "config.h"

#include <glib.h>
#include <glib-object.h>
#include <glib-unix.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include <locale.h>
#include <libgsystemservice/service.h>
#include <libmalcontent-timer/extension-agent-object.h>
#include <libmalcontent-timer/extension-agent-object-polkit.h>
#include <libmalcontent-timer/extension-agent-service.h>


static void mct_extension_agent_service_dispose (GObject *object);

static void mct_extension_agent_service_startup_async (GssService          *service,
                                                       GCancellable        *cancellable,
                                                       GAsyncReadyCallback  callback,
                                                       gpointer             user_data);
static void mct_extension_agent_service_startup_finish (GssService    *service,
                                                        GAsyncResult  *result,
                                                        GError       **error);
static void mct_extension_agent_service_shutdown (GssService *service);

static void notify_busy_cb (GObject    *obj,
                            GParamSpec *pspec,
                            gpointer    user_data);

/**
 * MctExtensionAgentService:
 *
 * The core implementation of the extension agent daemon, which exposes its
 * D-Bus API on the bus.
 *
 * Since: 0.14.0
 */
struct _MctExtensionAgentService
{
  GssService parent;

  MctExtensionAgentObject *extension_agent_object;  /* (owned) */
  unsigned long extension_agent_object_notify_busy_id;

  gboolean busy;
};

G_DEFINE_TYPE (MctExtensionAgentService, mct_extension_agent_service, GSS_TYPE_SERVICE)

static void
mct_extension_agent_service_class_init (MctExtensionAgentServiceClass *klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  GssServiceClass *service_class = (GssServiceClass *) klass;

  object_class->dispose = mct_extension_agent_service_dispose;

  service_class->startup_async = mct_extension_agent_service_startup_async;
  service_class->startup_finish = mct_extension_agent_service_startup_finish;
  service_class->shutdown = mct_extension_agent_service_shutdown;
}

static void
mct_extension_agent_service_init (MctExtensionAgentService *self)
{
}

static void
mct_extension_agent_service_dispose (GObject *object)
{
  MctExtensionAgentService *self = MCT_EXTENSION_AGENT_SERVICE (object);

  g_clear_signal_handler (&self->extension_agent_object_notify_busy_id, self->extension_agent_object);
  g_clear_object (&self->extension_agent_object);

  /* Chain up to the parent class */
  G_OBJECT_CLASS (mct_extension_agent_service_parent_class)->dispose (object);
}

static void
mct_extension_agent_service_startup_async (GssService          *service,
                                           GCancellable        *cancellable,
                                           GAsyncReadyCallback  callback,
                                           gpointer             user_data)
{
  MctExtensionAgentService *self = MCT_EXTENSION_AGENT_SERVICE (service);
  GDBusConnection *connection = gss_service_get_dbus_connection (GSS_SERVICE (self));
  g_autoptr(GTask) task = NULL;
  g_autoptr(GError) local_error = NULL;

  task = g_task_new (service, cancellable, callback, user_data);
  g_task_set_source_tag (task, mct_extension_agent_service_startup_async);

  /* For now we hardcode the polkit-based extension agent D-Bus object. If people
   * are to provide other implementations of the agent in future, this could be
   * refactored so the MctExtensionAgentService code is easier to reuse and only
   * the MctExtensionAgentObject needs to be custom. */
  self->extension_agent_object =
      MCT_EXTENSION_AGENT_OBJECT (mct_extension_agent_object_polkit_new (connection,
                                                                         "/org/freedesktop/MalcontentTimer1/ExtensionAgent"));
  self->extension_agent_object_notify_busy_id =
      g_signal_connect (self->extension_agent_object, "notify::busy",
                        (GCallback) notify_busy_cb, self);

  notify_busy_cb (NULL, NULL, self);

  if (!mct_extension_agent_object_register (self->extension_agent_object, &local_error))
    g_task_return_error (task, g_steal_pointer (&local_error));
  else
    g_task_return_boolean (task, TRUE);
}

static void
mct_extension_agent_service_startup_finish (GssService    *service,
                                            GAsyncResult  *result,
                                            GError       **error)
{
  g_task_propagate_boolean (G_TASK (result), error);
}

static void
notify_busy_cb (GObject    *obj,
                GParamSpec *pspec,
                gpointer    user_data)
{
  MctExtensionAgentService *self = MCT_EXTENSION_AGENT_SERVICE (user_data);

  gboolean was_busy = self->busy;
  gboolean now_busy = mct_extension_agent_object_get_busy (self->extension_agent_object);

  g_debug ("%s: was_busy: %s, now_busy: %s",
           G_STRFUNC,
           was_busy ? "yes" : "no",
           now_busy ? "yes" : "no");

  if (was_busy && !now_busy)
    gss_service_release (GSS_SERVICE (self));
  else if (!was_busy && now_busy)
    gss_service_hold (GSS_SERVICE (self));

  self->busy = now_busy;
}

static void
mct_extension_agent_service_shutdown (GssService *service)
{
  MctExtensionAgentService *self = MCT_EXTENSION_AGENT_SERVICE (service);

  mct_extension_agent_object_unregister (self->extension_agent_object);
}

/**
 * mct_extension_agent_service_new:
 *
 * Create a new [class@Malcontent.ExtensionAgentService].
 *
 * Returns: (transfer full): a new [class@Malcontent.ExtensionAgentService]
 * Since: 0.14.0
 */
MctExtensionAgentService *
mct_extension_agent_service_new (void)
{
  return g_object_new (MCT_TYPE_EXTENSION_AGENT_SERVICE,
                       "bus-type", G_BUS_TYPE_SYSTEM,
                       "service-id", "org.freedesktop.MalcontentTimer1.ExtensionAgent",
                       "inactivity-timeout", 30000  /* ms */,
                       "translation-domain", GETTEXT_PACKAGE,
                       "parameter-string", _("— decide on screen time limit extension requests for parental controls"),
                       "summary", _("Decide whether to grant extension "
                                    "requests from accounts which have screen "
                                    "time limits set as part of parental "
                                    "controls."),
                       NULL);
}

