/* GStreamer
 * Copyright (C) <2007> 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.
 */

/**
 * SECTION:element-rtpclient
 * @short_description: handle media from one RTP client
 * @see_also: rtpjitterbuffer, rtpbin, rtpsession
 *
 * <refsect2>
 * <para>
 * This element handles RTP data from one client. It accepts multiple RTP streams that
 * should be synchronized together.
 * </para>
 * <para>
 * Normally the SSRCs that map to the same CNAME (as given in the RTCP SDES messages)
 * should be synchronized.
 * </para>
 * <title>Example pipelines</title>
 * <para>
 * <programlisting>
 * </programlisting>
 * </para>
 * </refsect2>
 *
 * Last reviewed on 2007-04-02 (0.10.6)
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>

#include "gstrtpclient.h"

/* elementfactory information */
static const GstElementDetails rtpclient_details =
GST_ELEMENT_DETAILS ("RTP Client",
    "Filter/Editor/Video",
    "Implement an RTP client",
    "Wim Taymans <wim@fluendo.com>");

/* sink pads */
static GstStaticPadTemplate rtpclient_rtp_sink_template =
GST_STATIC_PAD_TEMPLATE ("rtp_sink_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("application/x-rtp")
    );

static GstStaticPadTemplate rtpclient_sync_sink_template =
GST_STATIC_PAD_TEMPLATE ("sync_sink_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("application/x-rtcp")
    );

/* src pads */
static GstStaticPadTemplate rtpclient_rtp_src_template =
GST_STATIC_PAD_TEMPLATE ("rtp_src_%d_%d",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS ("application/x-rtp")
    );

#define GST_RTP_CLIENT_GET_PRIVATE(obj)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTP_CLIENT, GstRTPClientPrivate))

struct _GstRTPClientPrivate
{
  gint foo;
};

/* all the info needed to handle the stream with SSRC */
typedef struct
{
  GstRTPClient *client;

  /* the SSRC of this stream */
  guint32 ssrc;

  /* RTP and RTCP in */
  GstPad *rtp_sink;
  GstPad *sync_sink;

  /* the jitterbuffer */
  GstElement *jitterbuffer;
  /* the payload demuxer */
  GstElement *ptdemux;
  /* the new-pad signal */
  gulong new_pad_sig;
} GstRTPClientStream;

/* the PT demuxer found a new payload type */
static void
new_pad (GstElement * element, GstPad * pad, GstRTPClientStream * stream)
{
}

/* create a new stream for SSRC.
 *
 * We create a jitterbuffer and an payload demuxer for the SSRC. The sinkpad of
 * the jitterbuffer is ghosted to the bin. We connect a pad-added signal to
 * rtpptdemux so that we can ghost the payload pads outside.
 *
 *       +-----------------+     +---------------+
 *       | rtpjitterbuffer |     |  rtpptdemux   |
 *   +- sink              src - sink             |
 *  /    +-----------------+     +---------------+
 *
 */
static GstRTPClientStream *
create_stream (GstRTPClient * rtpclient, guint32 ssrc)
{
  GstRTPClientStream *stream;
  gchar *name;
  GstPad *srcpad, *sinkpad;
  GstPadLinkReturn res;

  stream = g_new0 (GstRTPClientStream, 1);
  stream->ssrc = ssrc;
  stream->client = rtpclient;

  stream->jitterbuffer = gst_element_factory_make ("rtpjitterbuffer", NULL);
  if (!stream->jitterbuffer)
    goto no_jitterbuffer;

  stream->ptdemux = gst_element_factory_make ("rtpptdemux", NULL);
  if (!stream->ptdemux)
    goto no_ptdemux;

  /* add elements to bin */
  gst_bin_add (GST_BIN_CAST (rtpclient), stream->jitterbuffer);
  gst_bin_add (GST_BIN_CAST (rtpclient), stream->ptdemux);

  /* link jitterbuffer and PT demuxer */
  srcpad = gst_element_get_pad (stream->jitterbuffer, "src");
  sinkpad = gst_element_get_pad (stream->ptdemux, "sink");
  res = gst_pad_link (srcpad, sinkpad);
  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);

  if (res != GST_PAD_LINK_OK)
    goto could_not_link;

  /* add stream to list */
  rtpclient->streams = g_list_prepend (rtpclient->streams, stream);

  /* ghost sinkpad */
  name = g_strdup_printf ("rtp_sink_%d", ssrc);
  sinkpad = gst_element_get_pad (stream->jitterbuffer, "sink");
  stream->rtp_sink = gst_ghost_pad_new (name, sinkpad);
  gst_object_unref (sinkpad);
  g_free (name);
  gst_element_add_pad (GST_ELEMENT_CAST (rtpclient), stream->rtp_sink);

  /* add signal to ptdemuxer */
  stream->new_pad_sig =
      g_signal_connect (G_OBJECT (stream->ptdemux), "pad-added",
      G_CALLBACK (new_pad), stream);

  return stream;

  /* ERRORS */
