/*
  Copyright (C) 2015  ABRT team

  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, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_POLKIT
#include <polkit/polkit.h>
#endif

#include <glib-object.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "libabrt.h"
#include "problem_api.h"
#include "abrt_problems2_task_new_problem.h"
#include "abrt_problems2_task.h"
#include "abrt_problems2_generated_interfaces.h"
#include "abrt_problems2_service.h"
#include "abrt_problems2_session.h"
#include "abrt_problems2_entry.h"

#include <dbus/dbus.h>

/* Shared polkit authority */
PolkitAuthority *g_polkit_authority;
int g_polkit_authority_refs;

/*
 * DBus object type
 */
struct problems2_object_type
{
    GDBusNodeInfo *node;
    GDBusInterfaceInfo *iface;
    GDBusInterfaceVTable *vtable;
    GHashTable *objects;
};

static int problems2_object_type_init(struct problems2_object_type *type,
            const char *xml_node,
            GDBusInterfaceVTable *vtable)
{
    GError *local_error = NULL;
    type->node = g_dbus_node_info_new_for_xml(xml_node, &local_error);
    if (local_error != NULL)
    {
        log_info("Failed to parse XML interface file: %s", local_error->message);
        g_error_free(local_error);
        return -1;
    }

    type->iface = type->node->interfaces[0];
    type->vtable = vtable;
    type->objects = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);

    return 0;
}

static void problems2_object_type_destroy(struct problems2_object_type *type)
{
    if (type->objects != NULL)
    {
        g_hash_table_destroy(type->objects);
        type->objects = NULL;
    }

    if (type->node != NULL)
    {
        g_dbus_node_info_unref(type->node);
        type->node = NULL;
    }
}

#if 0
/* Debuging function */
static void problems2_object_type_print_all_objects(struct problems2_object_type *type,
            const char *prefix)
{
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, type->objects);

    const char *p;
    AbrtP2Object *obj;
    while(g_hash_table_iter_next(&iter, (gpointer)&p, (gpointer)&obj))
        log_warning("%s: '%s' : %p", prefix, p, obj);
}
#endif

static AbrtP2Object *problems2_object_type_get_object(struct problems2_object_type *type,
            const char *path)
{
    AbrtP2Object *obj = g_hash_table_lookup(type->objects, path);
    return obj;
}

static GList *problems2_object_type_get_all_objects(struct problems2_object_type *type)
{
    GList *objects = g_hash_table_get_values(type->objects);
    return objects;
}

/*
 * User details
 */
struct user_info
{
    GList *sessions;
    long unsigned problems;
    unsigned new_problems;
    time_t new_problem_last;
};

static struct user_info *user_info_new(void)
{
    struct user_info *user = xzalloc(sizeof(*user));
    return user;
}

static void user_info_free(struct user_info *info)
{
    if (info == NULL)
        return;

    g_list_free(info->sessions);
    info->sessions = (void *)0xDAEDBEEF;

    free(info);
}

/*
 * AbrtP2Service GObject Type
 */
enum {
    SERVICE_SIGNALS_NEW_CLIENT_CONNECTED,
    SERVICE_SIGNALS_ALL_CLIENTS_DISCONNECTED,
    SERVICE_SIGNALS_NUM,
};
static guint service_signals[SERVICE_SIGNALS_NUM] = { 0 };

typedef struct
{
    GDBusConnection *p2srv_dbus;
    GDBusProxy      *p2srv_proxy_dbus;
    GHashTable      *p2srv_connected_users;
    PolkitAuthority *p2srv_pk_authority;

    struct problems2_object_type p2srv_p2_type;
    struct problems2_object_type p2srv_p2_entry_type;
    struct problems2_object_type p2srv_p2_session_type;
    struct problems2_object_type p2srv_p2_task_type;

    long     p2srv_max_message_size;
    long     p2srv_max_message_unix_fds;
    unsigned p2srv_limit_clients;
    unsigned p2srv_limit_elements;
    off_t    p2srv_limit_data_size;
    unsigned p2srv_limit_user_problems;
    unsigned p2srv_limit_new_problem_throttling_magnitude;
    unsigned p2srv_limit_new_problems_batch;

    AbrtP2Object *p2srv_p2_object;
} AbrtP2ServicePrivate;

struct _AbrtP2Service
{
    GObject parent_instance;
    AbrtP2ServicePrivate *pv;
};

G_DEFINE_TYPE_WITH_PRIVATE(AbrtP2Service, abrt_p2_service, G_TYPE_OBJECT)

/*
 * Private functions
 */
static struct user_info *abrt_p2_service_user_lookup(AbrtP2Service *service,
            uid_t uid);

static struct user_info *abrt_p2_service_user_insert(AbrtP2Service *service,
            uid_t uid,
            struct user_info *user);

static struct user_info *abrt_p2_service_user_new(AbrtP2Service *service,
            uid_t uid);

static GDBusConnection *abrt_p2_service_dbus(AbrtP2Service *service);

/*
 * DBus object
 */
struct _AbrtP2Object
{
    AbrtP2Service *p2o_service;
    struct problems2_object_type *p2o_type;
    char *p2o_path;
    guint p2o_regid;
    void *node;
    void (*destructor)(AbrtP2Object *);
};

static void abrt_p2_object_free(AbrtP2Object *obj)
{
    if (obj == NULL)
        return;

    /* remove the destroyed object before destructing it */
    g_hash_table_remove(obj->p2o_type->objects, obj->p2o_path);

    if (obj->destructor)
        obj->destructor(obj);

    obj->node = (void *)0xDEADBEAF;
    obj->destructor = (void *)0xDEADBEAF;

    free(obj->p2o_path);
    obj->p2o_path = (void *)0xDEADBEAF;

    obj->p2o_regid = (guint)-1;

    obj->p2o_service = NULL;

    free(obj);
}

static AbrtP2Service *abrt_p2_object_service(AbrtP2Object *object)
{
    return object->p2o_service;
}

void *abrt_p2_object_get_node(AbrtP2Object *object)
{
    return object->node;
}

void abrt_p2_object_destroy(AbrtP2Object *object)
{
    log_debug("Unregistering object: %s", object->p2o_path);

    g_dbus_connection_unregister_object(abrt_p2_service_dbus(object->p2o_service),
                                        object->p2o_regid);
}

static void abrt_p2_object_emit_signal_with_destination(AbrtP2Object *object,
            const char *member,
            GVariant *parameters,
            const char *destination)
{
    GDBusMessage *message = g_dbus_message_new_signal(object->p2o_path,
                                                      object->p2o_type->iface->name,
                                                      member);
    if (destination != NULL)
        g_dbus_message_set_destination(message, destination);

    g_dbus_message_set_sender(message, ABRT_P2_BUS);
    g_dbus_message_set_body(message, parameters);

    if (g_verbose > 2)
    {
        gchar *pstr = g_variant_print(parameters, TRUE);
        log_debug("Emitting signal '%s' : (%s)", member, pstr);
        g_free(pstr);
    }

    GError *error = NULL;
    g_dbus_connection_send_message(abrt_p2_service_dbus(object->p2o_service),
                                   message,
                                   G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                   NULL,
                                   &error);
    g_object_unref(message);
    if (error != NULL)
    {
        error_msg("Failed to emit signal '%s': %s", member, error->message);
        g_free(error);
    }
}

static AbrtP2Object *abrt_p2_object_new(AbrtP2Service *service,
            struct problems2_object_type *type,
            char *path,
            void *node,
            void (*destructor)(AbrtP2Object *),
            GError **error)
{
    AbrtP2Object *obj = NULL;
    obj = xzalloc(sizeof(*obj));
    obj->p2o_path = path;
    obj->node = node;
    obj->destructor = destructor;
    obj->p2o_type = type;
    obj->p2o_service = service;

    /* Register the interface parsed from a XML file */
    log_debug("Registering PATH %s iface %s", path, type->iface->name);
    const guint registration_id = g_dbus_connection_register_object(abrt_p2_service_dbus(service),
                                                                    path,
                                                                    type->iface,
                                                                    type->vtable,
                                                                    obj,
                                                                    (GDestroyNotify)abrt_p2_object_free,
                                                                    error);

    if (registration_id == 0)
    {
        g_prefix_error(error, "Failed to register path:'%s', interface: %s: ",
                       path,
                       type->iface->name);

        abrt_p2_object_free(obj);

        return NULL;
    }

    log_debug("Registered object: %d", registration_id);

    obj->p2o_regid = registration_id;

    g_hash_table_insert(type->objects, path, obj);

    return obj;
}

const char *abrt_p2_object_path(AbrtP2Object *obj)
{
    return obj->p2o_path;
}

/*
 * /org/freedesktop/Problems2/Session/XYZ
 */
