/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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:
 *
 * Copyright (C) 2008 - 2009 Novell, Inc.
 * Copyright (C) 2009 - 2010 Red Hat, Inc.
 */

#include <config.h>
#include <string.h>
#include <dbus/dbus-glib.h>
#include "mm-modem.h"
#include "mm-options.h"
#include "mm-errors.h"
#include "mm-callback-info.h"
#include "mm-marshal.h"

static void impl_modem_enable (MMModem *modem, gboolean enable, DBusGMethodInvocation *context);
static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodInvocation *context);
static void impl_modem_disconnect (MMModem *modem, DBusGMethodInvocation *context);
static void impl_modem_get_ip4_config (MMModem *modem, DBusGMethodInvocation *context);
static void impl_modem_get_info (MMModem *modem, DBusGMethodInvocation *context);

#include "mm-modem-glue.h"

/* Should be used from callbacks to check whether the modem was removed after
 * the callback's operation was started, but before the callback itself was
 * called, in which case the MMModem passed to the callback is NULL.
 */
GError *
mm_modem_check_removed (MMModem *self, const GError *error)
{
    if (!self) {
        /* If the modem was NULL, the error *should* have been
         * MM_MODEM_ERROR_REMOVED.  If it wasn't, make it that.
         */
        return g_error_new_literal (MM_MODEM_ERROR,
                                    MM_MODEM_ERROR_REMOVED,
                                    "The modem was removed.");
    }

    return error ? g_error_copy (error) : NULL;
}

static void
async_op_not_supported (MMModem *self,
                        MMModemFn callback,
                        gpointer user_data)
{
    MMCallbackInfo *info;

    info = mm_callback_info_new (self, callback, user_data);
    info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
                                       "Operation not supported");
    mm_callback_info_schedule (info);
}

static void
async_call_done (MMModem *modem, GError *error, gpointer user_data)
{
    DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;

    if (error)
        dbus_g_method_return_error (context, error);
    else
        dbus_g_method_return (context);
}

void
mm_modem_enable (MMModem *self,
                 MMModemFn callback,
                 gpointer user_data)
{
    MMModemState state;

    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    state = mm_modem_get_state (self);
    if (state >= MM_MODEM_STATE_ENABLED) {
        MMCallbackInfo *info;

        info = mm_callback_info_new (self, callback, user_data);

        if (state == MM_MODEM_STATE_ENABLING) {
            info->error = g_error_new_literal (MM_MODEM_ERROR,
                                               MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
                                               "The device is already being enabled.");
        } else {
            /* Already enabled */
        }

        mm_callback_info_schedule (info);
        return;
    }

    if (MM_MODEM_GET_INTERFACE (self)->enable)
        MM_MODEM_GET_INTERFACE (self)->enable (self, callback, user_data);
    else
        async_op_not_supported (self, callback, user_data);
}

static void
finish_disable (MMModem *self,
                MMModemFn callback,
                gpointer user_data)
{
    if (MM_MODEM_GET_INTERFACE (self)->disable)
        MM_MODEM_GET_INTERFACE (self)->disable (self, callback, user_data);
    else
        async_op_not_supported (self, callback, user_data);
}

typedef struct {
    MMModemFn callback;
    gpointer user_data;
} DisableDisconnectInfo;

static void
disable_disconnect_done (MMModem *self,
                         GError *error,
                         gpointer user_data)
{
    DisableDisconnectInfo *cb_data = user_data;
    GError *tmp_error = NULL;

    /* Check for modem removal */
    if (g_error_matches (error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED))
        tmp_error = g_error_copy (error);
    else if (!self) {
        tmp_error = g_error_new_literal (MM_MODEM_ERROR,
                                         MM_MODEM_ERROR_REMOVED,
                                         "The modem was removed.");
    }

    /* And send an immediate error reply if the modem was removed */
    if (tmp_error) {
        cb_data->callback (NULL, tmp_error, cb_data->user_data);
        g_free (cb_data);
        g_error_free (tmp_error);
        return;
    }

    if (error) {
        char *device = mm_modem_get_device (self);

        /* Don't really care what the error was; log it and proceed to disable */
        g_warning ("%s: (%s): error disconnecting the modem while disabling: (%d) %s",
                   __func__,
                   device,
                   error ? error->code : -1,
                   error && error->message ? error->message : "(unknown)");
        g_free (device);
    }
    finish_disable (self, cb_data->callback, cb_data->user_data);
    g_free (cb_data);
}

