/*
 * properties-mixin.c - Source for TpPropertiesMixin
 * Copyright (C) 2006-2007 Collabora Ltd.
 * Copyright (C) 2006-2007 Nokia Corporation
 *   @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
 *   @author Robert McQueen <robert.mcqueen@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:properties-mixin
 * @title: TpPropertiesMixin
 * @short_description: a mixin implementation of the Telepathy.Properties
 *  interface
 * @see_also: #TpSvcPropertiesInterface
 *
 * This mixin can be added to any GObject class to implement the properties
 * interface in a general way.
 *
 * To use the properties mixin, include a #TpPropertiesMixinClass somewhere
 * in your class structure and a #TpPropertiesMixin somewhere in your
 * instance structure, and call tp_properties_mixin_class_init() from your
 * class_init function, tp_properties_mixin_init() from your init function
 * or constructor, and tp_properties_mixin_finalize() from your dispose
 * or finalize function.
 *
 * To use the properties mixin as the implementation of
 * #TpSvcPropertiesInterface, call
 * <literal>G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
 * tp_properties_mixin_iface_init)</literal> in the fourth argument to
 * <literal>G_DEFINE_TYPE_WITH_CODE</literal>.
 */

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

#include <dbus/dbus-glib.h>
#include <stdio.h>
#include <string.h>

#include <telepathy-glib/debug-ansi.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/intset.h>

#define DEBUG_FLAG TP_DEBUG_PROPERTIES

#include "debug-internal.h"

struct _TpPropertiesContext {
    TpPropertiesMixinClass *mixin_cls;
    TpPropertiesMixin *mixin;

    DBusGMethodInvocation *dbus_ctx;
    TpIntSet *remaining;
    GValue **values;
};

struct _TpPropertiesMixinPrivate {
    GObject *object;
    TpPropertiesContext context;
};

/*
 * tp_properties_mixin_class_get_offset_quark:
 *
 * Returns: the quark used for storing mixin offset on a GObjectClass
 */
GQuark
tp_properties_mixin_class_get_offset_quark ()
{
  static GQuark offset_quark = 0;
  if (!offset_quark)
    offset_quark = g_quark_from_static_string (
        "TpPropertiesMixinClassOffsetQuark");
  return offset_quark;
}

/*
 * tp_properties_mixin_get_offset_quark:
 *
 * Returns: the quark used for storing mixin offset on a GObject
 */
GQuark
tp_properties_mixin_get_offset_quark ()
{
  static GQuark offset_quark = 0;
  if (!offset_quark)
    offset_quark = g_quark_from_static_string (
        "TpPropertiesMixinOffsetQuark");
  return offset_quark;
}


/**
 * tp_properties_mixin_class_init:
 * @obj_cls: The class of an object that has this mixin
 * @offset: The offset of the TpPropertiesMixinClass structure in the class
 *          structure
 * @signatures: An array of property signatures
 * @num_properties: The number of entries in @signatures
 * @set_func: Callback used to set the properties
 *
 * Initialize the mixin. Should be called from the implementation's
 * class_init function like so:
 *
 * <informalexample><programlisting>
 * tp_properties_mixin_class_init ((GObjectClass *) klass,
 *                                 G_STRUCT_OFFSET (SomeObjectClass,
 *                                  properties_mixin));
 * </programlisting></informalexample>
 */

void
tp_properties_mixin_class_init (GObjectClass *obj_cls,
                                glong offset,
                                const TpPropertySignature *signatures,
                                guint num_properties,
                                TpPropertiesSetFunc set_func)
{
  TpPropertiesMixinClass *mixin_cls;

  g_assert (G_IS_OBJECT_CLASS (obj_cls));

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

  mixin_cls = TP_PROPERTIES_MIXIN_CLASS (obj_cls);

  mixin_cls->signatures = signatures;
  mixin_cls->num_props = num_properties;

  mixin_cls->set_properties = set_func;
}


