/*
 * Copyright (C) 2011 Igalia S.L.
 *
 * Contact: Iago Toral Quiroga <itoral@igalia.com>
 *
 * 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; 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:grl-operation-options
 * @short_description: Describes the options to be passed to an operation
 * @see_also: #GrlCaps, grl_source_resolve(),
 * grl_source_search(), grl_source_browse(),
 * grl_source_query()
 *
 */
#include <grl-operation-options.h>
#include <grl-value-helper.h>
#include <grl-range-value.h>
#include <grl-log.h>
#include <grl-registry.h>

#include "grl-operation-options-priv.h"
#include "grl-type-builtins.h"

G_DEFINE_TYPE (GrlOperationOptions, grl_operation_options, G_TYPE_OBJECT);

#define GRL_OPERATION_OPTIONS_GET_PRIVATE(o)\
    (G_TYPE_INSTANCE_GET_PRIVATE ((o), GRL_OPERATION_OPTIONS_TYPE, GrlOperationOptionsPrivate))

struct _GrlOperationOptionsPrivate {
  GHashTable *data;
  GHashTable *key_filter;
  GHashTable *key_range_filter;
  GrlCaps *caps;
};


#define SKIP_DEFAULT 0;
#define COUNT_DEFAULT GRL_COUNT_INFINITY;
#define FLAGS_DEFAULT GRL_RESOLVE_NORMAL;
#define TYPE_FILTER_DEFAULT GRL_TYPE_FILTER_ALL;

static void
grl_operation_options_dispose (GrlOperationOptions *self)
{
}

static void
grl_operation_options_finalize (GrlOperationOptions *self)
{
  g_hash_table_unref (self->priv->data);
  g_hash_table_unref (self->priv->key_filter);
  g_hash_table_unref (self->priv->key_range_filter);
  g_clear_object (&self->priv->caps);
  G_OBJECT_CLASS (grl_operation_options_parent_class)->finalize ((GObject *) self);
}

static void
grl_operation_options_init (GrlOperationOptions *self)
{
  self->priv = GRL_OPERATION_OPTIONS_GET_PRIVATE (self);

  self->priv->data = grl_g_value_hashtable_new ();
  self->priv->key_filter = grl_g_value_hashtable_new_direct ();
  self->priv->key_range_filter = grl_range_value_hashtable_new ();
  self->priv->caps = NULL;
}

static void
grl_operation_options_class_init (GrlOperationOptionsClass *self_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (self_class);

  g_type_class_add_private (self_class, sizeof (GrlOperationOptionsPrivate));
  object_class->dispose = (void (*) (GObject *object)) grl_operation_options_dispose;
  object_class->finalize = (void (*) (GObject *object)) grl_operation_options_finalize;
}

static void
set_value (GrlOperationOptions *options,
                const gchar *key,
                const GValue *value)
{
  g_hash_table_insert (options->priv->data,
                       g_strdup (key), grl_g_value_dup (value));
}

static void
copy_option (GrlOperationOptions *source,
             GrlOperationOptions *destination,
             const gchar *key)
{
  const GValue *value;

  value = g_hash_table_lookup (source->priv->data, key);

  if (value != NULL)
    set_value (destination, key, value);
}

static gboolean
check_and_copy_option (GrlOperationOptions *options,
                       GrlCaps *caps,
                       const gchar *key,
                       GrlOperationOptions **supported_options,
                       GrlOperationOptions **unsupported_options)
{
  if (grl_operation_options_key_is_set (options, key)) {
    GValue *value;
    gboolean filter_is_supported;

    value = g_hash_table_lookup (options->priv->data, key);
    filter_is_supported = grl_caps_test_option (caps, key, value);

    if (filter_is_supported && supported_options)
      set_value (*supported_options, key, value);
    else if (!filter_is_supported && unsupported_options)
      set_value (*unsupported_options, key, value);

    return filter_is_supported;
  }

  return TRUE;
}