void
mm_modem_disable (MMModem *self,
                  MMModemFn callback,
                  gpointer user_data)
{
    MMModemState state;

    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    state = mm_modem_get_state (self);
    if (state <= MM_MODEM_STATE_DISABLING) {
        MMCallbackInfo *info;

        info = mm_callback_info_new (self, callback, user_data);

        if (state == MM_MODEM_STATE_DISABLING) {
            info->error = g_error_new_literal (MM_MODEM_ERROR,
                                               MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
                                               "The device is already being disabled.");
        } else {
            /* Already disabled */
        }

        mm_callback_info_schedule (info);
        return;
    }

    /* If the modem is connected, disconnect it */
    if (state >= MM_MODEM_STATE_CONNECTING) {
        DisableDisconnectInfo *cb_data;

        cb_data = g_malloc0 (sizeof (DisableDisconnectInfo));
        cb_data->callback = callback;
        cb_data->user_data = user_data;
        mm_modem_disconnect (self, disable_disconnect_done, cb_data);
    } else
        finish_disable (self, callback, user_data);
}

static void
impl_modem_enable (MMModem *modem,
                   gboolean enable,
                   DBusGMethodInvocation *context)
{
    if (enable)
        mm_modem_enable (modem, async_call_done, context);
    else
        mm_modem_disable (modem, async_call_done, context);
}

void
mm_modem_connect (MMModem *self,
                  const char *number,
                  MMModemFn callback,
                  gpointer user_data)
{
    MMModemState state;

    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);
    g_return_if_fail (number != NULL);

    state = mm_modem_get_state (self);
    if (state >= MM_MODEM_STATE_CONNECTING) {
        MMCallbackInfo *info;

        /* Already connecting */
        info = mm_callback_info_new (self, callback, user_data);
        if (state == MM_MODEM_STATE_CONNECTING) {
            info->error = g_error_new_literal (MM_MODEM_ERROR,
                                               MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
                                               "The device is already being connected.");
        } else {
            /* already connected */
        }

        mm_callback_info_schedule (info);
        return;
    }

    if (MM_MODEM_GET_INTERFACE (self)->connect)
        MM_MODEM_GET_INTERFACE (self)->connect (self, number, callback, user_data);
    else
        async_op_not_supported (self, callback, user_data);
}

static void
impl_modem_connect (MMModem *modem,
                    const char *number,
                    DBusGMethodInvocation *context)
{
    mm_modem_connect (modem, number, async_call_done, context);
}

static void
get_ip4_invoke (MMCallbackInfo *info)
{
    MMModemIp4Fn callback = (MMModemIp4Fn) info->callback;

    callback (info->modem,
              GPOINTER_TO_UINT (mm_callback_info_get_data (info, "ip4-address")),
              (GArray *) mm_callback_info_get_data (info, "ip4-dns"),
              info->error, info->user_data);
}

void
mm_modem_get_ip4_config (MMModem *self,
                         MMModemIp4Fn callback,
                         gpointer user_data)
{
    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    if (MM_MODEM_GET_INTERFACE (self)->get_ip4_config)
        MM_MODEM_GET_INTERFACE (self)->get_ip4_config (self, callback, user_data);
    else {
        MMCallbackInfo *info;

        info = mm_callback_info_new_full (self,
                                          get_ip4_invoke,
                                          G_CALLBACK (callback),
                                          user_data);

        info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
                                           "Operation not supported");
        mm_callback_info_schedule (info);
    }
}

static void
value_array_add_uint (GValueArray *array, guint32 i)
{
    GValue value = { 0, };

    g_value_init (&value, G_TYPE_UINT);
    g_value_set_uint (&value, i);
    g_value_array_append (array, &value);
    g_value_unset (&value);
}

static void
get_ip4_done (MMModem *modem,
              guint32 address,
              GArray *dns,
              GError *error,
              gpointer user_data)
{
    DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;

    if (error)
        dbus_g_method_return_error (context, error);
    else {
        GValueArray *array;
        guint32 dns1 = 0;
        guint32 dns2 = 0;
        guint32 dns3 = 0;

        array = g_value_array_new (4);

        if (dns) {
            if (dns->len > 0)

                dns1 = g_array_index (dns, guint32, 0);
            if (dns->len > 1)
                dns2 = g_array_index (dns, guint32, 1);
            if (dns->len > 2)
                dns3 = g_array_index (dns, guint32, 2);
        }

        value_array_add_uint (array, address);
        value_array_add_uint (array, dns1);
        value_array_add_uint (array, dns2);
        value_array_add_uint (array, dns3);

        dbus_g_method_return (context, array);
    }
}

