/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/*
 * Copyright (C) 2009 Collabora Ltd.
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
 */

#include "config.h"
#include "empathy-connectivity.h"

#ifdef HAVE_NM
#include <nm-client.h>
#endif

#ifdef HAVE_CONNMAN
#include <dbus/dbus-glib.h>
#endif

#include <telepathy-glib/util.h>

#include "empathy-utils.h"

#define DEBUG_FLAG EMPATHY_DEBUG_CONNECTIVITY
#include "empathy-debug.h"

#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyConnectivity)

typedef struct {
#ifdef HAVE_NM
  NMClient *nm_client;
  gulong state_change_signal_id;
#endif

#ifdef HAVE_CONNMAN
  DBusGProxy *proxy;
#endif

  gboolean connected;
  gboolean use_conn;
} EmpathyConnectivityPriv;

enum {
  STATE_CHANGE,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_USE_CONN,
};

static guint signals[LAST_SIGNAL];
static EmpathyConnectivity *connectivity_singleton = NULL;

G_DEFINE_TYPE (EmpathyConnectivity, empathy_connectivity, G_TYPE_OBJECT);

static void
connectivity_change_state (EmpathyConnectivity *connectivity,
    gboolean new_state)
{
  EmpathyConnectivityPriv *priv;

  priv = GET_PRIV (connectivity);

  if (priv->connected == new_state)
    return;

  priv->connected = new_state;

  g_signal_emit (connectivity, signals[STATE_CHANGE], 0,
      priv->connected);
}

#ifdef HAVE_NM

#if !defined(NM_CHECK_VERSION)
#define NM_CHECK_VERSION(x,y,z) 0
#endif

static void
connectivity_nm_state_change_cb (NMClient *client,
    const GParamSpec *pspec,
    EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv;
  gboolean new_nm_connected;
  NMState state;

  priv = GET_PRIV (connectivity);

  if (!priv->use_conn)
    return;

  state = nm_client_get_state (priv->nm_client);
  new_nm_connected = !(state == NM_STATE_CONNECTING
#if NM_CHECK_VERSION(0,8,992)
      || state == NM_STATE_DISCONNECTING
#endif
      || state == NM_STATE_ASLEEP
      || state == NM_STATE_DISCONNECTED);

  DEBUG ("New NetworkManager network state %d (connected: %s)", state,
      new_nm_connected ? "true" : "false");

  connectivity_change_state (connectivity, new_nm_connected);
}
#endif

#ifdef HAVE_CONNMAN
static void
connectivity_connman_state_changed_cb (DBusGProxy *proxy,
    const gchar *new_state,
    EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv;
  gboolean new_connected;

  priv = GET_PRIV (connectivity);

  if (!priv->use_conn)
    return;

  new_connected = !tp_strdiff (new_state, "online");

  DEBUG ("New ConnMan network state %s", new_state);

  connectivity_change_state (connectivity, new_connected);
}

static void
connectivity_connman_check_state_cb (DBusGProxy *proxy,
    DBusGProxyCall *call_id,
    gpointer user_data)
{
  EmpathyConnectivity *connectivity = (EmpathyConnectivity *) user_data;
  GError *error = NULL;
  gchar *state;

  if (dbus_g_proxy_end_call (proxy, call_id, &error,
          G_TYPE_STRING, &state, G_TYPE_INVALID))
    {
      connectivity_connman_state_changed_cb (proxy, state,
          connectivity);
      g_free (state);
    }
  else
    {
      DEBUG ("Failed to call GetState: %s", error->message);
      connectivity_connman_state_changed_cb (proxy, "offline",
          connectivity);
    }
}

static void
connectivity_connman_check_state (EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv;

  priv = GET_PRIV (connectivity);

  dbus_g_proxy_begin_call (priv->proxy, "GetState",
      connectivity_connman_check_state_cb, connectivity, NULL,
      G_TYPE_INVALID);
}
#endif

static void
empathy_connectivity_init (EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv;
#ifdef HAVE_CONNMAN
  DBusGConnection *connection;
  GError *error = NULL;
#endif

  priv = G_TYPE_INSTANCE_GET_PRIVATE (connectivity,
      EMPATHY_TYPE_CONNECTIVITY, EmpathyConnectivityPriv);

  connectivity->priv = priv;

  priv->use_conn = TRUE;

#ifdef HAVE_NM
  priv->nm_client = nm_client_new ();
  if (priv->nm_client != NULL)
    {
      priv->state_change_signal_id = g_signal_connect (priv->nm_client,
          "notify::" NM_CLIENT_STATE,
          G_CALLBACK (connectivity_nm_state_change_cb), connectivity);

      connectivity_nm_state_change_cb (priv->nm_client, NULL, connectivity);
    }
  else
    {
      DEBUG ("Failed to get NetworkManager proxy");
    }
#endif

#ifdef HAVE_CONNMAN
  connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
  if (connection != NULL)
    {
      priv->proxy = dbus_g_proxy_new_for_name (connection,
          "net.connman", "/",
          "net.connman.Manager");

      dbus_g_object_register_marshaller (
          g_cclosure_marshal_generic,
          G_TYPE_NONE, G_TYPE_STRING, G_TYPE_INVALID);

      dbus_g_proxy_add_signal (priv->proxy, "StateChanged",
          G_TYPE_STRING, G_TYPE_INVALID);

      dbus_g_proxy_connect_signal (priv->proxy, "StateChanged",
          G_CALLBACK (connectivity_connman_state_changed_cb),
          connectivity, NULL);

      connectivity_connman_check_state (connectivity);
    }
  else
    {
      DEBUG ("Failed to get system bus connection: %s", error->message);
      g_error_free (error);
    }
#endif

#if !defined(HAVE_NM) && !defined(HAVE_CONNMAN)
  priv->connected = TRUE;
#endif
}

