/*
 * Farstream - Farstream Stream
 *
 * Copyright 2007 Collabora Ltd.
 *  @author: Philippe Kalaf <philippe.kalaf@collabora.co.uk>
 * Copyright 2007 Nokia Corp.
 *
 * fs-stream.c - A Farstream Stream gobject (base implementation)
 *
 * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

/**
 * SECTION:fs-stream
 * @short_description: A stream in a session in a conference
 *
 * This object is the base implementation of a Farstream Stream. It
 * needs to be derived and implemented by a Farstream conference GStreamer
 * element. A Farstream Stream is a media stream originating from a
 * #FsParticipant inside a #FsSession. In fact, a #FsStream instance is
 * obtained by adding a participant into a session using
 * fs_session_new_stream().
 *
 *
 * This will communicate asynchronous events to the user through #GstMessage
 * of type #GST_MESSAGE_ELEMENT sent over the #GstBus.
 *
 * <refsect2><title>The "<literal>farstream-new-local-candidate</literal>" message</title>
 * |[
 * "stream"           #FsStream          The stream that emits the message
 * "candidate"        #FsCandidate       The new candidate
 * ]|
 * <para>
 * This message is emitted when a new local candidate is discovered.
 * </para>
 * </refsect2>
 * <refsect2><title>The "<literal>farstream-local-candidates-prepared</literal>" message</title>
 * |[
 * "stream"           #FsStream          The stream that emits the message
 * ]|
 * <para>
 * This signal is emitted when all local candidates have been
 * prepared, an ICE implementation would send its SDP offer or answer.
 * </para>
 * </refsect2>
 * <refsect2><title>The "<literal>farstream-new-active-candidate-pair</literal>" message</title>
 * |[
 * "stream"           #FsStream          The stream that emits the message
 * "local-candidate"  #FsCandidate       Local candidate being used
 * "remote-candidate" #FsCandidate       Remote candidate being used
 * ]|
 * <para>
 * This message is emitted when there is a new active candidate pair that has
 * been established. This is specially useful for ICE where the active
 * candidate pair can change automatically due to network conditions. The user
 * must not modify the candidates and must copy them if he wants to use them
 * outside the callback scope. This message is emitted once per component.
 * </para>
 * </refsect2>
 * <refsect2><title>The "<literal>farstream-recv-codecs-changed</literal>" message</title>
 * |[
 * "stream"           #FsStream          The stream that emits the message
 * "codecs"           #FsCodecGList      A #GList of #FsCodec
 * ]|
 * <para>
 * This message is emitted when the content of the
 * #FsStream:current-recv-codecs property changes. It is normally emitted
 * right after the #FsStream::src-pad-added signal only if that codec was not
 * previously received in this stream, but it can also be emitted if the pad
 * already exists, but the source material that will come to it is different.
 * The list of new recv-codecs is included in the message
 * </para>
 * </refsect2>
 * <refsect2><title>The "<literal>farstream-component-state-changed</literal>" message</title>
 * |[
 * "stream"           #FsStream          The stream that emits the message
 * "component"        #guint             The component whose state changed
 * "state"            #FsStreamState     The new state of the component
 * ]|
 * <para>
 * This message is emitted the state of a component of a stream changes.
 * </para>
 * </refsect2>
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "fs-stream.h"

#include <gst/gst.h>

#include "fs-session.h"
#include "fs-codec.h"
#include "fs-candidate.h"
#include "fs-stream-transmitter.h"
#include "fs-conference.h"
#include "fs-enumtypes.h"
#include "fs-private.h"

/* Signals */
enum
{
  ERROR_SIGNAL,
  SRC_PAD_ADDED,
  LAST_SIGNAL
};

/* props */
enum
{
  PROP_0,
  PROP_REMOTE_CODECS,
  PROP_NEGOTIATED_CODECS,
  PROP_CURRENT_RECV_CODECS,
  PROP_DIRECTION,
  PROP_PARTICIPANT,
  PROP_SESSION,
  PROP_DECRYPTION_PARAMETERS,
  PROP_REQUIRE_ENCRYPTION
};