static void
key_filter_dup (gpointer key_p, GValue *value, GHashTable *destination)
{
  g_hash_table_insert (destination, key_p, grl_g_value_dup (value));
}

static void
key_range_filter_dup (GrlKeyID key, GrlRangeValue *value, GHashTable *destination)
{
  grl_range_value_hashtable_insert (destination, GRLKEYID_TO_POINTER (key), value->min, value->max);
}

/* ========== API ========== */

/**
 * grl_operation_options_new:
 * @caps: (allow-none): caps that options will have to match. If %NULL, all
 * options will be accepted.
 *
 * Creates a new GrlOperationOptions object.
 *
 * Returns: a new GrlOperationOptions instance.
 *
 * Since: 0.2.0
 */
GrlOperationOptions *
grl_operation_options_new (GrlCaps *caps)
{
  GrlOperationOptions *options = g_object_new (GRL_OPERATION_OPTIONS_TYPE, NULL);
  if (caps != NULL)
    options->priv->caps = g_object_ref (caps);

  return options;
}

/**
 * grl_operation_options_obey_caps:
 * @options: a #GrlOperationOptions instance
 * @caps: capabilities against which we want to test @options
 * @supported_options: (out callee-allocates): if not %NULL, will contain a
 * newly-allocated #GrlOperationOptions instance containing all the values of
 * @options that match @caps.
 * @unsupported_options: (out callee-allocates): if not %NULL, will contain a
 * newly-allocated #GrlOperationOptions instance containing all the values of
 * @options that do not match @caps.
 *
 * Check whether @options obey to @caps.
 * Optionally provide the options that match (respectively don't match) @caps
 * in @supported_options (respectively @unsupported_options).
 * This would typically (but not necessarily) be used with a
 * #GrlOperationOptions instance that was created with %NULL caps.
 *
 * Returns: %TRUE if @options obey to @caps, %FALSE otherwise.
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_obey_caps (GrlOperationOptions *options,
                                 GrlCaps *caps,
                                 GrlOperationOptions **supported_options,
                                 GrlOperationOptions **unsupported_options)
{
  gboolean ret = TRUE;
  GHashTableIter table_iter;
  gpointer key_ptr;
  GValue *value;
  GrlRangeValue *range_value;

  if (supported_options) {
    *supported_options = grl_operation_options_new (caps);

    /* these options are always supported */
    copy_option (options, *supported_options, GRL_OPERATION_OPTION_SKIP);
    copy_option (options, *supported_options, GRL_OPERATION_OPTION_COUNT);
    copy_option (options, *supported_options, GRL_OPERATION_OPTION_FLAGS);
  }

  if (unsupported_options)
    *unsupported_options = grl_operation_options_new (NULL);

  ret &= check_and_copy_option (options,
                                caps,
                                GRL_OPERATION_OPTION_TYPE_FILTER,
                                supported_options,
                                unsupported_options);

  /* Check filter-by-equal-key */
  g_hash_table_iter_init (&table_iter, options->priv->key_filter);
  while (g_hash_table_iter_next (&table_iter, &key_ptr, (gpointer *)&value)) {
    GrlKeyID key_id = GRLPOINTER_TO_KEYID (key_ptr);
    if (grl_caps_is_key_filter (caps, key_id)) {
      if (supported_options) {
        g_hash_table_insert ((*supported_options)->priv->key_filter,
                             key_ptr,
                             grl_g_value_dup (value));
      }
    } else {
      ret = FALSE;
      if (unsupported_options) {
        g_hash_table_insert ((*unsupported_options)->priv->key_filter,
                             key_ptr,
                             grl_g_value_dup (value));
      }
    }
  }

  /* Check filter-by-range-key */
  g_hash_table_iter_init (&table_iter, options->priv->key_range_filter);
  while (g_hash_table_iter_next (&table_iter, &key_ptr, (gpointer *)&range_value)) {
    GrlKeyID key_id = GRLPOINTER_TO_KEYID (key_ptr);
    if (grl_caps_is_key_range_filter (caps, key_id)) {
      if (supported_options) {
        g_hash_table_insert ((*supported_options)->priv->key_range_filter,
                             key_ptr,
                             grl_range_value_dup (range_value));
      }
    } else {
      ret = FALSE;
      if (unsupported_options) {
        g_hash_table_insert ((*unsupported_options)->priv->key_range_filter,
                             key_ptr,
                             grl_range_value_dup (range_value));
      }
    }
  }

  return ret;
}

