/*
 * fs-rtp-codec-specific.c - Per-codec SDP negotiation
 *
 * Farstream RTP/AVP/SAVP/AVPF Module
 * Copyright (C) 2007-2010 Collabora Ltd.
 * Copyright (C) 2007-2010 Nokia Corporation
 *   @author Olivier Crete <olivier.crete@collabora.co.uk>
 *
 * 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
 */

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

#include "fs-rtp-codec-specific.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <glib.h>
#include <gst/gst.h>

#include "fs-rtp-conference.h"

#define GST_CAT_DEFAULT fsrtpconference_nego

/*
 * This must be kept to the maximum number of parameters + 1
 */
#define MAX_PARAMS 20

struct SdpParam {
  gchar *name;
  /* The param type tell us if they should be added to the send
     or recv pipelines or both */
  FsParamType paramtype;
  gboolean (*negotiate_param) (const struct SdpParam *sdp_param,
      FsCodec *local_codec, FsCodecParameter *local_param,
      FsCodec *remote_codec, FsCodecParameter *remote_param,
      FsCodec *negotiated_codec);
  const gchar *default_value;
};

struct SdpNegoFunction {
  FsMediaType media_type;
  const gchar *encoding_name;
  FsCodec * (* sdp_negotiate_codec) (FsCodec *local_codec,
      FsParamType local_paramtypes,
      FsCodec *remote_codec,
      FsParamType remote_paramtypes,
      const struct SdpNegoFunction *nf);
  const struct SdpParam params[MAX_PARAMS];
};

struct SdpParamMinMax {
  const gchar *encoding_name;
  const gchar *param_name;
  guint min;
  guint max;
};


static FsCodec *
sdp_negotiate_codec_default (
    FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf);

static FsCodec *
sdp_negotiate_codec_h263_2000 (
    FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf);

static FsCodec *
sdp_negotiate_codec_mandatory (
    FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf);

/* Generic param negotiation functions */

