/*
  The oSIP library implements the Session Initiation Protocol (SIP -rfc3261-)
  Copyright (C) 2001,2002,2003  Aymeric MOIZARD jack@atosc.org
  
  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#include <osip2/osip_negotiation.h>
#include <osip2/internal.h>

static int sdp_partial_clone (osip_negotiation_t * config,
			      osip_negotiation_ctx_t * con,
			      sdp_message_t * remote, sdp_message_t ** dest);
static int sdp_confirm_media (osip_negotiation_t * config,
			      osip_negotiation_ctx_t * context,
			      sdp_message_t * remote, sdp_message_t ** dest);
static __payload_t *osip_negotiation_find_audio_payload (osip_negotiation_t *
							 cfg, char *payload);
static __payload_t *osip_negotiation_find_video_payload (osip_negotiation_t *
							 cfg, char *payload);
static __payload_t *osip_negotiation_find_other_payload (osip_negotiation_t *
							 cfg, char *payload);



int
osip_negotiation_ctx_init (osip_negotiation_ctx_t ** con)
{
  (*con) =
    (osip_negotiation_ctx_t *) osip_malloc (sizeof (osip_negotiation_ctx_t));
  if (*con == NULL)
    return -1;
  (*con)->mycontext = NULL;	/* fixed Sep 24 2003 */
  (*con)->remote = NULL;
  (*con)->local = NULL;
  return 0;
}

void
osip_negotiation_ctx_free (osip_negotiation_ctx_t * con)
{
  if (con == NULL)
    return;
  sdp_message_free (con->remote);
  sdp_message_free (con->local);
  osip_free (con);
}

/* this method is used by end-user application so any pointer can
   be associated with this context (usefull to link with your own context */
int
osip_negotiation_ctx_set_mycontext (osip_negotiation_ctx_t * con,
				    void *my_instance)
{
  if (con == NULL)
    return -1;
  con->mycontext = my_instance;
  return 0;
}

void *
osip_negotiation_ctx_get_mycontext (osip_negotiation_ctx_t * con)
{
  if (con == NULL)
    return NULL;
  return con->mycontext;
}

sdp_message_t *
osip_negotiation_ctx_get_local_sdp (osip_negotiation_ctx_t * con)
{
  if (con == NULL)
    return NULL;
  return con->local;
}

int
osip_negotiation_ctx_set_local_sdp (osip_negotiation_ctx_t * con,
				    sdp_message_t * sdp)
{
  if (con == NULL)
    return -1;
  con->local = sdp;
  return 0;
}

sdp_message_t *
osip_negotiation_ctx_get_remote_sdp (osip_negotiation_ctx_t * con)
{
  if (con == NULL)
    return NULL;
  return con->remote;
}

int
osip_negotiation_ctx_set_remote_sdp (osip_negotiation_ctx_t * con,
				     sdp_message_t * sdp)
{
  if (con == NULL)
    return -1;
  con->remote = sdp;
  return 0;
}

int
__payload_init (__payload_t ** payload)
{
  *payload = (__payload_t *) osip_malloc (sizeof (__payload_t));
  if (*payload == NULL)
    return -1;
  (*payload)->payload = NULL;
  (*payload)->number_of_port = NULL;
  (*payload)->proto = NULL;
  (*payload)->c_nettype = NULL;
  (*payload)->c_addrtype = NULL;
  (*payload)->c_addr = NULL;
  (*payload)->c_addr_multicast_ttl = NULL;
  (*payload)->c_addr_multicast_int = NULL;
  (*payload)->a_rtpmap = NULL;
  return 0;
}

void
__payload_free (__payload_t * payload)
{
  if (payload == NULL)
    return;
  osip_free (payload->payload);
  osip_free (payload->number_of_port);
  osip_free (payload->proto);
  osip_free (payload->c_nettype);
  osip_free (payload->c_addrtype);
  osip_free (payload->c_addr);
  osip_free (payload->c_addr_multicast_ttl);
  osip_free (payload->c_addr_multicast_int);
  osip_free (payload->a_rtpmap);
  osip_free (payload);
}

int
osip_negotiation_init (osip_negotiation_t ** config_out)
{
  osip_negotiation_t *config;

  config = (osip_negotiation_t *) osip_malloc (sizeof (osip_negotiation_t));
  if (config == NULL)
    return -1;
  config->o_username = NULL;
  config->o_session_id = NULL;
  config->o_session_version = NULL;
  config->o_nettype = NULL;
  config->o_addrtype = NULL;
  config->o_addr = NULL;

  config->c_nettype = NULL;
  config->c_addrtype = NULL;
  config->c_addr = NULL;
  config->c_addr_multicast_ttl = NULL;
  config->c_addr_multicast_int = NULL;

  /* supported codec for the SIP User Agent */
  config->audio_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->audio_codec);
  config->video_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->video_codec);
  config->other_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->other_codec);

  config->fcn_set_info = NULL;
  config->fcn_set_uri = NULL;
  config->fcn_set_emails = NULL;
  config->fcn_set_phones = NULL;
  config->fcn_set_attributes = NULL;
  config->fcn_accept_audio_codec = NULL;
  config->fcn_accept_video_codec = NULL;
  config->fcn_accept_other_codec = NULL;

  *config_out = config;

  return 0;
}