/**
 * grl_operation_options_copy:
 * @options: a #GrlOperationOptions instance
 *
 * Returns: (transfer full): a new #GrlOperationOptions instance with its values being copies of
 * the values of @options.
 *
 * Since: 0.2.0
 */
GrlOperationOptions *
grl_operation_options_copy (GrlOperationOptions *options)
{
  GrlOperationOptions *copy = grl_operation_options_new (options->priv->caps);

  copy_option (options, copy, GRL_OPERATION_OPTION_SKIP);
  copy_option (options, copy, GRL_OPERATION_OPTION_COUNT);
  copy_option (options, copy, GRL_OPERATION_OPTION_FLAGS);
  copy_option (options, copy, GRL_OPERATION_OPTION_TYPE_FILTER);

  g_hash_table_foreach (options->priv->key_filter,
                        (GHFunc) key_filter_dup,
                        copy->priv->key_filter);

  g_hash_table_foreach (options->priv->key_range_filter,
                        (GHFunc) key_range_filter_dup,
                        copy->priv->key_range_filter);

  return copy;
}

/**
 * grl_operation_options_key_is_set:
 * @options: a #GrlOperationOptions instance
 * @key: an operation option key
 *
 * This is an internal method that shouldn't be used outside of Grilo.
 *
 * Returns: whether @key is set in @options.
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_key_is_set (GrlOperationOptions *options,
                                  const gchar *key)
{
  return g_hash_table_lookup_extended (options->priv->data, key, NULL, NULL);
}

/**
 * grl_operation_options_set_skip:
 * @options: a #GrlOperationOptions instance
 * @skip: number of elements to skip in an operation
 *
 * Set the skip option for an operation. Will only succeed if @skip obeys to the
 * inherent capabilities of @options.
 *
 * Returns: %TRUE if @skip could be set, %FALSE otherwise.
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_set_skip (GrlOperationOptions *options,
                                guint skip)
{
  GValue skip_val = { 0, };

  g_value_init (&skip_val, G_TYPE_UINT);
  g_value_set_uint (&skip_val, skip);

  set_value (options, GRL_OPERATION_OPTION_SKIP, &skip_val);

  g_value_unset (&skip_val);

  return TRUE;
}

/**
 * grl_operation_options_get_skip:
 * @options: a #GrlOperationOptions instance
 *
 * Get the skip option, that is, the number of elements to skip before
 * retrieving media items in an operation done with @options.
 *
 * Returns: the value of the skip option, or a default value if it is not set.
 *
 * Since: 0.2.0
 */
guint
grl_operation_options_get_skip (GrlOperationOptions *options)
{
  const GValue *value  =
      g_hash_table_lookup (options->priv->data, GRL_OPERATION_OPTION_SKIP);

  if (value)
    return g_value_get_uint (value);

  return SKIP_DEFAULT;
}

