/*
 * Copyright (c) 2013 Frank Lahm <franklahm@gmail.com>
 *
 * 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.
 */

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

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>
#include <gio/gio.h>

#include <atalk/compat.h>
#include <atalk/errchk.h>
#include <atalk/logger.h>
#include <atalk/server_child.h>

#include "afpstats_obj.h"

/*
 * Beware: this struct is accessed and modified from the main thread
 * and from this thread, thus be careful to lock and unlock the mutex.
 */
static server_child_t *childs;
static GDBusNodeInfo *introspection_data = NULL;

static void handle_method_call(GDBusConnection *connection _U_,
                               const gchar           *sender _U_,
                               const gchar           *object_path _U_,
                               const gchar           *interface_name _U_,
                               const gchar           *method_name,
                               GVariant              *parameters _U_,
                               GDBusMethodInvocation *invocation,
                               gpointer               user_data)
{
    AFPStatsObj *obj = AFPSTATS_OBJECT(user_data);

    if (g_strcmp0(method_name, "GetUsers") == 0) {
        GError *local_error = NULL;
        gchar **users = NULL;

        if (afpstats_obj_get_users(obj, &users, &local_error)) {
            g_dbus_method_invocation_return_value(invocation, g_variant_new("(^as)",
                                                  users));
        } else {
            g_dbus_method_invocation_return_gerror(invocation, local_error);
        }

        g_strfreev(users);
        g_clear_error(&local_error);
    }
}

static const GDBusInterfaceVTable interface_vtable = {
    &handle_method_call,
    NULL,
    NULL,
    { 0 }
};

static void on_bus_acquired(GDBusConnection *connection _U_,
                            const gchar     *name,
                            gpointer         user_data _U_)
{
    LOG(log_debug, logtype_afpd, "[afpstats] on_bus_acquired(): %s", name);
}

static void on_name_acquired(GDBusConnection *connection _U_,
                             const gchar     *name,
                             gpointer         user_data _U_)
{
    LOG(log_debug, logtype_afpd, "[afpstats] on_name_acquired(): %s", name);
}

static void on_name_lost(GDBusConnection *connection _U_,
                         const gchar     *name,
                         gpointer         user_data _U_)
{
    LOG(log_debug, logtype_afpd, "[afpstats] on_name_lost(): %s", name);
}

static gpointer afpstats_thread(gpointer _data _U_)
{
    GDBusConnection *bus;
    GError *error = NULL;
    GMainContext *ctxt;
    GMainLoop *thread_loop;
    GBusNameOwnerFlags request_name_result;
    guint registration_id;
    sigset_t sigs;
    /* Block all signals in this thread */
    sigfillset(&sigs);
    pthread_sigmask(SIG_BLOCK, &sigs, NULL);
    ctxt = g_main_context_new();
    g_main_context_push_thread_default(ctxt);
    thread_loop = g_main_loop_new(ctxt, FALSE);

    if (!(bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM,
                               NULL,
                               &error))) {
        LOG(log_error, logtype_afpd, "Couldn't connect to system bus: %s",
            error->message);
        return NULL;
    }

    static const gchar introspection_xml[] =
        "<node>"
        "  <interface name='org.netatalk.AFPStats'>"
        "    <method name='GetUsers'>"
        "      <arg name='ret' type='as' direction='out'/>"
        "    </method>"
        "  </interface>"
        "</node>";
    introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
    g_assert(introspection_data != NULL);
    AFPStatsObj *obj = g_object_new(AFPSTATS_TYPE_OBJECT, NULL);
    registration_id = g_dbus_connection_register_object(bus,
                      "/org/netatalk/AFPStats",
                      introspection_data->interfaces[0],
                      &interface_vtable,
                      g_object_ref(obj),
                      g_object_unref,
                      &error);
    g_assert(registration_id > 0);
    request_name_result = g_bus_own_name(G_BUS_TYPE_SYSTEM,
                                         "org.netatalk.AFPStats",
                                         G_BUS_NAME_OWNER_FLAGS_NONE,
                                         on_bus_acquired,
                                         on_name_acquired,
                                         on_name_lost,
                                         NULL,
                                         NULL);
    g_main_loop_run(thread_loop);
    g_bus_unown_name(request_name_result);
    g_dbus_connection_unregister_object(bus, registration_id);
    g_dbus_node_info_unref(introspection_data);
    g_object_unref(obj);
    g_main_context_pop_thread_default(ctxt);
    g_main_context_unref(ctxt);
    return thread_loop;
}

static void my_glib_log(const gchar *log_domain,
                        GLogLevelFlags log_level _U_,
                        const gchar *message,
                        gpointer user_data _U_)
{
    LOG(log_debug, logtype_afpd, "[afpstats] %s: %s", log_domain, message);
}

server_child_t *afpstats_get_and_lock_childs(void)
{
    pthread_mutex_lock(&childs->servch_lock);
    return childs;
}

void afpstats_unlock_childs(void)
{
    pthread_mutex_unlock(&childs->servch_lock);
}

int afpstats_init(server_child_t *childs_in)
{
    GThread *thread;
    childs = childs_in;
    (void)g_log_set_default_handler(my_glib_log, NULL);
    thread = g_thread_new("afpstats", afpstats_thread, NULL);
    return 0;
}