void
osip_negotiation_free (osip_negotiation_t * config)
{
  if (config == NULL)
    return;
  osip_free (config->o_username);
  osip_free (config->o_session_id);
  osip_free (config->o_session_version);
  osip_free (config->o_nettype);
  osip_free (config->o_addrtype);
  osip_free (config->o_addr);

  osip_free (config->c_nettype);
  osip_free (config->c_addrtype);
  osip_free (config->c_addr);
  osip_free (config->c_addr_multicast_ttl);
  osip_free (config->c_addr_multicast_int);

  osip_list_special_free (config->audio_codec,
			  (void *(*)(void *)) &__payload_free);
  osip_list_special_free (config->video_codec,
			  (void *(*)(void *)) &__payload_free);
  osip_list_special_free (config->other_codec,
			  (void *(*)(void *)) &__payload_free);

  /* other are pointer to func, they don't need free() calls */

  /* yes, this is done here... :) */
  osip_free (config);
}

int
osip_negotiation_set_o_username (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_username = tmp;
  return 0;
}

int
osip_negotiation_set_o_session_id (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_session_id = tmp;
  return 0;
}

int
osip_negotiation_set_o_session_version (osip_negotiation_t * config,
					char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_session_version = tmp;
  return 0;
}

int
osip_negotiation_set_o_nettype (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_nettype = tmp;
  return 0;
}

int
osip_negotiation_set_o_addrtype (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_addrtype = tmp;
  return 0;
}

int
osip_negotiation_set_o_addr (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->o_addr = tmp;
  return 0;
}

int
osip_negotiation_set_c_nettype (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->c_nettype = tmp;
  return 0;
}

int
osip_negotiation_set_c_addrtype (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->c_addrtype = tmp;
  return 0;
}

int
osip_negotiation_set_c_addr (osip_negotiation_t * config, char *tmp)
{
  if (config == NULL)
    return -1;
  config->c_addr = tmp;
  return 0;
}

int
osip_negotiation_set_c_addr_multicast_ttl (osip_negotiation_t * config,
					   char *tmp)
{
  if (config == NULL)
    return -1;
  config->c_addr_multicast_ttl = tmp;
  return 0;
}

int
osip_negotiation_set_c_addr_multicast_int (osip_negotiation_t * config,
					   char *tmp)
{
  if (config == NULL)
    return -1;
  config->c_addr_multicast_int = tmp;
  return 0;
}

int
osip_negotiation_add_support_for_audio_codec (osip_negotiation_t * config,
					      char *payload,
					      char *number_of_port,
					      char *proto, char *c_nettype,
					      char *c_addrtype, char *c_addr,
					      char *c_addr_multicast_ttl,
					      char *c_addr_multicast_int,
					      char *a_rtpmap)
{
  int i;
  __payload_t *my_payload;

  i = __payload_init (&my_payload);
  if (i != 0)
    return -1;
  my_payload->payload = payload;
  my_payload->number_of_port = number_of_port;
  my_payload->proto = proto;
  my_payload->c_nettype = c_nettype;
  my_payload->c_addrtype = c_addrtype;
  my_payload->c_addr = c_addr;
  my_payload->c_addr_multicast_ttl = c_addr_multicast_ttl;
  my_payload->c_addr_multicast_int = c_addr_multicast_int;
  my_payload->a_rtpmap = a_rtpmap;
  osip_list_add (config->audio_codec, my_payload, -1);
  return 0;
}

int
osip_negotiation_add_support_for_video_codec (osip_negotiation_t * config,
					      char *payload,
					      char *number_of_port,
					      char *proto, char *c_nettype,
					      char *c_addrtype, char *c_addr,
					      char *c_addr_multicast_ttl,
					      char *c_addr_multicast_int,
					      char *a_rtpmap)
{
  int i;
  __payload_t *my_payload;

  i = __payload_init (&my_payload);
  if (i != 0)
    return -1;
  my_payload->payload = payload;
  my_payload->number_of_port = number_of_port;
  my_payload->proto = proto;
  my_payload->c_nettype = c_nettype;
  my_payload->c_addrtype = c_addrtype;
  my_payload->c_addr = c_addr;
  my_payload->c_addr_multicast_ttl = c_addr_multicast_ttl;
  my_payload->c_addr_multicast_int = c_addr_multicast_int;
  my_payload->a_rtpmap = a_rtpmap;
  osip_list_add (config->video_codec, my_payload, -1);
  return 0;
}

