/*
 * 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-caps
 * @short_description: Describes the capabilities of a source for a given
 * operation.
 * @see_also: #GrlOperationOptions, grl_source_get_caps()
 *
 * A #GrlCaps instance is here to help you know if a given set of operation
 * options is supported for a given operation.
 *
 * Here is an example of how this would be used.
 * |[
 * GrlCaps *caps = grl_source_get_caps (GRL_SOURCE (my_source),
 *                                      GRL_OP_SEARCH);
 * GrlOperationOptions *supported_options;
 * if (grl_operation_options_obey_caps (my_options, caps, &supported_options, NULL))
 *   grl_media_source_search (my_source, "blah", interesting_keys, my_options, ...);
 * else // only use a subset of the options we wanted to pass
 *   grl_media_source_search (my_source, "blah", interesting_keys, supported_options, ...);
 * ]|
 *
 * A #GrlCaps can also be passed to grl_operation_options_new(). The created
 * #GrlOperationOptions instance would then check any change against its caps.
 *
 */
#include <grl-caps.h>
#include <grl-value-helper.h>

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

#define GRL_CAPS_KEY_PAGINATION "pagination"
#define GRL_CAPS_KEY_FLAGS "flags"

struct _GrlCapsPrivate {
  GHashTable *data;
  GrlTypeFilter type_filter;
  GList *key_filter;
  GList *key_range_filter;
};

G_DEFINE_TYPE_WITH_PRIVATE (GrlCaps, grl_caps, G_TYPE_OBJECT);

static void
grl_caps_dispose (GrlCaps *self)
{
  G_OBJECT_CLASS (grl_caps_parent_class)->dispose ((GObject *) self);
}

static void
grl_caps_finalize (GrlCaps *self)
{
  g_hash_table_unref (self->priv->data);
  g_list_free (self->priv->key_filter);
  g_list_free (self->priv->key_range_filter);

  G_OBJECT_CLASS (grl_caps_parent_class)->finalize ((GObject *) self);
}

static void
grl_caps_init (GrlCaps *self)
{
  self->priv = grl_caps_get_instance_private (self);

  self->priv->data = grl_g_value_hashtable_new ();

  /* by default, type filtering is not considered to be supported. The source
   * has to explicitly modify its caps. */
  self->priv->type_filter = GRL_TYPE_FILTER_NONE;
  self->priv->key_filter = NULL;
  self->priv->key_range_filter = NULL;
}

static void
grl_caps_class_init (GrlCapsClass *self_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (self_class);

  object_class->dispose = (void (*) (GObject *object)) grl_caps_dispose;
  object_class->finalize = (void (*) (GObject *object)) grl_caps_finalize;
}

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

/**
 * grl_caps_new:
 *
 * Creates a new caps object.
 *
 * Returns: a new caps object.
 *
 * Since: 0.2.0
 **/
GrlCaps *
grl_caps_new (void)
{
  return g_object_new (GRL_CAPS_TYPE, NULL);
}

/**
 * grl_caps_test_option:
 * @caps: a #GrlCaps instance
 * @key: a key to test
 * @value: the value corresponding to @key to test against @caps
 *
 * Checks whether (@key, @value) are authorized by @caps.
 *
 * Returns: %TRUE if (@key, @value) obey to @caps, %FALSE otherwise.
 *
 * Since: 0.2.0
 */
gboolean
grl_caps_test_option (GrlCaps *caps, const gchar *key, const GValue *value)
{
  if (0 == g_strcmp0 (key, GRL_OPERATION_OPTION_SKIP)
      || 0 == g_strcmp0 (key, GRL_OPERATION_OPTION_COUNT)
      || 0 == g_strcmp0 (key, GRL_OPERATION_OPTION_RESOLUTION_FLAGS))
    /* these options must always be handled by plugins */
    return TRUE;

  if (0 == g_strcmp0 (key, GRL_OPERATION_OPTION_TYPE_FILTER)) {
    GrlTypeFilter filter, supported_filter;

    supported_filter = grl_caps_get_type_filter (caps);
    filter = g_value_get_flags (value);

    return filter == (filter & supported_filter);
  }

  if (0 == g_strcmp0 (key, GRL_OPERATION_OPTION_KEY_EQUAL_FILTER)) {
    GrlKeyID metadata_key = g_value_get_grl_key_id (value);
    return grl_caps_is_key_filter (caps, metadata_key);
  }

  if (0 == g_strcmp0 (key, GRL_OPERATION_OPTION_KEY_RANGE_FILTER)) {
    GrlKeyID grl_key = g_value_get_grl_key_id (value);
    return grl_caps_is_key_range_filter (caps, grl_key);
  }

  return FALSE;
}

