/* GStreamer
 * Copyright (C) <2005> Wim Taymans <wim@fluendo.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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 <stdlib.h>
#include <string.h>

#include "sdpmessage.h"

#define FREE_STRING(field)	g_free ((field)); (field) = NULL;
#define FREE_ARRAY(field)	\
G_STMT_START {			\
  if (field)			\
    g_array_free (field, TRUE);	\
  field = NULL;			\
} G_STMT_END
#define REPLACE_STRING(field,val)	FREE_STRING(field);field=g_strdup (val);

#define INIT_ARRAY(field,type)	\
G_STMT_START {			\
  if (field)			\
    g_array_set_size (field,0);	\
  else				\
    field = g_array_new (FALSE, TRUE, sizeof(type));	\
} G_STMT_END

#define DEFINE_STRING_SETTER(field)					\
RTSPResult sdp_message_set_##field (SDPMessage *msg, gchar *val) {	\
  g_free (msg->field);							\
  msg->field = g_strdup (val);						\
  return RTSP_OK;							\
}
#define DEFINE_STRING_GETTER(field)					\
char* sdp_message_get_##field (SDPMessage *msg) {			\
  return msg->field;							\
}

#define DEFINE_ARRAY_LEN(field)						\
gint sdp_message_##field##_len (SDPMessage *msg) {			\
  return ((msg)->field->len);						\
}
#define DEFINE_ARRAY_GETTER(method,field,type)					\
type sdp_message_get_##method (SDPMessage *msg, gint i) {		\
  return g_array_index ((msg)->field, type, i);				\
}
#define DEFINE_ARRAY_P_GETTER(method,field,type)					\
type * sdp_message_get_##method (SDPMessage *msg, gint i) {		\
  return &g_array_index ((msg)->field, type, i);				\
}
#define DEFINE_ARRAY_ADDER(method,field,type,dup_method)			\
RTSPResult sdp_message_add_##method (SDPMessage *msg, type val) {	\
  type v = dup_method(val);						\
  g_array_append_val((msg)->field, v);					\
  return RTSP_OK;							\
}



RTSPResult
sdp_message_new (SDPMessage ** msg)
{
  SDPMessage *newmsg;

  if (msg == NULL)
    return RTSP_EINVAL;

  newmsg = g_new0 (SDPMessage, 1);

  *msg = newmsg;

  return sdp_message_init (newmsg);
}

RTSPResult
sdp_message_init (SDPMessage * msg)
{
  if (msg == NULL)
    return RTSP_EINVAL;

  FREE_STRING (msg->version);
  FREE_STRING (msg->origin.username);
  FREE_STRING (msg->origin.sess_id);
  FREE_STRING (msg->origin.sess_version);
  FREE_STRING (msg->origin.nettype);
  FREE_STRING (msg->origin.addrtype);
  FREE_STRING (msg->origin.addr);
  FREE_STRING (msg->session_name);
  FREE_STRING (msg->information);
  FREE_STRING (msg->uri);
  INIT_ARRAY (msg->emails, gchar *);
  INIT_ARRAY (msg->phones, gchar *);
  FREE_STRING (msg->connection.nettype);
  FREE_STRING (msg->connection.addrtype);
  FREE_STRING (msg->connection.address);
  msg->connection.ttl = 0;
  msg->connection.addr_number = 0;
  INIT_ARRAY (msg->bandwidths, SDPBandwidth);
  INIT_ARRAY (msg->times, SDPTime);
  INIT_ARRAY (msg->zones, SDPZone);
  FREE_STRING (msg->key.type);
  FREE_STRING (msg->key.data);
  INIT_ARRAY (msg->attributes, SDPAttribute);
  INIT_ARRAY (msg->medias, SDPMedia);

  return RTSP_OK;
}

RTSPResult
sdp_message_clean (SDPMessage * msg)
{
  if (msg == NULL)
    return RTSP_EINVAL;

  FREE_ARRAY (msg->emails);
  FREE_ARRAY (msg->phones);
  FREE_ARRAY (msg->bandwidths);
  FREE_ARRAY (msg->times);
  FREE_ARRAY (msg->zones);
  FREE_ARRAY (msg->attributes);
  FREE_ARRAY (msg->medias);

  return RTSP_OK;
}

RTSPResult
sdp_message_free (SDPMessage * msg)
{
  if (msg == NULL)
    return RTSP_EINVAL;

  sdp_message_clean (msg);

  g_free (msg);

  return RTSP_OK;
}


RTSPResult
sdp_media_new (SDPMedia ** media)
{
  SDPMedia *newmedia;

  if (media == NULL)
    return RTSP_EINVAL;

  newmedia = g_new0 (SDPMedia, 1);

  *media = newmedia;

  return sdp_media_init (newmedia);
}

RTSPResult
sdp_media_init (SDPMedia * media)
{
  if (media == NULL)
    return RTSP_EINVAL;

  FREE_STRING (media->media);
  media->port = 0;
  media->num_ports = 0;
  FREE_STRING (media->proto);
  INIT_ARRAY (media->fmts, gchar *);
  FREE_STRING (media->information);
  INIT_ARRAY (media->connections, SDPConnection);
  INIT_ARRAY (media->bandwidths, SDPBandwidth);
  FREE_STRING (media->key.type);
  FREE_STRING (media->key.data);
  INIT_ARRAY (media->attributes, SDPAttribute);

  return RTSP_OK;
}

DEFINE_STRING_SETTER (version);
DEFINE_STRING_GETTER (version);

RTSPResult
sdp_message_set_origin (SDPMessage * msg, gchar * username, gchar * sess_id,
    gchar * sess_version, gchar * nettype, gchar * addrtype, gchar * addr)
{
  REPLACE_STRING (msg->origin.username, username);
  REPLACE_STRING (msg->origin.sess_id, sess_id);
  REPLACE_STRING (msg->origin.sess_version, sess_version);
  REPLACE_STRING (msg->origin.nettype, nettype);
  REPLACE_STRING (msg->origin.addrtype, addrtype);
  REPLACE_STRING (msg->origin.addr, addr);
  return RTSP_OK;
}

SDPOrigin *
sdp_message_get_origin (SDPMessage * msg)
{
  return &msg->origin;
}

DEFINE_STRING_SETTER (session_name);
DEFINE_STRING_GETTER (session_name);
DEFINE_STRING_SETTER (information);
DEFINE_STRING_GETTER (information);
DEFINE_STRING_SETTER (uri);
DEFINE_STRING_GETTER (uri);

DEFINE_ARRAY_LEN (emails);
DEFINE_ARRAY_GETTER (email, emails, gchar *);
DEFINE_ARRAY_ADDER (email, emails, gchar *, g_strdup);

DEFINE_ARRAY_LEN (phones);
DEFINE_ARRAY_GETTER (phone, phones, gchar *);
DEFINE_ARRAY_ADDER (phone, phones, gchar *, g_strdup);

RTSPResult
sdp_message_set_connection (SDPMessage * msg, gchar * nettype, gchar * addrtype,
    gchar * address, gint ttl, gint addr_number)
{
  REPLACE_STRING (msg->connection.nettype, nettype);
  REPLACE_STRING (msg->connection.addrtype, addrtype);
  REPLACE_STRING (msg->connection.address, address);
  msg->connection.ttl = ttl;
  msg->connection.addr_number = addr_number;
  return RTSP_OK;
}

SDPConnection *
sdp_message_get_connection (SDPMessage * msg)
{
  return &msg->connection;
}

DEFINE_ARRAY_LEN (bandwidths);
DEFINE_ARRAY_P_GETTER (bandwidth, bandwidths, SDPBandwidth);

RTSPResult
sdp_message_add_bandwidth (SDPMessage * msg, gchar * bwtype, gint bandwidth)
{
  SDPBandwidth bw;

  bw.bwtype = g_strdup (bwtype);
  bw.bandwidth = bandwidth;

  g_array_append_val (msg->bandwidths, bw);

  return RTSP_OK;
}

DEFINE_ARRAY_LEN (times);
DEFINE_ARRAY_P_GETTER (time, times, SDPTime);

RTSPResult
sdp_message_add_time (SDPMessage * msg, gchar * time)
{
  return RTSP_OK;
}

DEFINE_ARRAY_LEN (zones);
DEFINE_ARRAY_P_GETTER (zone, zones, SDPZone);
RTSPResult
sdp_message_add_zone (SDPMessage * msg, gchar * time, gchar * typed_time)
{
  SDPZone zone;

  zone.time = g_strdup (time);
  zone.typed_time = g_strdup (typed_time);

  g_array_append_val (msg->zones, zone);

  return RTSP_OK;
}

RTSPResult
sdp_message_set_key (SDPMessage * msg, gchar * type, gchar * data)
{
  REPLACE_STRING (msg->key.type, type);
  REPLACE_STRING (msg->key.data, data);
  return RTSP_OK;
}

SDPKey *
sdp_message_get_key (SDPMessage * msg)
{
  return &msg->key;
}


DEFINE_ARRAY_LEN (attributes);
DEFINE_ARRAY_P_GETTER (attribute, attributes, SDPAttribute);
gchar *
sdp_message_get_attribute_val (SDPMessage * msg, gchar * key)
{
  gint i;

  for (i = 0; i < msg->attributes->len; i++) {
    SDPAttribute *attr;

    attr = &g_array_index (msg->attributes, SDPAttribute, i);
    if (!strcmp (attr->key, key))
      return attr->value;
  }
  return NULL;
}

RTSPResult
sdp_message_add_attribute (SDPMessage * msg, gchar * key, gchar * value)
{
  SDPAttribute attr;

  attr.key = g_strdup (key);
  attr.value = g_strdup (value);

  g_array_append_val (msg->attributes, attr);

  return RTSP_OK;
}

DEFINE_ARRAY_LEN (medias);
DEFINE_ARRAY_P_GETTER (media, medias, SDPMedia);
RTSPResult
sdp_message_add_media (SDPMessage * msg, SDPMedia * media)
{
  gint len;
  SDPMedia *nmedia;

  len = msg->medias->len;
  g_array_set_size (msg->medias, len + 1);
  nmedia = &g_array_index (msg->medias, SDPMedia, len);

  memcpy (nmedia, media, sizeof (SDPMedia));

  return RTSP_OK;
}

/* media access */