static void
connectivity_finalize (GObject *object)
{
#ifdef HAVE_NM
  EmpathyConnectivity *connectivity = EMPATHY_CONNECTIVITY (object);
  EmpathyConnectivityPriv *priv = GET_PRIV (connectivity);

  if (priv->nm_client != NULL)
    {
      g_signal_handler_disconnect (priv->nm_client,
          priv->state_change_signal_id);
      priv->state_change_signal_id = 0;
      g_object_unref (priv->nm_client);
      priv->nm_client = NULL;
    }
#endif

#ifdef HAVE_CONNMAN
  EmpathyConnectivity *connectivity = EMPATHY_CONNECTIVITY (object);
  EmpathyConnectivityPriv *priv = GET_PRIV (connectivity);

  if (priv->proxy != NULL)
    {
      dbus_g_proxy_disconnect_signal (priv->proxy, "StateChanged",
          G_CALLBACK (connectivity_connman_state_changed_cb), connectivity);

      g_object_unref (priv->proxy);
      priv->proxy = NULL;
    }
#endif

  G_OBJECT_CLASS (empathy_connectivity_parent_class)->finalize (object);
}

static void
connectivity_dispose (GObject *object)
{
  G_OBJECT_CLASS (empathy_connectivity_parent_class)->dispose (object);
}

static GObject *
connectivity_constructor (GType type,
    guint n_construct_params,
    GObjectConstructParam *construct_params)
{
  GObject *retval;

  if (!connectivity_singleton)
    {
      retval = G_OBJECT_CLASS (empathy_connectivity_parent_class)->constructor
        (type, n_construct_params, construct_params);

      connectivity_singleton = EMPATHY_CONNECTIVITY (retval);
      g_object_add_weak_pointer (retval, (gpointer) &connectivity_singleton);
    }
  else
    {
      retval = g_object_ref (connectivity_singleton);
    }

  return retval;
}

static void
connectivity_get_property (GObject *object,
    guint param_id,
    GValue *value,
    GParamSpec *pspec)
{
  EmpathyConnectivity *connectivity = EMPATHY_CONNECTIVITY (object);

  switch (param_id)
    {
    case PROP_USE_CONN:
      g_value_set_boolean (value, empathy_connectivity_get_use_conn (
              connectivity));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    };
}

static void
connectivity_set_property (GObject *object,
    guint param_id,
    const GValue *value,
    GParamSpec *pspec)
{
  EmpathyConnectivity *connectivity = EMPATHY_CONNECTIVITY (object);

  switch (param_id)
    {
    case PROP_USE_CONN:
      empathy_connectivity_set_use_conn (connectivity,
          g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    };
}

static void
empathy_connectivity_class_init (EmpathyConnectivityClass *klass)
{
  GObjectClass *oclass = G_OBJECT_CLASS (klass);

  oclass->finalize = connectivity_finalize;
  oclass->dispose = connectivity_dispose;
  oclass->constructor = connectivity_constructor;
  oclass->get_property = connectivity_get_property;
  oclass->set_property = connectivity_set_property;

  signals[STATE_CHANGE] =
    g_signal_new ("state-change",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_generic,
        G_TYPE_NONE,
        1, G_TYPE_BOOLEAN, NULL);

  g_object_class_install_property (oclass,
      PROP_USE_CONN,
      g_param_spec_boolean ("use-conn",
          "Use connectivity managers",
          "Set presence according to connectivity managers",
          TRUE,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE));

  g_type_class_add_private (oclass, sizeof (EmpathyConnectivityPriv));
}

/* public methods */

EmpathyConnectivity *
empathy_connectivity_dup_singleton (void)
{
  return g_object_new (EMPATHY_TYPE_CONNECTIVITY, NULL);
}

gboolean
empathy_connectivity_is_online (EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv = GET_PRIV (connectivity);

  return priv->connected;
}

gboolean
empathy_connectivity_get_use_conn (EmpathyConnectivity *connectivity)
{
  EmpathyConnectivityPriv *priv = GET_PRIV (connectivity);

  return priv->use_conn;
}

void
empathy_connectivity_set_use_conn (EmpathyConnectivity *connectivity,
    gboolean use_conn)
{
  EmpathyConnectivityPriv *priv = GET_PRIV (connectivity);

  if (use_conn == priv->use_conn)
    return;

  DEBUG ("use_conn GSetting key changed; new value = %s",
      use_conn ? "true" : "false");

  priv->use_conn = use_conn;

#if defined(HAVE_NM) || defined(HAVE_CONNMAN)
  if (use_conn)
    {
#if defined(HAVE_NM)
      connectivity_nm_state_change_cb (priv->nm_client, NULL, connectivity);
#elif defined(HAVE_CONNMAN)
      connectivity_connman_check_state (connectivity);
#endif
    }
  else
#endif
    {
      connectivity_change_state (connectivity, TRUE);
    }

  g_object_notify (G_OBJECT (connectivity), "use-conn");
}