no_jitterbuffer:
  {
    g_free (stream);
    g_warning ("could not create rtpjitterbuffer element");
    return NULL;
  }
no_ptdemux:
  {
    gst_object_unref (stream->jitterbuffer);
    g_free (stream);
    g_warning ("could not create rtpptdemux element");
    return NULL;
  }
could_not_link:
  {
    gst_bin_remove (GST_BIN_CAST (rtpclient), stream->jitterbuffer);
    gst_bin_remove (GST_BIN_CAST (rtpclient), stream->ptdemux);
    g_free (stream);
    g_warning ("could not link jitterbuffer and rtpptdemux element");
    return NULL;
  }
}

#if 0
static void
free_stream (GstRTPClientStream * stream)
{
  gst_object_unref (stream->jitterbuffer);
  g_free (stream);
}
#endif

/* find the stream for the given SSRC, return NULL if the stream did not exist
 */
static GstRTPClientStream *
find_stream_by_ssrc (GstRTPClient * client, guint32 ssrc)
{
  GstRTPClientStream *stream;
  GList *walk;

  for (walk = client->streams; walk; walk = g_list_next (walk)) {
    stream = (GstRTPClientStream *) walk->data;
    if (stream->ssrc == ssrc)
      return stream;
  }
  return NULL;
}

/* signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
  PROP_0
};

/* GObject vmethods */
static void gst_rtp_client_finalize (GObject * object);
static void gst_rtp_client_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_rtp_client_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

/* GstElement vmethods */
static GstStateChangeReturn gst_rtp_client_change_state (GstElement * element,
    GstStateChange transition);
static GstPad *gst_rtp_client_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name);
static void gst_rtp_client_release_pad (GstElement * element, GstPad * pad);

/*static guint gst_rtp_client_signals[LAST_SIGNAL] = { 0 }; */

GST_BOILERPLATE (GstRTPClient, gst_rtp_client, GstBin, GST_TYPE_BIN);

static void
gst_rtp_client_base_init (gpointer klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  /* sink pads */
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&rtpclient_rtp_sink_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&rtpclient_sync_sink_template));

  /* src pads */
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&rtpclient_rtp_src_template));

  gst_element_class_set_details (element_class, &rtpclient_details);
}

static void
gst_rtp_client_class_init (GstRTPClientClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  g_type_class_add_private (klass, sizeof (GstRTPClientPrivate));

  gobject_class->finalize = gst_rtp_client_finalize;
  gobject_class->set_property = gst_rtp_client_set_property;
  gobject_class->get_property = gst_rtp_client_get_property;

  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_rtp_client_change_state);
  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_rtp_client_request_new_pad);
  gstelement_class->release_pad =
      GST_DEBUG_FUNCPTR (gst_rtp_client_release_pad);
}

static void
gst_rtp_client_init (GstRTPClient * rtpclient, GstRTPClientClass * klass)
{
  rtpclient->priv = GST_RTP_CLIENT_GET_PRIVATE (rtpclient);
}

