/*
 * contacts-mixin.c - Source for TpContactsMixin
 * Copyright (C) 2008 Collabora Ltd.
 * Copyright (C) 2008 Nokia Corporation
 *   @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
 *
 * 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
 */

/**
 * SECTION:contacts-mixin
 * @title: TpContactsMixin
 * @short_description: a mixin implementation of the contacts connection
 * interface
 * @see_also: #TpSvcConnectionInterfaceContacts
 *
 * This mixin can be added to a #TpBaseConnection subclass to implement the
 * Contacts interface in a generic way.
 *
 * To use the contacts mixin, include a #TpContactsMixinClass somewhere in
 * your class structure and a #TpContactsMixin somewhere in your instance
 * structure, and call tp_contacts_mixin_class_init() from your class_init
 * function, tp_contacts_mixin_init() from your init function or constructor,
 * and tp_contacts_mixin_finalize() from your dispose or finalize function.
 *
 * To use the contacts mixin as the implementation of
 * #TpSvcConnectionInterfaceContacts, in the function you pass to
 * G_IMPLEMENT_INTERFACE, you should call tp_contacts_mixin_iface_init.
 * TpContactsMixin implements all of the D-Bus methods and properties in the
 * Contacts interface.
 *
 * To add interfaces with contact attributes to this interface use
 * tp_contacts_mixin_add_contact_attributes_iface:
 *
 * Since: 0.7.14
 *
 */

#include <telepathy-glib/contacts-mixin.h>

#include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus-glib.h>

#include <telepathy-glib/base-connection.h>
#include <telepathy-glib/dbus.h>
#include <telepathy-glib/enums.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/gtypes.h>
#include <telepathy-glib/interfaces.h>

#define DEBUG_FLAG TP_DEBUG_CONNECTION

#include "debug-internal.h"

struct _TpContactsMixinPrivate
{
  /* String interface name -> FillContactAttributes func */
  GHashTable *interfaces;
};

enum {
  MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES,
  NUM_MIXIN_CONTACTS_DBUS_PROPERTIES
};

static TpDBusPropertiesMixinPropImpl known_contacts_props[] = {
  { "ContactAttributeInterfaces", NULL, NULL },
  { NULL }
};

static void
tp_presence_mixin_get_contacts_dbus_property (GObject *object,
                                              GQuark interface,
                                              GQuark name,
                                              GValue *value,
                                              gpointer unused
                                              G_GNUC_UNUSED)
{
  static GQuark q[NUM_MIXIN_CONTACTS_DBUS_PROPERTIES] = { 0, };
  TpContactsMixin *self = TP_CONTACTS_MIXIN (object);

  DEBUG ("called.");

  if (G_UNLIKELY (q[0] == 0))
    {
      q[MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES] =
        g_quark_from_static_string ("ContactAttributeInterfaces");
    }

  g_return_if_fail (object != NULL);

  if (name == q[MIXIN_DP_CONTACT_ATTRIBUTE_INTERFACES])
    {
      gchar **interfaces;
      GHashTableIter iter;
      gpointer key;
      int i = 0;

      g_assert (G_VALUE_HOLDS(value, G_TYPE_STRV));

      /* FIXME, cache this when connected ? */
      interfaces = g_malloc0(
        (g_hash_table_size (self->priv->interfaces) + 1) * sizeof (gchar *));

      g_hash_table_iter_init (&iter, self->priv->interfaces);
      while (g_hash_table_iter_next (&iter, &key, NULL))
          {
            interfaces[i] = g_strdup ((gchar *) key);
            i++;
          }
      g_value_take_boxed (value, interfaces);
    }
  else
    {
      g_assert_not_reached ();
    }
}


/**
 * tp_contacts_mixin_class_get_offset_quark:
 *
 * <!--no documentation beyond Returns: needed-->
 *
 * Returns: the quark used for storing mixin offset on a GObjectClass
 *
 * Since: 0.7.14
 *
 */
GQuark
tp_contacts_mixin_class_get_offset_quark ()
{
  static GQuark offset_quark = 0;

  if (G_UNLIKELY (offset_quark == 0))
    offset_quark = g_quark_from_static_string (
        "TpContactsMixinClassOffsetQuark");

  return offset_quark;
}

