/*
 * Copyright © 2014 Canonical Ltd.
 * Copyright © 2021 Robert Tari
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3, as published
 * by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY, 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:
 *     Ted Gould <ted@canonical.com>
 *     Robert Tari <robert@tari.in>
 */

#include <act/act.h>

#include "im-accounts-service.h"

typedef struct {
    ActUserManager * user_manager;
    GDBusProxy * touch_settings;
    GCancellable * cancel;
} ImAccountsServicePrivate;

static void im_accounts_service_class_init (ImAccountsServiceClass *klass);
static void im_accounts_service_init       (ImAccountsService *self);
static void im_accounts_service_dispose    (GObject *object);
static void im_accounts_service_finalize   (GObject *object);
static void user_changed (ActUserManager * manager, ActUser * user, gpointer user_data);
static void on_user_manager_loaded (ActUserManager * manager, GParamSpec * pspect, gpointer user_data);
static void security_privacy_ready (GObject * obj, GAsyncResult * res, gpointer user_data);

G_DEFINE_TYPE_WITH_PRIVATE (ImAccountsService, im_accounts_service, G_TYPE_OBJECT);

static void
im_accounts_service_class_init (ImAccountsServiceClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->dispose = im_accounts_service_dispose;
    object_class->finalize = im_accounts_service_finalize;
}

static void
im_accounts_service_init (ImAccountsService *self)
{
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);

    priv->cancel = g_cancellable_new();

    priv->user_manager = act_user_manager_get_default();
    g_signal_connect(priv->user_manager, "user-added", G_CALLBACK(user_changed), self);
    g_signal_connect(priv->user_manager, "user-changed", G_CALLBACK(user_changed), self);
    g_signal_connect(priv->user_manager, "notify::is-loaded", G_CALLBACK(on_user_manager_loaded), self);

    gboolean isLoaded = FALSE;
    g_object_get(G_OBJECT(priv->user_manager), "is-loaded", &isLoaded, NULL);
    if (isLoaded) {
        on_user_manager_loaded(priv->user_manager, NULL, NULL);
    }
}

static void
im_accounts_service_dispose (GObject *object)
{
    ImAccountsService * self = IM_ACCOUNTS_SERVICE(object);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);

    if (priv->cancel != NULL) {
        g_cancellable_cancel(priv->cancel);
        g_clear_object(&priv->cancel);
    }

    g_clear_object(&priv->user_manager);

    G_OBJECT_CLASS (im_accounts_service_parent_class)->dispose (object);
}

static void
im_accounts_service_finalize (GObject *object)
{
    G_OBJECT_CLASS (im_accounts_service_parent_class)->finalize (object);
}

/* Handles a User getting updated */
static void
user_changed (ActUserManager * manager, ActUser * user, gpointer user_data)
{
    if (g_strcmp0(act_user_get_user_name(user), g_get_user_name()) != 0) {
        return;
    }

    ImAccountsService * self = IM_ACCOUNTS_SERVICE(user_data);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);
    g_debug("User Updated");

    /* Clear old proxies */
    g_clear_object(&priv->touch_settings);

    g_cancellable_cancel(priv->cancel);
    g_clear_object(&priv->cancel);
    priv->cancel = g_cancellable_new();

    /* Start getting a new proxy */
    g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
        G_DBUS_PROXY_FLAGS_NONE,
        NULL,
        "org.freedesktop.Accounts",
        act_user_get_object_path(user),
        "com.lomiri.touch.AccountsService.SecurityPrivacy",
        priv->cancel,
        security_privacy_ready,
        user_data);
}

/* Respond to the async of setting up the proxy. Mostly we get it or we error. */
static void
security_privacy_ready (GObject * obj, GAsyncResult * res, gpointer user_data)
{
    GError * error = NULL;
    GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish(res, &error);

    if (error != NULL) {
        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
            g_warning("Unable to get a proxy on accounts service for touch settings: %s",
                      error->message);
        }
        g_error_free(error);
        return;
    }

    ImAccountsService * self = IM_ACCOUNTS_SERVICE(user_data);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);
    /* Ensure we didn't get a proxy while we weren't looking */
    g_clear_object(&priv->touch_settings);
    priv->touch_settings = proxy;
}

/* When the user manager is loaded see if we have a user already loaded
   along with. */
static void
on_user_manager_loaded (ActUserManager * manager, GParamSpec * pspect, gpointer user_data)
{
    ImAccountsService * self = IM_ACCOUNTS_SERVICE(user_data);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);
    ActUser * user = NULL;

    g_debug("Accounts Manager Loaded");

    user = act_user_manager_get_user(priv->user_manager, g_get_user_name());
    if (user != NULL) {
        user_changed(priv->user_manager, user, user_data);
    }
}

/* Not the most testable way to do this but, it is a less invasive one, and we'll
   probably restructure this codebase soonish */
/* Gets an account service wrapper reference, so then it can be free'd */
ImAccountsService *
im_accounts_service_ref_default (void)
{
    static ImAccountsService * as = NULL;
    if (as == NULL) {
        as = IM_ACCOUNTS_SERVICE(g_object_new(IM_ACCOUNTS_SERVICE_TYPE, NULL));
        g_object_add_weak_pointer(G_OBJECT(as), (gpointer *)&as);
        return as;
    }

    return g_object_ref(as);
}

/* The draws attention setting is very legacy right now, we've patched and not changed
   things much. We're gonna do better in the future, this function abstracts out the ugly */
void
im_accounts_service_set_draws_attention (ImAccountsService * service, gboolean draws_attention)
{
    g_return_if_fail(IM_IS_ACCOUNTS_SERVICE(service));
    ImAccountsService * self = IM_ACCOUNTS_SERVICE(service);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);

    if (priv->touch_settings == NULL) {
        return;
    }

    g_dbus_connection_call(g_dbus_proxy_get_connection(priv->touch_settings),
        g_dbus_proxy_get_name(priv->touch_settings),
        g_dbus_proxy_get_object_path(priv->touch_settings),
        "org.freedesktop.Accounts.User",
        "SetXHasMessages",
        g_variant_new("(b)", draws_attention),
        NULL, /* reply */
        G_DBUS_CALL_FLAGS_NONE,
        -1, /* timeout */
        priv->cancel, /* cancellable */
        NULL, NULL); /* cb */
}

/* Looks at the property that is set by settings. We default to off in any case
   that we can or we don't know what the state is. */
gboolean
im_accounts_service_get_show_on_greeter (ImAccountsService * service)
{
    g_return_val_if_fail(IM_IS_ACCOUNTS_SERVICE(service), FALSE);

    ImAccountsService * self = IM_ACCOUNTS_SERVICE(service);
    ImAccountsServicePrivate * priv = im_accounts_service_get_instance_private(self);

    if (priv->touch_settings == NULL) {
        return FALSE;
    }

    GVariant * val = g_dbus_proxy_get_cached_property(priv->touch_settings, "MessagesWelcomeScreen");
    if (val == NULL) {
        return FALSE;
    }

    gboolean retval = g_variant_get_boolean(val);
    g_variant_unref(val);
    return retval;
}
