/*
 * AT-SPI - Assistive Technology Service Provider Interface
 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
 *
 * Copyright 2001, 2002 Sun Microsystems Inc.,
 * Copyright 2001, 2002 Ximian, Inc.
 * Copyright 2010, 2011 Novell, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "atspi-private.h"

G_DEFINE_TYPE (AtspiMatchRule, atspi_match_rule, G_TYPE_OBJECT)

static void
atspi_match_rule_init (AtspiMatchRule *match_rule)
{
}

static void
atspi_match_rule_dispose (GObject *object)
{
  AtspiMatchRule *rule = ATSPI_MATCH_RULE (object);

  if (rule->states)
  {
    g_object_unref (rule->states);
    rule->states = NULL;
  }

  if (rule->attributes)
  {
    g_hash_table_unref (rule->attributes);
    rule->attributes = NULL;
  }

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

static void
atspi_match_rule_finalize (GObject *object)
{
  AtspiMatchRule *rule = ATSPI_MATCH_RULE (object);
  gint i;

  if (rule->interfaces)
  {
    for (i = 0; i < rule->interfaces->len; i++)
      g_free (g_array_index (rule->interfaces, gchar *, i));
    g_array_free (rule->interfaces, TRUE);
  }

  if (rule->attributes)
    g_hash_table_unref (rule->attributes);

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

static void
atspi_match_rule_class_init (AtspiMatchRuleClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = atspi_match_rule_dispose;
  object_class->finalize = atspi_match_rule_finalize;
}

/**
 * atspi_match_rule_new:
 * @states: An #AtspiStateSet specifying the states to match or NULL if none.
 * @statematchtype: An #AtspiCollectionMatchType specifying how to interpret
 *          @states.
 * @attributes: (element-type gchar* gchar*): A #GHashTable specifying
 *          attributes to match. To specify multiple attribute values,
 *          separate each value with a :: If an attribute value contains a :,
 *          then it can be escaped by preceding it with a \. A backslash can
 *          likewise be escaped by inserting a double backslash.
 * @attributematchtype: An #AtspiCollectionMatchType specifying how to
 *          interpret @attributes.
 * @interfaces: (element-type gchar*): An array of interfaces to match, or
 *          NULL if not applicable.  Interface names should be specified
 *          by their DBus names (org.a11y.Atspi.Accessible,
 *          org.a11y.Atspi.Component, etc).
 * @interfacematchtype: An #AtspiCollectionMatchType specifying how to
 *          interpret @interfaces.
 * @roles: (element-type AtspiRole): A #GArray of roles to match, or NULL if
 *          not applicable.
 * @rolematchtype: An #AtspiCollectionMatchType specifying how to
 *          interpret @roles.
 * @invert: if #TRUE, the match rule should be denied (inverted); if #FALSE,
 *          it should not. For example, if the match rule defines that a match is
 *          an object of ROLE_HEADING which has STATE_FOCUSABLE and a click action,
 *          inverting it would match all objects that are not of ROLE_HEADING,
 *          focusable and clickable at the same time.
 *
 * Creates a new #AtspiMatchRule with specified @states, @attributes, 
 * @interfaces, and @roles.
 *
 * Returns: (transfer full): A new #AtspiMatchRule.
 **/
