/*
 * Farstream - Farstream RTP Keyunit request manager
 *
 * Copyright 2011 Collabora Ltd.
 *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
 * Copyright 2011 Nokia Corp.
 *
 * fs-rtp-keyunit-request.h - A Farstream RTP Key Unit request manager
 *
 * 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-keyunit-manager.h"

#include <string.h>

#include <gst/rtp/gstrtcpbuffer.h>

/* Remove this line as soon as the types are merged
 * in gst-plugins-base
 */
#define GST_RTCP_PSFB_TYPE_FIR 4


struct _FsRtpKeyunitManagerClass
{
  GstObjectClass parent_class;
};

struct _FsRtpKeyunitManager
{
  GstObject parent;

  GObject *rtpbin_internal_session;

  GstElement *codecbin;
  gulong rtcp_feedback_id;
};


G_DEFINE_TYPE (FsRtpKeyunitManager, fs_rtp_keyunit_manager, GST_TYPE_OBJECT);

static void fs_rtp_keyunit_manager_dispose (GObject *obj);

static void
fs_rtp_keyunit_manager_class_init (FsRtpKeyunitManagerClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = fs_rtp_keyunit_manager_dispose;
}

static void
fs_rtp_keyunit_manager_init (FsRtpKeyunitManager *self)
{
}

static void
fs_rtp_keyunit_manager_dispose (GObject *obj)
{
  FsRtpKeyunitManager *self = FS_RTP_KEYUNIT_MANAGER (obj);

  GST_OBJECT_LOCK (self);

  if (self->rtcp_feedback_id)
    g_signal_handler_disconnect (self->rtpbin_internal_session,
        self->rtcp_feedback_id);
  self->rtcp_feedback_id = 0;

  if (self->rtpbin_internal_session)
    g_object_unref (self->rtpbin_internal_session);
  self->rtpbin_internal_session = NULL;

  if (self->codecbin)
    g_object_unref (self->codecbin);
  self->codecbin = NULL;

  GST_OBJECT_UNLOCK (self);

  G_OBJECT_CLASS (fs_rtp_keyunit_manager_parent_class)->dispose (obj);
}

FsRtpKeyunitManager *
fs_rtp_keyunit_manager_new (GObject *rtpbin_internal_session)
{
  FsRtpKeyunitManager *self =  g_object_new (FS_TYPE_RTP_KEYUNIT_MANAGER, NULL);

  self->rtpbin_internal_session = g_object_ref (rtpbin_internal_session);

  return self;
}

struct ElementProperty {
  gchar *element;
  gchar *property;
  guint value;
};

static const struct ElementProperty no_keyframe_property[] = {
  {"x264enc", "key-int-max", G_MAXINT},
  {"dsph263enc", "keyframe-interval", 600},
  {"dsph264enc", "keyframe-interval", 600},
  {"dsphdh264enc", "keyframe-interval", 0},
  {NULL, NULL, 0}
};

static void
disable_keyframes (const GValue *item, gpointer user_data)
{
  GstElement *element = g_value_get_object (item);
  GstElementFactory *factory;
  const gchar *factory_name;
  guint i;

  factory = gst_element_get_factory (element);
  if (!factory)
    return;

  factory_name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory));
  if (!factory_name)
    return;

  for (i = 0; no_keyframe_property[i].element; i++)
    if (!strcmp (no_keyframe_property[i].element, factory_name))
      g_object_set (element, no_keyframe_property[i].property,
          no_keyframe_property[i].value, NULL);

}

static void
fs_rtp_keyunit_manager_disable_keyframes (GstElement *codecbin)
{
  GstIterator *iter;

  iter = gst_bin_iterate_recurse (GST_BIN (codecbin));

  while (gst_iterator_foreach (iter, disable_keyframes, NULL) ==
      GST_ITERATOR_RESYNC)
    gst_iterator_resync (iter);

  gst_iterator_free (iter);
  g_object_unref (codecbin);
}

static void
on_feedback_rtcp (GObject *rtpsession, GstRTCPType type, GstRTCPFBType fbtype,
    guint sender_ssrc, guint media_ssrc, GstBuffer *fci, gpointer user_data)
{
  FsRtpKeyunitManager *self = FS_RTP_KEYUNIT_MANAGER (user_data);
  guint32 local_ssrc;
  GstElement *codecbin;

  if (type != GST_RTCP_TYPE_PSFB)
    return;

  g_object_get (rtpsession, "internal-ssrc", &local_ssrc, NULL);

  /* Let's check if the PLI or FIR is for us */
  if (fbtype == GST_RTCP_PSFB_TYPE_PLI)
  {
    if (media_ssrc != local_ssrc)
      return;
  }
  else if (fbtype == GST_RTCP_PSFB_TYPE_FIR)
  {
    guint position = 0;
    gboolean our_request = FALSE;
    GstMapInfo mapinfo;

    if (!gst_buffer_map (fci, &mapinfo, GST_MAP_READ))
      return;

    for (position = 0; position < mapinfo.size ; position += 8) {
      guint8 *data = mapinfo.data + position;
      guint32 ssrc;

      ssrc = GST_READ_UINT32_BE (data);

      if (ssrc == local_ssrc) {
        our_request = TRUE;
        break;
      }
    }
    gst_buffer_unmap (fci, &mapinfo);
    if (!our_request)
      return;
  }
  else
  {
    return;
  }

  GST_OBJECT_LOCK (self);
  codecbin = self->codecbin;
  self->codecbin = NULL;
  if (self->rtcp_feedback_id)
    g_signal_handler_disconnect (self->rtpbin_internal_session,
        self->rtcp_feedback_id);
  self->rtcp_feedback_id = 0;
  GST_OBJECT_UNLOCK (self);

  if (!codecbin)
    return;

  fs_rtp_keyunit_manager_disable_keyframes (codecbin);
}

gboolean
fs_rtp_keyunit_manager_has_key_request_feedback (FsCodec *send_codec)
{
  return !!fs_codec_get_feedback_parameter (send_codec, "nack", "pli", NULL);
}

void
fs_rtp_keyunit_manager_codecbin_changed (FsRtpKeyunitManager *self,
    GstElement *codecbin, FsCodec *send_codec)
{
  GST_OBJECT_LOCK (self);

  if (self->codecbin)
    g_object_unref (self->codecbin);
  self->codecbin = NULL;

  if (fs_rtp_keyunit_manager_has_key_request_feedback (send_codec))
  {
    self->codecbin = g_object_ref (codecbin);
    if (!self->rtcp_feedback_id)
      self->rtcp_feedback_id = g_signal_connect_object (
        self->rtpbin_internal_session, "on-feedback-rtcp",
        G_CALLBACK (on_feedback_rtcp), self, 0);
  }
  else
  {
    if (self->rtcp_feedback_id)
      g_signal_handler_disconnect (self->rtpbin_internal_session,
          self->rtcp_feedback_id);
    self->rtcp_feedback_id = 0;
  }

  GST_OBJECT_UNLOCK (self);
}