/**
 * grl_operation_options_set_count:
 * @options: a #GrlOperationOptions instance
 * @count: number of elements to retrieve in an operation
 *
 * Set the count option for an operation. Will only succeed if @count obey to
 * the inherent capabilities of @options.
 *
 * Returns: %TRUE if @count could be set, %FALSE otherwise.
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_set_count (GrlOperationOptions *options, gint count)
{
  GValue count_val = { 0, };

  g_value_init (&count_val, G_TYPE_INT);
  g_value_set_int (&count_val, count);

  set_value (options, GRL_OPERATION_OPTION_COUNT, &count_val);

  g_value_unset (&count_val);

  return TRUE;
}

/**
 * grl_operation_options_get_count:
 * @options: a #GrlOperationOptions instance
 *
 * Get the count option, that is, the number of elements to retrieve in an
 * operation done with @options.
 *
 * Returns: the value of the count option, or a default value if it is not set.
 *
 * Since: 0.2.0
 */
gint
grl_operation_options_get_count (GrlOperationOptions *options)
{
  const GValue *value  =
      g_hash_table_lookup (options->priv->data, GRL_OPERATION_OPTION_COUNT);

  if (value)
    return g_value_get_int (value);

  return COUNT_DEFAULT;
}

/**
 * grl_operation_options_set_flags:
 * @options: a #GrlOperationOptions instance
 * @flags: the resolution flags to be set for an operation. See
 * #GrlResolutionFlags for possible values.
 *
 * Set the resolution flags for an operation. Will only succeed if @flags obey
 * to the inherent capabilities of @options.
 *
 * Returns: %TRUE if @flags could be set, %FALSE otherwise.
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_set_flags (GrlOperationOptions *options,
                                 GrlResolutionFlags flags)
{
  GValue value = { 0, };

  /* FIXME: I think we should use mk_enum to have a GType for
   * GrlResolutionFlags */
  g_value_init (&value, G_TYPE_UINT);
  g_value_set_uint (&value, flags);
  set_value (options, GRL_OPERATION_OPTION_FLAGS, &value);
  g_value_unset (&value);

  return TRUE;
}

/**
 * grl_operation_options_get_flags:
 * @options: a #GrlOperationOptions instance
 *
 * Returns: resolution flags of @options.
 *
 * Since: 0.2.0
 */
GrlResolutionFlags
grl_operation_options_get_flags (GrlOperationOptions *options)
{
  const GValue *value  = g_hash_table_lookup (options->priv->data,
                                              GRL_OPERATION_OPTION_FLAGS);
  if (value)
    return g_value_get_uint (value);

  return FLAGS_DEFAULT;
}

/**
 * grl_operation_options_set_type_filter:
 * @options: a #GrlOperationOptions instance
 * @filter: the type of media to get
 *
 * Set the type of media filter for an operation. Only those media elements that
 * match the @filter will be returned. Will only succeed if @filter obey to the
 * inherent capabilities of @options.
 *
 * Returns: %TRUE if @flags could be set, %FALSE otherwise
 *
 * Since: 0.2.0
 **/
gboolean
grl_operation_options_set_type_filter (GrlOperationOptions *options,
                                       GrlTypeFilter filter)
{
  GValue value = { 0 };
  gboolean ret;

  g_value_init (&value, GRL_TYPE_TYPE_FILTER);
  g_value_set_flags (&value, filter);

  ret = (options->priv->caps == NULL) ||
      grl_caps_test_option (options->priv->caps,
                            GRL_OPERATION_OPTION_TYPE_FILTER, &value);

  if (ret)
    set_value (options, GRL_OPERATION_OPTION_TYPE_FILTER, &value);

  g_value_unset (&value);

  return ret;
}

/**
 * grl_operation_options_get_type_filter:
 * @options: a #GrlOperationOptions instance
 *
 *
 * Returns: resolution flags of @options
 *
 * Since: 0.2.0
 **/
GrlTypeFilter
grl_operation_options_get_type_filter (GrlOperationOptions *options)
{
  const GValue *value = g_hash_table_lookup (options->priv->data,
                                             GRL_OPERATION_OPTION_TYPE_FILTER);

  if (value) {
    return g_value_get_flags (value);
  }

  return TYPE_FILTER_DEFAULT;
}