RTSPResult
sdp_media_add_attribute (SDPMedia * media, gchar * key, gchar * value)
{
  SDPAttribute attr;

  attr.key = g_strdup (key);
  attr.value = g_strdup (value);

  g_array_append_val (media->attributes, attr);

  return RTSP_OK;
}

RTSPResult
sdp_media_add_bandwidth (SDPMedia * media, gchar * bwtype, gint bandwidth)
{
  SDPBandwidth bw;

  bw.bwtype = g_strdup (bwtype);
  bw.bandwidth = bandwidth;

  g_array_append_val (media->bandwidths, bw);

  return RTSP_OK;
}

RTSPResult
sdp_media_add_format (SDPMedia * media, gchar * format)
{
  gchar *fmt;

  fmt = g_strdup (format);

  g_array_append_val (media->fmts, fmt);

  return RTSP_OK;
}

gchar *
sdp_media_get_attribute_val (SDPMedia * media, gchar * key)
{
  gint i;

  for (i = 0; i < media->attributes->len; i++) {
    SDPAttribute *attr;

    attr = &g_array_index (media->attributes, SDPAttribute, i);
    if (!strcmp (attr->key, key))
      return attr->value;
  }
  return NULL;
}

gchar *
sdp_media_get_format (SDPMedia * media, gint i)
{
  if (i >= media->fmts->len)
    return NULL;
  return g_array_index (media->fmts, gchar *, i);
}

