/* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:rtsp-onvif-media
 * @short_description: The ONVIF media pipeline
 * @see_also: #GstRTSPMedia, #GstRTSPOnvifMediaFactory, #GstRTSPStream, #GstRTSPSession,
 *     #GstRTSPSessionMedia
 *
 * a #GstRTSPOnvifMedia contains the complete GStreamer pipeline to manage the
 * streaming to the clients. The actual data transfer is done by the
 * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
 *
 * On top of #GstRTSPMedia this subclass adds special ONVIF features.
 * Special ONVIF features that are currently supported is a backchannel for
 * the client to send back media to the server in a normal PLAY media. To
 * handle the ONVIF backchannel, a #GstRTSPOnvifMediaFactory and
 * #GstRTSPOnvifServer has to be used.
 *
 * Since: 1.14
 *
 */

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

#include "rtsp-onvif-media.h"
#include "rtsp-latency-bin.h"

struct GstRTSPOnvifMediaPrivate
{
  GMutex lock;
  guint backchannel_bandwidth;
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPOnvifMedia, gst_rtsp_onvif_media,
    GST_TYPE_RTSP_MEDIA);

static gboolean
gst_rtsp_onvif_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
    GstSDPInfo * info)
{
  guint i, n_streams;
  gchar *rangestr;
  gboolean res;

  /* Mostly a copy of gst_rtsp_sdp_from_media() which handles the backchannel
   * stream separately and adds sendonly/recvonly attributes to each media
   */

  n_streams = gst_rtsp_media_n_streams (media);

  rangestr = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  if (rangestr == NULL)
    goto not_prepared;

  gst_sdp_message_add_attribute (sdp, "range", rangestr);
  g_free (rangestr);

  res = TRUE;
  for (i = 0; res && (i < n_streams); i++) {
    GstRTSPStream *stream;
    GstCaps *caps = NULL;
    GstRTSPProfile profiles;
    guint mask;
    GstPad *sinkpad = NULL;
    guint n_caps, j;

    /* Mostly a copy of gst_rtsp_sdp_from_stream() which handles the
     * backchannel stream separately */

    stream = gst_rtsp_media_get_stream (media, i);

    if ((sinkpad = gst_rtsp_stream_get_sinkpad (stream))) {
      caps = gst_pad_query_caps (sinkpad, NULL);
    } else {
      caps = gst_rtsp_stream_get_caps (stream);
    }

    if (caps == NULL) {
      GST_ERROR ("stream %p has no caps", stream);
      res = FALSE;
      if (sinkpad)
        gst_object_unref (sinkpad);
      break;
    } else if (!sinkpad && !gst_caps_is_fixed (caps)) {
      GST_ERROR ("stream %p has unfixed caps", stream);
      res = FALSE;
      gst_caps_unref (caps);
      break;
    }

    n_caps = gst_caps_get_size (caps);
    for (j = 0; res && j < n_caps; j++) {
      GstStructure *s = gst_caps_get_structure (caps, j);
      GstCaps *media_caps = gst_caps_new_full (gst_structure_copy (s), NULL);

      if (!gst_caps_is_fixed (media_caps)) {
        GST_ERROR ("media caps for stream %p are not all fixed", stream);
        res = FALSE;
        gst_caps_unref (media_caps);
        break;
      }

      /* make a new media for each profile */
      profiles = gst_rtsp_stream_get_profiles (stream);
      mask = 1;
      res = TRUE;
      while (res && (profiles >= mask)) {
        GstRTSPProfile prof = profiles & mask;

        if (prof) {
          res = gst_rtsp_sdp_make_media (sdp, info, stream, media_caps, prof);
          if (res) {
            GstSDPMedia *smedia =
                &g_array_index (sdp->medias, GstSDPMedia, sdp->medias->len - 1);

            if (sinkpad) {
              GstRTSPOnvifMedia *onvif_media = GST_RTSP_ONVIF_MEDIA (media);

              gst_sdp_media_add_attribute (smedia, "sendonly", "");
              if (onvif_media->priv->backchannel_bandwidth > 0)
                gst_sdp_media_add_bandwidth (smedia, GST_SDP_BWTYPE_AS,
                    onvif_media->priv->backchannel_bandwidth);
            } else {
              gst_sdp_media_add_attribute (smedia, "recvonly", "");
            }
          }
        }

        mask <<= 1;
      }

      if (sinkpad) {
        GstStructure *s = gst_caps_get_structure (media_caps, 0);
        gint pt = -1;

        if (!gst_structure_get_int (s, "payload", &pt) || pt < 0) {
          GST_ERROR ("stream %p has no payload type", stream);
          res = FALSE;
          gst_caps_unref (media_caps);
          gst_object_unref (sinkpad);
          break;
        }

        gst_rtsp_stream_set_pt_map (stream, pt, media_caps);
      }

      gst_caps_unref (media_caps);
    }

    gst_caps_unref (caps);
    if (sinkpad)
      gst_object_unref (sinkpad);
  }

  {
    GstNetTimeProvider *provider;

    if ((provider =
            gst_rtsp_media_get_time_provider (media, info->server_ip, 0))) {
      GstClock *clock;
      gchar *address, *str;
      gint port;

      g_object_get (provider, "clock", &clock, "address", &address, "port",
          &port, NULL);

      str = g_strdup_printf ("GstNetTimeProvider %s %s:%d %" G_GUINT64_FORMAT,
          g_type_name (G_TYPE_FROM_INSTANCE (clock)), address, port,
          gst_clock_get_time (clock));

      gst_sdp_message_add_attribute (sdp, "x-gst-clock", str);
      g_free (str);
      gst_object_unref (clock);
      g_free (address);
      gst_object_unref (provider);
    }
  }

  return res;

  /* ERRORS */
not_prepared:
  {
    GST_ERROR ("media %p is not prepared", media);
    return FALSE;
  }
}