/**
 * tp_properties_mixin_init:
 * @obj: An object that has this mixin
 * @offset: The offset of the TpPropertiesMixin structure in the object
 *          structure
 *
 * Initialize the mixin. Should be called from the implementation's
 * instance init function like so:
 *
 * <informalexample><programlisting>
 * tp_properties_mixin_init ((GObject *) self,
 *                           G_STRUCT_OFFSET (SomeObject, properties_mixin),
 *                           self->contact_repo);
 * </programlisting></informalexample>
 */

void tp_properties_mixin_init (GObject *obj, glong offset)
{
  TpPropertiesMixinClass *mixin_cls;
  TpPropertiesMixin *mixin;
  TpPropertiesContext *ctx;

  g_assert (G_IS_OBJECT (obj));

  g_assert (TP_IS_SVC_PROPERTIES_INTERFACE (obj));

  g_type_set_qdata (G_OBJECT_TYPE (obj),
                    TP_PROPERTIES_MIXIN_OFFSET_QUARK,
                    GINT_TO_POINTER (offset));

  mixin = TP_PROPERTIES_MIXIN (obj);
  mixin_cls = TP_PROPERTIES_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));

  mixin->properties = g_new0 (TpProperty, mixin_cls->num_props);

  mixin->priv = g_slice_new0 (TpPropertiesMixinPrivate);
  mixin->priv->object = obj;

  ctx = &mixin->priv->context;
  ctx->mixin_cls = mixin_cls;
  ctx->mixin = mixin;
  ctx->values = g_new0 (GValue *, mixin_cls->num_props);
}

/**
 * tp_properties_mixin_finalize:
 * @obj: An object that has this mixin
 *
 * Free memory used by the TpPropertiesMixin.
 */

void tp_properties_mixin_finalize (GObject *obj)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  TpPropertiesContext *ctx = &mixin->priv->context;
  guint i;

  for (i = 0; i < mixin_cls->num_props; i++)
    {
      TpProperty *prop = &mixin->properties[i];

      if (prop->value)
        {
          g_value_unset (prop->value);
          g_slice_free (GValue, prop->value);
        }

      if (ctx->values[i])
        {
          g_value_unset (ctx->values[i]);
        }
    }

  g_free (ctx->values);

  g_slice_free (TpPropertiesMixinPrivate, mixin->priv);

  g_free (mixin->properties);
}


/**
 * tp_properties_mixin_list_properties:
 * @obj: An object with this mixin
 * @ret: Output parameter which will be set to a GPtrArray of D-Bus structures
 *       if %TRUE is returned
 * @error: Set to the error if %FALSE is returned
 *
 * List all available properties and their flags, as in the ListProperties
 * D-Bus method.
 *
 * Returns: %TRUE on success
 */
gboolean
tp_properties_mixin_list_properties (GObject *obj,
                                     GPtrArray **ret,
                                     GError **error)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  GType spec_type = TP_STRUCT_TYPE_PROPERTY_SPEC;
  guint i;

  *ret = g_ptr_array_sized_new (mixin_cls->num_props);

  for (i = 0; i < mixin_cls->num_props; i++)
    {
      const TpPropertySignature *sig = &mixin_cls->signatures[i];
      TpProperty *prop = &mixin->properties[i];
      const gchar *dbus_sig;
      GValue val = { 0, };

      switch (sig->type) {
        case G_TYPE_BOOLEAN:
          dbus_sig = "b";
          break;
        case G_TYPE_INT:
          dbus_sig = "i";
          break;
        case G_TYPE_UINT:
          dbus_sig = "u";
          break;
        case G_TYPE_STRING:
          dbus_sig = "s";
          break;
        default:
          g_assert_not_reached ();
          continue;
      };

      g_value_init (&val, spec_type);
      g_value_take_boxed (&val, dbus_g_type_specialized_construct (spec_type));

      dbus_g_type_struct_set (&val,
          0, i,
          1, sig->name,
          2, dbus_sig,
          3, prop->flags,
          G_MAXUINT);

      g_ptr_array_add (*ret, g_value_get_boxed (&val));
    }

  return TRUE;
}