static void
read_string (gchar * dest, gint size, gchar ** src)
{
  gint idx;

  idx = 0;
  /* skip spaces */
  while (g_ascii_isspace (**src))
    (*src)++;

  while (!g_ascii_isspace (**src) && **src != '\0') {
    if (idx < size - 1)
      dest[idx++] = **src;
    (*src)++;
  }
  if (size > 0)
    dest[idx] = '\0';
}

static void
read_string_del (gchar * dest, gint size, gchar del, gchar ** src)
{
  gint idx;

  idx = 0;
  /* skip spaces */
  while (g_ascii_isspace (**src))
    (*src)++;

  while (**src != del && **src != '\0') {
    if (idx < size - 1)
      dest[idx++] = **src;
    (*src)++;
  }
  if (size > 0)
    dest[idx] = '\0';
}

enum
{
  SDP_SESSION,
  SDP_MEDIA,
};

typedef struct
{
  gint state;
  SDPMessage *msg;
  SDPMedia *media;
} SDPContext;

static gboolean
sdp_parse_line (SDPContext * c, gchar type, gchar * buffer)
{
  gchar str[4096];
  gchar *p = buffer;

#define READ_STRING(field) read_string (str, sizeof(str), &p);REPLACE_STRING (field, str);
#define READ_INT(field) read_string (str, sizeof(str), &p);field = atoi(str);

  switch (type) {
    case 'v':
      if (buffer[0] != '0')
        g_warning ("wrong SDP version");
      sdp_message_set_version (c->msg, buffer);
      break;
    case 'o':
      READ_STRING (c->msg->origin.username);
      READ_STRING (c->msg->origin.sess_id);
      READ_STRING (c->msg->origin.sess_version);
      READ_STRING (c->msg->origin.nettype);
      READ_STRING (c->msg->origin.addrtype);
      READ_STRING (c->msg->origin.addr);
      break;
    case 's':
      REPLACE_STRING (c->msg->session_name, buffer);
      break;
    case 'i':
      if (c->state == SDP_SESSION) {
        REPLACE_STRING (c->msg->information, buffer);
      } else {
        REPLACE_STRING (c->media->information, buffer);
      }
      break;
    case 'u':
      REPLACE_STRING (c->msg->uri, buffer);
      break;
    case 'e':
      sdp_message_add_email (c->msg, buffer);
      break;
    case 'p':
      sdp_message_add_phone (c->msg, buffer);
      break;
    case 'c':
      READ_STRING (c->msg->connection.nettype);
      READ_STRING (c->msg->connection.addrtype);
      READ_STRING (c->msg->connection.address);
      READ_INT (c->msg->connection.ttl);
      READ_INT (c->msg->connection.addr_number);
      break;
    case 'b':
    {
      gchar str2[4096];

      read_string_del (str, sizeof (str), ':', &p);
      read_string (str2, sizeof (str2), &p);
      if (c->state == SDP_SESSION)
        sdp_message_add_bandwidth (c->msg, str, atoi (str2));
      else
        sdp_media_add_bandwidth (c->media, str, atoi (str2));
      break;
    }
    case 't':
      break;
    case 'k':

      break;
    case 'a':
      read_string_del (str, sizeof (str), ':', &p);
      if (p != '\0')
        p++;
      if (c->state == SDP_SESSION)
        sdp_message_add_attribute (c->msg, str, p);
      else
        sdp_media_add_attribute (c->media, str, p);
      break;
    case 'm':
    {
      gchar *slash;
      SDPMedia nmedia = { 0 };

      c->state = SDP_MEDIA;
      sdp_media_init (&nmedia);

      READ_STRING (nmedia.media);
      read_string (str, sizeof (str), &p);
      slash = g_strrstr (str, "/");
      if (slash) {
        *slash = '\0';
        nmedia.port = atoi (str);
        nmedia.num_ports = atoi (slash + 1);
      } else {
        nmedia.port = atoi (str);
        nmedia.num_ports = -1;
      }
      READ_STRING (nmedia.proto);
      do {
        read_string (str, sizeof (str), &p);
        sdp_media_add_format (&nmedia, str);
      } while (*p != '\0');

      sdp_message_add_media (c->msg, &nmedia);
      c->media =
          &g_array_index (c->msg->medias, SDPMedia, c->msg->medias->len - 1);
      break;
    }
    default:
      break;
  }
  return TRUE;
}