static void session_object_dbus_method_call(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *method_name,
            GVariant    *parameters,
            GDBusMethodInvocation *invocation,
            gpointer    user_data)
{
    log_debug("Problems2.Sessions method : %s", method_name);

    /* Check sanity */
    if (strcmp(interface_name, ABRT_P2_NS_MEMBER("Session")) != 0)
    {
        error_msg("Unsupported interface %s", interface_name);
        return;
    }

    GError *error = NULL;

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t caller_uid = abrt_p2_service_caller_real_uid(service, caller, &error);
    if (caller_uid == (uid_t)-1)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    AbrtP2Session *session = abrt_p2_object_get_node(user_data);
    if (abrt_p2_session_check_sanity(session, caller, caller_uid, &error) != 0)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    if (strcmp("Authorize", method_name) == 0)
    {
        GVariant *details = g_variant_get_child_value(parameters, 0);
        struct user_info *user = abrt_p2_service_user_lookup(service, caller_uid);
        const gint32 retval = abrt_p2_session_authorize(session,
                                                        details,
                                                        user->sessions,
                                                        &error);
        g_variant_unref(details);

        if (retval < 0)
        {
            g_prefix_error(&error, "Failed to authorize Session: ");
            g_dbus_method_invocation_return_gerror(invocation, error);
            g_error_free(error);
            return;
        }

        GVariant *response = g_variant_new("(i)", retval);
        g_dbus_method_invocation_return_value(invocation, response);
        return;
    }

    if (strcmp("GenerateToken", method_name) == 0)
    {
        guint duration = 0;
        g_variant_get(parameters, "(u)", &duration);
        const char *token = abrt_p2_session_generate_token(session, duration, &error);
        if (token == NULL)
        {
            g_prefix_error(&error, "Cannot generate token: ");
            g_dbus_method_invocation_return_gerror(invocation, error);
            g_error_free(error);
            return;
        }

        GVariant *response = g_variant_new("(s)", token);
        g_dbus_method_invocation_return_value(invocation, response);
        return;
    }

    if (strcmp("RevokeToken", method_name) == 0)
    {
        GVariant *token = g_variant_get_child_value(parameters, 0);
        if (abrt_p2_session_revoke_token(session, g_variant_get_string(token, NULL)) != 0)
            log_warning("Could not remove Session Token because it was already gone.");
        return;
    }

    if (strcmp("RevokeAuthorization", method_name) == 0)
    {
        abrt_p2_session_revoke_authorization(session);
        g_dbus_method_invocation_return_value(invocation, NULL);
        return;
    }

    error_msg("BUG: org.freedesktop.Problems2.Session does not have method: %s",
              method_name);
}

static GVariant *session_object_dbus_get_property(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *property_name,
            GError      **error,
            gpointer    user_data)
{
    log_debug("Problems2.Sessions get property : %s", property_name);

    if (strcmp(interface_name, "org.freedesktop.Problems2.Session") != 0)
    {
        error_msg("Unsupported interface %s", interface_name);
        return NULL;
    }

    if (strcmp("IsAuthorized", property_name))
    {
        error_msg("Unsupported property %s", property_name);
        return NULL;
    }

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t caller_uid = abrt_p2_service_caller_real_uid(service, caller, error);
    if (caller_uid == (uid_t)-1)
        return NULL;

    AbrtP2Session *node = abrt_p2_object_get_node(user_data);
    if (abrt_p2_session_check_sanity(node, caller, caller_uid, error) != 0)
        return NULL;

    return g_variant_new_boolean(abrt_p2_session_is_authorized(node));
}

static void session_object_destructor(AbrtP2Object *obj)
{
    AbrtP2Session *session = (AbrtP2Session *)obj->node;

    uid_t uid = abrt_p2_session_uid(session);

    struct user_info *user = abrt_p2_service_user_lookup(obj->p2o_service, uid);

    if (user->sessions == NULL)
    {
        error_msg("BUG: destructing session object for user who does not have session opened");
        abort();
    }

    abrt_p2_session_clean_tasks(session);
    abrt_p2_session_revoke_authorization(session);

    user->sessions = g_list_remove(user->sessions, session);

    const guint size = g_hash_table_size(obj->p2o_type->objects);
    if (size == 0)
    {
        g_signal_emit(obj->p2o_service,
                      service_signals[SERVICE_SIGNALS_ALL_CLIENTS_DISCONNECTED],
                      0/*details*/);
    }

    g_object_unref(session);
}

static void session_object_on_authorization_changed(AbrtP2Session *session,
            gint32 status,
            gpointer object)
{
    GVariant *params = g_variant_new("(i)", status);
    const char *session_bus_address = abrt_p2_session_caller(session);
    abrt_p2_object_emit_signal_with_destination(object,
                                                "AuthorizationChanged",
                                                params,
                                                session_bus_address);
}

static AbrtP2Object *session_object_register(AbrtP2Service *service,
            char *path,
            const char *caller,
            uid_t caller_uid,
            GError **error)
{
    struct user_info *user = abrt_p2_service_user_lookup(service, caller_uid);

    const unsigned client_limits = abrt_p2_service_user_clients_limit(service, caller_uid);
    if (user != NULL && g_list_length(user->sessions) >= client_limits)
    {
        log_warning("User %lu reached the limit of opened sessions (%d)",
                    (long unsigned)caller_uid,
                    client_limits);

        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Too many sessions opened");

        free(path);
        return NULL;
    }

    char *dup_caller = xstrdup(caller);

    AbrtP2Session *session = abrt_p2_session_new(dup_caller, caller_uid);

    AbrtP2Object *obj = abrt_p2_object_new(service,
                                           &(service->pv->p2srv_p2_session_type),
                                           path,
                                           session,
                                           session_object_destructor,
                                           error);

    if (obj == NULL)
    {
        g_prefix_error(error, "Failed to register Session object for caller '%s': ", caller);
        return NULL;
    }

    g_signal_connect(session, "authorization-changed", G_CALLBACK(session_object_on_authorization_changed), obj);

    if (user == NULL)
        user = abrt_p2_service_user_new(service, caller_uid);

    user->sessions = g_list_prepend(user->sessions, session);

    g_signal_emit(service,
                  service_signals[SERVICE_SIGNALS_NEW_CLIENT_CONNECTED],
                  0/*details*/);

    return obj;
}

static char *session_object_caller_to_path(const char *caller)
{
    char hash_str[SHA1_RESULT_LEN*2 + 1];
    str_to_sha1str(hash_str, caller);
    return xasprintf(ABRT_P2_PATH"/Session/%s", hash_str);
}

static AbrtP2Object *abrt_p2_service_get_session_for_caller(
            AbrtP2Service *service,
            const char *caller,
            uid_t caller_uid,
            GError **error)
{
    char *session_path = session_object_caller_to_path(caller);

    AbrtP2Object *obj = problems2_object_type_get_object(&(service->pv->p2srv_p2_session_type),
                                                         session_path);
    if (obj == NULL)
    {
        log_debug("Caller does not have Session: %s", caller);
        return session_object_register(service,
                                       session_path,
                                       caller,
                                       caller_uid,
                                       error);
    }

    free(session_path);

    AbrtP2Session *session = abrt_p2_object_get_node(obj);
    if (abrt_p2_session_check_sanity(session, caller, caller_uid, error) != 0)
    {
        log_debug("Cannot return session because the existing one did not pass sanity check.");
        return NULL;
    }

    return obj;
}

const char *abrt_p2_service_session_path(AbrtP2Service *service,
            const char *caller,
            GError **error)
{
    uid_t caller_uid = abrt_p2_service_caller_real_uid(service, caller, error);
    if (caller_uid == (uid_t)-1)
        return NULL;

    AbrtP2Object *obj = abrt_p2_service_get_session_for_caller(service,
                                                               caller,
                                                               caller_uid,
                                                               error);

    return obj == NULL ? NULL : obj->p2o_path;
}

static uid_t abrt_p2_service_get_session_uid(AbrtP2Service *service,
            AbrtP2Session *session)
{
    return abrt_p2_session_is_authorized(session)
            ? 0
            : abrt_p2_session_uid(session);
}

uid_t abrt_p2_service_caller_uid(AbrtP2Service *service,
            const char *caller,
            GError **error)
{
    uid_t caller_uid = abrt_p2_service_caller_real_uid(service, caller, error);
    if (caller_uid == (uid_t)-1)
        return (uid_t)-1;

    AbrtP2Object *obj = abrt_p2_service_get_session_for_caller(service,
                                                               caller,
                                                               caller_uid,
                                                               error);
    if (obj == NULL)
        return (uid_t) -1;

    AbrtP2Session *session = abrt_p2_object_get_node(obj);
    return abrt_p2_service_get_session_uid(service, session);
}

uid_t abrt_p2_service_caller_real_uid(AbrtP2Service *service,
            const char *caller,
            GError **error)
{
    guint caller_uid;

    if (service->pv->p2srv_proxy_dbus == NULL)
        return (uid_t) -1;

    GVariant *result = g_dbus_proxy_call_sync(service->pv->p2srv_proxy_dbus,
                                              "GetConnectionUnixUser",
                                              g_variant_new ("(s)",
                                                             caller),
                                              G_DBUS_CALL_FLAGS_NONE,
                                              -1,
                                              NULL,
                                              error);

    if (result == NULL)
        return (uid_t) -1;

    g_variant_get(result, "(u)", &caller_uid);
    g_variant_unref(result);

    log_info("Caller uid: %i", caller_uid);
    return caller_uid;
}

/*
 * /org/freedesktop/Problems2/Entry/XYZ
 */
void abrt_p2_service_notify_entry_object(AbrtP2Service *service,
            AbrtP2Object *obj,
            GError **error)
{
    AbrtP2Entry *entry = abrt_p2_object_get_node(obj);
    uid_t owner_uid = abrt_p2_entry_get_owner(entry, error);

    if (owner_uid >= 0)
    {
        GList *session_objects = problems2_object_type_get_all_objects(
                                         &service->pv->p2srv_p2_session_type);

        for (GList *iter = session_objects; iter != NULL; iter = g_list_next(iter))
        {
            AbrtP2Session *session = abrt_p2_object_get_node(iter->data);
            const uid_t session_uid = abrt_p2_service_get_session_uid(service,
                                                                      session);

            const char *session_bus_address = abrt_p2_session_caller(session);

            if ( 0 != abrt_p2_entry_accessible_by_uid(entry, session_uid, NULL))
            {
                log_debug("Crash signal not sent to not-authorized session: '%s'",
                          session_bus_address);
                continue;
            }

            log_debug("Crash signal sent to authorized session: '%s'",
                      session_bus_address);

            GVariant *parameters = g_variant_new("(oi)",
                                                 obj->p2o_path,
                                                 (gint32)owner_uid);

            abrt_p2_object_emit_signal_with_destination(
                                                 service->pv->p2srv_p2_object,
                                                 "Crash",
                                                 parameters,
                                                 session_bus_address);
        }

        g_list_free(session_objects);
    }
}