/**
 * tp_properties_mixin_get_properties:
 * @obj: An object with this mixin
 * @properties: an array of integer property IDs
 * @ret: set to an array of D-Bus structures if %TRUE is returned
 * @error: Set to the error if %FALSE is returned
 *
 * Retrieve the values of the given properties, as in the GetProperties
 * D-Bus method.
 *
 * Returns: %TRUE on success
 */
gboolean
tp_properties_mixin_get_properties (GObject *obj,
                                    const GArray *properties,
                                    GPtrArray **ret,
                                    GError **error)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  GType value_type = TP_STRUCT_TYPE_PROPERTY_VALUE;
  guint i;

  /* Check input property identifiers */
  for (i = 0; i < properties->len; i++)
    {
      guint prop_id = g_array_index (properties, guint, i);

      /* Valid? */
      if (prop_id >= mixin_cls->num_props)
        {
          g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
              "invalid property identifier %d", prop_id);

          return FALSE;
        }

      /* Permitted? */
      if (!tp_properties_mixin_is_readable (obj, prop_id))
        {
          g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
              "permission denied for property identifier %d", prop_id);

          return FALSE;
        }
    }

  /* If we got this far, return the actual values */
  *ret = g_ptr_array_sized_new (properties->len);

  for (i = 0; i < properties->len; i++)
    {
      guint prop_id = g_array_index (properties, guint, i);
      GValue val_struct = { 0, };

      /* id/value struct */
      g_value_init (&val_struct, value_type);
      g_value_take_boxed (&val_struct,
          dbus_g_type_specialized_construct (value_type));

      dbus_g_type_struct_set (&val_struct,
          0, prop_id,
          1, mixin->properties[prop_id].value,
          G_MAXUINT);

      g_ptr_array_add (*ret, g_value_get_boxed (&val_struct));
    }

  return TRUE;
}


/**
 * tp_properties_mixin_set_properties:
 * @obj: An object with this mixin
 * @properties: An array of D-Bus structures containing property ID and value
 * @context: A D-Bus method invocation context for the SetProperties method
 *
 * Start to change properties in response to user request via D-Bus.
 */
void
tp_properties_mixin_set_properties (GObject *obj,
                                    const GPtrArray *properties,
                                    DBusGMethodInvocation *context)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  TpPropertiesContext *ctx = &mixin->priv->context;
  GError *error = NULL;
  GType value_type = TP_STRUCT_TYPE_PROPERTY_VALUE;
  guint i;

  /* Is another SetProperties request already in progress? */
  if (ctx->dbus_ctx)
    {
      error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
                           "A SetProperties request is already in progress");
      dbus_g_method_return_error (context, error);
      g_error_free (error);
      return;
    }

  ctx->dbus_ctx = context;
  ctx->remaining = tp_intset_new ();
  error = NULL;

  if (properties->len == 0)
    {
      DEBUG ("immediately returning from SetProperties with 0 properties");
      tp_properties_context_return (ctx, NULL);
      return;
    }

  /* Check input property identifiers */
  for (i = 0; i < properties->len; i++)
    {
      GValue val_struct = { 0, };
      guint prop_id;
      GValue *prop_val;

      g_value_init (&val_struct, value_type);
      g_value_set_static_boxed (&val_struct,
          g_ptr_array_index (properties, i));

      dbus_g_type_struct_get (&val_struct,
          0, &prop_id,
          1, &prop_val,
          G_MAXUINT);

      /* Valid? */
      if (prop_id >= mixin_cls->num_props)
        {
          g_value_unset (prop_val);

          error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
                               "invalid property identifier %d", prop_id);
          goto ERROR;
        }

      /* Store the value in the context */
      tp_intset_add (ctx->remaining, prop_id);
      ctx->values[prop_id] = prop_val;

      /* Permitted? */
      if (!tp_properties_mixin_is_writable (obj, prop_id))
        {
          error = g_error_new (TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
                               "permission denied for property identifier %d",
                               prop_id);
          goto ERROR;
        }

      /* Compatible type? */
      if (!g_value_type_compatible (G_VALUE_TYPE (prop_val),
                                    mixin_cls->signatures[prop_id].type))
        {
          error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
                               "incompatible value type for property "
                               "identifier %d", prop_id);
          goto ERROR;
        }
    }

  if (mixin_cls->set_properties)
    {
      if (mixin_cls->set_properties (obj, ctx, &error))
        return;
    }
  else
    {
      tp_properties_context_return (ctx, NULL);
      return;
    }