AtspiMatchRule *
atspi_match_rule_new (AtspiStateSet *states,
                      AtspiCollectionMatchType statematchtype,
                      GHashTable *attributes,
                      AtspiCollectionMatchType attributematchtype,
                      GArray *roles,
                      AtspiCollectionMatchType rolematchtype,
                      GArray *interfaces,
                      AtspiCollectionMatchType interfacematchtype,
                      gboolean invert)
{
  AtspiMatchRule *rule = g_object_new (ATSPI_TYPE_MATCH_RULE, NULL);
  int i;

  if (states)
    rule->states = g_object_ref (states);
  rule->statematchtype = statematchtype;

  if (attributes)
  {
    GHashTableIter hash_table_iter;
    gchar *key, *value;
    rule->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
                                              (GDestroyNotify) g_free,
                                              (GDestroyNotify) g_free);
    g_hash_table_iter_init (&hash_table_iter, attributes);
            while (g_hash_table_iter_next (&hash_table_iter, (gpointer *)&key,
                   (gpointer *)&value))
      g_hash_table_insert (rule->attributes, g_strdup (key), g_strdup (value));
  } else
    rule->attributes = NULL;
  rule->attributematchtype = attributematchtype;

  if (interfaces)
  {
    rule->interfaces = g_array_new (TRUE, TRUE, sizeof (gchar *));
    for (i = 0; i < interfaces->len; i++)
    {
      gchar *val = g_strdup (g_array_index (interfaces, gchar *, i));
      rule->interfaces = g_array_append_val (rule->interfaces, val);
    }
  }
  rule->interfacematchtype = interfacematchtype;

  if (roles)
  {
    for (i = 0; i < roles->len; i++)
    {
      AtspiRole role = g_array_index (roles, AtspiRole, i);
      if (role < 128)
        rule->roles [role / 32] |= (1 << (role % 32));
      else
        g_warning ("AT-SPI: unexpected role %d\n", role);
    }
  }
  else
    rule->roles [0] = rule->roles [1] = 0;
  rule->rolematchtype = rolematchtype;

  rule->invert = invert;

  return rule;
}

static void
append_entry (gpointer key, gpointer val, gpointer data)
{
  DBusMessageIter *iter = data;
  DBusMessageIter iter_entry;

  if (!dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY, NULL,
                                        &iter_entry))
    return;
  dbus_message_iter_append_basic (&iter_entry, DBUS_TYPE_STRING, &key);
  dbus_message_iter_append_basic (&iter_entry, DBUS_TYPE_STRING, &val);
  dbus_message_iter_close_container (iter, &iter_entry);
}

gboolean
_atspi_match_rule_marshal (AtspiMatchRule *rule, DBusMessageIter *iter)
{
  DBusMessageIter iter_struct, iter_array, iter_dict;
  dbus_int32_t states [2];
  dbus_int32_t d_statematchtype = rule->statematchtype;
  dbus_int32_t d_attributematchtype = rule->attributematchtype;
  dbus_int32_t d_interfacematchtype = rule->interfacematchtype;
  dbus_uint32_t d_rolematchtype = rule->rolematchtype;
  dbus_bool_t d_invert = rule->invert;
  gint i;
  dbus_int32_t d_role;

  if (!dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT, NULL,
                                         &iter_struct))
    return FALSE;

  /* states */
  if (rule->states)
  {
    states [0] = rule->states->states & 0xffffffff;
    states [1] = rule->states->states >> 32;
  }
  else
  {
    states [0] = states [1] = 0;
  }
  dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "i", &iter_array);
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &states [0]);
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &states [1]);
  dbus_message_iter_close_container (&iter_struct, &iter_array);
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32, &d_statematchtype);

  /* attributes */
  if (!dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "{ss}",
                                         &iter_dict))
    return FALSE;
  if (rule->attributes)
    g_hash_table_foreach (rule->attributes, append_entry, &iter_dict);
  dbus_message_iter_close_container (&iter_struct, &iter_dict);
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32, &d_attributematchtype);

  if (!dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "i",
      &iter_array))
    return FALSE;
  d_role = rule->roles [0];
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &d_role);
  d_role = rule->roles [1];
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &d_role);
  d_role = rule->roles [2];
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &d_role);
  d_role = rule->roles [3];
  dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_INT32, &d_role);
  dbus_message_iter_close_container (&iter_struct, &iter_array);
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32,
                                  &d_rolematchtype);

  /* interfaces */
  if (!dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "s",
      &iter_array))
    return FALSE;
  if (rule->interfaces)
  {
    for (i = 0; i < rule->interfaces->len; i++)
    {
      char *val = g_array_index (rule->interfaces, gchar *, i);
      dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_STRING, &val);
    }
  }
  dbus_message_iter_close_container (&iter_struct, &iter_array);
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32, &d_interfacematchtype);

  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &d_invert);

  dbus_message_iter_close_container (iter, &iter_struct);
  return TRUE;
}