static void
gst_rtsp_onvif_media_finalize (GObject * object)
{
  GstRTSPOnvifMedia *media = GST_RTSP_ONVIF_MEDIA (object);

  g_mutex_clear (&media->priv->lock);

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

static void
gst_rtsp_onvif_media_class_init (GstRTSPOnvifMediaClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstRTSPMediaClass *media_class = (GstRTSPMediaClass *) klass;

  gobject_class->finalize = gst_rtsp_onvif_media_finalize;

  media_class->setup_sdp = gst_rtsp_onvif_media_setup_sdp;
}

static void
gst_rtsp_onvif_media_init (GstRTSPOnvifMedia * media)
{
  media->priv = gst_rtsp_onvif_media_get_instance_private (media);
  g_mutex_init (&media->priv->lock);
}

/**
 * gst_rtsp_onvif_media_collect_backchannel:
 * @media: a #GstRTSPOnvifMedia
 *
 * Find the ONVIF backchannel depayloader element. It should be named
 * 'depay_backchannel', be placed in a bin called 'onvif-backchannel'
 * and return all supported RTP caps on a caps query. Complete RTP caps with
 * at least the payload type, clock-rate and encoding-name are required.
 *
 * A new #GstRTSPStream is created for the backchannel if found.
 *
 * Returns: %TRUE if a backchannel stream could be found and created
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_collect_backchannel (GstRTSPOnvifMedia * media)
{
  GstElement *element, *backchannel_bin = NULL;
  GstElement *latency_bin;
  GstPad *pad = NULL;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media), FALSE);

  element = gst_rtsp_media_get_element (GST_RTSP_MEDIA (media));
  if (!element)
    return ret;

  backchannel_bin =
      gst_bin_get_by_name (GST_BIN (element), "onvif-backchannel");
  if (!backchannel_bin)
    goto out;

  /* We don't want the backchannel element, which is a receiver, to affect
   * latency on the complete pipeline. That's why we remove it from the
   * pipeline and add it to a @GstRTSPLatencyBin which will prevent it from
   * messing up pipelines latency. The extra reference is needed so that it
   * is not freed in case the pipeline holds the the only ref to it.
   *
   * TODO: a more generic solution should be implemented in
   * gst_rtsp_media_collect_streams() where all receivers are encapsulated
   * in a @GstRTSPLatencyBin in cases when there are senders too. */
  gst_object_ref (backchannel_bin);
  gst_bin_remove (GST_BIN (element), backchannel_bin);

  latency_bin = gst_rtsp_latency_bin_new (backchannel_bin);
  g_assert (latency_bin);

  gst_bin_add (GST_BIN (element), latency_bin);

  pad = gst_element_get_static_pad (latency_bin, "sink");
  if (!pad)
    goto out;

  gst_rtsp_media_create_stream (GST_RTSP_MEDIA (media), latency_bin, pad);
  ret = TRUE;

out:
  if (pad)
    gst_object_unref (pad);
  if (backchannel_bin)
    gst_object_unref (backchannel_bin);
  gst_object_unref (element);

  return ret;
}

/**
 * gst_rtsp_onvif_media_set_backchannel_bandwidth:
 * @media: a #GstRTSPMedia
 * @bandwidth: the bandwidth in bits per second
 *
 * Set the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Since: 1.14
 */
void
gst_rtsp_onvif_media_set_backchannel_bandwidth (GstRTSPOnvifMedia * media,
    guint bandwidth)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media));

  g_mutex_lock (&media->priv->lock);
  media->priv->backchannel_bandwidth = bandwidth;
  g_mutex_unlock (&media->priv->lock);
}

/**
 * gst_rtsp_onvif_media_get_backchannel_bandwidth:
 * @media: a #GstRTSPMedia
 *
 * Get the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Returns: the configured/supported backchannel bandwidth.
 *
 * Since: 1.14
 */
guint
gst_rtsp_onvif_media_get_backchannel_bandwidth (GstRTSPOnvifMedia * media)
{
  guint bandwidth;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media), 0);

  g_mutex_lock (&media->priv->lock);
  bandwidth = media->priv->backchannel_bandwidth;
  g_mutex_unlock (&media->priv->lock);

  return bandwidth;
}