ERROR:
  tp_properties_context_return (ctx, error);
}

/**
 * tp_properties_mixin_has_property:
 * @obj: an object with a properties mixin
 * @name: the string name of the property
 * @property: either %NULL, or a pointer to a location to receive the property
 *            index
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: %TRUE, setting @property, if @obj has a property of that name
 */
gboolean
tp_properties_mixin_has_property (GObject *obj, const gchar *name,
                                      guint *property)
{
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  guint i;

  for (i = 0; i < mixin_cls->num_props; i++)
    {
      if (strcmp (mixin_cls->signatures[i].name, name) == 0)
        {
          if (property)
            *property = i;

          return TRUE;
        }
    }

  return FALSE;
}


/**
 * tp_properties_context_has:
 * @ctx: the properties context representing a SetProperties call
 * @property: the property ID
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: %TRUE if @ctx indicates that @property still needs to be set on
 * the server.
 */
gboolean
tp_properties_context_has (TpPropertiesContext *ctx, guint property)
{
  g_assert (property < ctx->mixin_cls->num_props);

  return (tp_intset_is_member (ctx->remaining, property));
}


/**
 * tp_properties_context_has_other_than:
 * @ctx: the properties context representing a SetProperties call
 * @property: the property ID
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: %TRUE if @ctx has properties other than @property that still
 * need to be set on the server
 */
gboolean
tp_properties_context_has_other_than (TpPropertiesContext *ctx, guint property)
{
  gboolean has = tp_intset_is_member (ctx->remaining, property);

  g_assert (property < ctx->mixin_cls->num_props);

  return (tp_intset_size (ctx->remaining) > (has ? 1 : 0));
}


/**
 * tp_properties_context_get:
 * @ctx: the properties context representing a SetProperties call
 * @property: a property ID
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: the value to be set on the server for the property @property
 * in @ctx (whether it has been set already or not)
 */
const GValue *
tp_properties_context_get (TpPropertiesContext *ctx, guint property)
{
  g_assert (property < ctx->mixin_cls->num_props);

  return ctx->values[property];
}


/**
 * tp_properties_context_get_value_count:
 * @ctx: the properties context representing a SetProperties call
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: the number of properties in @ctx which still need to be set on
 *          the server, or have already been set
 */
guint
tp_properties_context_get_value_count (TpPropertiesContext *ctx)
{
  guint i, n;

  n = 0;
  for (i = 0; i < ctx->mixin_cls->num_props; i++)
    {
      if (ctx->values[i])
        n++;
    }

  return n;
}


/**
 * tp_properties_context_remove:
 * @ctx: the properties context representing a SetProperties call
 * @property: a property ID
 *
 * Mark the given property as having been set successfully.
 */
void
tp_properties_context_remove (TpPropertiesContext *ctx, guint property)
{
  g_assert (property < ctx->mixin_cls->num_props);

  tp_intset_remove (ctx->remaining, property);
}


/**
 * tp_properties_context_return
 * @ctx: the properties context representing a SetProperties call
 * @error: If %NULL, return successfully; otherwise return this error
 *
 * Commit the property changes and return from the pending D-Bus call.
 */