static void
impl_modem_get_ip4_config (MMModem *modem,
                           DBusGMethodInvocation *context)
{
    mm_modem_get_ip4_config (modem, get_ip4_done, context);
}

void
mm_modem_disconnect (MMModem *self,
                     MMModemFn callback,
                     gpointer user_data)
{
    MMModemState state;

    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    state = mm_modem_get_state (self);
    if (state <= MM_MODEM_STATE_DISCONNECTING) {
        MMCallbackInfo *info;

        /* Already connecting */
        info = mm_callback_info_new (self, callback, user_data);
        if (state == MM_MODEM_STATE_DISCONNECTING) {
            info->error = g_error_new_literal (MM_MODEM_ERROR,
                                               MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
                                               "The device is already being disconnected.");
        } else {
            /* already disconnected */
        }

        mm_callback_info_schedule (info);
        return;
    }

    if (MM_MODEM_GET_INTERFACE (self)->disconnect)
        MM_MODEM_GET_INTERFACE (self)->disconnect (self, callback, user_data);
    else
        async_op_not_supported (self, callback, user_data);
}

static void
impl_modem_disconnect (MMModem *modem,
                       DBusGMethodInvocation *context)
{
    mm_modem_disconnect (modem, async_call_done, context);
}

static void
info_call_done (MMModem *self,
                const char *manufacturer,
                const char *model,
                const char *version,
                GError *error,
                gpointer user_data)
{
    DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;

    if (error)
        dbus_g_method_return_error (context, error);
    else {
        GValueArray *array;
        GValue value = { 0, };

        array = g_value_array_new (3);

        /* Manufacturer */
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, manufacturer);
        g_value_array_append (array, &value);
        g_value_unset (&value);

        /* Model */
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, model);
        g_value_array_append (array, &value);
        g_value_unset (&value);

        /* Version */
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, version);
        g_value_array_append (array, &value);
        g_value_unset (&value);

        dbus_g_method_return (context, array);
    }
}

static void
info_invoke (MMCallbackInfo *info)
{
    MMModemInfoFn callback = (MMModemInfoFn) info->callback;

    callback (info->modem, NULL, NULL, NULL, info->error, info->user_data);
}

static void
info_call_not_supported (MMModem *self,
                         MMModemInfoFn callback,
                         gpointer user_data)
{
    MMCallbackInfo *info;

    info = mm_callback_info_new_full (MM_MODEM (self), info_invoke, G_CALLBACK (callback), user_data);
    info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
                                       "Operation not supported");

    mm_callback_info_schedule (info);
}

void
mm_modem_get_info (MMModem *self,
                   MMModemInfoFn callback,
                   gpointer user_data)
{
    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    if (MM_MODEM_GET_INTERFACE (self)->get_info)
        MM_MODEM_GET_INTERFACE (self)->get_info (self, callback, user_data);
    else
        info_call_not_supported (self, callback, user_data);
}

static void
impl_modem_get_info (MMModem *modem,
                     DBusGMethodInvocation *context)
{
    mm_modem_get_info (modem, info_call_done, context);
}

/*****************************************************************************/

void
mm_modem_get_supported_charsets (MMModem *self,
                                 MMModemUIntFn callback,
                                 gpointer user_data)
{
    MMCallbackInfo *info;

    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    if (MM_MODEM_GET_INTERFACE (self)->get_supported_charsets)
        MM_MODEM_GET_INTERFACE (self)->get_supported_charsets (self, callback, user_data);
    else {
        info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data);
        info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
                                           "Operation not supported");
        mm_callback_info_schedule (info);
    }
}

void
mm_modem_set_charset (MMModem *self,
                      MMModemCharset charset,
                      MMModemFn callback,
                      gpointer user_data)
{
    MMCallbackInfo *info;

    g_return_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN);
    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (callback != NULL);

    if (MM_MODEM_GET_INTERFACE (self)->set_charset)
        MM_MODEM_GET_INTERFACE (self)->set_charset (self, charset, callback, user_data);
    else {
        info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
        info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
                                           "Operation not supported");
        mm_callback_info_schedule (info);
    }
}