/**
 * grl_operation_options_set_key_filter_value:
 * @options: a #GrlOperationOptions instance
 * @key: a #GrlKeyID
 * @value: a #GValue
 *
 * Set filter as "@key == @value".
 *
 * Returns: %TRUE on success
 *
 * Since: 0.2.0
 **/
gboolean
grl_operation_options_set_key_filter_value (GrlOperationOptions *options,
                                            GrlKeyID key,
                                            GValue *value)
{
  gboolean ret;
  GrlRegistry *registry;
  GType key_type;

  registry = grl_registry_get_default ();
  key_type = grl_registry_lookup_metadata_key_type (registry, key);

  if (G_VALUE_TYPE (value) != key_type)
    return FALSE;

  ret = (options->priv->caps == NULL) ||
    grl_caps_is_key_filter (options->priv->caps, key);

  if (ret) {
    if (value) {
      g_hash_table_insert (options->priv->key_filter,
                           GRLKEYID_TO_POINTER (key),
                           grl_g_value_dup (value));
    } else {
      g_hash_table_remove (options->priv->key_filter,
                           GRLKEYID_TO_POINTER (key));
    }
  }

  return ret;
}

/**
 * grl_operation_options_set_key_filters:
 * @options: a #GrlOperationOptions instance
 * @...: pairs of #GrlKeyID, value
 *
 * Set filter as "k1 == v1 AND k2 == v2 AND ..."
 *
 * <example>
 *  Elements from album "Frozen" with a bitrate of 256kbs.
 *  <programlisting>
 *   grl_operation_options_set_key_filters (my_options,
 *                                          GRL_METADATA_KEY_ALBUM, "Frozen",
 *                                          GRL_METADATA_KEY_BITRATE, 256,
 *                                          NULL);
 *  </programlisting>
 * </example>
 *
 * Returns: %TRUE on success
 *
 * Since: 0.2.0
 **/
gboolean
grl_operation_options_set_key_filters (GrlOperationOptions *options,
                                       ...)
{
  GType key_type;
  GValue value = { 0 };
  GrlKeyID next_key;
  gboolean skip;
  gboolean success = TRUE;
  va_list args;

  va_start (args, options);
  next_key = va_arg (args, GrlKeyID);
  while (next_key) {
    key_type = GRL_METADATA_KEY_GET_TYPE (next_key);
    g_value_init (&value, key_type);
    skip = FALSE;
    if (key_type == G_TYPE_STRING) {
      g_value_set_string (&value, va_arg (args, gchar *));
    } else if (key_type == G_TYPE_INT) {
      g_value_set_int (&value, va_arg (args, gint));
    } else if (key_type == G_TYPE_BOOLEAN) {
      g_value_set_boolean (&value, va_arg (args, gboolean));
    } else if (key_type == G_TYPE_DATE_TIME) {
      g_value_set_boxed (&value, va_arg (args, gconstpointer));
    } else {
      GRL_WARNING ("Unexpected key type when setting up the filter");
      success = FALSE;
      skip = TRUE;
    }

    if (!skip) {
      success &= grl_operation_options_set_key_filter_value (options,
                                                             next_key,
                                                             &value);
    }

    g_value_unset (&value);
    next_key = va_arg (args, GrlKeyID);
  }

  va_end (args);

  return success;
}

/**
 * grl_operation_options_set_key_filter_dictionary: (rename-to grl_operation_options_set_key_filters)
 * @options: a #GrlOperationOptions instance
 * @filters: (transfer none) (element-type GrlKeyID GValue):
 *
 * Returns: %TRUE on success
 *
 * Since: 0.2.0
 */