void
tp_properties_context_return (TpPropertiesContext *ctx, GError *error)
{
  GObject *obj = ctx->mixin->priv->object;
  TpIntSet *changed_props_val, *changed_props_flags;
  guint i;

  DEBUG ("%s", (error) ? "failure" : "success");

  changed_props_val = tp_intset_sized_new (ctx->mixin_cls->num_props);
  changed_props_flags = tp_intset_sized_new (ctx->mixin_cls->num_props);

  for (i = 0; i < ctx->mixin_cls->num_props; i++)
    {
      if (ctx->values[i])
        {
          if (!error)
            {
              tp_properties_mixin_change_value (obj, i, ctx->values[i],
                  changed_props_val);

              tp_properties_mixin_change_flags (obj, i,
                  TP_PROPERTY_FLAG_READ, 0, changed_props_flags);
            }

          g_value_unset (ctx->values[i]);
          ctx->values[i] = NULL;
        }
    }

  if (!error)
    {
      tp_properties_mixin_emit_changed (obj, changed_props_val);
      tp_properties_mixin_emit_flags (obj, changed_props_flags);
      tp_intset_destroy (changed_props_val);
      tp_intset_destroy (changed_props_flags);

      dbus_g_method_return (ctx->dbus_ctx);
    }
  else
    {
      dbus_g_method_return_error (ctx->dbus_ctx, error);
      g_error_free (error);
    }

  ctx->dbus_ctx = NULL;
  tp_intset_destroy (ctx->remaining);
  ctx->remaining = NULL;
  /* The context itself is not freed - it's a static part of the mixin */
}


/**
 * tp_properties_context_return_if_done:
 * @ctx: the properties context representing a SetProperties call
 *
 * Return from the pending D-Bus call if there are no more properties to be
 * dealt with.
 *
 * Returns: %TRUE if we returned from the D-Bus call.
 */
gboolean
tp_properties_context_return_if_done (TpPropertiesContext *ctx)
{
  if (tp_intset_size (ctx->remaining) == 0)
    {
      tp_properties_context_return (ctx, NULL);
      return TRUE;
    }

  return FALSE;
}

#define RPTS_APPEND_FLAG_IF_SET(flag) \
  if (flags & flag) \
    { \
      if (i++ > 0) \
        g_string_append (str, "|"); \
      g_string_append (str, #flag + 17); \
    }

static gchar *
property_flags_to_string (TpPropertyFlags flags)
{
  gint i = 0;
  GString *str;

  str = g_string_new ("[" TP_ANSI_BOLD_OFF);

  RPTS_APPEND_FLAG_IF_SET (TP_PROPERTY_FLAG_READ);
  RPTS_APPEND_FLAG_IF_SET (TP_PROPERTY_FLAG_WRITE);

  g_string_append (str, TP_ANSI_BOLD_ON "]");

  return g_string_free (str, FALSE);
}

static gboolean
values_are_equal (const GValue *v1, const GValue *v2)
{
  GType type = G_VALUE_TYPE (v1);

  switch (type) {
    case G_TYPE_BOOLEAN:
      return (g_value_get_boolean (v1) == g_value_get_boolean (v2));

    case G_TYPE_STRING:
      return !tp_strdiff (g_value_get_string (v1), g_value_get_string (v2));

    case G_TYPE_UINT:
      return (g_value_get_uint (v1) == g_value_get_uint (v2));

    case G_TYPE_INT:
      return (g_value_get_int (v1) == g_value_get_int (v2));
  }

  return FALSE;
}


/**
 * tp_properties_mixin_change_value:
 * @obj: An object with the properties mixin
 * @prop_id: A property ID on which to act
 * @new_value: Property value
 * @props: either %NULL, or a pointer to a TpIntSet
 *
 * Change the value of the given property ID in response to a server state
 * change.
 *
 * If the old and new values match, nothing happens; no signal is emitted and
 * @props is ignored. Otherwise, the following applies:
 *
 * If @props is %NULL the PropertiesChanged signal is emitted for this one
 * property.
 *
 * Otherwise, the property ID is added to the set; the caller is responsible
 * for passing the set to tp_properties_mixin_emit_changed() once a batch of
 * properties have been changed.
 */
void
tp_properties_mixin_change_value (GObject *obj,
                                  guint prop_id,
                                  const GValue *new_value,
                                  TpIntSet *props)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
      G_OBJECT_GET_CLASS (obj));
  TpProperty *prop;

  g_assert (prop_id < mixin_cls->num_props);

  prop = &mixin->properties[prop_id];

  if (prop->value)
    {
      if (values_are_equal (prop->value, new_value))
        return;
    }
  else
    {
      prop->value = tp_g_value_slice_new (mixin_cls->signatures[prop_id].type);
    }

  g_value_copy (new_value, prop->value);

  if (props)
    {
      tp_intset_add (props, prop_id);
    }
  else
    {
      TpIntSet *changed_props = tp_intset_sized_new (prop_id + 1);

      tp_intset_add (changed_props, prop_id);
      tp_properties_mixin_emit_changed (obj, changed_props);
      tp_intset_destroy (changed_props);
    }
}