RTSPResult
sdp_message_parse_buffer (guint8 * data, guint size, SDPMessage * msg)
{
  gchar *p;
  SDPContext c;
  gchar type;
  gchar buffer[4096];
  gint idx = 0;

  if (msg == NULL || data == NULL || size == 0)
    return RTSP_EINVAL;

  c.state = SDP_SESSION;
  c.msg = msg;
  c.media = NULL;

  p = (gchar *) data;
  while (TRUE) {
    while (g_ascii_isspace (*p))
      p++;

    type = *p++;
    if (type == '\0')
      break;

    if (*p != '=')
      goto line_done;
    p++;

    idx = 0;
    while (*p != '\n' && *p != '\r' && *p != '\0') {
      if (idx < sizeof (buffer) - 1)
        buffer[idx++] = *p;
      p++;
    }
    buffer[idx] = '\0';
    sdp_parse_line (&c, type, buffer);

  line_done:
    while (*p != '\n' && *p != '\0')
      p++;
    if (*p == '\n')
      p++;
  }

  return RTSP_OK;
}

static void
print_media (SDPMedia * media)
{
  g_print ("   media:       '%s'\n", media->media);
  g_print ("   port:        '%d'\n", media->port);
  g_print ("   num_ports:   '%d'\n", media->num_ports);
  g_print ("   proto:       '%s'\n", media->proto);
  if (media->fmts->len > 0) {
    gint i;

    g_print ("   formats:\n");
    for (i = 0; i < media->fmts->len; i++) {
      g_print ("    format  '%s'\n", g_array_index (media->fmts, gchar *, i));
    }
  }
  g_print ("   information: '%s'\n", media->information);
  g_print ("   key:\n");
  g_print ("    type:       '%s'\n", media->key.type);
  g_print ("    data:       '%s'\n", media->key.data);
  if (media->attributes->len > 0) {
    gint i;

    g_print ("   attributes:\n");
    for (i = 0; i < media->attributes->len; i++) {
      SDPAttribute *attr = &g_array_index (media->attributes, SDPAttribute, i);

      g_print ("    attribute '%s' : '%s'\n", attr->key, attr->value);
    }
  }
}