struct entry_object_save_elements_context
{
    GDBusMethodInvocation *invocation;
    GVariant *elements;
};

static void entry_object_save_elements_cb(GObject *source_object,
            GAsyncResult *result,
            gpointer user_data)
{
    AbrtP2Entry *entry = ABRT_P2_ENTRY(source_object);
    struct entry_object_save_elements_context *context = user_data;

    g_variant_unref(context->elements);

    GError *error = NULL;
    GVariant *response = abrt_p2_entry_save_elements_finish(entry,
                                                            result,
                                                            &error);

    if (error == NULL)
    {
        g_dbus_method_invocation_return_value(context->invocation, response);
    }
    else
    {
        g_dbus_method_invocation_return_gerror(context->invocation, error);
        g_error_free(error);
    }

    g_object_unref(context->invocation);
    free(context);
}

struct entry_object_read_elements_context
{
    GDBusMethodInvocation *invocation;
    GVariant *elements;
    GUnixFDList *out_fd_list;
};

static void entry_object_read_elements_cb(GObject *source_object,
            GAsyncResult *result,
            gpointer user_data)
{
    AbrtP2Entry *entry = ABRT_P2_ENTRY(source_object);
    struct entry_object_read_elements_context *context = user_data;

    g_variant_unref(context->elements);

    GError *error = NULL;
    GVariant *response = abrt_p2_entry_read_elements_finish(entry, result, &error);
    if (error == NULL)
    {
        g_dbus_method_invocation_return_value_with_unix_fd_list(context->invocation,
                                                                response,
                                                                context->out_fd_list);
    }
    else
    {
        g_dbus_method_invocation_return_gerror(context->invocation, error);
        g_error_free(error);
    }

    g_object_unref(context->out_fd_list);
    g_object_unref(context->invocation);
    free(context);
}

static void entry_object_dbus_method_call(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *method_name,
            GVariant    *parameters,
            GDBusMethodInvocation *invocation,
            gpointer    user_data)
{
    log_debug("Problems2.Entry method : %s", method_name);

    AbrtP2Service *service = abrt_p2_object_service(user_data);

    GError *error = NULL;
    uid_t caller_uid = abrt_p2_service_caller_uid(service, caller, &error);
    if (caller_uid == (uid_t)-1)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    GVariant *response = NULL;
    AbrtP2Entry *entry = abrt_p2_object_get_node(user_data);
    if (strcmp(method_name, "GetSemanticElement") == 0)
    {
        return;
    }
    else if (strcmp(method_name, "SetSemanticElement") == 0)
    {
        return;
    }
    else if (strcmp(method_name, "ReadElements") == 0)
    {
        struct entry_object_read_elements_context *context = xmalloc(sizeof(*context));
        context->invocation = g_object_ref(invocation);
        context->elements = g_variant_get_child_value(parameters, 0);
        context->out_fd_list = g_unix_fd_list_new();

        gint32 flags;
        g_variant_get_child(parameters, 1, "i", &flags);

        GCancellable *cancellable = g_cancellable_new();

        abrt_p2_entry_read_elements_async(entry,
                                          flags,
                                          context->elements,
                                          context->out_fd_list,
                                          caller_uid,
                                          service->pv->p2srv_max_message_size,
                                          service->pv->p2srv_max_message_unix_fds,
                                          cancellable,
                                          entry_object_read_elements_cb,
                                          context);

        return;
    }
    else if (strcmp(method_name, "SaveElements") == 0)
    {
        GDBusMessage *msg = g_dbus_method_invocation_get_message(invocation);
        GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(msg);

        struct entry_object_save_elements_context *context = xmalloc(sizeof(*context));

        context->invocation = g_object_ref(invocation);
        context->elements = g_variant_get_child_value(parameters, 0);

        gint32 flags;
        g_variant_get_child(parameters, 1, "i", &flags);

        ABRT_P2_ENTRY_SAVE_ELEMENTS_LIMITS_ON_STACK(limits,
                        abrt_p2_service_elements_limit(service, caller_uid),
                        abrt_p2_service_data_size_limit(service, caller_uid));

        GCancellable *cancellable = g_cancellable_new();

        abrt_p2_entry_save_elements_async(entry,
                                          flags,
                                          context->elements,
                                          fd_list,
                                          caller_uid,
                                          &limits,
                                          cancellable,
                                          entry_object_save_elements_cb,
                                          context);

        return;
    }
    else if (strcmp(method_name, "DeleteElements") == 0)
    {
        GVariant *elements = g_variant_get_child_value(parameters, 0);

        response = abrt_p2_entry_delete_elements(entry,
                                                 caller_uid,
                                                 elements,
                                                 &error);

        g_variant_unref(elements);
    }
    else
    {
        error_msg("BUG: org.freedesktop.Problems2.Entry does not have method: %s",
                  method_name);
        g_dbus_method_invocation_return_error(invocation,
                                              G_DBUS_ERROR,
                                              G_DBUS_ERROR_UNKNOWN_METHOD,
                                              "The method has to be implemented");
    }

    if (error != NULL)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
    }
    else
        g_dbus_method_invocation_return_value(invocation, response);
}


#define GET_PLAIN_TEXT_PROPERTY(name, element) \
        if (strcmp(name, property_name) == 0) \
        { \
            char *tmp_value = dd_load_text_ext(dd, element, DD_FAIL_QUIETLY_ENOENT); \
            retval = g_variant_new_string(tmp_value ? tmp_value : ""); \
            free(tmp_value); \
            goto return_property_value; \
        }

#define GET_INTEGER_PROPERTY(name, element, S, def) \
        if (strcmp(name, property_name) == 0) \
        { \
            uint##S##_t tmp_value = def; \
            dd_load_uint##S (dd, element, &tmp_value); \
            retval = g_variant_new_uint##S ((guint##S)tmp_value); \
            goto return_property_value; \
        }

#define GET_UINT32_PROPERTY(name, element, def) GET_INTEGER_PROPERTY(name, element, 32, def)

static GVariant *entry_object_dbus_get_property(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *property_name,
            GError      **error,
            gpointer    user_data)
{
    log_debug("Problems2.Entry get property : %s", property_name);

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t caller_uid = abrt_p2_service_caller_uid(service, caller, error);
    if (caller_uid == (uid_t)-1)
        return NULL;

    GVariant *retval;
    AbrtP2Entry *entry = abrt_p2_object_get_node(user_data);
    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(entry,
                                                      caller_uid,
                                                      DD_DONT_WAIT_FOR_LOCK | DD_OPEN_READONLY,
                                                      error);
    if (dd == NULL)
        return NULL;

    if (strcmp("ID", property_name) == 0)
    {
        retval = g_variant_new_string(dd->dd_dirname);
        goto return_property_value;
    }

    GET_PLAIN_TEXT_PROPERTY("User", FILENAME_USERNAME)
    GET_PLAIN_TEXT_PROPERTY("Hostname", FILENAME_HOSTNAME)
    GET_PLAIN_TEXT_PROPERTY("Type", FILENAME_TYPE)
    GET_PLAIN_TEXT_PROPERTY("Executable", FILENAME_EXECUTABLE)
    GET_PLAIN_TEXT_PROPERTY("CommandLineArguments", FILENAME_CMDLINE)
    GET_PLAIN_TEXT_PROPERTY("Component", FILENAME_COMPONENT)
    GET_PLAIN_TEXT_PROPERTY("UUID", FILENAME_UUID)
    GET_PLAIN_TEXT_PROPERTY("Duphash", FILENAME_DUPHASH)
    GET_PLAIN_TEXT_PROPERTY("Reason", FILENAME_REASON)
    GET_PLAIN_TEXT_PROPERTY("TechnicalDetails", FILENAME_NOT_REPORTABLE)

    GET_UINT32_PROPERTY("UID", FILENAME_UID, 0)
    GET_UINT32_PROPERTY("Count", FILENAME_COUNT, 1)

    if (strcmp("FirstOccurrence", property_name) == 0)
    {
        time_t tm = dd_get_first_occurrence(dd);
        if (tm == (time_t) -1)
        {
            dd_close(dd);
            g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                        "Invalid problem data: FirstOccurrence cannot be returned");
            return NULL;
        }

        retval = g_variant_new_uint64((guint64)tm);
        goto return_property_value;
    }

    if (strcmp("LastOccurrence", property_name) == 0)
    {
        time_t ltm = dd_get_last_occurrence(dd);
        if (ltm == (time_t) -1)
        {
            dd_close(dd);
            g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                        "Invalid problem data: LastOccurrence cannot be returned");
            return NULL;
        }

        retval = g_variant_new_uint64((guint64)ltm);
        goto return_property_value;
    }

    if (strcmp("Package", property_name) == 0)
    {
        const char *const elements[] = { FILENAME_PACKAGE,
                                         FILENAME_PKG_EPOCH,
                                         FILENAME_PKG_NAME,
                                         FILENAME_PKG_VERSION,
                                         FILENAME_PKG_RELEASE };

        GVariantBuilder builder;
        g_variant_builder_init(&builder, G_VARIANT_TYPE("(sssss)"));
        for (size_t i = 0; i < ARRAY_SIZE(elements); ++i)
        {
            char *data = dd_load_text_ext(dd, elements[i], DD_FAIL_QUIETLY_ENOENT);
            g_variant_builder_add(&builder, "s", data);
            free(data);
        }

        retval = g_variant_builder_end(&builder);
        goto return_property_value;
    }

    if (strcmp("Reports", property_name) == 0)
    {
        GVariantBuilder top_builder;
        g_variant_builder_init(&top_builder, G_VARIANT_TYPE("a(sa{sv})"));

        GList *reports = read_entire_reported_to(dd);
        for (GList *iter = reports; iter != NULL; iter = g_list_next(iter))
        {
            GVariantBuilder value_builder;
            g_variant_builder_init(&value_builder, G_VARIANT_TYPE("a{sv}"));

            struct report_result *r = (struct report_result *)iter->data;

            if (r->url != NULL)
            {
                GVariant *data = g_variant_new_variant(g_variant_new_string(r->url));
                g_variant_builder_add(&value_builder, "{sv}", "URL", data);
            }
            if (r->msg != NULL)
            {
                GVariant *data = g_variant_new_variant(g_variant_new_string(r->msg));
                g_variant_builder_add(&value_builder, "{sv}", "MSG", data);
            }
            if (r->bthash != NULL)
            {
                GVariant *data = g_variant_new_variant(g_variant_new_string(r->bthash));
                g_variant_builder_add(&value_builder, "{sv}", "BTHASH", data);
            }

            GVariant *children[2];
            children[0] = g_variant_new_string(r->label);
            children[1] = g_variant_builder_end(&value_builder);
            GVariant *entry = g_variant_new_tuple(children, 2);

            g_variant_builder_add_value(&top_builder, entry);
        }

        g_list_free_full(reports, (GDestroyNotify)free_report_result);

        retval = g_variant_builder_end(&top_builder);

        goto return_property_value;
    }

    if (strcmp("Solutions", property_name) == 0)
    {
        GVariantBuilder builder;
        g_variant_builder_init(&builder, G_VARIANT_TYPE("a(sssssi)"));

        /* TODO: not-yet-implemented - we don't know where to get the data */

        retval = g_variant_builder_end(&builder);
        goto return_property_value;
    }

    if (strcmp("Elements", property_name) == 0)
    {
        GVariantBuilder builder;
        g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
        dd_init_next_file(dd);
        char *short_name;
        while (dd_get_next_file(dd, &short_name, NULL))
        {
            g_variant_builder_add(&builder, "s", short_name);
            free(short_name);
        }
        retval = g_variant_builder_end(&builder);
        goto return_property_value;
    }

    if (strcmp("SemanticElements", property_name) == 0)
    {
        GVariantBuilder builder;
        g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
        /* no semantic elements yet */
        retval = g_variant_builder_end(&builder);
        goto return_property_value;
    }

    if (strcmp("IsReported", property_name) == 0)
    {
       retval = g_variant_new_boolean(dd_exist(dd, FILENAME_REPORTED_TO));
       goto return_property_value;
    }

    if (strcmp("CanBeReported", property_name) == 0)
    {
       retval = g_variant_new_boolean(!dd_exist(dd, FILENAME_NOT_REPORTABLE));
       goto return_property_value;
    }

    if (strcmp("IsRemote", property_name) == 0)
    {
       retval = g_variant_new_boolean(dd_exist(dd, FILENAME_REMOTE));
       goto return_property_value;
    }

    dd_close(dd);
    error_msg("Unknown property %s", property_name);
    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
            "BUG: the property getter has to be implemented");
    return NULL;