/**
 * tp_properties_mixin_change_flags:
 * @obj: An object with the properties mixin
 * @prop_id: A property ID on which to act
 * @add: Property flags to be added via bitwise OR
 * @del: Property flags to be removed via bitwise AND
 * @props: either %NULL, or a pointer to a TpIntSet
 *
 * Change the flags for the given property ID in response to a server state
 * change.
 *
 * Flags removed by @del override flags added by @add. This should not be
 * relied upon.
 *
 * If @props is %NULL the PropertyFlagsChanged signal is emitted for this
 * single property.
 *
 * Otherwise, the property ID is added to the set; the caller is responsible
 * for passing the set to tp_properties_mixin_emit_flags() once a batch of
 * properties have been changed.
 */
void
tp_properties_mixin_change_flags (GObject *obj,
                                  guint prop_id,
                                  TpPropertyFlags add,
                                  TpPropertyFlags del,
                                  TpIntSet *props)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));
  TpProperty *prop;
  guint prev_flags;

  g_assert (prop_id < mixin_cls->num_props);

  prop = &mixin->properties[prop_id];

  prev_flags = prop->flags;

  prop->flags |= add;
  prop->flags &= ~del;

  if (prop->flags == prev_flags)
    return;

  if (props)
    {
      tp_intset_add (props, prop_id);
    }
  else
    {
      TpIntSet *changed_props = tp_intset_sized_new (prop_id + 1);

      tp_intset_add (changed_props, prop_id);
      tp_properties_mixin_emit_flags (obj, changed_props);
      tp_intset_destroy (changed_props);
    }
}

/**
 * tp_properties_mixin_emit_changed:
 * @obj: an object with the properties mixin
 * @props: a set of property IDs
 *
 * Emit the PropertiesChanged signal to indicate that the values of the
 * given property IDs have changed; the actual values are automatically
 * added using their stored values.
 */
