/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/* vim:set et sts=4: */
/* ibus - The Input Bus
 * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
 * Copyright (C) 2008-2010 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "ibusproxy.h"
#include "ibusmarshalers.h"
#include "ibusinternal.h"
#include "ibusobject.h"

#define IBUS_PROXY_GET_PRIVATE(o)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_PROXY, IBusProxyPrivate))

enum {
    DESTROY,
    LAST_SIGNAL,
};

static guint            proxy_signals[LAST_SIGNAL] = { 0 };

/* functions prototype */
static void      ibus_proxy_dispose         (GObject            *object);
static void      ibus_proxy_real_destroy    (IBusProxy          *proxy);

static void      ibus_proxy_connection_closed_cb
                                            (GDBusConnection    *connection,
                                             gboolean            remote_peer_vanished,
                                             GError             *error,
                                             IBusProxy          *proxy);
static void      initable_iface_init        (GInitableIface     *initable_iface);
static void      async_initable_iface_init  (GAsyncInitableIface
                                                                *async_initable_iface);
G_DEFINE_TYPE_WITH_CODE (IBusProxy, ibus_proxy, G_TYPE_DBUS_PROXY,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
                         );

static void
ibus_proxy_class_init (IBusProxyClass *class)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (class);

    gobject_class->dispose = ibus_proxy_dispose;

    class->destroy = ibus_proxy_real_destroy;

    /* install signals */
    /**
     * IBusProxy::destroy:
     * @object: An IBusProxy.
     *
     * Destroy and free an IBusProxy
     *
     * See also:  ibus_proxy_destroy().
     *
     * <note><para>Argument @user_data is ignored in this function.</para></note>
     */
    proxy_signals[DESTROY] =
        g_signal_new (I_("destroy"),
            G_TYPE_FROM_CLASS (gobject_class),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (IBusProxyClass, destroy),
            NULL, NULL,
            _ibus_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
}

static void
ibus_proxy_init (IBusProxy *proxy)
{
    proxy->own = TRUE;
}

/**
 * ibus_proxy_dispose:
 *
 * Override GObject's dispose function.
 */
static void
ibus_proxy_dispose (GObject *object)
{
    if (! (IBUS_PROXY_FLAGS (object) & IBUS_IN_DESTRUCTION)) {
        IBUS_PROXY_SET_FLAGS (object, IBUS_IN_DESTRUCTION);
        if (! (IBUS_PROXY_FLAGS (object) & IBUS_DESTROYED)) {
            g_signal_emit (object, proxy_signals[DESTROY], 0);
            IBUS_PROXY_SET_FLAGS (object, IBUS_DESTROYED);
        }
        IBUS_PROXY_UNSET_FLAGS (object, IBUS_IN_DESTRUCTION);
    }

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

/**
 * ibus_proxy_real_destroy:
 *
 * Handle "destroy" signal which is emitted by ibus_proxy_dispose.
 */
static void
ibus_proxy_real_destroy (IBusProxy *proxy)
{
    GDBusConnection *connection = g_dbus_proxy_get_connection ((GDBusProxy *) proxy);
    g_assert (connection != NULL);
    if (!g_dbus_connection_is_closed (connection) && proxy->own) {
        g_dbus_proxy_call ((GDBusProxy *)proxy,
                           "org.freedesktop.IBus.Service.Destroy",
                           NULL,
                           G_DBUS_CALL_FLAGS_NONE,
                           -1, NULL, NULL, NULL);
    }
    g_signal_handlers_disconnect_by_func (connection,
                                          (GCallback) ibus_proxy_connection_closed_cb,
                                          proxy);
}

static void
ibus_proxy_connection_closed_cb (GDBusConnection *connection,
                                 gboolean         remote_peer_vanished,
                                 GError          *error,
                                 IBusProxy       *proxy)
{
    ibus_proxy_destroy (proxy);
}

void
ibus_proxy_destroy (IBusProxy *proxy)
{
    g_assert (IBUS_IS_PROXY (proxy));

    if (! (IBUS_PROXY_FLAGS (proxy) & IBUS_IN_DESTRUCTION)) {
        g_object_run_dispose (G_OBJECT (proxy));
    }
}

static gboolean
ibus_proxy_init_finish (IBusProxy  *proxy,
                        GError    **error)
{
    g_assert (IBUS_IS_PROXY (proxy));
    g_assert (error == NULL || *error == NULL);

    GDBusConnection *connection =
            g_dbus_proxy_get_connection ((GDBusProxy *)proxy);

    if (connection == NULL || g_dbus_connection_is_closed (connection)) {
        /*
         * When proxy is created asynchronously, the connection may be closed
         * before proxy is ready. In this case, we need override interfaces
         * GInitable and GAsyncInitable to report the error.
         */
        if (error != NULL)
            *error = g_error_new (G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Connection is closed.");
        return FALSE;
    }

    g_signal_connect (connection, "closed",
            G_CALLBACK (ibus_proxy_connection_closed_cb), proxy);

    return TRUE;
}


static GInitableIface *initable_iface_parent = NULL;

static gboolean
initable_init (GInitable     *initable,
               GCancellable  *cancellable,
               GError       **error)
{
    if (!initable_iface_parent->init (initable, cancellable, error))
        return FALSE;
    return ibus_proxy_init_finish ((IBusProxy *)initable, error);
}

static void
initable_iface_init (GInitableIface *initable_iface)
{
    initable_iface_parent = g_type_interface_peek_parent (initable_iface);
    initable_iface->init = initable_init;
}

static GAsyncInitableIface *async_initable_iface_parent = NULL;

static void
async_initable_init_async (GAsyncInitable      *initable,
                           gint                 io_priority,
                           GCancellable        *cancellable,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
{
    async_initable_iface_parent->init_async (initable,
            io_priority, cancellable, callback, user_data);
}

static gboolean
async_initable_init_finish (GAsyncInitable  *initable,
                            GAsyncResult    *res,
                            GError         **error)
{
    if (!async_initable_iface_parent->init_finish (initable, res, error))
        return FALSE;
    return ibus_proxy_init_finish ((IBusProxy *)initable, error);
}

static void
async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
{
    async_initable_iface_parent = g_type_interface_peek_parent (async_initable_iface);
    async_initable_iface->init_async = async_initable_init_async;
    async_initable_iface->init_finish = async_initable_init_finish;
}