return_property_value:
    dd_close(dd);
    return retval;
}

#ifdef PROBLEMS2_PROPERTY_SET
static gboolean entry_object_dbus_set_property(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *property_name,
            GVariant    *args,
            GError      **error,
            gpointer    user_data)
{
    log_debug("Problems2.Entry set property : %s", property_name);

    uid_t caller_uid = abrt_p2_service_caller_uid(connection, caller, error);
    if (caller_uid == (uid_t)-1)
        return FALSE;

    AbrtP2Entry *entry = abrt_p2_object_get_node(user_data);
    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(entry,
                                                      caller_uid,
                                                      DD_DONT_WAIT_FOR_LOCK,
                                                      error);
    if (entry == NULL)
        return FALSE;

    if (strcmp("id", property_name) == 0)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_PROPERTY_READ_ONLY);
        return FALSE;
    }

    if (strcmp("uid", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("user", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("hostname", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("type", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("first_occurrence", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("last_occurrence", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("count", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("executable", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("command_line_arguments", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("component", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("package", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("uuid", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("duphash", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("reports", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("reason", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("solutions", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("technical_details", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("elements", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("semantic_elements", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("is_reported", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("can_be_reported", property_name) == 0)
    {
        return FALSE;
    }

    if (strcmp("is_remote", property_name) == 0)
    {
        return FALSE;
    }

    error_msg("Unknown property %s", property_name);
    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
                "BUG: the property setter has to be implemented");
    return FALSE;
}
#endif/*PROBLEMS2_PROPERTY_SET*/

static void entry_object_destructor(AbrtP2Object *obj)
{
    AbrtP2Entry *entry = (AbrtP2Entry *)obj->node;
    g_object_unref(entry);
}

static char *entry_object_dir_name_to_path(const char *dd_dirname)
{
    char hash_str[SHA1_RESULT_LEN*2 + 1];
    str_to_sha1str(hash_str, dd_dirname);
    return xasprintf(ABRT_P2_PATH"/Entry/%s", hash_str);
}

static AbrtP2Object *entry_object_register_dump_dir(AbrtP2Service *service,
                const char *dd_dirname,
                GError **error)
{
    char *const dup_dirname = xstrdup(dd_dirname);
    AbrtP2Entry *entry = abrt_p2_entry_new(dup_dirname);

    return abrt_p2_service_register_entry(service, entry, error);
}

AbrtP2Object *abrt_p2_service_register_entry(AbrtP2Service *service,
            struct _AbrtP2Entry *entry,
            GError **error)
{
    const char *dd_dirname = abrt_p2_entry_problem_id(entry);
    log_debug("Registering problem entry for directory: %s", dd_dirname);
    char *path = entry_object_dir_name_to_path(dd_dirname);

    AbrtP2Object *obj = abrt_p2_object_new(service,
                                           &(service->pv->p2srv_p2_entry_type),
                                           path,
                                           entry,
                                           entry_object_destructor,
                                           error);

    if (obj == NULL)
    {
        g_prefix_error(error, "Failed to register Entry object for directory '%s': ",
                       dd_dirname);
        return NULL;
    }

    struct dump_dir *dd = dd_opendir(dd_dirname, DD_OPEN_FD_ONLY);
    uid_t owner = dd_get_owner(dd);
    dd_close(dd);

    struct user_info *user = abrt_p2_service_user_lookup(service, owner);

    if (user == NULL)
        user = abrt_p2_service_user_new(service, owner);

    if (user->problems == ULONG_MAX)
    {
        /* Give up, we cannot recover from this. */
        error_msg_and_die("Too many problems owned by a single user: uid=%lu",
                          (long unsigned)owner);
    }

    user->problems++;

    return obj;
}

struct entry_object_save_problem_args
{
    AbrtP2EntrySaveElementsLimits limits;
    GVariant *problem_info;
    GUnixFDList *fd_list;
    uid_t caller_uid;
    GError **error;
};

static int entry_object_wrapped_abrt_p2_entry_save_elements(struct dump_dir *dd,
            struct entry_object_save_problem_args *args)
{
    return abrt_p2_entry_save_elements_in_dump_dir(dd,
                                                   ABRT_P2_ENTRY_ALL_FATAL,
                                                   args->problem_info,
                                                   args->fd_list,
                                                   args->caller_uid,
                                                   &(args->limits),
                                                   args->error);
}

static GList *abrt_g_variant_get_dict_keys(GVariant *dict)
{
    gchar *name = NULL;
    GVariant *value = NULL;
    GVariantIter iter;
    g_variant_iter_init(&iter, dict);

    GList *retval = NULL;
    /* No need to free 'name' and 'container' unless breaking out of the loop */
    while (g_variant_iter_loop(&iter, "{sv}", &name, &value))
        retval = g_list_prepend(retval, xstrdup(name));

    return retval;
}

char *abrt_p2_service_save_problem( AbrtP2Service *service,
            GVariant *problem_info,
            GUnixFDList *fd_list,
            uid_t caller_uid,
            GError **error)
{
    GVariantDict pd;
    g_variant_dict_init(&pd, problem_info);

    /* Re-implement problem_data_add_basics(problem_info); - I don't want to
     * convert GVariant* to problem_data_t and back.
     *
     * The problem data should be converted to some kind of interface!
     */
    char *analyzer_str = NULL;
    GVariant *analyzer_element = g_variant_dict_lookup_value(&pd,
                                                             FILENAME_ANALYZER,
                                                             G_VARIANT_TYPE_STRING);
    if (analyzer_element == NULL)
    {
        analyzer_str = xstrdup("libreport");
        g_variant_dict_insert(&pd, FILENAME_ANALYZER, "s", analyzer_str);
    }
    else
    {
        analyzer_str = xstrdup(g_variant_get_string(analyzer_element, NULL));
        g_variant_unref(analyzer_element);
    }

    char *type_str = NULL;
    GVariant *type_element = g_variant_dict_lookup_value(&pd,
                                                         FILENAME_TYPE,
                                                         G_VARIANT_TYPE_STRING);
    if (type_element == NULL)
    {
         type_str = xstrdup(analyzer_str);
    }
    else
    {
         type_str = xstrdup(g_variant_get_string(type_element, NULL));
         g_variant_unref(type_element);
    }

    GVariant *uuid_element = g_variant_dict_lookup_value(&pd,
                                                         FILENAME_UUID,
                                                         G_VARIANT_TYPE_STRING);
    if (uuid_element != NULL)
    {
        g_variant_unref(uuid_element);
    }
    else
    {
        GVariant *duphash_element = g_variant_dict_lookup_value(&pd,
                                                                FILENAME_DUPHASH,
                                                                G_VARIANT_TYPE_STRING);
        if (duphash_element != NULL)
        {
            g_variant_dict_insert_value(&pd, FILENAME_UUID, duphash_element);
            g_variant_unref(duphash_element);
        }
        else
        {
            /* start hash */
            sha1_ctx_t sha1ctx;
            sha1_begin(&sha1ctx);

            /*
             * To avoid spurious hash differences, sort keys so that elements are
             * always processed in the same order:
             */
            GList *list = abrt_g_variant_get_dict_keys(problem_info);
            list = g_list_sort(list, (GCompareFunc)strcmp);
            for (GList *l = list; l != NULL; l = g_list_next(l))
            {
                GVariant *element = g_variant_dict_lookup_value(&pd,
                                                                (const char *)l->data,
                                                                G_VARIANT_TYPE_STRING);
                /* do not hash items which are binary or file descriptor */
                if (element == NULL)
                    continue;

                gsize size = 0;
                const char *content = g_variant_get_string(element, &size);
                sha1_hash(&sha1ctx, content, size);

                g_variant_unref(element);
            }
            g_list_free_full(list, free);

            /* end hash */
            char hash_bytes[SHA1_RESULT_LEN];
            sha1_end(&sha1ctx, hash_bytes);
            char hash_str[SHA1_RESULT_LEN*2 + 1];
            bin2hex(hash_str, hash_bytes, SHA1_RESULT_LEN)[0] = '\0';

            g_variant_dict_insert(&pd, FILENAME_UUID, "s", hash_str);
        }
    }

    /* Sanitize UID
     */
    GVariant *uid_element =  g_variant_dict_lookup_value(&pd,
                                                         FILENAME_UID,
                                                         G_VARIANT_TYPE_STRING);
    if (caller_uid != 0 || uid_element == NULL)
    {   /* set uid field to caller's uid
           if caller is not root or root doesn't pass own uid */
        log_info("Adding UID %lu to the problem info", (long unsigned)caller_uid);
        char *uid_str = xasprintf("%lu", (long unsigned)caller_uid);
        g_variant_dict_insert(&pd, FILENAME_UID, "s", uid_str);
        free(uid_str);
    }

    if (uid_element != NULL)
        g_variant_unref(uid_element);

    struct entry_object_save_problem_args args = {
        .problem_info = g_variant_dict_end(&pd),
        .fd_list = fd_list,
        .caller_uid = caller_uid,
        .error = error,
    };

    ABRT_P2_ENTRY_SAVE_ELEMENTS_LIMITS_INITIALIZER(args.limits,
                        abrt_p2_service_elements_limit(service, caller_uid),
                        abrt_p2_service_data_size_limit(service, caller_uid));

    struct dump_dir *dd = create_dump_dir(g_settings_dump_location,
                                          type_str,
                                          /*fs owner*/0,
                                          (save_data_call_back)entry_object_wrapped_abrt_p2_entry_save_elements,
                                          (void *)&args);

    g_variant_unref(args.problem_info);
    free(type_str);
    free(analyzer_str);

    if (dd == NULL)
    {
        g_prefix_error(error, "Failed to create new problem directory: ");
        return NULL;
    }

    char *retval = xstrdup(dd->dd_dirname);
    dd_close(dd);

    return retval;
}

static
AbrtP2Object *abrt_p2_service_get_entry_object(AbrtP2Service *service,
            const char *entry_path,
            int flags,
            GError **error)
{
    AbrtP2Object *obj = problems2_object_type_get_object(&(service->pv->p2srv_p2_entry_type),
                                                         entry_path);

    if (obj == NULL && !(flags & ABRT_P2_SERVICE_ENTRY_LOOKUP_OPTIONAL))
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_BAD_ADDRESS,
                "Requested Entry does not exist");
    }

    return obj;
}

AbrtP2Object *abrt_p2_service_get_entry_for_problem(AbrtP2Service *service,
            const char *problem_id,
            AbrtP2ServiceEntryLookupFlags flags,
            GError **error)
{
    char *entry_path = entry_object_dir_name_to_path(problem_id);
    AbrtP2Object *obj = abrt_p2_service_get_entry_object(service,
                                                         entry_path,
                                                         flags,
                                                         error);
    free(entry_path);

    return obj;
}

int abrt_p2_service_remove_problem(AbrtP2Service *service,
            const char *entry_path,
            uid_t caller_uid,
            GError **error)
{
    AbrtP2Object *obj = abrt_p2_service_get_entry_object(service,
                                                         entry_path,
                                                         ABRT_P2_SERVICE_ENTRY_LOOKUP_NOFLAGS,
                                                         error);
    if (obj == NULL)
    {
        log_debug("The requested Problem Entry does not exist");
        return -ENOENT;
    }

    AbrtP2Entry *entry = ABRT_P2_ENTRY(abrt_p2_object_get_node(obj));
    if (abrt_p2_entry_state(entry) != ABRT_P2_ENTRY_STATE_COMPLETE)
    {
        log_debug("Cannot remove temporary/deleted Problem Entry");
        return -EINVAL;
    }

    const int ret = abrt_p2_entry_delete(entry, caller_uid, error);
    if (ret != 0)
    {
        log_debug("Failed to remove Entry's data directory");
        return ret;
    }

    abrt_p2_object_destroy(obj);
    return 0;
}

GVariant *abrt_p2_service_entry_problem_data(AbrtP2Service *service,
            const char *entry_path,
            uid_t caller_uid,
            GError **error)
{
    AbrtP2Object *obj = abrt_p2_service_get_entry_object(service,
                                                         entry_path,
                                                         ABRT_P2_SERVICE_ENTRY_LOOKUP_NOFLAGS,
                                                         error);
    if (obj == NULL)
        return NULL;

    return abrt_p2_entry_problem_data(ABRT_P2_ENTRY(obj->node), caller_uid, error);
}

/*
 * /org/freedesktop/Problems2/Task
 */
static void task_object_on_status_changed(AbrtP2Task *task,
            gint32 status,
            gpointer user_data);

static void task_object_on_canceled_task(AbrtP2Task *task,
            gint32 status,
            gpointer user_data);

static void task_object_dispose(AbrtP2Object *obj)
{
    AbrtP2Task *task = abrt_p2_object_get_node(obj);

    gulong sig_handler = g_signal_handler_find(task,
                                               G_SIGNAL_MATCH_FUNC,
                                               0,
                                               0,
                                               NULL,
                                               task_object_on_status_changed,
                                               NULL);

    if (sig_handler != 0)
        g_signal_handler_disconnect(task, sig_handler);

    sig_handler = g_signal_handler_find(task,
                                        G_SIGNAL_MATCH_FUNC,
                                        0,
                                        0,
                                        NULL,
                                        task_object_on_canceled_task,
                                        NULL);

    if (sig_handler != 0)
        g_signal_handler_disconnect(task, sig_handler);

    abrt_p2_object_destroy(obj);
}

struct task_cancel_args
{
    AbrtP2Object  *tca_object;
    AbrtP2Session *tca_session;
};

static void task_object_on_canceled_task(AbrtP2Task *task,
            gint32 status,
            gpointer user_data)
{
    struct task_cancel_args *args = (struct task_cancel_args *)user_data;

    if (status != ABRT_P2_TASK_STATUS_CANCELED)
        log_warning("Canceled task moved to another state than CANCELLED");

    GError *local_error = NULL;
    abrt_p2_session_remove_task(args->tca_session, task, &local_error);
    g_object_unref(args->tca_session);

    if (local_error != NULL)
    {
        error_msg("BUG: failed to remove task from session: %s",
                  local_error->message);
        g_error_free(local_error);
    }

    log_debug("Disposing canceled task");
    task_object_dispose(args->tca_object);

    free(args);

    g_object_unref(task);
}

static void task_object_on_status_changed(AbrtP2Task *task,
            gint32 status,
            gpointer user_data)
{
    AbrtP2Object *object = (AbrtP2Object *)user_data;
    GDBusMessage *message = g_dbus_message_new_signal(object->p2o_path,
                                                     "org.freedesktop.DBus.Properties",
                                                     "PropertiesChanged");

    g_dbus_message_set_sender(message, ABRT_P2_BUS);

    GVariantDict properties_changed;
    g_variant_dict_init(&properties_changed, NULL);
    g_variant_dict_insert(&properties_changed, "status", "v", g_variant_new_int32(status));

    GVariant *children[3];
    children[0] = g_variant_new_string(object->p2o_type->iface->name);
    children[1] = g_variant_dict_end(&properties_changed);
    children[2] = g_variant_new("as", NULL);

    GVariant *parameters = g_variant_new_tuple(children, 3);

    g_dbus_message_set_body(message, parameters);

    if (g_verbose > 2)
    {
        gchar *pstr = g_variant_print(parameters, TRUE);
        log_debug("Emitting signal '%s' : (%s)",
                  "PropertiesChanged",
                  pstr);
        g_free(pstr);
    }

    GError *error = NULL;
    g_dbus_connection_send_message(abrt_p2_service_dbus(object->p2o_service),
                                   message,
                                   G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                   NULL,
                                   &error);
    g_object_unref(message);
    if (error != NULL)
    {
        error_msg("Failed to emit signal '%s': %s",
                  "PropertiesChanged",
                  error->message);
        g_free(error);
    }
}

static AbrtP2Object *task_object_register(AbrtP2Service* service,
            AbrtP2Object *session_obj,
            AbrtP2Task *task,
            GError **error)
{
    AbrtP2Session *session = abrt_p2_object_get_node(session_obj);
    uint32_t regid = abrt_p2_session_add_task(session, task, error);
    if (*error != NULL)
        return NULL;

    const char *session_path = abrt_p2_object_path(session_obj);
    char *path = xasprintf("%s/Task/%u", session_path, regid);

    AbrtP2Object *obj = abrt_p2_object_new(service,
                                           &(service->pv->p2srv_p2_task_type),
                                           path,
                                           task,
                                           NULL,
                                           error);

    if (obj == NULL)
    {
        g_prefix_error(error, "Failed to register Task object: ");
        return NULL;
    }

    g_signal_connect(task,
                     "status-changed",
                     G_CALLBACK(task_object_on_status_changed),
                     obj);

    return obj;
}

static void task_object_dbus_method_call(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *method_name,
            GVariant    *parameters,
            GDBusMethodInvocation *invocation,
            gpointer    user_data)
{
    log_debug("Problems2.Task method : %s", method_name);

    /* Check sanity */
    if (strcmp(interface_name, ABRT_P2_NS_MEMBER("Task")) != 0)
    {
        error_msg("Unsupported interface %s", interface_name);
        return;
    }

    GError *error = NULL;

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t real_caller_uid = abrt_p2_service_caller_real_uid(service, caller, &error);
    if (real_caller_uid == (uid_t) -1)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    AbrtP2Task *task = abrt_p2_object_get_node(user_data);
    AbrtP2Object *session_obj = abrt_p2_service_get_session_for_caller(service,
                                                                       caller,
                                                                       real_caller_uid,
                                                                       &error);
    if (error != NULL)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    AbrtP2Session *session = abrt_p2_object_get_node(session_obj);
    if (abrt_p2_session_owns_task(session, task) != 0)
    {
        g_dbus_method_invocation_return_error(invocation,
                                              G_DBUS_ERROR,
                                              G_DBUS_ERROR_ACCESS_DENIED,
                                              "The task does not belong to your session");
        return;
    }

    GVariant *response = NULL;

    if (strcmp("Start", method_name) == 0)
    {
        GVariant *options = g_variant_get_child_value(parameters, 0);
        abrt_p2_task_start(task, options, &error);
        g_variant_unref(options);
    }
    else if (strcmp("Cancel", method_name) == 0)
    {
        struct task_cancel_args *tca = xmalloc(sizeof(*tca));
        tca->tca_object = user_data;
        tca->tca_session = g_object_ref(session);

        int s = g_signal_connect(task,
                                 "status-changed",
                                 G_CALLBACK(task_object_on_canceled_task),
                                 tca);

        abrt_p2_task_cancel(task, &error);

        if (error != NULL)
        {
            g_signal_handler_disconnect(task, s);
            free(tca);
        }
    }
    else if (strcmp("Finish", method_name) == 0)
    {
        GVariant *results = NULL;
        gint32 code = -1;
        abrt_p2_task_finish(task, &results, &code, &error);

        if (error == NULL)
        {
            GVariant *children[2];
            children[0] = results;
            children[1] = g_variant_new_int32(code);

            response = g_variant_new_tuple(children, 2);

            GError *local_error = NULL;
            abrt_p2_session_remove_task(session, task, &local_error);
            if (local_error != NULL)
            {
                error_msg("BUG: failed to remove task from session: %s",
                          local_error->message);
                g_error_free(local_error);
            }

            task_object_dispose(user_data);

            g_object_unref(task);
        }
    }
    else
    {
        error_msg("BUG: org.freedesktop.Problems2.Task does not have method: %s",
                  method_name);

        g_dbus_method_invocation_return_error(invocation,
                                              G_DBUS_ERROR,
                                              G_DBUS_ERROR_UNKNOWN_METHOD,
                                              "org.freedesktop.Problems2.Task is missing the implementation of the method");
        return;
    }

    if (error)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
    }
    else
        g_dbus_method_invocation_return_value(invocation, response);
}

static GVariant *task_object_dbus_get_property(GDBusConnection *connection,
            const gchar *caller,
            const gchar *object_path,
            const gchar *interface_name,
            const gchar *property_name,
            GError      **error,
            gpointer    user_data)
{
    log_debug("Problems2.Task get property : %s", property_name);

    if (strcmp(interface_name, ABRT_P2_NS_MEMBER("Task")) != 0)
    {
        error_msg("Unsupported interface %s", interface_name);
        return NULL;
    }

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t real_caller_uid = abrt_p2_service_caller_real_uid(service,
                                                            caller,
                                                            error);
    if (real_caller_uid == (uid_t) -1)
    {
        error_msg("Failed to get uid of the caller");
        return NULL;
    }

    AbrtP2Task *task = abrt_p2_object_get_node(user_data);
    AbrtP2Object *session_obj = abrt_p2_service_get_session_for_caller(service,
                                                                       caller,
                                                                       real_caller_uid,
                                                                       error);
    if (*error != NULL)
    {
        error_msg("Failed to get session for the caller");
        return NULL;
    }

    AbrtP2Session *session = abrt_p2_object_get_node(session_obj);
    if (abrt_p2_session_owns_task(session, task) != 0)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED,
                    "The task does not belong to your session");
        return NULL;
    }

    if (strcmp("Details", property_name) == 0)
    {
        return abrt_p2_task_details(task);
    }

    if (strcmp("Status", property_name) == 0)
    {
        return g_variant_new_int32(abrt_p2_task_status(task));
    }

    error_msg("BUG: org.freedesktop.Problems2.Task does not have property: %s", property_name);

    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD,
                "org.freedesktop.Problems2.Task is missing the implementation of the property");

    return NULL;
}


/*
 * /org/freedesktop/Problems2
 */
GVariant *abrt_p2_service_new_problem(AbrtP2Service *service,
                AbrtP2Object *session_obj,
                GVariant *problem_info,
                gint32 flags,
                uid_t caller_uid,
                GUnixFDList *fd_list,
                GError **error)
{
    AbrtP2TaskNewProblem *p2t_np = abrt_p2_task_new_problem_new(service,
                                                                problem_info,
                                                                caller_uid,
                                                                fd_list ? g_object_ref(fd_list) : NULL);

    log_debug("Created task '%p' for session '%s'", p2t_np, abrt_p2_object_path(session_obj));
    if (!(flags & 0x1))
    {
        log_debug("Running NewProblem task '%p' in autonomous mode", p2t_np);
        abrt_p2_task_autonomous_run(ABRT_P2_TASK(p2t_np), error);
        return g_variant_new("(o)", "/");
    }

    if (flags & 0x2)
    {
        log_debug("Configuring NewProblem task '%p' to stop after creating a temporary directory", p2t_np);
        abrt_p2_task_new_problem_wait_before_notify(p2t_np, true);
    }

    AbrtP2Object *obj = task_object_register(service, session_obj, ABRT_P2_TASK(p2t_np), error);
    if (obj == NULL)
    {
        g_object_unref(p2t_np);
        g_prefix_error(error, "Cannot export NewProblem task on D-Bus: ");
        return NULL;
    }

    if (flags & 0x4)
    {
        log_debug("NewProblem task '%p' will be automatically started", p2t_np);
        abrt_p2_task_start(ABRT_P2_TASK(p2t_np), NULL, error);
    }

    return g_variant_new("(o)", obj->p2o_path);
}

/**
 * Converts caller to the path of session object
 */
GVariant *abrt_p2_service_callers_session(AbrtP2Service *service,
                const char *caller,
                GError **error)
{
    const char *session_path = abrt_p2_service_session_path(service, caller, error);

    if (session_path == NULL)
        return NULL;

    return g_variant_new("(o)", session_path);
}

GVariant *abrt_p2_service_get_problems(AbrtP2Service *service,
                uid_t caller_uid,
                AbrtP2ServiceGetProblemsFlags flags,
                GVariant *options,
                GError **error)
{
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("ao"));

    GHashTableIter iter;
    g_hash_table_iter_init(&iter, service->pv->p2srv_p2_entry_type.objects);

    log_debug("Going through entries");
    const char *entry_path;
    AbrtP2Object *entry_obj;
    while(g_hash_table_iter_next(&iter, (gpointer)&entry_path, (gpointer)&entry_obj))
    {
        bool singleout = flags == 0;

        AbrtP2Entry *entry = abrt_p2_object_get_node(entry_obj);
        int state = abrt_p2_entry_state(entry);

        log_debug("Entry: %s", entry_path);
        if (state == ABRT_P2_ENTRY_STATE_DELETED)
        {
            continue;
        }
        else if (state == ABRT_P2_ENTRY_STATE_NEW)
        {
            if (flags == 0)
                continue;

            singleout = singleout || (flags & ABRT_P2_SERVICE_GET_PROBLEM_FLAGS_NEW);
        }

        if (0 != abrt_p2_entry_accessible_by_uid(entry, caller_uid, NULL))
        {
            if (flags == 0)
                continue;

            log_debug("Entry not accessible: %s", entry_path);
            singleout = singleout || (flags & ABRT_P2_SERVICE_GET_PROBLEM_FLAGS_FOREIGN);
        }

        if (singleout)
        {
            log_debug("Adding entry: %s", entry_path);
            g_variant_builder_add(&builder, "o", entry_path);
        }
    }


    GVariant *retval_body[1];
    retval_body[0] = g_variant_builder_end(&builder);
    return  g_variant_new_tuple(retval_body, ARRAY_SIZE(retval_body));
}


GVariant *abrt_p2_service_delete_problems(AbrtP2Service *service,
                GVariant *entries,
                uid_t caller_uid,
                GError **error)
{
    GVariantIter *iter;
    gchar *entry_path;
    g_variant_get(entries, "ao", &iter);
    while (g_variant_iter_loop(iter, "o", &entry_path))
    {
        log_debug("Removing Problem Entry: '%s'", entry_path);
        const int r = abrt_p2_service_remove_problem(service,
                                                     entry_path,
                                                     caller_uid,
                                                     error);

        if (r != 0)
        {
            g_free(entry_path);
            break;
        }
    }

    g_variant_iter_free(iter);
    return NULL;
}

/* D-Bus method handler
 */
static void p2_object_dbus_method_call(GDBusConnection *connection,
                        const gchar *caller,
                        const gchar *object_path,
                        const gchar *interface_name,
                        const gchar *method_name,
                        GVariant    *parameters,
                        GDBusMethodInvocation *invocation,
                        gpointer    user_data)
{
    log_debug("Problems2 method : %s", method_name);

    /* Check sanity */
    if (strcmp(interface_name, "org.freedesktop.Problems2") != 0)
    {
        error_msg("Unsupported interface %s", interface_name);
        return;
    }

    GVariant *response = NULL;
    GError *error = NULL;

    AbrtP2Service *service = abrt_p2_object_service(user_data);
    uid_t caller_uid = abrt_p2_service_caller_uid(service, caller, &error);
    if (caller_uid == (uid_t) -1)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    if (strcmp("NewProblem", method_name) == 0)
    {
        AbrtP2Object *session_obj = abrt_p2_service_get_session_for_caller(service,
                                                                           caller,
                                                                           caller_uid,
                                                                           &error);
        if (session_obj != NULL)
        {
            GDBusMessage *msg = g_dbus_method_invocation_get_message(invocation);
            GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(msg);

            GVariant *data = g_variant_get_child_value(parameters, 0);
            gint32 flags;
            g_variant_get_child(parameters, 1, "i", &flags);

            response = abrt_p2_service_new_problem(service,
                                                   session_obj,
                                                   data,
                                                   flags,
                                                   caller_uid,
                                                   fd_list,
                                                   &error);
        }
    }
    else if (strcmp("GetSession", method_name) == 0)
    {
        response = abrt_p2_service_callers_session(service, caller, &error);
    }
    else if (strcmp("GetProblems", method_name) == 0)
    {
        GVariant *flags_param = g_variant_get_child_value(parameters, 0);
        GVariant *options_param = g_variant_get_child_value(parameters, 1);

        response = abrt_p2_service_get_problems(service,
                                                caller_uid,
                                                g_variant_get_int32(flags_param),
                                                options_param,
                                                &error);

        g_variant_unref(options_param);
        g_variant_unref(flags_param);
    }
    else if (strcmp("GetProblemData", method_name) == 0)
    {
        /* Parameter tuple is (0) */
        const char *entry_path;
        g_variant_get(parameters, "(&o)", &entry_path);

        response = abrt_p2_service_entry_problem_data(service,
                                                      entry_path,
                                                      caller_uid,
                                                      &error);
    }
    else if (strcmp("DeleteProblems", method_name) == 0)
    {
        GVariant *array = g_variant_get_child_value(parameters, 0);
        response = abrt_p2_service_delete_problems(service, array, caller_uid, &error);
        g_variant_unref(array);
    }
    else
    {
        error_msg("BUG: org.freedesktop.Problems2 does not have method: %s",
                  method_name);
        g_dbus_method_invocation_return_error(invocation,
                                              G_DBUS_ERROR,
                                              G_DBUS_ERROR_UNKNOWN_METHOD,
                                              "The method has to be implemented");
        return;
    }

    if (error != NULL)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        return;
    }

    g_dbus_method_invocation_return_value(invocation, response);
    return;
}

/*
 * Service functions
 */
static void abrt_p2_service_private_destroy(AbrtP2ServicePrivate *pv)
{
    if (pv->p2srv_connected_users != NULL)
    {
        g_hash_table_destroy(pv->p2srv_connected_users);
        pv->p2srv_connected_users = NULL;
    }

    problems2_object_type_destroy(&(pv->p2srv_p2_type));
    problems2_object_type_destroy(&(pv->p2srv_p2_session_type));
    problems2_object_type_destroy(&(pv->p2srv_p2_entry_type));
    problems2_object_type_destroy(&(pv->p2srv_p2_task_type));

    if (pv->p2srv_proxy_dbus != NULL)
    {
        g_object_unref(pv->p2srv_proxy_dbus);
        pv->p2srv_proxy_dbus = NULL;
    }

    if (pv->p2srv_pk_authority != NULL)
    {
        pv->p2srv_pk_authority = NULL;
        --g_polkit_authority_refs;

        if (g_polkit_authority_refs == 0)
        {
            PolkitAuthority *pk = abrt_p2_session_class_release_polkit_authority();
            if (pk != g_polkit_authority)
                log_notice("Session class uses custom Polkit Authority");
            else
            {
                g_object_unref(g_polkit_authority);
                g_polkit_authority = NULL;
            }
        }
    }
}

static int abrt_p2_service_private_init(AbrtP2ServicePrivate *pv,
            GError **unused)
{
    pv->p2srv_max_message_size = DBUS_MAXIMUM_MESSAGE_LENGTH;
    pv->p2srv_max_message_unix_fds = 16;

    pv->p2srv_limit_clients = 5;
    pv->p2srv_limit_elements = 100;
    pv->p2srv_limit_data_size = 2L*1024L*1024L*1023L;
    pv->p2srv_limit_user_problems = 1000;
    pv->p2srv_limit_new_problem_throttling_magnitude = 4;
    pv->p2srv_limit_new_problems_batch = 10;

    int r = 0;
    {
        static GDBusInterfaceVTable p2_object_vtable = {
            .method_call = p2_object_dbus_method_call,
            .get_property = NULL,
            .set_property = NULL,
        };

        r = problems2_object_type_init(&(pv->p2srv_p2_type),
                                       g_org_freedesktop_Problems2_xml,
                                       &p2_object_vtable);
        if (r != 0)
        {
            log_notice("Failed to initialize org.freedesktop.Problems2 type");
            goto error_return;
        }
    }

    {
        static GDBusInterfaceVTable session_object_vtable = {
            .method_call = session_object_dbus_method_call,
            .get_property = session_object_dbus_get_property,
            .set_property = NULL,
        };

        r = problems2_object_type_init(&(pv->p2srv_p2_session_type),
                                       g_org_freedesktop_Problems2_Session_xml,
                                       &session_object_vtable);
        if (r != 0)
        {
            log_notice("Failed to initialize org.freedesktop.Problems2.Session type");
            goto error_return;
        }
    }

    {
        static GDBusInterfaceVTable entry_object_vtable = {
            .method_call = entry_object_dbus_method_call,
            .get_property = entry_object_dbus_get_property,
            .set_property = NULL,
        };

        r = problems2_object_type_init(&(pv->p2srv_p2_entry_type),
                                       g_org_freedesktop_Problems2_Entry_xml,
                                       &entry_object_vtable);
        if (r != 0)
        {
            log_notice("Failed to initialize org.freedesktop.Problems2.Entry type");
            goto error_return;
        }
    }

    {
        static GDBusInterfaceVTable task_object_vtable = {
            .method_call = task_object_dbus_method_call,
            .get_property = task_object_dbus_get_property,
            .set_property = NULL,
        };

        r = problems2_object_type_init(&(pv->p2srv_p2_task_type),
                                       g_org_freedesktop_Problems2_Task_xml,
                                       &task_object_vtable);
        if (r != 0)
        {
            log_notice("Failed to initialize org.freedesktop.Problems2.Task type");
            goto error_return;
        }
    }

    pv->p2srv_connected_users = g_hash_table_new_full(g_direct_hash,
                                                      g_direct_equal,
                                                      NULL,
                                                      (GDestroyNotify)user_info_free);

    if (g_polkit_authority != NULL)
    {
        ++g_polkit_authority_refs;
        pv->p2srv_pk_authority = g_polkit_authority;
        return 0;
    }

#ifdef HAVE_POLKIT
    GError *local_error = NULL;
    g_polkit_authority = pv->p2srv_pk_authority = polkit_authority_get_sync(NULL,
                                                                            &local_error);
    if (pv->p2srv_pk_authority == NULL)
    {
        r = -1;
        log_notice("Failed to get PolkitAuthority: %s", local_error->message);
        g_error_free(local_error);
        goto error_return;
    }

    ++g_polkit_authority_refs;
    abrt_p2_session_class_set_polkit_authority(g_polkit_authority);
#endif
    return 0;

error_return:
    abrt_p2_service_private_destroy(pv);
    return r;
}

static void abrt_p2_service_finalize(GObject *gobject)
{
    AbrtP2ServicePrivate *pv = abrt_p2_service_get_instance_private(ABRT_P2_SERVICE(gobject));
    abrt_p2_service_private_destroy(pv);
}

static void abrt_p2_service_class_init(AbrtP2ServiceClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    object_class->finalize = abrt_p2_service_finalize;

    service_signals[SERVICE_SIGNALS_NEW_CLIENT_CONNECTED] =
        g_signal_newv("new-client-connected",
                     G_TYPE_FROM_CLASS(klass),
                     G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                     NULL /* closure */,
                     NULL /* accumulator */,
                     NULL /* accumulator data */,
                     NULL /* C marshaller */,
                     G_TYPE_NONE /* return_type */,
                     0     /* n_params */,
                     NULL  /* param_types */);

    service_signals[SERVICE_SIGNALS_ALL_CLIENTS_DISCONNECTED] =
        g_signal_newv("all-clients-disconnected",
                     G_TYPE_FROM_CLASS(klass),
                     G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                     NULL /* closure */,
                     NULL /* accumulator */,
                     NULL /* accumulator data */,
                     NULL /* C marshaller */,
                     G_TYPE_NONE /* return_type */,
                     0     /* n_params */,
                     NULL  /* param_types */);
}

static void abrt_p2_service_init(AbrtP2Service *self)
{
    self->pv = abrt_p2_service_get_instance_private(self);
}

AbrtP2Service *abrt_p2_service_new(GError **error)
{
    AbrtP2Service *service = g_object_new(TYPE_ABRT_P2_SERVICE, NULL);

    if (abrt_p2_service_private_init(service->pv, error) != 0)
    {
        g_object_unref(service);
        return NULL;
    }

    return service;
}

static struct user_info *abrt_p2_service_user_lookup(AbrtP2Service *service,
            uid_t uid)
{
    return  g_hash_table_lookup(service->pv->p2srv_connected_users,
                                GUINT_TO_POINTER((guint)uid));
}

static struct user_info *abrt_p2_service_user_insert(AbrtP2Service *service,
            uid_t uid,
            struct user_info *user)
{
    g_hash_table_insert(service->pv->p2srv_connected_users,
                        GUINT_TO_POINTER((guint)uid),
                        user);
    return user;
}

static struct user_info *abrt_p2_service_user_new(AbrtP2Service *service,
            uid_t uid)
{
    struct user_info *user = user_info_new();
    return abrt_p2_service_user_insert(service, uid, user);
}

static GDBusConnection *abrt_p2_service_dbus(AbrtP2Service *service)
{
    return service->pv->p2srv_dbus;
}

struct bridge_call_args
{
    AbrtP2Service *service;
    GError **error;
};

static int bridge_register_dump_dir_entry_node(struct dump_dir *dd,
            void *call_args)
{
    struct bridge_call_args *args = call_args;
    return NULL == entry_object_register_dump_dir(args->service,
                                                  dd->dd_dirname,
                                                  args->error);
}

static void on_g_signal(GDBusProxy *proxy,
            gchar      *sender_name,
            gchar      *signal_name,
            GVariant   *parameters,
            gpointer    user_data)
{
    if (0 != strcmp(signal_name, "NameOwnerChanged"))
        return;

    const gchar *bus_name = NULL;
    const gchar *old_owner = NULL;
    const gchar *new_owner = NULL;

    g_variant_get(parameters, "(&s&s&s)", &bus_name, &old_owner, &new_owner);

    if (bus_name[0] == '\0' || old_owner[0] == '\0' || new_owner[0] != '\0')
        return;

    AbrtP2Service *service = ABRT_P2_SERVICE(user_data);
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, service->pv->p2srv_p2_session_type.objects);

    const char *session_path;
    AbrtP2Object *session_obj;
    while(g_hash_table_iter_next(&iter, (gpointer)&session_path, (gpointer)&session_obj))
    {
        AbrtP2Session *session = abrt_p2_object_get_node(session_obj);
        const char *session_caller = abrt_p2_session_caller(session);
        if (strcmp(bus_name, session_caller) != 0)
            continue;

        log_debug("Bus '%s' disconnected: destroying session: %s", bus_name, session_path);

        GHashTableIter task_iter;
        g_hash_table_iter_init(&task_iter, service->pv->p2srv_p2_task_type.objects);

        const char *task_path;
        AbrtP2Object *task_obj;
        const size_t session_path_len = strlen(session_path);
        while(g_hash_table_iter_next(&task_iter, (gpointer)&task_path, (gpointer)&task_obj))
        {
            if (strncmp(task_path, session_path, session_path_len) != 0)
                continue;

            if (strncmp(task_path + session_path_len, "/Task/", strlen("/Task/")) != 0)
                continue;

            log_debug("Destroying Task of disconnected session: %s", task_path);

            /* Not touching the linked task, destroying of the session object
             * below should call abrt_p2_session_clean_tasks() which will unref
             * all session tasks.
             */
            task_object_dispose(task_obj);
        }

        /* session_path belongs to session_obj */
        abrt_p2_object_destroy(session_obj);
    }
}

int abrt_p2_service_register_objects(AbrtP2Service *service, GDBusConnection *connection, GError **error)
{
    if (service->pv->p2srv_dbus != NULL)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Problems2 service objects are already registered");
        return -EALREADY;
    }

    service->pv->p2srv_dbus = connection;

    service->pv->p2srv_p2_object = abrt_p2_object_new(service,
                                                      &(service->pv->p2srv_p2_type),
                                                      (char *)ABRT_P2_PATH,
                                                      /*node*/NULL,
                                                      /*node destructor*/NULL,
                                                      error);

    if (service->pv->p2srv_p2_object == 0)
    {
        g_prefix_error(error, "Failed to register Problems2 node: ");
        return -1;
    }

    struct bridge_call_args args;
    args.service = service;
    args.error = error;

    for_each_problem_in_dir(g_settings_dump_location, (uid_t)-1, bridge_register_dump_dir_entry_node, &args);

    if (*args.error != NULL)
    {
        g_prefix_error(error, "Failed to register Problems objects: ");
        return -1;
    }

    GError *local_error = NULL;
    service->pv->p2srv_proxy_dbus = g_dbus_proxy_new_sync(connection,
                                                          G_DBUS_PROXY_FLAGS_NONE,
                                                          NULL,
                                                          "org.freedesktop.DBus",
                                                          "/org/freedesktop/DBus",
                                                          "org.freedesktop.DBus",
                                                          NULL,
                                                          &local_error);


    if (local_error == NULL)
    {
        g_signal_connect(service->pv->p2srv_proxy_dbus,
                         "g-signal",
                         G_CALLBACK(on_g_signal),
                         service);
    }
    else
    {
        error_msg("Failed to initialize proxy to DBus: %s",
                  local_error->message);
        g_error_free(local_error);
    }

    return 0;
}

/*
 * Service configuration
 */
long abrt_p2_service_max_message_size(AbrtP2Service *service)
{
    return service->pv->p2srv_max_message_size;
}

void abrt_p2_service_set_max_message_size(AbrtP2Service *service,
            long max_message_size)
{
    service->pv->p2srv_max_message_size = max_message_size;
}

long abrt_p2_service_max_message_unix_fds(AbrtP2Service *service)
{
    return service->pv->p2srv_max_message_unix_fds;
}

void abrt_p2_service_set_max_message_unix_fds(AbrtP2Service *service,
            long max_message_unix_fds)
{
    service->pv->p2srv_max_message_unix_fds = max_message_unix_fds;
}

unsigned abrt_p2_service_user_clients_limit(AbrtP2Service *service,
            uid_t uid)
{
    return service->pv->p2srv_limit_clients;
}

void abrt_p2_service_set_user_clients_limit(AbrtP2Service *service,
            uid_t uid,
            unsigned limit)
{
    service->pv->p2srv_limit_clients = limit;
}

unsigned abrt_p2_service_elements_limit(AbrtP2Service *service,
            uid_t uid)
{
    return uid == 0 ? 0 : service->pv->p2srv_limit_elements;
}

void abrt_p2_service_set_elements_limit(AbrtP2Service *service,
            uid_t uid,
            unsigned limit)
{
    service->pv->p2srv_limit_elements = limit;
}

off_t abrt_p2_service_data_size_limit(AbrtP2Service *service,
            uid_t uid)
{
    return uid == 0 ? 0 : service->pv->p2srv_limit_data_size;
}

void abrt_p2_service_set_data_size_limit(AbrtP2Service *service,
            uid_t uid, off_t limit)
{
    service->pv->p2srv_limit_data_size = limit;
}

unsigned abrt_p2_service_user_problems_limit(AbrtP2Service *service,
            uid_t uid)
{
    return uid == 0 ? 0 : service->pv->p2srv_limit_user_problems;
}

void abrt_p2_service_set_user_problems_limit(AbrtP2Service *service,
            uid_t uid,
            unsigned limit)
{
    service->pv->p2srv_limit_user_problems = limit;
}

unsigned abrt_p2_service_new_problem_throttling_magnitude(AbrtP2Service *service,
            uid_t uid)
{
    return service->pv->p2srv_limit_new_problem_throttling_magnitude;
}

void abrt_p2_service_set_new_problem_throttling_magnitude(AbrtP2Service *service,
            uid_t uid,
            unsigned limit)
{
    service->pv->p2srv_limit_new_problem_throttling_magnitude = limit;
}

unsigned abrt_p2_service_new_problems_batch(AbrtP2Service *service,
            uid_t uid)
{
    return service->pv->p2srv_limit_new_problems_batch;
}

void abrt_p2_service_set_new_problems_batch(AbrtP2Service *service,
            uid_t uid,
            unsigned limit)
{
    service->pv->p2srv_limit_new_problems_batch = limit;
}

int abrt_p2_service_user_can_create_new_problem(AbrtP2Service *service,
            uid_t uid)
{
    if (uid == 0)
        return 1;

    time_t current = time(NULL);
    if (current == (time_t)-1)
    {
        perror_msg("time");
        return -1;
    }

    struct user_info *user = abrt_p2_service_user_lookup(service, uid);
    if (user == NULL)
    {
        error_msg("User does not have Session: uid=%lu",
                  (long unsigned)uid);
        return -1;
    }

    const unsigned upl = abrt_p2_service_user_problems_limit(service, uid);
    if (upl != 0 && user->problems >= upl)
        return -E2BIG;

    if (current < user->new_problem_last)
    {
        error_msg("The last problem was created in future: uid=%lu",
                  (long unsigned)uid);
        return -1;
    }

    /* Allows Y new problems to be created in a batch but then allow only 1 new
     * problem per Xs.
     *
     *  number of problems = minimum( ((last ts - current ts) / (2^magnitude)),
     *                                (configured number))
     */
    const long unsigned off = current - user->new_problem_last;
    const unsigned throttling = abrt_p2_service_new_problem_throttling_magnitude(service, uid);
    const long unsigned incr = (off >> throttling);

    const unsigned npb = abrt_p2_service_new_problems_batch(service, uid);
    /* Avoid overflow. Beware of adding operation inside the condition! */
    if (   incr > npb
        || (user->new_problems += incr) > npb)
        user->new_problems = npb;

    log_debug("NewProblem limit: last %lu, "
              "current %lu, "
              "increment %lu, "
              "remaining %u",
              (long unsigned)user->new_problem_last,
              (long unsigned)current,
              incr,
              user->new_problems);

    if (user->new_problems == 0)
        return 0;

    user->new_problem_last = current;
    return user->new_problems--;
}