/**
 * tp_contacts_mixin_get_offset_quark:
 *
 * <!--no documentation beyond Returns: needed-->
 *
 * Returns: the quark used for storing mixin offset on a GObject
 *
 * Since: 0.7.14
 *
 */
GQuark
tp_contacts_mixin_get_offset_quark ()
{
  static GQuark offset_quark = 0;

  if (G_UNLIKELY (offset_quark == 0))
    offset_quark = g_quark_from_static_string ("TpContactsMixinOffsetQuark");

  return offset_quark;
}


/**
 * tp_contacts_mixin_class_init:
 * @obj_cls: The class of the implementation that uses this mixin
 * @offset: The byte offset of the TpContactsMixinClass within the class
 *          structure
 *
 * Initialize the contacts mixin. Should be called from the implementation's
 * class_init function like so:
 *
 * <informalexample><programlisting>
 * tp_contacts_mixin_class_init ((GObjectClass *) klass,
 *                          G_STRUCT_OFFSET (SomeObjectClass, contacts_mixin));
 * </programlisting></informalexample>
 *
 * Since: 0.7.14
 *
 */

void
tp_contacts_mixin_class_init (GObjectClass *obj_cls, glong offset)
{
  g_assert (G_IS_OBJECT_CLASS (obj_cls));

  g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
      TP_CONTACTS_MIXIN_CLASS_OFFSET_QUARK,
      GINT_TO_POINTER (offset));

  tp_dbus_properties_mixin_implement_interface (obj_cls,
      TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS,
      tp_presence_mixin_get_contacts_dbus_property,
      NULL, known_contacts_props);
}


/**
 * tp_contacts_mixin_init:
 * @obj: An instance of the implementation that uses this mixin
 * @offset: The byte offset of the TpContactsMixin within the object structure
 *
 * Initialize the contacts mixin. Should be called from the implementation's
 * instance init function like so:
 *
 * <informalexample><programlisting>
 * tp_contacts_mixin_init ((GObject *) self,
 *                     G_STRUCT_OFFSET (SomeObject, contacts_mixin));
 * </programlisting></informalexample>
 *
 * Since: 0.7.14
 *
 */
void
tp_contacts_mixin_init (GObject *obj, gsize offset)
{
  TpContactsMixin *mixin;

  g_assert (G_IS_OBJECT (obj));

  g_type_set_qdata (G_OBJECT_TYPE (obj),
                    TP_CONTACTS_MIXIN_OFFSET_QUARK,
                    GSIZE_TO_POINTER (offset));

  mixin = TP_CONTACTS_MIXIN (obj);

  mixin->priv = g_slice_new0 (TpContactsMixinPrivate);
  mixin->priv->interfaces = g_hash_table_new_full (g_str_hash, g_str_equal,
    g_free, NULL);
}

/**
 * tp_contacts_mixin_finalize:
 * @obj: An object with this mixin.
 *
 * Free resources held by the contacts mixin.
 *
 * Since: 0.7.14
 *
 */
void
tp_contacts_mixin_finalize (GObject *obj)
{
  TpContactsMixin *mixin = TP_CONTACTS_MIXIN (obj);

  DEBUG ("%p", obj);

  /* free any data held directly by the object here */
  g_hash_table_destroy (mixin->priv->interfaces);
  g_slice_free (TpContactsMixinPrivate, mixin->priv);
}