void
tp_properties_mixin_emit_changed (GObject *obj, const TpIntSet *props)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
      G_OBJECT_GET_CLASS (obj));
  GPtrArray *prop_arr;
  GValue prop_list = { 0, };
  TpIntSetIter iter = TP_INTSET_ITER_INIT (props);
  guint len = tp_intset_size (props);

  if (len == 0)
    {
      return;
    }

  prop_arr = g_ptr_array_sized_new (len);

  if (DEBUGGING)
    printf (TP_ANSI_BOLD_ON TP_ANSI_FG_CYAN
            "%s: emitting properties changed for propert%s:\n",
            G_STRFUNC, (len > 1) ? "ies" : "y");

  while (tp_intset_iter_next (&iter))
    {
      GValue prop_val = { 0, };
      guint prop_id = iter.element;

      g_value_init (&prop_val, TP_STRUCT_TYPE_PROPERTY_VALUE);
      g_value_take_boxed (&prop_val,
          dbus_g_type_specialized_construct (TP_STRUCT_TYPE_PROPERTY_VALUE));

      dbus_g_type_struct_set (&prop_val,
          0, prop_id,
          1, mixin->properties[prop_id].value,
          G_MAXUINT);

      g_ptr_array_add (prop_arr, g_value_get_boxed (&prop_val));

      if (DEBUGGING)
        printf ("  %s\n", mixin_cls->signatures[prop_id].name);
    }

  if (DEBUGGING)
    {
      printf (TP_ANSI_RESET);
      fflush (stdout);
    }

  tp_svc_properties_interface_emit_properties_changed (
      (TpSvcPropertiesInterface *) obj, prop_arr);

  g_value_init (&prop_list, TP_ARRAY_TYPE_PROPERTY_VALUE_LIST);
  g_value_take_boxed (&prop_list, prop_arr);
  g_value_unset (&prop_list);
}


/**
 * tp_properties_mixin_emit_flags:
 * @obj: an object with the properties mixin
 * @props: a set of property IDs
 *
 * Emit the PropertyFlagsChanged signal to indicate that the flags of the
 * given property IDs have changed; the actual flags are automatically
 * added using their stored values.
 */
void
tp_properties_mixin_emit_flags (GObject *obj, const TpIntSet *props)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
      G_OBJECT_GET_CLASS (obj));
  GPtrArray *prop_arr;
  GValue prop_list = { 0, };
  TpIntSetIter iter = TP_INTSET_ITER_INIT (props);
  guint len = tp_intset_size (props);

  if (len == 0)
    {
      return;
    }

  prop_arr = g_ptr_array_sized_new (len);

  if (DEBUGGING)
    printf (TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE
            "%s: emitting properties flags changed for propert%s:\n",
            G_STRFUNC, (len > 1) ? "ies" : "y");

  while (tp_intset_iter_next (&iter))
    {
      GValue prop_val = { 0, };
      guint prop_id = iter.element;
      guint prop_flags;

      prop_flags = mixin->properties[prop_id].flags;

      g_value_init (&prop_val, TP_STRUCT_TYPE_PROPERTY_FLAGS_CHANGE);
      g_value_take_boxed (&prop_val,
          dbus_g_type_specialized_construct
              (TP_STRUCT_TYPE_PROPERTY_FLAGS_CHANGE));

      dbus_g_type_struct_set (&prop_val,
          0, prop_id,
          1, prop_flags,
          G_MAXUINT);

      g_ptr_array_add (prop_arr, g_value_get_boxed (&prop_val));

      if (DEBUGGING)
        {
          gchar *str_flags = property_flags_to_string (prop_flags);

          printf ("  %s's flags now: %s\n",
                  mixin_cls->signatures[prop_id].name, str_flags);

          g_free (str_flags);
        }
    }

  if (DEBUGGING)
    {
      printf (TP_ANSI_RESET);
      fflush (stdout);
    }

  tp_svc_properties_interface_emit_property_flags_changed (
      (TpSvcPropertiesInterface *) obj, prop_arr);

  g_value_init (&prop_list, TP_ARRAY_TYPE_PROPERTY_FLAGS_CHANGE_LIST);
  g_value_take_boxed (&prop_list, prop_arr);
  g_value_unset (&prop_list);
}


/**
 * tp_properties_mixin_is_readable:
 * @obj: an object with this mixin
 * @prop_id: an integer property ID
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: %TRUE if the given property has the READ flag
 */
gboolean
tp_properties_mixin_is_readable (GObject *obj, guint prop_id)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));

  if (prop_id >= mixin_cls->num_props)
    return FALSE;

  return ((mixin->properties[prop_id].flags & TP_PROPERTY_FLAG_READ) != 0);
}