static gboolean param_minimum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_maximum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_both_maximum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_equal_or_ignore (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_equal_or_not_default (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_equal_or_reject (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_list_commas (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_copy (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);

/* Codec specific negotiation functions */

static gboolean param_ilbc_mode (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_h263_1998_custom (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_h263_1998_cpcf (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_telephone_events (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_h264_profile_level_id (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);
static gboolean param_h264_min_req_profile (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec);


const static struct SdpParamMinMax sdp_min_max_params[] = {
  {"H261", "qcif", 1, 4},
  {"H261", "cif", 1, 4},
  {"H263-1998", "sqcif", 1, 32},
  {"H263-1998", "qcif", 1, 32},
  {"H263-1998", "cif", 1, 32},
  {"H263-1998", "cif4", 1, 32},
  {"H263-1998", "cif16", 1, 32},
  {"H263-1998", "bpp", 1, 65536},
  {"H263-2000", "level", 0, 100},
  {NULL, NULL}
};

static const struct SdpNegoFunction sdp_nego_functions[] = {
  /* iLBC: RFC 3959 */
  {FS_MEDIA_TYPE_AUDIO, "iLBC", sdp_negotiate_codec_default,
   {
     {"mode", FS_PARAM_TYPE_BOTH, param_ilbc_mode},
     {NULL, 0, NULL}
   }
  },
  /* H261: RFC 4587 */
  {FS_MEDIA_TYPE_VIDEO, "H261", sdp_negotiate_codec_default,
   {
     {"qcif", FS_PARAM_TYPE_SEND, param_maximum},
     {"cif", FS_PARAM_TYPE_SEND, param_both_maximum},
     {"d", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {NULL, 0, NULL}
   }
  },
  /* H263-1998 and H263-2000: RFC 4629 */
  {FS_MEDIA_TYPE_VIDEO, "H263-1998", sdp_negotiate_codec_default,
   {
     {"sqcif", FS_PARAM_TYPE_SEND, param_maximum},
     {"qcif", FS_PARAM_TYPE_SEND, param_maximum},
     {"cif", FS_PARAM_TYPE_SEND, param_both_maximum},
     {"cif4", FS_PARAM_TYPE_SEND, param_both_maximum},
     {"cif16", FS_PARAM_TYPE_SEND, param_both_maximum},
     {"custom", FS_PARAM_TYPE_SEND, param_h263_1998_custom},
     {"f", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"i", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"j", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"t", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"k", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"n", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"p", FS_PARAM_TYPE_SEND, param_list_commas},
     {"par", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"cpcf", FS_PARAM_TYPE_SEND, param_h263_1998_cpcf},
     {"bpp", FS_PARAM_TYPE_SEND, param_minimum},
     {"hrd", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"interlace", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {NULL, 0, NULL}
   }
  },
  {FS_MEDIA_TYPE_VIDEO, "H263-2000", sdp_negotiate_codec_h263_2000,
   {
     /* Add H263-1998 params here */
     {"profile", FS_PARAM_TYPE_BOTH, param_equal_or_reject, "0"},
     {"level", FS_PARAM_TYPE_SEND, param_minimum, "0"},
     {NULL, 0, NULL}
   }
  },
  /* VORBIS: RFC 5215 */
  {FS_MEDIA_TYPE_AUDIO, "VORBIS", sdp_negotiate_codec_default,
   {
     {"configuration", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY,
      param_copy},
     {NULL, 0, NULL}
   }
  },
  /* THEORA: as an extension from vorbis using RFC 5215 */
  {FS_MEDIA_TYPE_VIDEO, "THEORA", sdp_negotiate_codec_default,
   {
     {"configuration", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY,
      param_copy},
     {"delivery-method", FS_PARAM_TYPE_CONFIG, param_copy},
     {NULL, 0, NULL}
   }
  },
  {FS_MEDIA_TYPE_AUDIO, "G729", sdp_negotiate_codec_default,
   {
     {"annexb", FS_PARAM_TYPE_SEND, param_equal_or_not_default, "yes"},
     {NULL, 0, NULL}
   }
  },
  {FS_MEDIA_TYPE_VIDEO, "H264", sdp_negotiate_codec_default,
   {
     {"profile-level-id", FS_PARAM_TYPE_SEND, param_h264_profile_level_id},
     {"max-mbps", FS_PARAM_TYPE_SEND, param_h264_min_req_profile},
     {"max-fs", FS_PARAM_TYPE_SEND, param_h264_min_req_profile},
     {"max-cpb", FS_PARAM_TYPE_SEND, param_h264_min_req_profile},
     {"max-dpb", FS_PARAM_TYPE_SEND, param_h264_min_req_profile},
     {"max-br", FS_PARAM_TYPE_SEND, param_h264_min_req_profile},
     {"redundant-pic-cap", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"parameter-add", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"packetization-mode", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
     {"deint-buf-cap", FS_PARAM_TYPE_SEND, param_minimum},
     {"max-rcmd-nalu-size", FS_PARAM_TYPE_SEND, param_minimum},
     {"sprop-parameter-sets", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY,
      param_copy},
     {"sprop-interleaving-depth",  FS_PARAM_TYPE_CONFIG, param_copy},
     {"sprop-deint-buf-req", FS_PARAM_TYPE_CONFIG, param_copy},
     {"sprop-init-buf-time",  FS_PARAM_TYPE_CONFIG, param_copy},
     {"sprop-max-don-diff",  FS_PARAM_TYPE_CONFIG, param_copy},
     {NULL, 0, NULL}
   }
  },
  {FS_MEDIA_TYPE_AUDIO, "telephone-event", sdp_negotiate_codec_default,
    {
      {"", FS_PARAM_TYPE_SEND, param_telephone_events},
      {"events", FS_PARAM_TYPE_SEND, param_telephone_events},
      {NULL, 0, NULL}
    }
  },
  /* JPEG2000: RFC 5371 */
  {FS_MEDIA_TYPE_VIDEO, "JPEG2000", sdp_negotiate_codec_mandatory,
    {
      {"sampling", FS_PARAM_TYPE_BOTH | FS_PARAM_TYPE_MANDATORY,
          param_equal_or_reject},
      {"interlace", FS_PARAM_TYPE_SEND, param_equal_or_ignore},
      {"width", FS_PARAM_TYPE_SEND, param_minimum},
      {"height", FS_PARAM_TYPE_SEND, param_minimum}
    }
  },
  /* OPUS: RFC 7687 */
  {FS_MEDIA_TYPE_AUDIO, "OPUS", sdp_negotiate_codec_default,
    {
      {"sprop-maxcapturerate", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_RECV, param_copy},
      {"sprop-stereo",  FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_RECV, param_copy},
      /* Not sure how to handle those */
      {"maxplaybackrate", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_copy},
      {"stereo", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_copy},
      {"cbr", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_copy},
      {"useinbandfec", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_copy},
      {"usedtx", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_copy},
    }
  },
  {0, NULL, NULL}
};

static const struct SdpNegoFunction *
get_sdp_nego_function (FsMediaType media_type, const gchar *encoding_name)
{
  int i;

  for (i = 0; sdp_nego_functions[i].sdp_negotiate_codec; i++)
    if (sdp_nego_functions[i].media_type == media_type &&
        !g_ascii_strcasecmp (sdp_nego_functions[i].encoding_name,
            encoding_name))
      return &sdp_nego_functions[i];

  return NULL;
}


/*
 * This function currently returns %TRUE if any configuration parameter is there
 * if some codecs require something more complicated, we will need a custom
 * functions for each codec
 */

gboolean
codec_needs_config (FsCodec *codec)
{
  const struct SdpNegoFunction *nf;
  int i;

  g_return_val_if_fail (codec, FALSE);

  nf = get_sdp_nego_function (codec->media_type, codec->encoding_name);

  if (!nf)
    return FALSE;

  for (i = 0; nf->params[i].name; i++)
  {
    if (nf->params[i].paramtype & FS_PARAM_TYPE_CONFIG &&
        nf->params[i].paramtype & FS_PARAM_TYPE_MANDATORY)
    {
      if (!fs_codec_get_optional_parameter (codec, nf->params[i].name, NULL))
        return TRUE;
    }
  }

  return FALSE;
}


static gboolean
codec_param_check_type (const struct SdpNegoFunction *nf,
    const gchar *param_name, FsParamType paramtypes)
{
  gint i;

  if (!nf)
    return FALSE;

  for (i = 0; nf->params[i].name; i++)
    if (nf->params[i].paramtype & paramtypes &&
        !g_ascii_strcasecmp (nf->params[i].name, param_name))
      return TRUE;

  return FALSE;
}


gboolean
codec_has_config_data_named (FsCodec *codec, const gchar *param_name)
{
  const struct SdpNegoFunction *nf;

  g_return_val_if_fail (codec, FALSE);
  g_return_val_if_fail (param_name, FALSE);

  nf = get_sdp_nego_function (codec->media_type, codec->encoding_name);

  if (nf)
    return codec_param_check_type (nf, param_name, FS_PARAM_TYPE_CONFIG);
  else
    return FALSE;
}

/**
 * codec_copy_filtered
 * @codec: a #FsCodec
 * @paramtypes: bitmask of types of parameters to remove
 *
 * Makes a copy of a #FsCodec, but removes all parameters that match
 * of the bits from the paramtypes element
 *
 * Returns: the newly-allocated #FsCodec
 */

FsCodec *
codec_copy_filtered (FsCodec *codec, FsParamType paramtypes)
{
  FsCodec *copy = fs_codec_copy (codec);
  GList *item = NULL;
  const struct SdpNegoFunction *nf;

  nf = get_sdp_nego_function (codec->media_type, codec->encoding_name);

  if (nf)
  {
    for (item = copy->optional_params; item;)
    {
      FsCodecParameter *param = item->data;
      GList *next = g_list_next (item);

      if (codec_param_check_type (nf, param->name, paramtypes))
        fs_codec_remove_optional_parameter (copy, param);

      item = next;
    }
  }

  return copy;
}


/**
 * sdp_negotiate_codec:
 *
 * This function performs SDP offer-answer negotiation on a codec, it compares
 * the local codec (the one that would be sent in an offer) and the remote
 * codec (the one that would be received from the other side)  and tries to see
 * if they can be negotiated into a new codec (what would be sent in a reply).
 * If such a codec can be created, it returns it, otherwise it returns NULL.
 *
 * RFC 3264
 */

FsCodec *
sdp_negotiate_codec (FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes)
{
  const struct SdpNegoFunction *nf;

  g_return_val_if_fail (local_codec, NULL);
  g_return_val_if_fail (remote_codec, NULL);

  if (local_codec->media_type != remote_codec->media_type)
  {
    GST_LOG ("Wrong media type, local: %s, remote: %s",
        fs_media_type_to_string (local_codec->media_type),
        fs_media_type_to_string (remote_codec->media_type));
    return NULL;
  }
  if (g_ascii_strcasecmp (local_codec->encoding_name,
        remote_codec->encoding_name))
  {
    GST_LOG ("Encoding names dont match, local: %s, remote: %s",
        local_codec->encoding_name, remote_codec->encoding_name);
    return NULL;
  }

  if (local_codec->clock_rate && remote_codec->clock_rate &&
      local_codec->clock_rate != remote_codec->clock_rate)
  {
    GST_LOG ("Clock rates differ local=%u remote=%u", local_codec->clock_rate,
        remote_codec->clock_rate);
    return NULL;
  }

  nf = get_sdp_nego_function (local_codec->media_type,
      local_codec->encoding_name);

  if (nf)
    return nf->sdp_negotiate_codec (local_codec, local_paramtypes,
        remote_codec, remote_paramtypes, nf);
  else
    return sdp_negotiate_codec_default (local_codec, local_paramtypes,
        remote_codec, remote_paramtypes, NULL);
}

static const struct SdpParam *
get_sdp_param (const struct SdpNegoFunction *nf, const gchar *param_name)
{
  static const struct SdpParam ptime_params = {
    "ptime", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_minimum
  };
  static const struct SdpParam maxptime_params = {
    "maxptime", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_minimum
  };

  if (nf)
  {
    gint i;

    for (i = 0; nf->params[i].name; i++)
      if (!g_ascii_strcasecmp (param_name, nf->params[i].name))
        return &nf->params[i];

    if (nf->media_type != FS_MEDIA_TYPE_AUDIO)
      return NULL;
  }

  if (!g_ascii_strcasecmp (param_name, "ptime"))
    return &ptime_params;

  if (!g_ascii_strcasecmp (param_name, "maxptime"))
    return &maxptime_params;

  return NULL;
}


static gboolean
param_negotiate (const struct SdpNegoFunction *nf, const gchar *param_name,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsParamType local_paramtypes,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsParamType remote_paramtypes,
    FsCodec *negotiated_codec)
{
  const struct SdpParam *sdp_param = NULL;

  sdp_param = get_sdp_param (nf, param_name);

  if (sdp_param)
  {
    if ((sdp_param->paramtype & FS_PARAM_TYPE_BOTH) != FS_PARAM_TYPE_BOTH)
    {
      if (!(sdp_param->paramtype & local_paramtypes))
        local_param = NULL;
      if (!(sdp_param->paramtype & remote_paramtypes))
        remote_param = NULL;
    }

    if (local_param || remote_param)
      return sdp_param->negotiate_param (sdp_param,
          local_codec, local_param, remote_codec,
          remote_param, negotiated_codec);
    else
      return TRUE;
  }
  else
  {
    /* Assume unknown parameters are of type SEND */
    if (!((remote_paramtypes | local_paramtypes) & FS_PARAM_TYPE_SEND))
      return TRUE;

    if (local_param && remote_param)
    {
      /* Only accept codecs where unknown parameters are IDENTICAL if
       * they are present on both sides */
      if (!g_ascii_strcasecmp (local_param->value, remote_param->value))
      {
        fs_codec_add_optional_parameter (negotiated_codec, local_param->name,
            local_param->value);
      }
      else
      {
        GST_LOG ("Codec %s has different values for %s (\"%s\" and \"%s\")",
            local_codec->encoding_name, param_name,
            local_param->value, remote_param->value);
        return FALSE;
      }
    }
    else if (local_param)
    {
      fs_codec_add_optional_parameter (negotiated_codec, local_param->name,
          local_param->value);
    }
    else if (remote_param)
    {
      fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
          remote_param->value);
    }
  }

  return TRUE;
}

static FsCodec *
sdp_negotiate_codec_default (FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf)
{
  FsCodec *negotiated_codec = NULL;
  FsCodec *local_codec_copy = NULL;
  GList *local_param_e = NULL, *remote_param_e = NULL;

  GST_LOG ("Using default codec negotiation function for %s",
      local_codec->encoding_name);

  if (local_codec->channels && remote_codec->channels &&
      local_codec->channels != remote_codec->channels)
  {
    GST_LOG ("Channel counts differ local=%u remote=%u",
        local_codec->channels,
        remote_codec->channels);
    return NULL;
  }

  negotiated_codec = fs_codec_copy (remote_codec);
  while (negotiated_codec->optional_params)
    fs_codec_remove_optional_parameter (negotiated_codec,
        negotiated_codec->optional_params->data);


  /* Lets fix here missing clock rates and channels counts */
  if (negotiated_codec->channels == 0 && local_codec->channels)
    negotiated_codec->channels = local_codec->channels;
  if (negotiated_codec->clock_rate == 0)
    negotiated_codec->clock_rate = local_codec->clock_rate;

  local_codec_copy = fs_codec_copy (local_codec);

  for (remote_param_e = remote_codec->optional_params;
       remote_param_e;
       remote_param_e = g_list_next (remote_param_e))
  {
    FsCodecParameter *remote_param = remote_param_e->data;
    FsCodecParameter *local_param =  fs_codec_get_optional_parameter (
        local_codec_copy, remote_param->name, NULL);

    if (!param_negotiate (nf, remote_param->name,
            local_codec, local_param, local_paramtypes,
            remote_codec, remote_param, remote_paramtypes,
            negotiated_codec))
      goto non_matching_codec;

    if (local_param)
      fs_codec_remove_optional_parameter (local_codec_copy, local_param);
  }

  for (local_param_e = local_codec_copy->optional_params;
       local_param_e;
       local_param_e = g_list_next (local_param_e))
  {
    FsCodecParameter *local_param = local_param_e->data;

    if (!param_negotiate (nf, local_param->name,
            local_codec, local_param, local_paramtypes,
            remote_codec, NULL, remote_paramtypes, negotiated_codec))
      goto non_matching_codec;
  }

  fs_codec_destroy (local_codec_copy);

  return negotiated_codec;

non_matching_codec:

  GST_LOG ("Codecs don't really match");
  fs_codec_destroy (local_codec_copy);
  fs_codec_destroy (negotiated_codec);
  return NULL;
}

/*
 * sdp_negotiate_codec_h263_2000:
 *
 * For H263-2000, the "profile" must be exactly the same. If it is not,
 * it must be rejected. If there is none, we assume its 0.
 *
 * If profile or level is used, no other parameter should be there.
 *
 * RFC 4629
 */

static FsCodec *
sdp_negotiate_codec_h263_2000 (
    FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf)
{
  const struct SdpNegoFunction *h263_1998_nf;

  GST_DEBUG ("Using H263-2000 negotiation function");

  if (fs_codec_get_optional_parameter (remote_codec, "profile", NULL) &&
      !fs_codec_get_optional_parameter (remote_codec, "level", NULL))
  {
    GST_WARNING ("Can not accept a profile without a level");
    return NULL;
  }

  if (fs_codec_get_optional_parameter (local_codec, "profile", NULL) &&
      !fs_codec_get_optional_parameter (local_codec, "level", NULL))
  {
    GST_WARNING ("Can not accept a profile without a level");
    return NULL;
  }

  if (fs_codec_get_optional_parameter (remote_codec, "profile", NULL) ||
      fs_codec_get_optional_parameter (remote_codec, "level", NULL) ||
      fs_codec_get_optional_parameter (local_codec, "profile", NULL) ||
      fs_codec_get_optional_parameter (local_codec, "level", NULL))
    return sdp_negotiate_codec_default (local_codec, local_paramtypes,
        remote_codec, remote_paramtypes, nf);


  h263_1998_nf = get_sdp_nego_function (FS_MEDIA_TYPE_VIDEO, "H263-1998");

  return sdp_negotiate_codec_default (local_codec, local_paramtypes,
      remote_codec, remote_paramtypes, h263_1998_nf);
}

static FsCodec *
sdp_negotiate_codec_mandatory (
    FsCodec *local_codec, FsParamType local_paramtypes,
    FsCodec *remote_codec, FsParamType remote_paramtypes,
    const struct SdpNegoFunction *nf)
{
  gint i;

  for (i = 0; nf->params[i].name; i++)
  {
    if (nf->params[i].paramtype & FS_PARAM_TYPE_MANDATORY)
    {
      if ((nf->params[i].paramtype & local_paramtypes) ||
          ((nf->params[i].paramtype & FS_PARAM_TYPE_BOTH) ==
              FS_PARAM_TYPE_BOTH))
        if (!fs_codec_get_optional_parameter (local_codec, nf->params[i].name,
                NULL))
          return NULL;

      if ((nf->params[i].paramtype & remote_paramtypes) ||
          ((nf->params[i].paramtype & FS_PARAM_TYPE_BOTH) ==
              FS_PARAM_TYPE_BOTH))
        if (!fs_codec_get_optional_parameter (remote_codec, nf->params[i].name,
                NULL))
          return NULL;
    }
  }

  return sdp_negotiate_codec_default (local_codec, local_paramtypes,
      remote_codec, remote_paramtypes, nf);
}


struct event_range {
  int first;
  int last;
};

static gint
event_range_cmp (gconstpointer a, gconstpointer b)
{
  const struct event_range *era = a;
  const struct event_range *erb = b;

  return era->first - erb->first;
}

static GList *
parse_events (const gchar *events)
{
  gchar **ranges_strv;
  GList *ranges = NULL;
  int i;

  ranges_strv = g_strsplit (events, ",", 0);

  for (i = 0; ranges_strv[i]; i++)
  {
    struct event_range *er = g_slice_new (struct event_range);
    gchar *p = NULL;

    er->first = atoi (ranges_strv[i]);
    p = strchr (ranges_strv[i], '-');
    if (p)
      er->last = atoi (p + 1);
    else
      er->last = er->first;

    ranges = g_list_insert_sorted (ranges, er, event_range_cmp);
  }

  g_strfreev (ranges_strv);

  return ranges;
}

static void
event_range_free (gpointer data)
{
  g_slice_free (struct event_range, data);
}

static gchar *
event_intersection (const gchar *remote_events, const gchar *local_events)
{
  GList *remote_ranges = NULL;
  GList *local_ranges = NULL;
  GList *intersected_ranges = NULL;
  GList *item;
  GString *intersection_gstr;

  if (!g_regex_match_simple ("^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$",
          remote_events, 0, 0))
  {
    GST_WARNING ("Invalid remote events (events=%s)", remote_events);
    return NULL;
  }

  if (!g_regex_match_simple ("^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$",
          local_events, 0, 0))
  {
    GST_WARNING ("Invalid local events (events=%s)", local_events);
    return NULL;
  }

  remote_ranges = parse_events (remote_events);
  local_ranges = parse_events (local_events);

  while ((item = remote_ranges) != NULL)
  {
    struct event_range *er1 = item->data;
    GList *item2;

    item2 = local_ranges;
    while (item2)
    {
      struct event_range *er2 = item2->data;

      if (er1->last < er2->first) {
        break;
      }

      if (er1->first <= er2->last)
      {
        struct event_range *new_er = g_slice_new (struct event_range);

        new_er->first = MAX (er1->first, er2->first);
        new_er->last = MIN (er1->last, er2->last);
        intersected_ranges = g_list_append (intersected_ranges, new_er);
      }

      item2 = item2->next;
      if (er2->last < er1->last)
      {
        local_ranges = g_list_remove (local_ranges, er2);
        event_range_free (er2);
      }
    }

    remote_ranges = g_list_delete_link (remote_ranges, item);
    event_range_free (er1);
  }

  while (local_ranges)
  {
    event_range_free (local_ranges->data);
    local_ranges = g_list_delete_link (local_ranges, local_ranges);
  }

  if (!intersected_ranges)
  {
    GST_DEBUG ("There is no intersection before the events %s and %s",
        remote_events, local_events);
    return NULL;
  }

  intersection_gstr = g_string_new ("");

  while ((item = intersected_ranges) != NULL)
  {
    struct event_range *er = item->data;

    if (intersection_gstr->len)
      g_string_append_c (intersection_gstr, ',');

    if (er->first == er->last)
      g_string_append_printf (intersection_gstr, "%d", er->first);
    else
      g_string_append_printf (intersection_gstr, "%d-%d", er->first, er->last);

    intersected_ranges = g_list_delete_link (intersected_ranges, item);
    event_range_free (er);
  }

  return g_string_free (intersection_gstr, FALSE);
}


/**
 * param_telephone_events:
 *
 * For telephone events, it finds the list of events that are the same.
 * So it tried to intersect both lists to come up with a list of events that
 * both sides support.
 *
 * RFC  4733
 */

static gboolean
param_telephone_events (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  gchar *events;

  if (fs_codec_get_optional_parameter (negotiated_codec, "", NULL) ||
      fs_codec_get_optional_parameter (negotiated_codec, "events", NULL))
    return TRUE;

  if (!local_param)
    local_param = fs_codec_get_optional_parameter (local_codec, "", NULL);
  if (!local_param)
    local_param = fs_codec_get_optional_parameter (local_codec, "events", NULL);

  if (!remote_param)
    remote_param = fs_codec_get_optional_parameter (remote_codec, "", NULL);
  if (!remote_param)
    remote_param = fs_codec_get_optional_parameter (remote_codec, "events",
        NULL);

  if (!local_param)
  {
    fs_codec_add_optional_parameter (negotiated_codec, "events",
        remote_param->value);
    return TRUE;
  }

  if (!remote_param)
  {
    fs_codec_add_optional_parameter (negotiated_codec, "events",
        local_param->value);
    return TRUE;
  }

  events = event_intersection (local_param->value, remote_param->value);
  if (!events)
  {
    GST_LOG ("Non-intersecting values for \"events\" local=%s remote=%s",
       local_param->value, remote_param->value);
    return FALSE;
  }

  fs_codec_add_optional_parameter (negotiated_codec, "events", events);
  g_free (events);

  return TRUE;
}

/**
 * param_min_max:
 *
 * Expects both parameters to have numerical values.
 * If the type is known, verifies that is is valid. If it is, puts the
 * minimum or maximum depending on the gboolean value in the result.
 */

static gboolean
param_min_max (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec, gboolean min, gboolean keep_single)
{
  guint local_value = 0;
  gboolean local_valid = FALSE;
  guint remote_value = 0;
  gboolean remote_valid = FALSE;
  gchar *encoding_name =
    remote_codec ? remote_codec->encoding_name : local_codec->encoding_name;
  gchar *param_name =
    remote_param ? remote_param->name : local_param->name;
  int i;

  if (local_param)
  {
    local_value = strtol (local_param->value, NULL, 10);
    if (local_value || errno != EINVAL)
      local_valid = TRUE;
  }
  else if (sdp_param->default_value)
  {
    local_value = strtol (sdp_param->default_value, NULL, 10);
    if (local_value || errno != EINVAL)
      local_valid = TRUE;
  }

  if (remote_param)
  {
    remote_value = strtol (remote_param->value, NULL, 10);
    if (remote_value || errno != EINVAL)
      remote_valid = TRUE;
  }
  else if (sdp_param->default_value)
  {
    remote_value = strtol (sdp_param->default_value, NULL, 10);
    if (remote_value || errno != EINVAL)
      remote_valid = TRUE;
  }


  /* Validate values against min/max from table */
  for (i = 0; sdp_min_max_params[i].encoding_name; i++)
  {
    if (!g_ascii_strcasecmp (encoding_name,
            sdp_min_max_params[i].encoding_name) &&
        !g_ascii_strcasecmp (param_name,
            sdp_min_max_params[i].param_name))
    {
      if (local_valid && (local_value < sdp_min_max_params[i].min ||
              local_value > sdp_min_max_params[i].max))
        local_valid = FALSE;

      if (remote_valid && (remote_value < sdp_min_max_params[i].min ||
              remote_value > sdp_min_max_params[i].max))
        return TRUE;

      break;
    }
  }

  if (local_valid && remote_valid)
  {
    gchar *tmp = g_strdup_printf ("%d",
        min ? MIN (local_value, remote_value):MAX (local_value, remote_value));

    fs_codec_add_optional_parameter (negotiated_codec, param_name, tmp);
    g_free (tmp);
  }
  else if (remote_valid && keep_single)
  {
    fs_codec_add_optional_parameter (negotiated_codec, param_name,
        remote_param ? remote_param->value : sdp_param->default_value);
  }
  else if (local_valid && keep_single)
  {
    fs_codec_add_optional_parameter (negotiated_codec, param_name,
        local_param->value);
  }

  return TRUE;
}

/**
 * param_equal_or_ignore:
 *
 * If both params are equal, it is the result, otherwise they are removed.
 * Otherwise the result is nothing
 */

static gboolean
param_equal_or_ignore (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  if (local_param && remote_param &&
      !strcmp (local_param->value, remote_param->value))
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        remote_param->value);

  return TRUE;
}



/**
 * param_equal_or_not_default:
 *
 * If both params are equal, it is the result. Otherwise, if one is not equal to
 * the default, the result is this param.
 */

static gboolean
param_equal_or_not_default (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  if (local_param && remote_param &&
      !strcmp (local_param->value, remote_param->value))
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        remote_param->value);
  else if (remote_param &&
      g_ascii_strcasecmp (remote_param->value, sdp_param->default_value))
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        remote_param->value);
  else if (local_param &&
      g_ascii_strcasecmp (local_param->value, sdp_param->default_value))
    fs_codec_add_optional_parameter (negotiated_codec, local_param->name,
        local_param->value);

  return TRUE;
}

/**
 * param_minimum:
 *
 * Expects both parameters to have numerical values.
 * If the type is known, verifies that is is valid. If it is, puts the
 * minimum value in the result.
 */

static gboolean
param_minimum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  return param_min_max (sdp_param, local_codec, local_param, remote_codec,
      remote_param, negotiated_codec, TRUE, TRUE);
}

/**
 * param_maximum:
 *
 * Expects both parameters to have numerical values.
 * If the type is known, verifies that is is valid. If it is, puts the
 * maximum value in the result.
 */

static gboolean
param_maximum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  return param_min_max (sdp_param, local_codec, local_param, remote_codec,
      remote_param, negotiated_codec, FALSE, TRUE);
}

/**
 * param_both_maximum:
 *
 * Expects both parameters to have numerical values.
 * If the type is known, verifies that is is valid. If it is, puts the
 * maximum value in the result.
 *
 * Only gives a result if both sides have a value
 */

static gboolean
param_both_maximum (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  return param_min_max (sdp_param, local_codec, local_param, remote_codec,
      remote_param, negotiated_codec, FALSE, FALSE);
}

/**
 * param_equal_or_reject:
 *
 * Reject the codec if both params are not equal (taking into account the
 * default value if there is one).
 *
 */

static gboolean
param_equal_or_reject (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  const gchar *local_value = NULL;
  const gchar *remote_value = NULL;

  if (local_param)
    local_value = local_param->value;
  else if (sdp_param->default_value)
    local_value = sdp_param->default_value;

  if (remote_param)
    remote_value = remote_param->value;
  else if (sdp_param->default_value)
    remote_value = sdp_param->default_value;

  if (!local_value || !remote_value)
  {
    GST_DEBUG ("Missed a remote or a local value and don't have a default");
    return FALSE;
  }

  if (strcmp (local_value, remote_value))
  {
    GST_DEBUG ("Local value and remove value differ (%s != %s)", local_value,
        remote_value);
    return FALSE;
  }

  if (remote_param)
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        remote_param->value);
  else if (local_param)
    fs_codec_add_optional_parameter (negotiated_codec, local_param->name,
        local_param->value);

  return TRUE;
}


/**
 * param_list_commas:
 *
 * Does the intersection of two comma separated lists, returns a list
 * of elements that are in both.
 */

static gboolean
param_list_commas (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  gchar **remote_strv = NULL;
  gchar **local_strv = NULL;
  GString *result = NULL;
  gint i;

  /* If one of them does not have it, just ignore it */
  if (!remote_param || !local_param)
    return TRUE;

  remote_strv = g_strsplit (remote_param->value, ",", -1);
  local_strv = g_strsplit (local_param->value, ",", -1);

  for (i = 0; remote_strv[i]; i++)
  {
    gint j;

    for (j = 0; local_strv[j]; j++)
    {
      if (!g_ascii_strcasecmp (remote_strv[i], local_strv[j]))
      {
        if (!result)
          result = g_string_new (remote_strv[i]);
        else
          g_string_append_printf (result, ",%s", remote_strv[i]);
      }
    }
  }

  if (result)
  {
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        result->str);
    g_string_free (result, TRUE);
  }

  g_strfreev (remote_strv);
  g_strfreev (local_strv);

  return TRUE;
}

/**
 * param_copy:
 *
 * Copies the incoming parameter
 */

static gboolean
param_copy (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  if (remote_param)
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        remote_param->value);
  else if (local_param)
    fs_codec_add_optional_parameter (negotiated_codec, local_param->name,
        local_param->value);

  return TRUE;
}

/**
 * param_ilbc_mode:
 *
 * For iLBC, the mode is 20 is both sides agree on 20, otherwise it is 30.
 *
 * RFC 3952
 */

static gboolean
param_ilbc_mode (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  if (local_param && strcmp (local_param->value, "20")
      && strcmp (local_param->value, "30"))
  {
    GST_DEBUG ("local iLBC has mode that is not 20 or 30 but %s",
        local_param->value);
    return FALSE;
  }

  if (remote_param && strcmp (remote_param->value, "20")
      && strcmp (remote_param->value, "30"))
  {
    GST_DEBUG ("remote iLBC has mode that is not 20 or 30 but %s",
        remote_param->value);
    return FALSE;
  }

  /* Only do mode=20 if both have it */
  if (!local_param || !remote_param)
    return TRUE;

  if (!strcmp (local_param->value, "20")
      && !strcmp (remote_param->value, "20"))
    fs_codec_add_optional_parameter (negotiated_codec, "mode", "20");
  else
    fs_codec_add_optional_parameter (negotiated_codec, "mode", "30");

  return TRUE;
}

/**
 * param_h263_1998_custom
 *
 * If the first two params (x/y) are the same, it takes the maximum of the
 * 3rd params.
 */

static gboolean
param_h263_1998_custom (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  guint remote_x, remote_y;
  gchar *match_string;
  guint match_len;
  guint final_mpi;
  GList *elem;
  gboolean got_one = FALSE;
  gchar *tmp;

  if (!remote_param || !local_param)
    return TRUE;

  /* Invalid param, can't parse, ignore it */
  if (sscanf (remote_param->value, "%u,%u,%u", &remote_x, &remote_y,
          &final_mpi) != 3)
    return TRUE;

  match_string = g_strdup_printf ("%u,%u,", remote_x, remote_y);
  match_len = strlen (match_string);

  for (elem = local_codec->optional_params; elem; elem = g_list_next (elem))
  {
    FsCodecParameter *local_param = elem->data;
    if (!g_ascii_strcasecmp (local_param->name, remote_param->name))
    {
      if (!strncmp (local_param->value, match_string, match_len))
      {
        guint local_x, local_y, local_mpi;

        if (sscanf (local_param->value, "%u,%u,%u", &local_x, &local_y,
                &local_mpi) == 3 &&
            local_x == remote_x && local_y == remote_y)
        {
          final_mpi = MAX (final_mpi, local_mpi);
          got_one = TRUE;
        }
      }
    }
  }

  g_free (match_string);

  if (got_one)
  {
    tmp = g_strdup_printf ("%u,%u,%u", remote_x, remote_y, final_mpi);
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        tmp);
    g_free (tmp);
  }

  return TRUE;
}


/**
 * param_h263_1998_cpcf
 *
 * If the first two params (x/y) are the same, it takes the maximum of the
 * 6 other params
 */

static gboolean
param_h263_1998_cpcf (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  guint remote_cd, remote_cf;
  gchar *match_string;
  guint match_len;
  guint final_sqcif, final_qcif, final_cif, final_4cif, final_16cif,
    final_custom;
  GList *elem;
  gboolean got_one = FALSE;
  gchar *tmp;

  if (!remote_param || !local_param)
    return TRUE;

  /* Invalid param, can't parse, ignore it */
  if (sscanf (remote_param->value, "%u,%u,%u,%u,%u,%u,%u,%u",
          &remote_cd, &remote_cf,
          &final_sqcif, &final_qcif, &final_cif, &final_4cif, &final_16cif,
          &final_custom) != 8)
    return TRUE;

  match_string = g_strdup_printf ("%u,%u,", remote_cd, remote_cf);
  match_len = strlen (match_string);

  for (elem = local_codec->optional_params; elem; elem = g_list_next (elem))
  {
    FsCodecParameter *local_param = elem->data;
    if (!g_ascii_strcasecmp (local_param->name, remote_param->name))
    {
      if (!strncmp (local_param->value, match_string, match_len))
      {
        guint local_cd, local_cf, local_sqcif, local_qcif, local_cif,
          local_4cif, local_16cif, local_custom;

        if (sscanf (local_param->value, "%u,%u,%u,%u,%u,%u,%u,%u",
                &local_cd, &local_cf,
                &local_sqcif, &local_qcif, &local_cif, &local_4cif,
                &local_16cif, &local_custom) == 8 &&
            local_cd == remote_cd && local_cf == remote_cf)
        {
          final_sqcif = MAX(final_sqcif, local_sqcif);
          final_qcif = MAX (final_qcif, local_qcif);
          final_cif = MAX(final_cif, local_cif);
          final_4cif = MAX(final_4cif, local_4cif);
          final_16cif = MAX(final_16cif, local_16cif);
          final_custom = MAX(final_custom, local_custom);
          got_one = TRUE;
        }
      }
    }
  }

  g_free (match_string);

  if (got_one)
  {
    tmp = g_strdup_printf ("%u,%u,%u,%u,%u,%u,%u,%u", remote_cd, remote_cf,
        final_sqcif, final_qcif, final_cif, final_4cif, final_16cif,
        final_custom);
    fs_codec_add_optional_parameter (negotiated_codec, remote_param->name,
        tmp);
    g_free (tmp);
  }

  return TRUE;
}


static gboolean
param_h264_profile_level_id (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  guint remote_value;
  guint local_value;
  guint remote_profile_idc;
  guint local_profile_idc;
  guint remote_profile_iop;
  guint local_profile_iop;
  guint nego_profile_iop;
  guint remote_level_idc;
  guint local_level_idc;
  guint nego_level_idc;
  gchar buf[7];

  /* If either of them is not present, then we can only do baseline profile
   * with the minimum level */

  if (!remote_param || !local_param)
    return TRUE;

  remote_value = strtol (remote_param->value, NULL, 16);
  if (remote_value == 0 && errno == EINVAL)
      return TRUE;

  local_value = strtol (local_param->value, NULL, 16);
  if (local_value == 0 && errno == EINVAL)
    return TRUE;

  remote_profile_idc = 0xFF & (remote_value>>16);
  local_profile_idc = 0xFF & (local_value>>16);

  if (remote_profile_idc != local_profile_idc)
    return TRUE;

  remote_profile_iop = 0xFF & (remote_value>>8);
  local_profile_iop = 0xFF & (local_value>>8);
  nego_profile_iop = remote_profile_iop | local_profile_iop;

  remote_level_idc = 0xFF & remote_value;
  local_level_idc = 0xFF & local_value;
  nego_level_idc = MIN (remote_level_idc, local_level_idc);

  g_snprintf (buf, 7, "%02X%02X%02X", local_profile_idc, nego_profile_iop,
      nego_level_idc);

  fs_codec_add_optional_parameter (negotiated_codec, sdp_param->name, buf);

  return TRUE;
}

static gboolean
param_h264_min_req_profile (const struct SdpParam *sdp_param,
    FsCodec *local_codec, FsCodecParameter *local_param,
    FsCodec *remote_codec, FsCodecParameter *remote_param,
    FsCodec *negotiated_codec)
{
  if (!fs_codec_get_optional_parameter (negotiated_codec, "profile-level-id",
          NULL))
  {
    FsCodecParameter *local_profile =
      fs_codec_get_optional_parameter (local_codec, "profile-level-id", NULL);
    FsCodecParameter *remote_profile =
      fs_codec_get_optional_parameter (remote_codec, "profile-level-id", NULL);

    if (!local_profile || !remote_profile)
      return TRUE;

    param_h264_profile_level_id (NULL, local_codec, local_profile,
        remote_codec, remote_profile, negotiated_codec);

    if (!fs_codec_get_optional_parameter (negotiated_codec, "profile-level-id",
            NULL))
      return TRUE;
  }

  return param_minimum (sdp_param, local_codec, local_param,
      remote_codec, remote_param, negotiated_codec);
}

static gboolean
has_config_param_changed (FsCodec *codec1, FsCodec *codec2)
{
  GList *itemp;

  for (itemp = codec1->optional_params;
       itemp;
       itemp = g_list_next (itemp))
  {
    FsCodecParameter *param1 = itemp->data;

    if (codec_has_config_data_named (codec1, param1->name))
    {
      FsCodecParameter *param2 = fs_codec_get_optional_parameter (codec2,
          param1->name, NULL);

      if (!param2 || strcmp (param1->value, param2->value))
        return TRUE;
    }
  }

  return FALSE;
}

/**
 * codecs_list_has_codec_config_changed:
 *
 * Compares two lists of codecs and returns the codecs present in the
 * @new list have any of their config param changed since old (meaning that
 * this list should be sent to the other side in a reliable way).
 */

GList *
codecs_list_has_codec_config_changed (GList *old, GList *new)
{
  GList *item_new, *item_old;
  GQueue result = G_QUEUE_INIT;

  for (item_new = new; item_new; item_new = g_list_next (item_new))
  {
    FsCodec *codec_new = item_new->data;

    for (item_old = old; item_old; item_old = g_list_next (item_old))
    {
      FsCodec *codec_old = item_old->data;
      FsCodec *nego = sdp_negotiate_codec (codec_new, FS_PARAM_TYPE_BOTH,
          item_old->data, FS_PARAM_TYPE_BOTH);
      fs_codec_destroy (nego);

      if (nego)
      {
        if (has_config_param_changed (codec_new, codec_old) ||
            has_config_param_changed (codec_old, codec_new))
        {
          g_queue_push_tail (&result, fs_codec_copy (codec_new));
          break;
        }
      }
    }
  }

  return result.head;
}