int
osip_negotiation_add_support_for_other_codec (osip_negotiation_t * config,
					      char *payload,
					      char *number_of_port,
					      char *proto, char *c_nettype,
					      char *c_addrtype, char *c_addr,
					      char *c_addr_multicast_ttl,
					      char *c_addr_multicast_int,
					      char *a_rtpmap)
{
  int i;
  __payload_t *my_payload;

  i = __payload_init (&my_payload);
  if (i != 0)
    return -1;
  my_payload->payload = payload;
  my_payload->number_of_port = number_of_port;
  my_payload->proto = proto;
  my_payload->c_nettype = c_nettype;
  my_payload->c_addrtype = c_addrtype;
  my_payload->c_addr = c_addr;
  my_payload->c_addr_multicast_ttl = c_addr_multicast_ttl;
  my_payload->c_addr_multicast_int = c_addr_multicast_int;
  my_payload->a_rtpmap = a_rtpmap;
  osip_list_add (config->other_codec, my_payload, -1);
  return 0;
}

int
osip_negotiation_remove_audio_payloads (osip_negotiation_t * config)
{
  osip_list_special_free (config->audio_codec,
			  (void *(*)(void *)) &__payload_free);
  config->audio_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->audio_codec);
  return 0;
}

int
osip_negotiation_remove_video_payloads (osip_negotiation_t * config)
{
  osip_list_special_free (config->video_codec,
			  (void *(*)(void *)) &__payload_free);
  config->video_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->video_codec);
  return 0;
}

int
osip_negotiation_remove_other_payloads (osip_negotiation_t * config)
{
  osip_list_special_free (config->other_codec,
			  (void *(*)(void *)) &__payload_free);
  config->other_codec = (osip_list_t *) osip_malloc (sizeof (osip_list_t));
  osip_list_init (config->other_codec);
  return 0;
}

static __payload_t *
osip_negotiation_find_audio_payload (osip_negotiation_t * config,
				     char *payload)
{
  __payload_t *my;
  size_t length = strlen (payload);
  int pos = 0;

  if (payload == NULL)
    return NULL;
  while (!osip_list_eol (config->audio_codec, pos))
    {
      my = (__payload_t *) osip_list_get (config->audio_codec, pos);
      if (strlen (my->payload) == length)
	if (0 == strncmp (my->payload, payload, length))
	  return my;
      pos++;
    }
  return NULL;
}

static __payload_t *
osip_negotiation_find_video_payload (osip_negotiation_t * config,
				     char *payload)
{
  __payload_t *my;
  size_t length = strlen (payload);
  int pos = 0;

  if (payload == NULL)
    return NULL;
  while (!osip_list_eol (config->video_codec, pos))
    {
      my = (__payload_t *) osip_list_get (config->video_codec, pos);
      if (strlen (my->payload) == length)
	if (0 == strncmp (my->payload, payload, length))
	  return my;
      pos++;
    }
  return NULL;
}

static __payload_t *
osip_negotiation_find_other_payload (osip_negotiation_t * config,
				     char *payload)
{
  __payload_t *my;
  size_t length = strlen (payload);
  int pos = 0;

  if (payload == NULL)
    return NULL;
  while (!osip_list_eol (config->other_codec, pos))
    {
      my = (__payload_t *) osip_list_get (config->other_codec, pos);
      if (strlen (my->payload) == length)
	if (0 == strncmp (my->payload, payload, length))
	  return my;
      pos++;
    }
  return NULL;
}