gboolean
grl_operation_options_set_key_filter_dictionary (GrlOperationOptions *options,
                                                 GHashTable *filters)
{
  GHashTableIter iter;
  gpointer _key, _value;
  gboolean ret = TRUE;

  g_hash_table_iter_init (&iter, filters);
  while (g_hash_table_iter_next (&iter, &_key, &_value)) {
    GrlKeyID key = GRLPOINTER_TO_KEYID (_key);
    GValue *value = (GValue *)_value;
    ret &=
        grl_operation_options_set_key_filter_value (options, key, value);
  }

  return ret;
}

/**
 * grl_operation_options_get_key_filter:
 * @options: a #GrlOperationOptions instance
 * @key:
 *
 * Returns: (transfer none): the filter
 *
 * Since: 0.2.0
 */
GValue *
grl_operation_options_get_key_filter (GrlOperationOptions *options,
                                      GrlKeyID key)
{
  return g_hash_table_lookup (options->priv->key_filter,
                              GRLKEYID_TO_POINTER (key));
}

/**
 * grl_operation_options_get_key_filter_list:
 * @options: a #GrlOperationOptions instance
 *
 * Returns: (transfer container) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
GList *
grl_operation_options_get_key_filter_list (GrlOperationOptions *options)
{
  return g_hash_table_get_keys (options->priv->key_filter);
}

/**
 * grl_operation_options_set_key_range_filter_value:
 * @options: a #GrlOperationOptions instance
 * @key: a #GrlKeyID
 * @min_value: (in) (allow-none): minimum value for range
 * @max_value: (in) (allow-none): maximum value for range
 *
 * Set filter as "@min_value <= @key <= @max_value".
 *
 * If @min_value is %NULL, then filter is "@key <= @max_value".
 *
 * If @max_value is %NULL, then filter is "@key >= @min_value".
 *
 * Returns: %TRUE on success
 *
 * Since: 0.2.0
 **/
gboolean
grl_operation_options_set_key_range_filter_value (GrlOperationOptions *options,
                                                  GrlKeyID key,
                                                  GValue *min_value,
                                                  GValue *max_value)
{
  gboolean ret;

  ret = (options->priv->caps == NULL) ||
    grl_caps_is_key_range_filter (options->priv->caps, key);

  if (ret) {
    if (min_value || max_value) {
      grl_range_value_hashtable_insert (options->priv->key_range_filter,
                                        GRLKEYID_TO_POINTER (key),
                                        min_value, max_value);
    } else {
      g_hash_table_remove (options->priv->key_range_filter,
                           GRLKEYID_TO_POINTER (key));
    }
  }

  return ret;
}

/**
 * grl_operation_options_set_key_range_filter:
 * @options: a #GrlOperationOptions instance
 * @...: triplets of #GrlKeyID, minvalue, maxvalue
 *
 * Set filter as "min1 <= k1 <= max1 AND min2 <= k2 <= max2 AND ..."
 *
 * The range can be open if some of the minX, maxX values are %NULL.

 * <example>
 *  Album must start with "T" and the bitrate should be 256kbs or greater.
 *  <programlisting>
 *   grl_operation_options_set_key_range_filters (my_options,
 *                                                GRL_METADATA_KEY_ALBUM, "Ta", "Tz",
 *                                                GRL_METADATA_KEY_BITRATE, 256, NULL,
 *                                                NULL);
 *  </programlisting>
 * </example>
 *
 * Returns: %TRUE on success
 *
 * Since: 0.2.0
 **/