static void
tp_contacts_mixin_get_contact_attributes (
  TpSvcConnectionInterfaceContacts *iface, const GArray *handles,
  const char **interfaces, gboolean hold, DBusGMethodInvocation *context)
{
  TpContactsMixin *self = TP_CONTACTS_MIXIN (iface);
  GHashTable *result;
  guint i;
  TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
        TP_HANDLE_TYPE_CONTACT);
  GArray *valid_handles;
  TpContactsMixinFillContactAttributesFunc func;

  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);

  /* Setup handle array and hash with valid handles, optionally holding them */
  valid_handles = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle),
      handles->len);
  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
      (GDestroyNotify) g_hash_table_destroy);

  for (i = 0 ; i < handles->len ; i++)
    {
      TpHandle h;
      h = g_array_index (handles, TpHandle, i);
      if (tp_handle_is_valid (contact_repo, h, NULL))
        {
          GHashTable *attr_hash = g_hash_table_new_full (g_str_hash,
              g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free);
          g_array_append_val (valid_handles, h);
          g_hash_table_insert (result, GUINT_TO_POINTER(h), attr_hash);
        }
    }

  if (hold)
    {
      gchar *sender = dbus_g_method_get_sender (context);

      tp_handles_client_hold (contact_repo, sender, valid_handles, NULL);
      g_free (sender);
    }

  /* ensure the handles don't disappear while calling out to various functions
   */
  tp_handles_ref (contact_repo, valid_handles);

  func = g_hash_table_lookup (self->priv->interfaces, TP_IFACE_CONNECTION);

  if (func != NULL)
    func (G_OBJECT (iface), valid_handles, result);

  for (i = 0; interfaces[i] != NULL; i++)
    {

      func = g_hash_table_lookup (self->priv->interfaces, interfaces[i]);

      if (func == NULL)
        DEBUG ("non-inspectable interface %s given; ignoring", interfaces[i]);
      else
        func (G_OBJECT(iface), valid_handles, result);
    }

  tp_svc_connection_interface_contacts_return_from_get_contact_attributes (
    context, result);

  g_hash_table_destroy (result);

  tp_handles_unref (contact_repo, valid_handles);
  g_array_free (valid_handles, TRUE);
}

/**
 * tp_contacts_mixin_iface_init:
 * @g_iface: A pointer to the #TpSvcConnectionInterfaceContacts in an object
 * class
 * @iface_data: Ignored
 *
 * Fill in the vtable entries needed to implement the contacts interface
 * using this mixin. This function should usually be called via
 * G_IMPLEMENT_INTERFACE.
 *
 * Since: 0.7.14
 *
 */
void
tp_contacts_mixin_iface_init (gpointer g_iface, gpointer iface_data)
{
  TpSvcConnectionInterfaceContactsClass *klass =
    (TpSvcConnectionInterfaceContactsClass *) g_iface;

#define IMPLEMENT(x) tp_svc_connection_interface_contacts_implement_##x ( \
    klass, tp_contacts_mixin_##x)
  IMPLEMENT(get_contact_attributes);
#undef IMPLEMENT
}

/**
 * tp_contacts_mixin_add_contact_attributes_iface:
 * @obj: An instance of the implementation that uses this mixin
 * @interface: Name of the interface that has ContactAttributes
 * @fill_contact_attributes: Contact attribute filler function
 *
 * Declare that the given interface has contact attributes which can be added
 * to the attributes hash using the filler function. All the handles in the
 * handle array passed to the filler function are guaranteed to be valid and
 * referenced.
 *
 * Since: 0.7.14
 *
 */

void
tp_contacts_mixin_add_contact_attributes_iface (GObject *obj,
    const gchar *interface,
    TpContactsMixinFillContactAttributesFunc fill_contact_attributes)
{
  TpContactsMixin *self = TP_CONTACTS_MIXIN (obj);

  g_assert (g_hash_table_lookup (self->priv->interfaces, interface) == NULL);
  g_assert (fill_contact_attributes != NULL);

  g_hash_table_insert (self->priv->interfaces, g_strdup (interface),
    fill_contact_attributes);
}

/**
 * tp_contacts_mixin_set_contact_attribute:
 * @contact_attributes: contacts attribute hash as passed to
 *   TpContactsMixinFillContactAttributesFunc
 * @handle: Handle to set the attribute on
 * @attribute: attribute name
 * @value: slice allocated GValue containing the value of the attribute, for
 * instance with tp_g_value_slice_new. Ownership of the GValue is taken over by
 * the mixin
 *
 * Utility function to set attribute for handle to value in the attributes hash
 * as passed to a TpContactsMixinFillContactAttributesFunc.
 *
 * Since: 0.7.14
 *
 */

void
tp_contacts_mixin_set_contact_attribute (GHashTable *contact_attributes,
    TpHandle handle, const gchar *attribute, GValue *value)
{
  GHashTable *attributes;

  attributes = g_hash_table_lookup (contact_attributes,
    GUINT_TO_POINTER (handle));

  g_assert (attributes != NULL);
  g_assert (G_IS_VALUE (value));

  g_hash_table_insert (attributes, g_strdup (attribute), value);
}