int
osip_negotiation_set_fcn_set_info (osip_negotiation_t * config,
				   int (*fcn) (osip_negotiation_ctx_t *,
					       sdp_message_t *))
{
  if (config == NULL)
    return -1;
  config->fcn_set_info = (int (*)(void *, sdp_message_t *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_set_uri (osip_negotiation_t * config,
				  int (*fcn) (osip_negotiation_ctx_t *,
					      sdp_message_t *))
{
  if (config == NULL)
    return -1;
  config->fcn_set_uri = (int (*)(void *, sdp_message_t *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_set_emails (osip_negotiation_t * config,
				     int (*fcn) (osip_negotiation_ctx_t *,
						 sdp_message_t *))
{
  if (config == NULL)
    return -1;
  config->fcn_set_emails = (int (*)(void *, sdp_message_t *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_set_phones (osip_negotiation_t * config,
				     int (*fcn) (osip_negotiation_ctx_t *,
						 sdp_message_t *))
{
  if (config == NULL)
    return -1;
  config->fcn_set_phones = (int (*)(void *, sdp_message_t *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_set_attributes (osip_negotiation_t * config,
					 int (*fcn) (osip_negotiation_ctx_t *,
						     sdp_message_t *, int))
{
  if (config == NULL)
    return -1;
  config->fcn_set_attributes = (int (*)(void *, sdp_message_t *, int)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_accept_audio_codec (osip_negotiation_t * config,
					     int (*fcn)
					     (osip_negotiation_ctx_t *,
					      char *, char *, int, char *))
{
  if (config == NULL)
    return -1;
  config->fcn_accept_audio_codec = (int (*)(void *, char *,
					    char *, int, char *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_accept_video_codec (osip_negotiation_t * config,
					     int (*fcn)
					     (osip_negotiation_ctx_t *,
					      char *, char *, int, char *))
{
  if (config == NULL)
    return -1;
  config->fcn_accept_video_codec = (int (*)(void *, char *,
					    char *, int, char *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_accept_other_codec (osip_negotiation_t * config,
					     int (*fcn)
					     (osip_negotiation_ctx_t *,
					      char *, char *, char *, char *))
{
  if (config == NULL)
    return -1;
  config->fcn_accept_other_codec = (int (*)(void *, char *,
					    char *, char *, char *)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_get_audio_port (osip_negotiation_t * config,
					 char *(*fcn) (osip_negotiation_ctx_t
						       *, int))
{
  if (config == NULL)
    return -1;
  config->fcn_get_audio_port = (char *(*)(void *, int)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_get_video_port (osip_negotiation_t * config,
					 char *(*fcn) (osip_negotiation_ctx_t
						       *, int))
{
  if (config == NULL)
    return -1;
  config->fcn_get_video_port = (char *(*)(void *, int)) fcn;
  return 0;
}

int
osip_negotiation_set_fcn_get_other_port (osip_negotiation_t * config,
					 char *(*fcn) (osip_negotiation_ctx_t
						       *, int))
{
  if (config == NULL)
    return -1;
  config->fcn_get_other_port = (char *(*)(void *, int)) fcn;
  return 0;
}

static int
sdp_partial_clone (osip_negotiation_t * config,
		   osip_negotiation_ctx_t * con, sdp_message_t * remote,
		   sdp_message_t ** dest)
{
  int i;

  sdp_message_v_version_set (*dest, osip_strdup ("0"));

  /* those fields MUST be set */
  sdp_message_o_origin_set (*dest,
			    osip_strdup (config->o_username),
			    osip_strdup (config->o_session_id),
			    osip_strdup (config->o_session_version),
			    osip_strdup (config->o_nettype),
			    osip_strdup (config->o_addrtype),
			    osip_strdup (config->o_addr));
  sdp_message_s_name_set (*dest, osip_strdup (remote->s_name));
  if (config->fcn_set_info != NULL)
    config->fcn_set_info (con, *dest);
  if (config->fcn_set_uri != NULL)
    config->fcn_set_uri (con, *dest);
  if (config->fcn_set_emails != NULL)
    config->fcn_set_emails (con, *dest);
  if (config->fcn_set_phones != NULL)
    config->fcn_set_phones (con, *dest);
  if (config->c_nettype != NULL)
    sdp_message_c_connection_add (*dest, -1,
				  osip_strdup (config->c_nettype),
				  osip_strdup (config->c_addrtype),
				  osip_strdup (config->c_addr),
				  osip_strdup (config->c_addr_multicast_ttl),
				  osip_strdup (config->c_addr_multicast_int));

  {				/* offer-answer draft says we must copy the "t=" line */
    char *tmp = sdp_message_t_start_time_get (remote, 0);
    char *tmp2 = sdp_message_t_stop_time_get (remote, 0);

    if (tmp == NULL || tmp2 == NULL)
      return -1;		/* no t line?? */
    i =
      sdp_message_t_time_descr_add (*dest, osip_strdup (tmp),
				    osip_strdup (tmp2));
    if (i != 0)
      return -1;
  }
  if (config->fcn_set_attributes != NULL)
    config->fcn_set_attributes (con, *dest, -1);
  return 0;
}

static int
sdp_confirm_media (osip_negotiation_t * config,
		   osip_negotiation_ctx_t * context, sdp_message_t * remote,
		   sdp_message_t ** dest)
{
  char *payload;
  char *tmp, *tmp2, *tmp3, *tmp4;
  int ret;
  int i;
  int k;
  int audio_qty = 0;		/* accepted audio line: do not accept more than one */
  int video_qty = 0;

  i = 0;
  while (!sdp_message_endof_media (remote, i))
    {
      tmp = sdp_message_m_media_get (remote, i);
      tmp2 = sdp_message_m_port_get (remote, i);
      tmp3 = sdp_message_m_number_of_port_get (remote, i);
      tmp4 = sdp_message_m_proto_get (remote, i);

      if (tmp == NULL)
	return -1;
      sdp_message_m_media_add (*dest, osip_strdup (tmp), osip_strdup ("0"),
			       NULL, osip_strdup (tmp4));
      k = 0;
      if (0 == strncmp (tmp, "audio", 5))
	{
	  do
	    {
	      payload = sdp_message_m_payload_get (remote, i, k);
	      if (payload != NULL)
		{
		  __payload_t *my_payload =
		    osip_negotiation_find_audio_payload (config, payload);

		  if (my_payload != NULL)	/* payload is supported */
		    {
		      ret = -1;	/* somtimes, codec can be refused even if supported */
		      if (config->fcn_accept_audio_codec != NULL)
			ret = config->fcn_accept_audio_codec (context, tmp2,
							      tmp3, audio_qty,
							      payload);
		      if (0 == ret)
			{
			  sdp_message_m_payload_add (*dest, i,
						     osip_strdup (payload));
			  if (my_payload->a_rtpmap != NULL)
			    sdp_message_a_attribute_add (*dest, i,
							 osip_strdup
							 ("rtpmap"),
							 osip_strdup
							 (my_payload->
							  a_rtpmap));
			  if (my_payload->c_nettype != NULL)
			    {
			      sdp_media_t *med =
				osip_list_get ((*dest)->m_medias, i);

			      if (osip_list_eol (med->c_connections, 0))
				sdp_message_c_connection_add (*dest, i,
							      osip_strdup
							      (my_payload->
							       c_nettype),
							      osip_strdup
							      (my_payload->
							       c_addrtype),
							      osip_strdup
							      (my_payload->
							       c_addr),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_ttl),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_int));
			    }
			}
		    }
		}
	      k++;
	    }
	  while (payload != NULL);
	  if (NULL != sdp_message_m_payload_get (*dest, i, 0))
	    audio_qty = 1;
	}
      else if (0 == strncmp (tmp, "video", 5))
	{
	  do
	    {
	      payload = sdp_message_m_payload_get (remote, i, k);
	      if (payload != NULL)
		{
		  __payload_t *my_payload =
		    osip_negotiation_find_video_payload (config, payload);

		  if (my_payload != NULL)	/* payload is supported */
		    {
		      ret = -1;
		      if (config->fcn_accept_video_codec != NULL)
			ret =
			  config->fcn_accept_video_codec (context, tmp2, tmp3,
							  video_qty, payload);
		      if (0 == ret)
			{
			  sdp_message_m_payload_add (*dest, i,
						     osip_strdup (payload));
			  /* TODO  set the attribute list (rtpmap..) */
			  if (my_payload->a_rtpmap != NULL)
			    sdp_message_a_attribute_add (*dest, i,
							 osip_strdup
							 ("rtpmap"),
							 osip_strdup
							 (my_payload->
							  a_rtpmap));
			  if (my_payload->c_nettype != NULL)
			    {
			      sdp_media_t *med =
				osip_list_get ((*dest)->m_medias, i);

			      if (osip_list_eol (med->c_connections, 0))
				sdp_message_c_connection_add (*dest, i,
							      osip_strdup
							      (my_payload->
							       c_nettype),
							      osip_strdup
							      (my_payload->
							       c_addrtype),
							      osip_strdup
							      (my_payload->
							       c_addr),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_ttl),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_int));
			    }
			}
		    }
		}
	      k++;
	    }
	  while (payload != NULL);
	  if (NULL != sdp_message_m_payload_get (*dest, i, 0))
	    video_qty = 1;
	}
      else
	{
	  do
	    {
	      payload = sdp_message_m_payload_get (remote, i, k);
	      if (payload != NULL)
		{
		  __payload_t *my_payload =
		    osip_negotiation_find_other_payload (config, payload);

		  if (my_payload != NULL)	/* payload is supported */
		    {
		      ret = -1;
		      if (config->fcn_accept_other_codec != NULL)
			ret =
			  config->fcn_accept_other_codec (context, tmp, tmp2,
							  tmp3, payload);
		      if (0 == ret)
			{
			  sdp_message_m_payload_add (*dest, i,
						     osip_strdup (payload));
			  /* rtpmap has no meaning here! */
			  if (my_payload->c_nettype != NULL)
			    {
			      sdp_media_t *med =
				osip_list_get ((*dest)->m_medias, i);

			      if (osip_list_eol (med->c_connections, 0))
				sdp_message_c_connection_add (*dest, i,
							      osip_strdup
							      (my_payload->
							       c_nettype),
							      osip_strdup
							      (my_payload->
							       c_addrtype),
							      osip_strdup
							      (my_payload->
							       c_addr),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_ttl),
							      osip_strdup
							      (my_payload->
							       c_addr_multicast_int));
			    }
			}
		    }
		}
	      k++;
	    }
	  while (payload != NULL);
	}
      i++;
    }
  return 0;
}

int
osip_negotiation_ctx_execute_negotiation (osip_negotiation_t * config,
					  osip_negotiation_ctx_t * context)
{
  int m_lines_that_match = 0;
  sdp_message_t *remote;
  sdp_message_t *local;
  int i;

  if (context == NULL)
    return -1;
  remote = context->remote;
  if (remote == NULL)
    return -1;

  i = sdp_message_init (&local);
  if (i != 0)
    return -1;

  if (0 != strncmp (remote->v_version, "0", 1))
    {
      sdp_message_free (local);
      /*      sdp_context->fcn_wrong_version(context); */
      return 406;		/* Not Acceptable */
    }

  i = sdp_partial_clone (config, context, remote, &local);
  if (i != 0)
    {
      sdp_message_free (local);
      return -1;
    }
  i = sdp_confirm_media (config, context, remote, &local);
  if (i != 0)
    {
      sdp_message_free (local);
      return i;
    }

  i = 0;
  while (!sdp_message_endof_media (local, i))
    {
      /* this is to refuse each line with no codec that matches! */
      if (NULL == sdp_message_m_payload_get (local, i, 0))
	{
	  sdp_media_t *med = osip_list_get ((local)->m_medias, i);
	  char *str = sdp_message_m_payload_get (remote, i, 0);

	  sdp_message_m_payload_add (local, i, osip_strdup (str));
	  osip_free (med->m_port);
	  med->m_port = osip_strdup ("0");	/* refuse this line */
	}
      else
	{			/* number of "m" lines that match */
	  sdp_media_t *med = osip_list_get (local->m_medias, i);

	  m_lines_that_match++;
	  osip_free (med->m_port);
	  /* AMD: use the correct fcn_get_xxx_port method: */
	  if (0 == strcmp (med->m_media, "audio"))
	    {
	      if (config->fcn_get_audio_port != NULL)
		med->m_port = config->fcn_get_audio_port (context, i);
	      else
		med->m_port = osip_strdup ("0");	/* should never happen */
	    }
	  else if (0 == strcmp (med->m_media, "video"))
	    {
	      if (config->fcn_get_video_port != NULL)
		med->m_port = config->fcn_get_video_port (context, i);
	      else
		med->m_port = osip_strdup ("0");	/* should never happen */
	    }
	  else
	    {
	      if (config->fcn_get_other_port != NULL)
		med->m_port = config->fcn_get_other_port (context, i);
	      else
		med->m_port = osip_strdup ("0");	/* should never happen */
	    }
	}
      i++;
    }
  if (m_lines_that_match > 0)
    {
      context->local = local;
      return 200;
    }
  else
    {
      sdp_message_free (local);
      return 415;
    }

}

int
osip_negotiation_sdp_build_offer (osip_negotiation_t * config,
				  osip_negotiation_ctx_t * con,
				  sdp_message_t ** sdp, char *audio_port,
				  char *video_port)
{
  int i;
  int media_line = 0;

  i = sdp_message_init (sdp);
  if (i != 0)
    return -1;

  sdp_message_v_version_set (*sdp, osip_strdup ("0"));

  /* those fields MUST be set */
  sdp_message_o_origin_set (*sdp,
			    osip_strdup (config->o_username),
			    osip_strdup (config->o_session_id),
			    osip_strdup (config->o_session_version),
			    osip_strdup (config->o_nettype),
			    osip_strdup (config->o_addrtype),
			    osip_strdup (config->o_addr));
  sdp_message_s_name_set (*sdp, osip_strdup ("A call"));
  if (config->fcn_set_info != NULL)
    config->fcn_set_info (con, *sdp);
  if (config->fcn_set_uri != NULL)
    config->fcn_set_uri (con, *sdp);
  if (config->fcn_set_emails != NULL)
    config->fcn_set_emails (con, *sdp);
  if (config->fcn_set_phones != NULL)
    config->fcn_set_phones (con, *sdp);
  if (config->c_nettype != NULL)
    sdp_message_c_connection_add (*sdp, -1,
				  osip_strdup (config->c_nettype),
				  osip_strdup (config->c_addrtype),
				  osip_strdup (config->c_addr),
				  osip_strdup (config->c_addr_multicast_ttl),
				  osip_strdup (config->c_addr_multicast_int));

  {				/* offer-answer draft says we must copy the "t=" line */
    int now = time (NULL);
    char *tmp = osip_malloc (15);
    char *tmp2 = osip_malloc (15);

    sprintf (tmp, "%i", now);
    sprintf (tmp2, "%i", now + 3600);

    i = sdp_message_t_time_descr_add (*sdp, tmp, tmp2);
    if (i != 0)
      return -1;
  }
  if (config->fcn_set_attributes != NULL)
    config->fcn_set_attributes (con, *sdp, -1);


  /* add all audio codec */
  if (!osip_list_eol (config->audio_codec, 0))
    {
      int pos = 0;
      __payload_t *my =
	(__payload_t *) osip_list_get (config->audio_codec, pos);

      /* all media MUST have the same PROTO, PORT. */
      sdp_message_m_media_add (*sdp, osip_strdup ("audio"),
			       osip_strdup (audio_port),
			       osip_strdup (my->number_of_port),
			       osip_strdup (my->proto));

      while (!osip_list_eol (config->audio_codec, pos))
	{
	  my = (__payload_t *) osip_list_get (config->audio_codec, pos);
	  sdp_message_m_payload_add (*sdp, media_line,
				     osip_strdup (my->payload));
	  if (my->a_rtpmap != NULL)
	    sdp_message_a_attribute_add (*sdp, media_line,
					 osip_strdup ("rtpmap"),
					 osip_strdup (my->a_rtpmap));
	  pos++;
	}
      media_line++;
    }

  /* add all video codec */
  if (!osip_list_eol (config->video_codec, 0))
    {
      int pos = 0;
      __payload_t *my =
	(__payload_t *) osip_list_get (config->video_codec, pos);

      /* all media MUST have the same PROTO, PORT. */
      sdp_message_m_media_add (*sdp, osip_strdup ("video"),
			       osip_strdup (video_port),
			       osip_strdup (my->number_of_port),
			       osip_strdup (my->proto));

      while (!osip_list_eol (config->video_codec, pos))
	{
	  my = (__payload_t *) osip_list_get (config->video_codec, pos);
	  sdp_message_m_payload_add (*sdp, media_line,
				     osip_strdup (my->payload));
	  if (my->a_rtpmap != NULL)
	    sdp_message_a_attribute_add (*sdp, media_line,
					 osip_strdup ("rtpmap"),
					 osip_strdup (my->a_rtpmap));
	  pos++;
	}
      media_line++;
    }
  return 0;
}

/* build the SDP packet with only one audio codec and one video codec.
 * - Usefull if you don't want to restrict proposal to one codec only -
 * - Limitation, only one codec will be proposed
 */
int
__osip_negotiation_sdp_build_offer (osip_negotiation_t * config,
				    osip_negotiation_ctx_t * con,
				    sdp_message_t ** sdp, char *audio_port,
				    char *video_port, char *audio_codec,
				    char *video_codec)
{
  int i;
  int media_line = 0;

  i = sdp_message_init (sdp);
  if (i != 0)
    return -1;

  sdp_message_v_version_set (*sdp, osip_strdup ("0"));

  /* those fields MUST be set */
  sdp_message_o_origin_set (*sdp,
			    osip_strdup (config->o_username),
			    osip_strdup (config->o_session_id),
			    osip_strdup (config->o_session_version),
			    osip_strdup (config->o_nettype),
			    osip_strdup (config->o_addrtype),
			    osip_strdup (config->o_addr));
  sdp_message_s_name_set (*sdp, osip_strdup ("A call"));
  if (config->fcn_set_info != NULL)
    config->fcn_set_info (con, *sdp);
  if (config->fcn_set_uri != NULL)
    config->fcn_set_uri (con, *sdp);
  if (config->fcn_set_emails != NULL)
    config->fcn_set_emails (con, *sdp);
  if (config->fcn_set_phones != NULL)
    config->fcn_set_phones (con, *sdp);
  if (config->c_nettype != NULL)
    sdp_message_c_connection_add (*sdp, -1,
				  osip_strdup (config->c_nettype),
				  osip_strdup (config->c_addrtype),
				  osip_strdup (config->c_addr),
				  osip_strdup (config->c_addr_multicast_ttl),
				  osip_strdup (config->c_addr_multicast_int));

  {				/* offer-answer draft says we must copy the "t=" line */
    int now = time (NULL);
    char *tmp = osip_malloc (15);
    char *tmp2 = osip_malloc (15);

    sprintf (tmp, "%i", now);
    sprintf (tmp2, "%i", now + 3600);

    i = sdp_message_t_time_descr_add (*sdp, tmp, tmp2);
    if (i != 0)
      return -1;
  }
  if (config->fcn_set_attributes != NULL)
    config->fcn_set_attributes (con, *sdp, -1);


  /* add all audio codec */
  if (audio_codec != NULL)
    {
      if (!osip_list_eol (config->audio_codec, 0))
	{
	  int pos = 0;
	  __payload_t *my =
	    (__payload_t *) osip_list_get (config->audio_codec, pos);

	  while (!osip_list_eol (config->audio_codec, pos))
	    {
	      my = (__payload_t *) osip_list_get (config->audio_codec, pos);
	      if (0 == strcmp (audio_codec, my->payload))
		{
		  /* all media MUST have the same PROTO, PORT. */
		  sdp_message_m_media_add (*sdp, osip_strdup ("audio"),
					   osip_strdup (audio_port),
					   osip_strdup (my->number_of_port),
					   osip_strdup (my->proto));
		  sdp_message_m_payload_add (*sdp, media_line,
					     osip_strdup (my->payload));
		  if (my->a_rtpmap != NULL)
		    sdp_message_a_attribute_add (*sdp, media_line,
						 osip_strdup ("rtpmap"),
						 osip_strdup (my->a_rtpmap));
		  media_line++;
		  break;
		}
	      pos++;
	    }
	}
    }

  /* add all video codec */
  if (video_codec != NULL)
    {
      if (!osip_list_eol (config->video_codec, 0))
	{
	  int pos = 0;
	  __payload_t *my =
	    (__payload_t *) osip_list_get (config->video_codec, pos);

	  while (!osip_list_eol (config->video_codec, pos))
	    {
	      my = (__payload_t *) osip_list_get (config->video_codec, pos);
	      if (0 == strcmp (video_codec, my->payload))
		{
		  /* all media MUST have the same PROTO, PORT. */
		  sdp_message_m_media_add (*sdp, osip_strdup ("video"),
					   osip_strdup (video_port),
					   osip_strdup (my->number_of_port),
					   osip_strdup (my->proto));
		  sdp_message_m_payload_add (*sdp, media_line,
					     osip_strdup (my->payload));
		  if (my->a_rtpmap != NULL)
		    sdp_message_a_attribute_add (*sdp, media_line,
						 osip_strdup ("rtpmap"),
						 osip_strdup (my->a_rtpmap));
		  media_line++;
		  break;
		}
	      pos++;
	    }
	}
    }
  return 0;
}


int
osip_negotiation_sdp_message_put_on_hold (sdp_message_t * sdp)
{
  int pos;
  int pos_media = -1;
  char *rcvsnd;
  int recv_send = -1;

  pos = 0;
  rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
  while (rcvsnd != NULL)
    {
      if (rcvsnd != NULL && 0 == strcmp (rcvsnd, "sendonly"))
	{
	  recv_send = 0;
	}
      else if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "recvonly")
				  || 0 == strcmp (rcvsnd, "sendrecv")))
	{
	  recv_send = 0;
	  sprintf (rcvsnd, "sendonly");
	}
      pos++;
      rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
    }

  pos_media = 0;
  while (!sdp_message_endof_media (sdp, pos_media))
    {
      pos = 0;
      rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
      while (rcvsnd != NULL)
	{
	  if (rcvsnd != NULL && 0 == strcmp (rcvsnd, "sendonly"))
	    {
	      recv_send = 0;
	    }
	  else if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "recvonly")
				      || 0 == strcmp (rcvsnd, "sendrecv")))
	    {
	      recv_send = 0;
	      sprintf (rcvsnd, "sendonly");
	    }
	  pos++;
	  rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
	}
      pos_media++;
    }

  if (recv_send == -1)
    {
      /* we need to add a global attribute with a field set to "sendonly" */
      sdp_message_a_attribute_add (sdp, -1, osip_strdup ("sendonly"), NULL);
    }

  return 0;
}

int
osip_negotiation_sdp_message_put_off_hold (sdp_message_t * sdp)
{
  int pos;
  int pos_media = -1;
  char *rcvsnd;

  pos = 0;
  rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
  while (rcvsnd != NULL)
    {
      if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "sendonly")
			     || 0 == strcmp (rcvsnd, "recvonly")))
	{
	  sprintf (rcvsnd, "sendrecv");
	}
      pos++;
      rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
    }

  pos_media = 0;
  while (!sdp_message_endof_media (sdp, pos_media))
    {
      pos = 0;
      rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
      while (rcvsnd != NULL)
	{
	  if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "sendonly")
				 || 0 == strcmp (rcvsnd, "recvonly")))
	    {
	      sprintf (rcvsnd, "sendrecv");
	    }
	  pos++;
	  rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
	}
      pos_media++;
    }

  return 0;
}