gboolean
grl_operation_options_set_key_range_filter (GrlOperationOptions *options,
                                            ...)
{
  GType key_type;
  GValue min_value = { 0 };
  GValue *min_p_value;
  gint min_int_value;
  gchar *min_str_value;
  gfloat min_float_value;
  gconstpointer min_date_value;
  GValue max_value = { 0 };
  GValue *max_p_value;
  gint max_int_value;
  gchar *max_str_value;
  gfloat max_float_value;
  gconstpointer max_date_value;
  GrlKeyID next_key;
  gboolean skip;
  gboolean success = TRUE;
  va_list args;

  va_start (args, options);
  next_key = va_arg (args, GrlKeyID);
  while (next_key) {
    key_type = GRL_METADATA_KEY_GET_TYPE (next_key);
    g_value_init (&min_value, key_type);
    g_value_init (&max_value, key_type);
    min_p_value = NULL;
    max_p_value = NULL;
    skip = FALSE;
    if (key_type == G_TYPE_STRING) {
      min_str_value = va_arg (args, gchar *);
      max_str_value = va_arg (args, gchar *);
      if (min_str_value) {
        g_value_set_string (&min_value, min_str_value);
        min_p_value = &min_value;
      }
      if (max_str_value) {
        g_value_set_string (&max_value, max_str_value);
        max_p_value = &max_value;
      }
    } else if (key_type == G_TYPE_INT) {
      min_int_value = va_arg (args, gint);
      max_int_value = va_arg (args, gint);
      if (min_int_value > G_MININT) {
        g_value_set_int (&min_value, min_int_value);
        min_p_value = &min_value;
      }
      if (max_int_value < G_MAXINT) {
        g_value_set_int (&max_value, max_int_value);
        max_p_value = &max_value;
      }
    } else if (key_type == G_TYPE_FLOAT) {
      min_float_value = va_arg (args, gdouble);
      max_float_value = va_arg (args, gdouble);
      if (min_float_value > G_MINFLOAT) {
        g_value_set_float (&min_value, min_float_value);
        min_p_value = &min_value;
      }
      if (max_float_value < G_MAXFLOAT) {
        g_value_set_float (&max_value, max_float_value);
        max_p_value = &max_value;
      }
    } else if (key_type == G_TYPE_DATE_TIME) {
      min_date_value = va_arg (args, gconstpointer);
      max_date_value = va_arg (args, gconstpointer);
      if (min_date_value) {
        g_value_set_boxed (&min_value, min_date_value);
        min_p_value = &min_value;
      }
      if (max_date_value) {
        g_value_set_boxed (&max_value, max_date_value);
        max_p_value = &max_value;
      }
    } else {
      GRL_WARNING ("Unexpected key type when setting up the filter");
      success = FALSE;
      skip = TRUE;
    }

    if (!skip) {
      success &= grl_operation_options_set_key_range_filter_value (options,
                                                                   next_key,
                                                                   min_p_value,
                                                                   max_p_value);
    }

    g_value_unset (&min_value);
    g_value_unset (&max_value);
    next_key = va_arg (args, GrlKeyID);
  }

  va_end (args);

  return success;
}

/**
 * grl_operation_options_get_key_range_filter:
 * @options: a #GrlOperationOptions instance
 * @key: a #GrlKeyID
 * @min_value: (out) (allow-none): the minimum value for the range
 * @max_value: (out) (allow-none): the maximum value for the range
 *
 * Stores the limits of the range in the filter for @key in @min_value and
 * @max_value. If some of the values has no limit, it will set a %NULL.
 *
 * Since: 0.2.0
 **/
void
grl_operation_options_get_key_range_filter (GrlOperationOptions *options,
                                            GrlKeyID key,
                                            GValue **min_value,
                                            GValue **max_value)
{
  GrlRangeValue *range =
    (GrlRangeValue *) g_hash_table_lookup (options->priv->key_range_filter,
                                           GRLKEYID_TO_POINTER (key));

  if (min_value) {
    if (range && range->min) {
      *min_value = range->min;
    } else {
      *min_value = NULL;
    }
  }

  if (max_value) {
    if (range && range->max) {
      *max_value = range->max;
    } else {
      *max_value = NULL;
    }
  }
}

/**
 * grl_operation_options_get_key_range_filter_list:
 * @options: a #GrlOperationOptions instance
 *
 * Returns: (transfer container) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
GList *
grl_operation_options_get_key_range_filter_list (GrlOperationOptions *options)
{
  return g_hash_table_get_keys (options->priv->key_range_filter);
}