RTSPResult
sdp_message_dump (SDPMessage * msg)
{
  if (msg == NULL)
    return RTSP_EINVAL;

  g_print ("sdp packet %p:\n", msg);
  g_print (" version:       '%s'\n", msg->version);
  g_print (" origin:\n");
  g_print ("  username:     '%s'\n", msg->origin.username);
  g_print ("  sess_id:      '%s'\n", msg->origin.sess_id);
  g_print ("  sess_version: '%s'\n", msg->origin.sess_version);
  g_print ("  nettype:      '%s'\n", msg->origin.nettype);
  g_print ("  addrtype:     '%s'\n", msg->origin.addrtype);
  g_print ("  addr:         '%s'\n", msg->origin.addr);
  g_print (" session_name:  '%s'\n", msg->session_name);
  g_print (" information:   '%s'\n", msg->information);
  g_print (" uri:           '%s'\n", msg->uri);
  if (msg->emails->len > 0) {
    gint i;

    g_print (" emails:\n");
    for (i = 0; i < msg->emails->len; i++) {
      g_print ("  email '%s'\n", g_array_index (msg->emails, gchar *, i));
    }
  }
  if (msg->phones->len > 0) {
    gint i;

    g_print (" phones:\n");
    for (i = 0; i < msg->phones->len; i++) {
      g_print ("  phone '%s'\n", g_array_index (msg->phones, gchar *, i));
    }
  }
  g_print (" connection:\n");
  g_print ("  nettype:      '%s'\n", msg->connection.nettype);
  g_print ("  addrtype:     '%s'\n", msg->connection.addrtype);
  g_print ("  address:      '%s'\n", msg->connection.address);
  g_print ("  ttl:          '%d'\n", msg->connection.ttl);
  g_print ("  addr_number:  '%d'\n", msg->connection.addr_number);
  g_print (" key:\n");
  g_print ("  type:         '%s'\n", msg->key.type);
  g_print ("  data:         '%s'\n", msg->key.data);
  if (msg->attributes->len > 0) {
    gint i;

    g_print (" attributes:\n");
    for (i = 0; i < msg->attributes->len; i++) {
      SDPAttribute *attr = &g_array_index (msg->attributes, SDPAttribute, i);

      g_print ("  attribute '%s' : '%s'\n", attr->key, attr->value);
    }
  }
  if (msg->medias->len > 0) {
    gint i;

    g_print (" medias:\n");
    for (i = 0; i < msg->medias->len; i++) {
      g_print ("  media %d:\n", i);
      print_media (&g_array_index (msg->medias, SDPMedia, i));
    }
  }


  return RTSP_OK;
}
