static GList *
list_insert_local_ca (GList *list, CodecAssociation *ca)
{
  if (codec_association_is_valid_for_sending (ca, TRUE))
  {
    GList *item;

    for (item = list; item; item = item->next)
      if (!codec_association_is_valid_for_sending (item->data, TRUE))
        break;
    if (item)
      return g_list_insert_before (list, item, ca);
  }

  return g_list_append (list, ca);
}
static gboolean
_is_law_codec (CodecAssociation *ca, gpointer user_data)
{
    if (codec_association_is_valid_for_sending (ca, FALSE) &&
            (ca->codec->id == 0 || ca->codec->id == 8))
        return TRUE;
    else
        return FALSE;
}
static gboolean
_is_telephony_codec (CodecAssociation *ca, gpointer user_data)
{
    guint clock_rate = GPOINTER_TO_UINT (user_data);

    if (codec_association_is_valid_for_sending (ca, FALSE) &&
            ca->codec->media_type == FS_MEDIA_TYPE_AUDIO &&
            !g_ascii_strcasecmp (ca->codec->encoding_name, "telephone-event") &&
            ca->codec->clock_rate == clock_rate)
        return TRUE;
    else
        return FALSE;
}
CodecAssociation *
lookup_codec_association_by_codec_for_sending (GList *codec_associations,
    FsCodec *codec)
{
  GList *item;

  for (item = codec_associations; item; item = g_list_next (item))
  {
    CodecAssociation *ca = item->data;

    if (codec_association_is_valid_for_sending (ca, FALSE) &&
        fs_codec_are_equal (ca->codec, codec))
      return ca;
  }

  return NULL;
}
GList *
negotiate_stream_codecs (
    const GList *remote_codecs,
    GList *current_codec_associations,
    gboolean multi_stream)
{
  GList *new_codec_associations = NULL;
  const GList *rcodec_e = NULL;
  GList *item = NULL;

  GST_DEBUG ("Negotiating stream codecs (for %s)",
      multi_stream ? "a single stream" : "multiple streams");

  for (rcodec_e = remote_codecs;
       rcodec_e;
       rcodec_e = g_list_next (rcodec_e)) {
    FsCodec *remote_codec = rcodec_e->data;
    FsCodec *nego_codec = NULL;
    FsCodec *nego_send_codec = NULL;
    CodecAssociation *old_ca = NULL;

    gchar *tmp = fs_codec_to_string (remote_codec);
    GST_DEBUG ("Remote codec %s", tmp);
    g_free (tmp);

    /* First lets try the codec that is in the same PT */

    old_ca = lookup_codec_association_by_pt_list (current_codec_associations,
        remote_codec->id, FALSE);

    if (old_ca) {
      GST_DEBUG ("Have local codec in the same PT, lets try it first");
      negotiate_stream_codec (old_ca, remote_codec, multi_stream,
          &nego_codec, &nego_send_codec);
    }

    if (!nego_codec) {

      for (item = current_codec_associations;
           item;
           item = g_list_next (item))
      {
        old_ca = item->data;

        if (old_ca->disable || old_ca->reserved)
          continue;

        negotiate_stream_codec (old_ca, remote_codec, multi_stream,
            &nego_codec, &nego_send_codec);

        if (nego_codec)
        {
          /* If we have multiple streams with codecs,
           * then priorize the local IDs */
          if (multi_stream)
            nego_send_codec->id = nego_codec->id = old_ca->codec->id;

          break;
        }
      }
    }

    if (nego_codec) {
      CodecAssociation *new_ca = g_slice_new0 (CodecAssociation);
      gchar *tmp;

      new_ca->need_config = old_ca->need_config;
      new_ca->codec = nego_codec;
      new_ca->send_codec = nego_send_codec;
      new_ca->blueprint = old_ca->blueprint;
      new_ca->send_profile = g_strdup (old_ca->send_profile);
      new_ca->recv_profile = g_strdup (old_ca->recv_profile);

      tmp = fs_codec_to_string (nego_codec);
      GST_DEBUG ("Negotiated codec %s", tmp);
      g_free (tmp);

      new_codec_associations = g_list_append (new_codec_associations,
          new_ca);
    } else {
      gchar *tmp = fs_codec_to_string (remote_codec);
      CodecAssociation *new_ca = g_slice_new0 (CodecAssociation);
      GST_DEBUG ("Could not find a valid intersection... for codec %s",
          tmp);
      g_free (tmp);

      new_ca->codec = fs_codec_copy (remote_codec);
      new_ca->disable = TRUE;

      new_codec_associations = g_list_append (new_codec_associations, new_ca);
    }
  }

  /*
   * Check if there is a non-disabled codec left that we can use
   * for sending
   */
  for (item = new_codec_associations;
       item;
       item = g_list_next (item))
  {
    CodecAssociation *ca = item->data;

    if (codec_association_is_valid_for_sending (ca, TRUE))
      return new_codec_associations;
  }

  /* Else we destroy when and return NULL.. ie .. an error */
  codec_association_list_destroy (new_codec_associations);

  return NULL;
}
GList *
create_local_codec_associations (
    GList *blueprints,
    GList *codec_prefs,
    GList *current_codec_associations)
{
  GList *codec_associations = NULL;
  GList *bp_e = NULL;
  GList *codec_pref_e = NULL;
  GList *lca_e = NULL;
  gboolean has_valid_codec = FALSE;
  CodecAssociation *oldca = NULL;

  if (blueprints == NULL)
    return NULL;

  GST_DEBUG ("Creating local codec associations");

  /* First, lets create the original table by looking at our preferred codecs */
  for (codec_pref_e = codec_prefs;
       codec_pref_e;
       codec_pref_e = g_list_next (codec_pref_e))
  {
    FsCodec *codec_pref = codec_pref_e->data;
    CodecBlueprint *bp = _find_matching_blueprint (codec_pref, blueprints);
    CodecAssociation *ca = NULL;
    GList *bp_param_e = NULL;

    /* If its a negative pref, ignore it in this stage */
    if (codec_pref->id == FS_CODEC_ID_DISABLE)
      continue;

    /* If we want to disable a codec ID, we just insert a reserved codec assoc
     * in the list
     */
    if (codec_pref->id >= 0 && codec_pref->id < 128 &&
        codec_pref->encoding_name &&
        !g_ascii_strcasecmp (codec_pref->encoding_name, "reserve-pt"))
    {
      CodecAssociation *ca = g_slice_new0 (CodecAssociation);
      ca->codec = fs_codec_copy (codec_pref);
      ca->reserved = TRUE;
      codec_associations = g_list_append (codec_associations, ca);
      continue;
    }

    /* No matching blueprint, can't use this codec */
    if (!bp &&
        !fs_codec_get_optional_parameter (codec_pref, RECV_PROFILE_ARG,
                NULL))
    {
      GST_LOG ("Could not find matching blueprint for preferred codec %s/%s",
          fs_media_type_to_string (codec_pref->media_type),
          codec_pref->encoding_name);
      continue;
    }

    /* Now lets see if there is an existing codec that matches this preference
     */

    if (codec_pref->id == FS_CODEC_ID_ANY)
    {
      oldca = lookup_codec_association_custom_internal (
          current_codec_associations, TRUE,
          match_original_codec_and_codec_pref, codec_pref);
    }
    else
    {
      oldca = lookup_codec_association_by_pt_list (current_codec_associations,
          codec_pref->id, FALSE);
      if (oldca && oldca->reserved)
        oldca = NULL;
    }

    /* In this case, we have a matching codec association, lets keep the
     * payload type from it
     */
    if (oldca)
    {
      FsCodec *codec = sdp_negotiate_codec (
          oldca->codec, FS_PARAM_TYPE_BOTH | FS_PARAM_TYPE_CONFIG,
          codec_pref, FS_PARAM_TYPE_ALL);
      FsCodec *send_codec;

      if (codec)
      {
        fs_codec_destroy (codec);

        send_codec = sdp_negotiate_codec (
            oldca->send_codec, FS_PARAM_TYPE_SEND,
            codec_pref, FS_PARAM_TYPE_SEND | FS_PARAM_TYPE_SEND_AVOID_NEGO);
        if (send_codec)
          fs_codec_destroy (send_codec);
        else
          oldca = NULL;
      }
      else
      {
        oldca = NULL;
      }
    }

    ca = g_slice_new0 (CodecAssociation);
    ca->blueprint = bp;
    ca->codec = fs_codec_copy (codec_pref);
    codec_remove_parameter (ca->codec, SEND_PROFILE_ARG);
    codec_remove_parameter (ca->codec, RECV_PROFILE_ARG);
    ca->send_codec = codec_copy_filtered (codec_pref, FS_PARAM_TYPE_CONFIG);
    codec_remove_parameter (ca->send_codec, SEND_PROFILE_ARG);
    codec_remove_parameter (ca->send_codec, RECV_PROFILE_ARG);
    if (oldca)
      ca->send_codec->id = ca->codec->id = oldca->codec->id;
    ca->send_profile = dup_param_value (codec_pref, SEND_PROFILE_ARG);
    ca->recv_profile = dup_param_value (codec_pref, RECV_PROFILE_ARG);

    if (bp)
    {
      /* Codec pref does not come with a number, but
       * The blueprint has its own id, lets use it */
      if (ca->codec->id == FS_CODEC_ID_ANY &&
          (bp->codec->id >= 0 || bp->codec->id < 128))
      {
        ca->send_codec->id = ca->codec->id = bp->codec->id;
      }

      if (ca->codec->clock_rate == 0)
        ca->codec->clock_rate = bp->codec->clock_rate;

      if (ca->codec->channels == 0)
        ca->codec->channels = bp->codec->channels;

      for (bp_param_e = bp->codec->optional_params;
           bp_param_e;
           bp_param_e = g_list_next (bp_param_e))
      {
        FsCodecParameter *bp_param = bp_param_e->data;

        if (fs_codec_get_optional_parameter (ca->codec, bp_param->name, NULL))
          fs_codec_add_optional_parameter (ca->codec, bp_param->name,
              bp_param->value);
      }
    }

    {
      gchar *tmp = fs_codec_to_string (ca->codec);
      GST_LOG ("Added preferred codec %s", tmp);
      g_free (tmp);
    }

    codec_associations = list_insert_local_ca (codec_associations, ca);
  }

  /* Now, only codecs with specified ids are here,
   * the rest are dynamic
   * Lets attribute them here */
  for (lca_e = codec_associations;
       lca_e;
       lca_e = g_list_next (lca_e))
  {
    CodecAssociation *lca = lca_e->data;

    if (lca->reserved)
      continue;

    if (lca->codec->id < 0)
    {
      lca->send_codec->id = lca->codec->id = _find_first_empty_dynamic_entry (
          current_codec_associations, codec_associations);
      if (lca->codec->id < 0)
      {
        GST_ERROR ("We've run out of dynamic payload types");
        goto error;
      }
    }
  }

  /* Now, lets add all other codecs from the blueprints */
  for (bp_e = g_list_first (blueprints); bp_e; bp_e = g_list_next (bp_e)) {
    CodecBlueprint *bp = bp_e->data;
    CodecAssociation *ca = NULL;
    GList *tmpca_e = NULL;
    gboolean next = FALSE;
    FsCodec *codec;

    /* Lets skip codecs that dont have all of the required informations */
    if (bp->codec->clock_rate == 0)
      continue;

    /* Check if its already used */
    for (tmpca_e = codec_associations;
         tmpca_e;
         tmpca_e = g_list_next (tmpca_e))
    {
      CodecAssociation *tmpca = tmpca_e->data;
      if (tmpca->blueprint == bp)
        break;
    }
    if (tmpca_e)
      continue;

    /* Check if it is disabled in the list of preferred codecs */
    if (_is_disabled (codec_prefs, bp))
    {
      gchar *tmp = fs_codec_to_string (bp->codec);
      GST_DEBUG ("Codec %s disabled by config", tmp);
      g_free (tmp);
      continue;
    }

    /* Re-use already existing codec associations with this blueprint
     * if any, we only keep the PT from the old assoc
     * (the rest will be regenerated by the renegotiation)
     */
    for (tmpca_e = current_codec_associations;
         tmpca_e;
         tmpca_e = g_list_next (tmpca_e))
    {
      CodecAssociation *tmpca = tmpca_e->data;

      if (tmpca->blueprint == bp)
      {
        /* Ignore reserved (we've just regenerated them )*/
        if (tmpca->reserved)
          continue;

        /* Ignore it if there is already something for this PT */
        if (lookup_codec_association_by_pt_list (codec_associations,
                tmpca->codec->id, TRUE))
          continue;

        /* Can't keep this codec, for some reason its wrong */
        codec = sdp_negotiate_codec (tmpca->codec, FS_PARAM_TYPE_CONFIG,
            bp->codec, FS_PARAM_TYPE_ALL);
        if (!codec)
          continue;
        fs_codec_destroy (codec);

        ca = g_slice_new0 (CodecAssociation);
        ca->blueprint = bp;
        ca->codec = fs_codec_copy (bp->codec);
        ca->send_codec = codec_copy_filtered (bp->codec, FS_PARAM_TYPE_CONFIG);
        ca->codec->id = ca->send_codec->id = tmpca->codec->id;

        codec_associations = list_insert_local_ca (codec_associations, ca);
        next = TRUE;
      }
    }
    if (next)
      continue;

    codec = sdp_negotiate_codec (bp->codec, FS_PARAM_TYPE_ALL,
        bp->codec, FS_PARAM_TYPE_ALL);

    /* If it does not negotiate against itself, there must be something wrong */
    if (!codec)
      continue;
    fs_codec_destroy (codec);

    ca = g_slice_new0 (CodecAssociation);
    ca->blueprint = bp;
    ca->codec = fs_codec_copy (bp->codec);

    if (ca->codec->id < 0)
    {
      ca->codec->id = _find_first_empty_dynamic_entry (
          current_codec_associations, codec_associations);
      if (ca->codec->id < 0)
      {
        GST_WARNING ("We've run out of dynamic payload types");
        goto error;
      }
    }

    ca->send_codec = codec_copy_filtered (ca->codec, FS_PARAM_TYPE_CONFIG);

    codec_associations = list_insert_local_ca (codec_associations, ca);
  }

  for (lca_e = codec_associations;
       lca_e;
       lca_e = g_list_next (lca_e))
  {
    CodecAssociation *ca = lca_e->data;

    if (codec_association_is_valid_for_sending (ca, TRUE))
      has_valid_codec = TRUE;
  }

  if (!has_valid_codec)
  {
    GST_WARNING ("All codecs disabled by preferences");
    goto error;
  }

  return codec_associations;

 error:
  codec_association_list_destroy (codec_associations);

  return NULL;
}