/*****************************************************************************/

gboolean
mm_modem_owns_port (MMModem *self,
                    const char *subsys,
                    const char *name)
{
    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
    g_return_val_if_fail (subsys, FALSE);
    g_return_val_if_fail (name, FALSE);

    g_assert (MM_MODEM_GET_INTERFACE (self)->owns_port);
    return MM_MODEM_GET_INTERFACE (self)->owns_port (self, subsys, name);
}

gboolean
mm_modem_grab_port (MMModem *self,
                    const char *subsys,
                    const char *name,
                    MMPortType suggested_type,
                    gpointer user_data,
                    GError **error)
{
    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
    g_return_val_if_fail (subsys, FALSE);
    g_return_val_if_fail (name, FALSE);

    g_assert (MM_MODEM_GET_INTERFACE (self)->grab_port);
    return MM_MODEM_GET_INTERFACE (self)->grab_port (self, subsys, name, suggested_type, user_data, error);
}

void
mm_modem_release_port (MMModem *self,
                       const char *subsys,
                       const char *name)
{
    g_return_if_fail (self != NULL);
    g_return_if_fail (MM_IS_MODEM (self));
    g_return_if_fail (subsys);
    g_return_if_fail (name);

    g_assert (MM_MODEM_GET_INTERFACE (self)->release_port);
    MM_MODEM_GET_INTERFACE (self)->release_port (self, subsys, name);
}

gboolean
mm_modem_get_valid (MMModem *self)
{
    gboolean valid = FALSE;

    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);

    g_object_get (G_OBJECT (self), MM_MODEM_VALID, &valid, NULL);
    return valid;
}

char *
mm_modem_get_device (MMModem *self)
{
    char *device;

    g_return_val_if_fail (self != NULL, NULL);
    g_return_val_if_fail (MM_IS_MODEM (self), NULL);

    g_object_get (G_OBJECT (self), MM_MODEM_MASTER_DEVICE, &device, NULL);
    return device;
}

MMModemState
mm_modem_get_state (MMModem *self)
{
    MMModemState state = MM_MODEM_STATE_UNKNOWN;

    g_object_get (G_OBJECT (self), MM_MODEM_STATE, &state, NULL);
    return state;
}

static const char *
state_to_string (MMModemState state)
{
    switch (state) {
    case MM_MODEM_STATE_UNKNOWN:
        return "unknown";
    case MM_MODEM_STATE_DISABLED:
        return "disabled";
    case MM_MODEM_STATE_DISABLING:
        return "disabling";
    case MM_MODEM_STATE_ENABLING:
        return "enabling";
    case MM_MODEM_STATE_ENABLED:
        return "enabled";
    case MM_MODEM_STATE_SEARCHING:
        return "searching";
    case MM_MODEM_STATE_REGISTERED:
        return "registered";
    case MM_MODEM_STATE_DISCONNECTING:
        return "disconnecting";
    case MM_MODEM_STATE_CONNECTING:
        return "connecting";
    case MM_MODEM_STATE_CONNECTED:
        return "connected";
    default:
        g_assert_not_reached ();
        break;
    }

    g_assert_not_reached ();
    return "(invalid)";
}

void
mm_modem_set_state (MMModem *self,
                    MMModemState new_state,
                    MMModemStateReason reason)
{
    MMModemState old_state = MM_MODEM_STATE_UNKNOWN;
    const char *dbus_path;

    g_object_get (G_OBJECT (self), MM_MODEM_STATE, &old_state, NULL);

    if (new_state != old_state) {
        g_object_set (G_OBJECT (self), MM_MODEM_STATE, new_state, NULL);
        g_signal_emit_by_name (G_OBJECT (self), "state-changed", new_state, old_state, reason);

        dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG);
        if (dbus_path) {
            if (mm_options_debug ()) {
                GTimeVal tv;

                g_get_current_time (&tv);
                g_debug ("<%ld.%ld> Modem %s: state changed (%s -> %s)",
                         tv.tv_sec,
                         tv.tv_usec,
                         dbus_path,
                         state_to_string (old_state),
                         state_to_string (new_state));
            } else {
                g_message ("Modem %s: state changed (%s -> %s)",
                           dbus_path,
                           state_to_string (old_state),
                           state_to_string (new_state));
            }
        }
    }
}