/**
 * grl_caps_get_type_filter:
 * @caps: a #GrlCaps instance
 *
 * Returns: the supported #GrlTypeFilter
 *
 * Since: 0.2.0
 **/
GrlTypeFilter
grl_caps_get_type_filter (GrlCaps *caps)
{
  g_return_val_if_fail (caps != NULL, GRL_TYPE_FILTER_NONE);

  return caps->priv->type_filter;
}

/**
 * grl_caps_set_type_filter:
 * @caps: a #GrlCaps instance
 * @filter: a #GrlTypeFilter
 *
 * Sets the supported filter capability.
 *
 * Since: 0.2.0
 **/
void
grl_caps_set_type_filter (GrlCaps *caps, GrlTypeFilter filter)
{
  g_return_if_fail (caps != NULL);

  caps->priv->type_filter = filter;
}

/**
 * grl_caps_get_key_filter:
 * @caps: a #GrlCaps instance
 *
 * Returns: (transfer none) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
GList *
grl_caps_get_key_filter (GrlCaps *caps)
{
  g_return_val_if_fail (caps, NULL);

  return caps->priv->key_filter;
}

/**
 * grl_caps_set_key_filter:
 * @caps: a #GrlCaps instance
 * @keys: (transfer none) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
void
grl_caps_set_key_filter (GrlCaps *caps, GList *keys)
{
  g_return_if_fail (caps);

  g_clear_pointer (&caps->priv->key_filter, g_list_free);

  caps->priv->key_filter = g_list_copy (keys);
}

/**
 * grl_caps_is_key_filter:
 * @caps: a #GrlCaps instance
 * @key: a #GrlKeyID
 *
 * Checks if @key is supported for filtering in @caps.
 *
 * Returns: %TRUE if @key can be used for filtering
 *
 * Since: 0.2.0
 **/
gboolean
grl_caps_is_key_filter (GrlCaps *caps, GrlKeyID key)
{
  g_return_val_if_fail (caps, FALSE);

  if(caps->priv->key_filter) {
    return g_list_find (caps->priv->key_filter,
                        GRLKEYID_TO_POINTER(key)) != NULL;
  }

  return FALSE;
}

/**
 * grl_caps_get_key_range_filter:
 * @caps: a #GrlCaps instance
 *
 * Returns: (transfer none) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
GList *
grl_caps_get_key_range_filter (GrlCaps *caps)
{
  g_return_val_if_fail (caps, NULL);

  return caps->priv->key_range_filter;
}

/**
 * grl_caps_set_key_range_filter:
 * @caps: a #GrlCaps instance
 * @keys: (transfer none) (element-type GrlKeyID):
 *
 * Since: 0.2.0
 */
void
grl_caps_set_key_range_filter (GrlCaps *caps, GList *keys)
{
  g_return_if_fail (caps);

  g_clear_pointer (&caps->priv->key_range_filter, g_list_free);

  caps->priv->key_range_filter = g_list_copy (keys);
}

/**
 * grl_caps_is_key_range_filter:
 * @caps: a #grlCaps instance
 * @key: a #GrlKeyID
 *
 * Checks if @key is supported for filtering by range in @caps.
 *
 * Returns: %TRUE if @key can be used for filtering
 *
 * Since: 0.2.0
 **/
gboolean
grl_caps_is_key_range_filter (GrlCaps *caps, GrlKeyID key)
{
  g_return_val_if_fail (caps, FALSE);

  if(caps->priv->key_range_filter) {
    return g_list_find (caps->priv->key_range_filter, GRLKEYID_TO_POINTER (key)) != NULL;
  }

  return FALSE;
}