static void
gst_rtp_client_finalize (GObject * object)
{
  GstRTPClient *rtpclient;

  rtpclient = GST_RTP_CLIENT (object);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_rtp_client_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstRTPClient *rtpclient;

  rtpclient = GST_RTP_CLIENT (object);

  switch (prop_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_rtp_client_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstRTPClient *rtpclient;

  rtpclient = GST_RTP_CLIENT (object);

  switch (prop_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static GstStateChangeReturn
gst_rtp_client_change_state (GstElement * element, GstStateChange transition)
{
  GstStateChangeReturn res;
  GstRTPClient *rtpclient;

  rtpclient = GST_RTP_CLIENT (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      break;
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      break;
    default:
      break;
  }

  res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      break;
    case GST_STATE_CHANGE_READY_TO_NULL:
      break;
    default:
      break;
  }
  return res;
}

/* We have 2 request pads (rtp_sink_%d and sync_sink_%d), the %d is assumed to
 * be the SSRC of the stream.
 *
 * We require that the rtp pad is requested first for a particular SSRC, then
 * (optionaly) the sync pad can be requested. If no sync pad is requested, no
 * sync information can be exchanged for this stream.
 */
static GstPad *
gst_rtp_client_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name)
{
  GstRTPClient *rtpclient;
  GstElementClass *klass;
  GstPadTemplate *rtp_sink_templ, *sync_sink_templ;
  guint32 ssrc;
  GstRTPClientStream *stream;
  GstPad *result;

  g_return_val_if_fail (templ != NULL, NULL);
  g_return_val_if_fail (GST_IS_RTP_CLIENT (element), NULL);

  if (templ->direction != GST_PAD_SINK)
    goto wrong_direction;

  rtpclient = GST_RTP_CLIENT (element);
  klass = GST_ELEMENT_GET_CLASS (element);

  /* figure out the template */
  rtp_sink_templ = gst_element_class_get_pad_template (klass, "rtp_sink_%d");
  sync_sink_templ = gst_element_class_get_pad_template (klass, "sync_sink_%d");

  if (templ != rtp_sink_templ && templ != sync_sink_templ)
    goto wrong_template;

  if (templ == rtp_sink_templ) {
    /* create new rtp sink pad. If a stream with the pad number already exists
     * we have an error, else we create the sinkpad, add a jitterbuffer and
     * ptdemuxer. */
    if (name == NULL || strlen (name) < 9)
      goto no_name;

    ssrc = atoi (&name[9]);

    /* see if a stream with that name exists, if so we have an error. */
    stream = find_stream_by_ssrc (rtpclient, ssrc);
    if (stream != NULL)
      goto stream_exists;

    /* ok, create new stream */
    stream = create_stream (rtpclient, ssrc);
    if (stream == NULL)
      goto stream_not_found;

    result = stream->rtp_sink;
  } else {
    /* create new rtp sink pad. We can only do this if the RTP pad was
     * requested before, meaning the session with the padnumber must exist. */
    if (name == NULL || strlen (name) < 10)
      goto no_name;

    ssrc = atoi (&name[10]);

    /* find stream */
    stream = find_stream_by_ssrc (rtpclient, ssrc);
    if (stream == NULL)
      goto stream_not_found;

    stream->sync_sink =
        gst_pad_new_from_static_template (&rtpclient_sync_sink_template, name);
    gst_element_add_pad (GST_ELEMENT_CAST (rtpclient), stream->sync_sink);

    result = stream->sync_sink;
  }

  return result;

  /* ERRORS */
wrong_direction:
  {
    g_warning ("rtpclient: request pad that is not a SINK pad");
    return NULL;
  }
wrong_template:
  {
    g_warning ("rtpclient: this is not our template");
    return NULL;
  }
no_name:
  {
    g_warning ("rtpclient: no padname was specified");
    return NULL;
  }
stream_exists:
  {
    g_warning ("rtpclient: stream with SSRC %d already registered", ssrc);
    return NULL;
  }
stream_not_found:
  {
    g_warning ("rtpclient: stream with SSRC %d not yet registered", ssrc);
    return NULL;
  }
}

static void
gst_rtp_client_release_pad (GstElement * element, GstPad * pad)
{
}