/**
 * tp_properties_mixin_is_writable:
 * @obj: an object with this mixin
 * @prop_id: an integer property ID
 *
 * <!--Returns: says it all; this comment is to keep gtkdoc happy-->
 *
 * Returns: %TRUE if the given property has the WRITE flag
 */
gboolean
tp_properties_mixin_is_writable (GObject *obj, guint prop_id)
{
  TpPropertiesMixin *mixin = TP_PROPERTIES_MIXIN (obj);
  TpPropertiesMixinClass *mixin_cls = TP_PROPERTIES_MIXIN_CLASS (
                                            G_OBJECT_GET_CLASS (obj));

  if (prop_id >= mixin_cls->num_props)
    return FALSE;

  return ((mixin->properties[prop_id].flags & TP_PROPERTY_FLAG_WRITE) != 0);
}


/*
 * get_properties
 *
 * Implements D-Bus method GetProperties
 * on interface org.freedesktop.Telepathy.Properties
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns FALSE.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
static void
get_properties (TpSvcPropertiesInterface *iface,
                const GArray *properties,
                DBusGMethodInvocation *context)
{
  GPtrArray *ret;
  GError *error = NULL;
  gboolean ok = tp_properties_mixin_get_properties (G_OBJECT (iface),
      properties, &ret, &error);
  if (!ok)
    {
      dbus_g_method_return_error (context, error);
      g_error_free (error);
      return;
    }
  tp_svc_properties_interface_return_from_get_properties (
      context, ret);
  g_ptr_array_free (ret, TRUE);
}


/*
 * list_properties
 *
 * Implements D-Bus method ListProperties
 * on interface org.freedesktop.Telepathy.Properties
 *
 * @error: Used to return a pointer to a GError detailing any error
 *         that occurred, D-Bus will throw the error only if this
 *         function returns false.
 *
 * Returns: TRUE if successful, FALSE if an error was thrown.
 */
static void
list_properties (TpSvcPropertiesInterface *iface,
                 DBusGMethodInvocation *context)
{
  GPtrArray *ret;
  GError *error = NULL;
  gboolean ok = tp_properties_mixin_list_properties (G_OBJECT (iface), &ret,
      &error);
  guint i;

  if (!ok)
    {
      dbus_g_method_return_error (context, error);
      g_error_free (error);
    }
  tp_svc_properties_interface_return_from_list_properties (
      context, ret);

  for (i = 0; i < ret->len; i++)
    g_boxed_free (TP_STRUCT_TYPE_PROPERTY_SPEC, ret->pdata[i]);

  g_ptr_array_free (ret, TRUE);
}


/*
 * set_properties
 *
 * Implements D-Bus method SetProperties
 * on interface org.freedesktop.Telepathy.Properties
 *
 * @context: The D-Bus invocation context to use to return values
 *           or throw an error.
 */
static void
set_properties (TpSvcPropertiesInterface *iface,
                const GPtrArray *properties,
                DBusGMethodInvocation *context)
{
  tp_properties_mixin_set_properties (G_OBJECT (iface), properties, context);
}


/**
 * tp_properties_mixin_iface_init:
 * @g_iface: A pointer to the #TpSvcPropertiesInterfaceClass in an object class
 * @iface_data: Ignored
 *
 * Fill in this mixin's method implementations in the given interface vtable.
 * This function should usually be called via G_IMPLEMENT_INTERFACE
 * inside the G_DEFINE_TYPE_WITH_CODE macro.
 */
void
tp_properties_mixin_iface_init (gpointer g_iface, gpointer iface_data)
{
  TpSvcPropertiesInterfaceClass *klass = g_iface;

#define IMPLEMENT(x) tp_svc_properties_interface_implement_##x (klass, x)
  IMPLEMENT(get_properties);
  IMPLEMENT(list_properties);
  IMPLEMENT(set_properties);
#undef IMPLEMENT
}