/*****************************************************************************/

gboolean
mm_modem_auth_request (MMModem *self,
                       const char *authorization,
                       DBusGMethodInvocation *context,
                       MMAuthRequestCb callback,
                       gpointer callback_data,
                       GDestroyNotify notify,
                       GError **error)
{
    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
    g_return_val_if_fail (authorization != NULL, FALSE);
    g_return_val_if_fail (context != NULL, FALSE);
    g_return_val_if_fail (callback != NULL, FALSE);

    g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_request, FALSE);
    return MM_MODEM_GET_INTERFACE (self)->auth_request (self,
                                                        authorization,
                                                        context,
                                                        callback,
                                                        callback_data,
                                                        notify,
                                                        error);
}

gboolean
mm_modem_auth_finish (MMModem *self,
                      MMAuthRequest *req,
                      GError **error)
{
    gboolean success;

    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
    g_return_val_if_fail (req != NULL, FALSE);

    g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_finish, FALSE);
    success = MM_MODEM_GET_INTERFACE (self)->auth_finish (self, req, error);

    /* If the request failed, the implementor *should* return an error */
    if (!success && error)
        g_warn_if_fail (*error != NULL);

    return success;
}

/*****************************************************************************/

static void
mm_modem_init (gpointer g_iface)
{
    GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
    static gboolean initialized = FALSE;

    if (initialized)
        return;

    /* Properties */
    g_object_interface_install_property
        (g_iface,
         g_param_spec_string (MM_MODEM_DATA_DEVICE,
                              "Device",
                              "Data device",
                              NULL,
                              G_PARAM_READWRITE));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_string (MM_MODEM_MASTER_DEVICE,
                              "MasterDevice",
                              "Master modem parent device of all the modem's ports",
                              NULL,
                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_string (MM_MODEM_DRIVER,
                              "Driver",
                              "Driver",
                              NULL,
                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_string (MM_MODEM_PLUGIN,
                              "Plugin",
                              "Plugin name",
                              NULL,
                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_uint (MM_MODEM_TYPE,
                            "Type",
                            "Type",
                            0, G_MAXUINT32, 0,
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_uint (MM_MODEM_IP_METHOD,
                            "IP method",
                            "IP configuration method",
                            MM_MODEM_IP_METHOD_PPP,
                            MM_MODEM_IP_METHOD_DHCP,
                            MM_MODEM_IP_METHOD_PPP,
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_boolean (MM_MODEM_VALID,
                               "Valid",
                               "Modem is valid",
                               FALSE,
                               G_PARAM_READABLE));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_uint (MM_MODEM_STATE,
                            "State",
                            "State",
                            MM_MODEM_STATE_UNKNOWN,
                            MM_MODEM_STATE_LAST,
                            MM_MODEM_STATE_UNKNOWN,
                            G_PARAM_READWRITE));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_boolean (MM_MODEM_ENABLED,
                               "Enabled",
                               "Modem is enabled",
                               FALSE,
                               G_PARAM_READABLE));

    g_object_interface_install_property
        (g_iface,
         g_param_spec_string (MM_MODEM_UNLOCK_REQUIRED,
                               "UnlockRequired",
                               "Whether or not the modem requires an unlock "
                               "code to become usable, and if so, which unlock code is required",
                               NULL,
                               G_PARAM_READABLE));

    /* Signals */
    g_signal_new ("state-changed",
                  iface_type,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (MMModem, state_changed),
                  NULL, NULL,
                  mm_marshal_VOID__UINT_UINT_UINT,
                  G_TYPE_NONE, 3,
                  G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);

    initialized = TRUE;
}

GType
mm_modem_get_type (void)
{
    static GType modem_type = 0;

    if (!G_UNLIKELY (modem_type)) {
        const GTypeInfo modem_info = {
            sizeof (MMModem), /* class_size */
            mm_modem_init,   /* base_init */
            NULL,       /* base_finalize */
            NULL,
            NULL,       /* class_finalize */
            NULL,       /* class_data */
            0,
            0,              /* n_preallocs */
            NULL
        };

        modem_type = g_type_register_static (G_TYPE_INTERFACE,
                                             "MMModem",
                                             &modem_info, 0);

        g_type_interface_add_prerequisite (modem_type, G_TYPE_OBJECT);

        dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_object_info);
    }

    return modem_type;
}