struct _FsStreamPrivate
{
  GMutex mutex;
  GList *src_pads;
  guint32 src_pads_cookie;
};

#define FS_STREAM_GET_PRIVATE(o)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_STREAM, FsStreamPrivate))


G_DEFINE_ABSTRACT_TYPE(FsStream, fs_stream, G_TYPE_OBJECT)

static void fs_stream_constructed (GObject *obj);
static void fs_stream_get_property (GObject *object,
                                    guint prop_id,
                                    GValue *value,
                                    GParamSpec *pspec);
static void fs_stream_set_property (GObject *object,
                                    guint prop_id,
                                    const GValue *value,
                                    GParamSpec *pspec);
static void fs_stream_finalize (GObject *obj);

static void fs_stream_pad_removed (FsStream *stream, GstPad *pad);

static guint signals[LAST_SIGNAL] = { 0 };

#define FS_STREAM_LOCK(self)   g_mutex_lock(&(self)->priv->mutex)
#define FS_STREAM_UNLOCK(self) g_mutex_unlock(&(self)->priv->mutex)

static void
fs_stream_class_init (FsStreamClass *klass)
{
  GObjectClass *gobject_class;

  gobject_class = (GObjectClass *) klass;

  gobject_class->set_property = fs_stream_set_property;
  gobject_class->get_property = fs_stream_get_property;
  gobject_class->finalize = fs_stream_finalize;
  gobject_class->constructed = fs_stream_constructed;


  /**
   * FsStream:remote-codecs: (type GLib.List(FsCodec)) (transfer full)
   *
   * This is the list of remote codecs for this stream. They must be set by the
   * user as soon as they are known using fs_stream_set_remote_codecs()
   * (generally through external signaling). It is a #GList of #FsCodec.
   */
  g_object_class_install_property (gobject_class,
      PROP_REMOTE_CODECS,
      g_param_spec_boxed ("remote-codecs",
        "List of remote codecs",
        "A GList of FsCodecs of the remote codecs",
        FS_TYPE_CODEC_LIST,
        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:negotiated-codecs: (type GLib.List(FsCodec)) (transfer full)
   *
   * This is the list of negotiatied codecs, it is the same list as the list
   * of #FsCodec from the parent #FsSession, except that the codec config data
   * has been replaced with the data from the remote codecs for this stream.
   * This is the list of #FsCodec used to receive data from this stream.
   * It is a #GList of #FsCodec.
   */
  g_object_class_install_property (gobject_class,
      PROP_NEGOTIATED_CODECS,
      g_param_spec_boxed ("negotiated-codecs",
        "List of remote codecs",
        "A GList of FsCodecs of the negotiated codecs for this stream",
        FS_TYPE_CODEC_LIST,
        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:current-recv-codecs: (type GLib.List(FsCodec)) (transfer full)
   *
   * This is the list of codecs that have been received by this stream.
   * The user must free the list if fs_codec_list_destroy().
   * The "farstream-recv-codecs-changed" message is send on the #GstBus
   * when the value of this property changes.
   * It is normally emitted right after #FsStream::src-pad-added
   * only if that codec was not previously received in this stream, but it can
   * also be emitted if the pad already exists, but the source material that
   * will come to it is different.
   */
  g_object_class_install_property (gobject_class,
      PROP_CURRENT_RECV_CODECS,
      g_param_spec_boxed ("current-recv-codecs",
          "The codecs currently being received",
          "A GList of FsCodec representing the codecs that have been received",
          FS_TYPE_CODEC_LIST,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:direction:
   *
   * The direction of the stream. This property is set initially as a parameter
   * to the fs_session_new_stream() function. It can be changed later if
   * required by setting this property.
   *
   */
  g_object_class_install_property (gobject_class,
      PROP_DIRECTION,
      g_param_spec_flags ("direction",
        "The direction of the stream",
        "An enum to set and get the direction of the stream",
        FS_TYPE_STREAM_DIRECTION,
        FS_DIRECTION_NONE,
        G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:participant:
   *
   * The #FsParticipant for this stream. This property is a construct param and
   * is read-only construction.
   *
   */
  g_object_class_install_property (gobject_class,
      PROP_PARTICIPANT,
      g_param_spec_object ("participant",
        "The participant of the stream",
        "An FsParticipant represented by the stream",
        FS_TYPE_PARTICIPANT,
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:session:
   *
   * The #FsSession for this stream. This property is a construct param and
   * is read-only construction.
   *
   */
  g_object_class_install_property (gobject_class,
      PROP_SESSION,
      g_param_spec_object ("session",
        "The session of the stream",
        "An FsSession represented by the stream",
        FS_TYPE_SESSION,
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:decryption-parameters:
   *
   * Retrieves previously set decryption parameters
   */
  g_object_class_install_property (gobject_class,
      PROP_DECRYPTION_PARAMETERS,
      g_param_spec_boxed ("decryption-parameters",
          "Decryption parameters",
          "Parameters used to decrypt the stream",
          GST_TYPE_STRUCTURE,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream:require-encryption:
   *
   * If set to TRUE, only encrypted content will be accepted on this
   * stream.
   */
  g_object_class_install_property (gobject_class,
      PROP_REQUIRE_ENCRYPTION,
      g_param_spec_boolean ("require-encryption",
          "Require Encryption",
          "If TRUE, only encrypted content will be accepted",
          FALSE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * FsStream::error:
   * @self: #FsStream that emitted the signal
   * @errorno: The number of the error
   * @error_msg: Error message to be displayed to user
   *
   * This signal is emitted in any error condition
   *
   */
  signals[ERROR_SIGNAL] = g_signal_new ("error",
      G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST,
      0, NULL, NULL, NULL,
      G_TYPE_NONE, 2, FS_TYPE_ERROR, G_TYPE_STRING);

  /**
   * FsStream::src-pad-added:
   * @self: #FsStream that emitted the signal
   * @pad: #GstPad of the new source pad
   * @codec: #FsCodec of the codec being received on the new source pad
   *
   * This signal is emitted when a new gst source pad has been created for a
   * specific codec being received. There will be a different source pad for
   * each codec that is received. The user must ref the #GstPad if he wants to
   * keep it. The user should not modify the #FsCodec and must copy it if he
   * wants to use it outside the callback scope.
   *
   * This signal is not emitted on the main thread, but on GStreamer's streaming
   * thread!
   *
   */
  signals[SRC_PAD_ADDED] = g_signal_new ("src-pad-added",
      G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST,
      0, NULL, NULL, NULL,
      G_TYPE_NONE, 2, GST_TYPE_PAD, FS_TYPE_CODEC);

  g_type_class_add_private (klass, sizeof (FsStreamPrivate));
}

static void
fs_stream_init (FsStream *self)
{
  /* member init */
  self->priv = FS_STREAM_GET_PRIVATE (self);
  g_mutex_init (&self->priv->mutex);
}

static void
fs_stream_constructed (GObject *obj)
{
  FsStream *stream = FS_STREAM (obj);
  FsSession *session;
  FsConference *conference;

  g_object_get (stream, "session", &session, NULL);
  g_object_get (session, "conference", &conference, NULL);

  g_signal_connect_object (conference, "pad-removed",
      G_CALLBACK (fs_stream_pad_removed), obj, G_CONNECT_SWAPPED);
  g_object_unref (session);
  g_object_unref (conference);
}

static void
fs_stream_finalize (GObject *obj)
{
  FsStream *stream = FS_STREAM (obj);

  g_list_free_full (stream->priv->src_pads, gst_object_unref);
  g_mutex_clear (&stream->priv->mutex);

  G_OBJECT_CLASS (fs_stream_parent_class)->finalize (obj);
}

static void
fs_stream_get_property (GObject *object,
                        guint prop_id,
                        GValue *value,
                        GParamSpec *pspec)
{
  switch (prop_id) {
    case PROP_DECRYPTION_PARAMETERS:
      g_value_set_boxed (value, NULL);
      /* Not having parameters is valid, in this case set nothing */
      break;
    case PROP_REQUIRE_ENCRYPTION:
      g_value_set_boxed (value, FALSE);
      break;
    default:
      GST_WARNING ("Subclass %s of FsStream does not override the %s property"
          " getter",
          G_OBJECT_TYPE_NAME(object),
          g_param_spec_get_name (pspec));
      break;
  }
}

static void
fs_stream_set_property (GObject *object,
                        guint prop_id,
                        const GValue *value,
                        GParamSpec *pspec)
{
  GST_WARNING ("Subclass %s of FsStream does not override the %s property"
      " setter",
      G_OBJECT_TYPE_NAME(object),
      g_param_spec_get_name (pspec));
}

/**
 * fs_stream_add_remote_candidates:
 * @stream: an #FsStream
 * @candidates: (element-type FsCandidate): an #GList of #FsCandidate
 *  representing the remote candidates
 * @error: location of a #GError, or %NULL if no error occured
 *
 * This function adds remote candidates. Any new candidates are
 * added to the list. The candidates will be used to establish a connection
 * with the peer. A copy will be made so the user must free the
 * passed candidate using fs_candidate_destroy() when done.
 *
 * Return value: TRUE if the candidate was valid, FALSE otherwise
 */
gboolean
fs_stream_add_remote_candidates (FsStream *stream,
    GList *candidates,
    GError **error)
{
  FsStreamClass *klass;

  g_return_val_if_fail (stream, FALSE);
  g_return_val_if_fail (FS_IS_STREAM (stream), FALSE);
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->add_remote_candidates) {
    return klass->add_remote_candidates (stream, candidates, error);
  } else {
    g_set_error (error, FS_ERROR, FS_ERROR_NOT_IMPLEMENTED,
      "add_remote_candidate not defined in class");
  }

  return FALSE;
}

/**
 * fs_stream_force_remote_candidates:
 * @stream: a #FsStream
 * @remote_candidates: (element-type FsCandidate):
 *   a #GList of #FsCandidate to force
 * @error: location of a #GError, or %NULL if no error occured
 *
 * This function forces data to be sent immediately to the selected remote
 * candidate, by-passing any connectivity checks. There should be at most
 * one candidate per component.
 *
 * Returns: %TRUE if the candidates could be forced, %FALSE otherwise
 */

gboolean
fs_stream_force_remote_candidates (FsStream *stream,
    GList *remote_candidates,
    GError **error)
{
  FsStreamClass *klass;

  g_return_val_if_fail (stream, FALSE);
  g_return_val_if_fail (FS_IS_STREAM (stream), FALSE);
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->force_remote_candidates) {
    return klass->force_remote_candidates (stream,
        remote_candidates,
        error);
  } else {
    g_set_error (error, FS_ERROR, FS_ERROR_NOT_IMPLEMENTED,
      "force_remote_candidates not defined in class");
  }

  return FALSE;
}

/**
 * fs_stream_set_remote_codecs:
 * @stream: a #FsStream
 * @remote_codecs: (element-type FsCodec): a #GList of #FsCodec representing
 *   the remote codecs
 * @error: location of a #GError, or %NULL if no error occured
 *
 * This function will set the list of remote codecs for this stream. If
 * the given remote codecs couldn't be negotiated with the list of local
 * codecs or already negotiated codecs for the corresponding #FsSession, @error
 * will be set and %FALSE will be returned. The @remote_codecs list will be
 * copied so it must be free'd using fs_codec_list_destroy() when done.
 *
 * Returns: %FALSE if the remote codecs couldn't be set.
 */
gboolean
fs_stream_set_remote_codecs (FsStream *stream,
                             GList *remote_codecs, GError **error)
{
  FsStreamClass *klass;

  g_return_val_if_fail (stream, FALSE);
  g_return_val_if_fail (FS_IS_STREAM (stream), FALSE);
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->set_remote_codecs) {
    return klass->set_remote_codecs (stream, remote_codecs, error);
  } else {
    g_set_error (error, FS_ERROR, FS_ERROR_NOT_IMPLEMENTED,
      "set_remote_codecs not defined in class");
  }

  return FALSE;
}

/**
 * fs_stream_add_id:
 * @stream: a #FsStream
 * @id: The id to add to the stream
 *
 * This function is used to add data identifiers that allow the
 * plugin to recognize packets that are meant for id. For example, in RTP,
 * one would set the SSRCs that are expected.
 *
 * Depending on the protocol, one may be able to add more than one ID
 * to a stream (in RTP you can have multiple SSRCs in a stream).
 * If a protocol supports only one id, adding a new one will overwrite it.
 * If an ID was already set on a stream, adding it to another stream will
 * override the previdous decision.
 *
 * For most protocols, calling this function is optional as the incoming data
 * can be matched with a stream by its source IP address. This is mostly useful
 * if one is using multicast or is behind a muxer server.
 */
void
fs_stream_add_id (FsStream *stream,
                  guint id)
{
  FsStreamClass *klass;

  g_return_if_fail (stream);
  g_return_if_fail (FS_IS_STREAM (stream));
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->add_id)
    klass->add_id (stream, id);
}

/**
 * fs_stream_emit_error:
 * @stream: #FsStream on which to emit the error signal
 * @error_no: The number of the error
 * @error_msg: Error message to be displayed to user
 *
 * This function emits the #FsStream::error" signal, it should only be
 * called by subclasses.
 */
void
fs_stream_emit_error (FsStream *stream,
    gint error_no,
    const gchar *error_msg)
{
  g_signal_emit (stream, signals[ERROR_SIGNAL], 0, error_no, error_msg);
}


static void
fs_stream_pad_removed (FsStream *stream, GstPad *pad)
{
  GList *item;

  FS_STREAM_LOCK (stream);
  item = g_list_find (stream->priv->src_pads, pad);
  if (item)
  {
    stream->priv->src_pads = g_list_delete_link (stream->priv->src_pads, item);
    gst_object_unref (pad);
    stream->priv->src_pads_cookie++;
  }
  FS_STREAM_UNLOCK (stream);
}

/**
 * fs_stream_emit_src_pad_added:
 * @stream: #FsStream on which to emit the src-pad-added signal
 * @pad: the #GstPad that this #FsStream has created
 * @codec: The #FsCodec for this pad
 *
 * Emits the #FsStream::src-pad-added" signal, it should only be
 * called by subclasses.
 */

void
fs_stream_emit_src_pad_added (FsStream *stream,
    GstPad *pad,
    FsCodec *codec)
{
  FS_STREAM_LOCK (stream);
  g_assert (!g_list_find (stream->priv->src_pads, pad));
  stream->priv->src_pads = g_list_prepend (stream->priv->src_pads,
      gst_object_ref (pad));
  stream->priv->src_pads_cookie++;
  FS_STREAM_UNLOCK (stream);

  g_signal_emit (stream, signals[SRC_PAD_ADDED], 0, pad, codec);
}

/**
 * fs_stream_iterate_src_pads:
 * @stream: a #FsStream
 *
 * Creates a #GstIterator that can be used to iterate the src pads of this
 * stream. These are the pads that were announced by #FsStream:src-pad-added
 * and are still valid.
 *
 * Returns: (transfer full): The #GstIterator
 */

GstIterator *
fs_stream_iterate_src_pads (FsStream *stream)
{
  return gst_iterator_new_list (GST_TYPE_PAD, &stream->priv->mutex,
      &stream->priv->src_pads_cookie, &stream->priv->src_pads,
      G_OBJECT (stream), NULL);
}


/**
 * fs_stream_set_transmitter:
 * @stream: a #FsStream
 * @transmitter: Name of the type of transmitter to use for this stream
 * @stream_transmitter_n_parameters: Number of parametrs passed to the stream
 *  transmitter
 * @stream_transmitter_parameters: (array length=stream_transmitter_n_parameters) (allow-none):
 *   an array of n_parameters #GParameter struct that will be passed
 *   to the newly-create #FsStreamTransmitter
 * @error: location of a #GError, or %NULL if no error occured
 *
 * Set the transmitter to use for this stream. This function will only succeed
 * once.
 *
 * The parameters correspond to the varios GObject properties of the
 * selected stream transmitter.
 *
 * Returns: %TRUE if the transmitter could be set, %FALSE otherwise
 */

gboolean
fs_stream_set_transmitter (FsStream *stream,
    const gchar *transmitter,
    GParameter *stream_transmitter_parameters,
    guint stream_transmitter_n_parameters,
    GError **error)
{
  FsStreamClass *klass;

  g_return_val_if_fail (stream, FALSE);
  g_return_val_if_fail (FS_IS_STREAM (stream), FALSE);
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->set_transmitter)
    return klass->set_transmitter (stream, transmitter,
        stream_transmitter_parameters, stream_transmitter_n_parameters, error);


  g_set_error (error, FS_ERROR, FS_ERROR_NOT_IMPLEMENTED,
      "set_transmitter not defined in class");

  return FALSE;
}

/**
 * fs_stream_set_transmitter_ht:
 * @stream: a #FsStream
 * @transmitter: Name of the type of transmitter to use for this stream
 * @stream_transmitter_parameters: (element-type utf8 GValue) (allow-none):
 *   A #GHashTable of string->GValue containing the parameters.
 * @error: location of a #GError, or %NULL if no error occured
 *
 * Set the transmitter to use for this stream. This function will only succeed
 * once.
 *
 * The parameters correspond to the varios GObject properties of the
 * selected stream transmitter.
 *
 * This is the same as fs_stream_set_transmitter() except that the parameters
 * are passed in a #GHashTable to make it more friendly to GObject introspection
 *
 * Returns: %TRUE if the transmitter could be set, %FALSE otherwise
 */
gboolean fs_stream_set_transmitter_ht (FsStream *stream,
    const gchar *transmitter,
    GHashTable *stream_transmitter_parameters,
    GError **error)
{
  GParameter *params = NULL;
  gboolean ret = FALSE;
  guint n_params = 0;
  guint i = 0;

  if (stream_transmitter_parameters &&
      g_hash_table_size (stream_transmitter_parameters) != 0)
  {
    FsSession *session = NULL;
    GType st_type;
    GObjectClass *st_class  = NULL;
    GHashTableIter iter;
    gpointer key, value;

    n_params = g_hash_table_size (stream_transmitter_parameters);

    g_object_get (stream, "session", &session, NULL);

    if (!session) {
      g_set_error_literal (error, FS_ERROR, FS_ERROR_DISPOSED,
          "Stream has already been disposed");
      return FALSE;
    }

    st_type = fs_session_get_stream_transmitter_type (session,
        transmitter);
    g_object_unref (session);

    if (st_type == 0) {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "Unknown transmitter: %s", transmitter);
      return FALSE;
    }

    params = g_new0 (GParameter, n_params);
    st_class = g_type_class_ref (st_type);

    g_hash_table_iter_init (&iter, stream_transmitter_parameters);
    while (g_hash_table_iter_next (&iter, &key, &value)) {
      GParamSpec *spec;
      gchar *name = key;
      const GValue *v = value;

      spec = g_object_class_find_property (st_class, name);

      if (!spec) {
        g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
            "Unknown argument %s for transmitter %s", name, transmitter);
        goto end;
      }

      params[i].name = name;
      g_value_init (&params[i].value, G_PARAM_SPEC_VALUE_TYPE(spec));
      if (!g_value_transform (v, &params[i].value)) {
        g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
            "Invalid type of argument %s for transmitter %s",
            name, transmitter);
        goto end;
      }
      i++;
    }
  }

  ret = fs_stream_set_transmitter (stream, transmitter, params, n_params,
      error);

end:

  for (i = 0; i < n_params; i++)
    g_value_unset (&params[i].value);
  g_free (params);

  return ret;
}

/**
 * fs_stream_set_decryption_parameters:
 * @stream: a #FsStream
 * @parameters: (transfer none): a #GstStructure containing the decryption
 *  parameters
 * @error: the location where to store a #GError or %NULL
 *
 * Sets decryption parameters. The exact parameters depend on the type of
 * plugin being used.
 *
 * Returns: %TRUE if the decryption parameters could be set, %FALSE otherwise
 * Since: UNRELEASED
 */
gboolean
fs_stream_set_decryption_parameters (FsStream *stream,
    GstStructure *parameters, GError **error)
{
  FsStreamClass *klass;

  g_return_val_if_fail (stream, FALSE);
  g_return_val_if_fail (FS_IS_STREAM (stream), FALSE);
  klass = FS_STREAM_GET_CLASS (stream);

  if (klass->set_decryption_parameters)
    return klass->set_decryption_parameters (stream, parameters, error);

  g_set_error (error, FS_ERROR, FS_ERROR_NOT_IMPLEMENTED,
      "Does not support decryption");

  return FALSE;
}

/**
 * fs_stream_destroy:
 * @stream: a #FsStream
 *
 * This will cause the stream to remove all links to other objects and to
 * remove itself from the #FsSession. Once a #FsStream has been destroyed, it
 * can not be used anymore.
 *
 * It is strongly recommended to call this function from the main thread because
 * releasing the application's reference to a stream.
 */

void
fs_stream_destroy (FsStream *stream)
{
  g_return_if_fail (stream);
  g_return_if_fail (FS_IS_STREAM (stream));

  g_object_run_dispose (G_OBJECT (stream));
}



static gboolean
check_message (GstMessage *message,
    FsStream *stream,
    const gchar *message_name)
{
  const GstStructure *s;
  const GValue *value;
  FsStream *message_stream;

  if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
    return FALSE;

  s = gst_message_get_structure (message);

  if (!gst_structure_has_name (s, message_name))
    return FALSE;

  value = gst_structure_get_value (s, "stream");
  if (!value || !G_VALUE_HOLDS (value, FS_TYPE_STREAM))
    return FALSE;
  message_stream = g_value_get_object (value);

  if (stream != message_stream)
    return FALSE;

  return TRUE;
}

/**
 * fs_stream_parse_new_local_candidate:
 * @stream: a #FsStream to match against the message
 * @message: a #GstMessage to parse
 * @candidate: (out) (transfer none): Returns the #FsCandidate in the message
 *  if not %NULL.
 *
 * Parses a "farstream-new-local-candidate" message and checks if it matches
 * the @stream parameters.
 *
 * Returns: %TRUE if the message matches the stream and is valid.
 */
gboolean
fs_stream_parse_new_local_candidate (FsStream *stream,
    GstMessage *message,
    FsCandidate **candidate)
{
  const GstStructure *s;
  const GValue *value;

  g_return_val_if_fail (stream != NULL, FALSE);

  if (!check_message (message, stream, "farstream-new-local-candidate"))
    return FALSE;

  s = gst_message_get_structure (message);

  value = gst_structure_get_value (s, "candidate");
  if (!value || !G_VALUE_HOLDS (value, FS_TYPE_CANDIDATE))
    return FALSE;
  if (candidate)
    *candidate = g_value_get_boxed (value);

  return TRUE;
}


/**
 * fs_stream_parse_local_candidates_prepared:
 * @stream: a #FsStream to match against the message
 * @message: a #GstMessage to parse
 *
 * Parses a "farstream-local-candidates-prepared" message and checks if it matches
 * the @stream parameters.
 *
 * Returns: %TRUE if the message matches the stream and is valid.
 */
gboolean
fs_stream_parse_local_candidates_prepared (FsStream *stream,
    GstMessage *message)
{
  g_return_val_if_fail (stream != NULL, FALSE);

  return check_message (message, stream, "farstream-local-candidates-prepared");
}


/**
 * fs_stream_parse_new_active_candidate_pair:
 * @stream: a #FsStream to match against the message
 * @message: a #GstMessage to parse
 * @local_candidate: (out) (transfer none): Returns the local #FsCandidate in
 *  the message if not %NULL.
 * @remote_candidate: (out) (transfer none): Returns the remote #FsCandidate in
 *  the message if not %NULL.
 *
 * Parses a "farstream-new-active-candidate-pair" message and checks
 * if it matches the @stream parameters.
 *
 * Returns: %TRUE if the message matches the stream and is valid.
 */
gboolean
fs_stream_parse_new_active_candidate_pair (FsStream *stream,
    GstMessage *message,
    FsCandidate **local_candidate,
    FsCandidate **remote_candidate)
{
  const GstStructure *s;
  const GValue *value;

  g_return_val_if_fail (stream != NULL, FALSE);

  if (!check_message (message, stream, "farstream-new-active-candidate-pair"))
    return FALSE;

  s = gst_message_get_structure (message);

  value = gst_structure_get_value (s, "local-candidate");
  if (!value || !G_VALUE_HOLDS (value, FS_TYPE_CANDIDATE))
    return FALSE;
  if (local_candidate)
    *local_candidate = g_value_get_boxed (value);


  value = gst_structure_get_value (s, "remote-candidate");
  if (!value || !G_VALUE_HOLDS (value, FS_TYPE_CANDIDATE))
    return FALSE;
  if (remote_candidate)
    *remote_candidate = g_value_get_boxed (value);

  return TRUE;
}


/**
 * fs_stream_parse_recv_codecs_changed:
 * @stream: a #FsStream to match against the message
 * @message: a #GstMessage to parse
 * @codecs: (out) (transfer none) (element-type FsCodec):
 *  Returns a #GList of #FsCodec of the message if not %NULL
 *
 * Parses a "farstream-recv-codecs-changed" message and checks if it matches
 * the @stream parameters.
 *
 * Returns: %TRUE if the message matches the stream and is valid.
 */
gboolean
fs_stream_parse_recv_codecs_changed (FsStream *stream,
    GstMessage *message,
    GList **codecs)
{
  const GstStructure *s;
  const GValue *value;

  g_return_val_if_fail (stream != NULL, FALSE);

  if (!check_message (message, stream, "farstream-recv-codecs-changed"))
    return FALSE;

  s = gst_message_get_structure (message);

  value = gst_structure_get_value (s, "codecs");
  if (!value || !G_VALUE_HOLDS (value, FS_TYPE_CODEC_LIST))
    return FALSE;
  if (codecs)
    *codecs = g_value_get_boxed (value);

  return TRUE;
}



/**
 * fs_stream_parse_component_state_changed:
 * @stream: a #FsStream to match against the message
 * @message: a #GstMessage to parse
 * @component: (out): Returns the component from the #GstMessage if not %NULL
 * @state: (out): Returns the #FsStreamState from the #GstMessage if not %NULL
 *
 * Parses a "farstream-component-state-changed" message and checks if it matches
 * the @stream parameters.
 *
 * Returns: %TRUE if the message matches the stream and is valid.
 */
gboolean
fs_stream_parse_component_state_changed (FsStream *stream,
    GstMessage *message,
    guint *component,
    FsStreamState *state)
{
  const GstStructure *s;
  const GValue *value;

  g_return_val_if_fail (stream != NULL, FALSE);

  if (!check_message (message, stream, "farstream-component-state-changed"))
    return FALSE;

  s = gst_message_get_structure (message);

  value = gst_structure_get_value (s, "component");
  if (!value || !G_VALUE_HOLDS (value, G_TYPE_UINT))
    return FALSE;
  if (component)
    *component = g_value_get_uint (value);


  value = gst_structure_get_value (s, "state");
  if (!value || !G_VALUE_HOLDS (value, G_TYPE_ENUM))
    return FALSE;
  if (state)
    *state = g_value_get_enum (value);

  return TRUE;
}

