/* 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-factory
 * @short_description: A factory for ONVIF media pipelines
 * @see_also: #GstRTSPMediaFactory, #GstRTSPOnvifMedia
 *
 * The #GstRTSPOnvifMediaFactory is responsible for creating or recycling
 * #GstRTSPMedia objects based on the passed URL. Different to
 * #GstRTSPMediaFactory, this supports special ONVIF features and can create
 * #GstRTSPOnvifMedia in addition to normal #GstRTSPMedia.
 *
 * 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, see
 * gst_rtsp_onvif_media_factory_set_backchannel_launch() and
 * gst_rtsp_onvif_media_factory_set_backchannel_bandwidth().
 *
 * Since: 1.14
 */

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

#include <string.h>

#include "rtsp-onvif-media-factory.h"
#include "rtsp-onvif-media.h"
#include "rtsp-onvif-server.h"

struct GstRTSPOnvifMediaFactoryPrivate
{
  GMutex lock;
  gchar *backchannel_launch;
  guint backchannel_bandwidth;
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPOnvifMediaFactory,
    gst_rtsp_onvif_media_factory, GST_TYPE_RTSP_MEDIA_FACTORY);

/**
 * gst_rtsp_onvif_media_factory_requires_backchannel:
 * @factory: a #GstRTSPMediaFactory
 *
 * Checks whether the client request requires backchannel.
 *
 * Returns: %TRUE if the client request requires backchannel.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_factory_requires_backchannel (GstRTSPMediaFactory *
    factory, GstRTSPContext * ctx)
{
  GstRTSPMessage *msg = ctx->request;
  GstRTSPResult res;
  gint i;
  gchar *reqs = NULL;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), FALSE);

  i = 0;
  do {
    res = gst_rtsp_message_get_header (msg, GST_RTSP_HDR_REQUIRE, &reqs, i++);

    if (res == GST_RTSP_ENOTIMPL)
      break;

    if (strcmp (reqs, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT) == 0)
      return TRUE;
  } while (TRUE);

  return FALSE;
}

static gchar *
gst_rtsp_onvif_media_factory_gen_key (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Only medias where no backchannel was requested can be shared */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx))
    return NULL;

  return
      GST_RTSP_MEDIA_FACTORY_CLASS
      (gst_rtsp_onvif_media_factory_parent_class)->gen_key (factory, url);
}

static GstRTSPMedia *
gst_rtsp_onvif_media_factory_construct (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMedia *media;
  GstElement *element, *pipeline;
  GstRTSPMediaFactoryClass *klass;
  GType media_gtype;
  gboolean got_backchannel_stream;
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Mostly a copy of the default implementation but with backchannel support below,
   * unfortunately we can't re-use the default one because of how the virtual
   * method is define */

  /* Everything but play is unsupported */
  if (gst_rtsp_media_factory_get_transport_mode (factory) !=
      GST_RTSP_TRANSPORT_MODE_PLAY)
    return NULL;

  /* we only support onvif media here: otherwise a plain GstRTSPMediaFactory
   * could've been used as well */
  media_gtype = gst_rtsp_media_factory_get_media_gtype (factory);
  if (!g_type_is_a (media_gtype, GST_TYPE_RTSP_ONVIF_MEDIA))
    return NULL;

  klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory);

  if (!klass->create_pipeline)
    goto no_create;

  element = gst_rtsp_media_factory_create_element (factory, url);
  if (element == NULL)
    goto no_element;

  /* create a new empty media */
  media =
      g_object_new (media_gtype, "element", element,
      "transport-mode", GST_RTSP_TRANSPORT_MODE_PLAY, NULL);

  /* this adds the non-backchannel streams */
  gst_rtsp_media_collect_streams (media);

  /* this adds the backchannel stream */
  got_backchannel_stream =
      gst_rtsp_onvif_media_collect_backchannel (GST_RTSP_ONVIF_MEDIA (media));
  /* FIXME: This should not happen! We checked for that before */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx) &&
      !got_backchannel_stream) {
    g_object_unref (media);
    return NULL;
  }

  pipeline = klass->create_pipeline (factory, media);
  if (pipeline == NULL)
    goto no_pipeline;

  gst_rtsp_onvif_media_set_backchannel_bandwidth (GST_RTSP_ONVIF_MEDIA (media),
      GST_RTSP_ONVIF_MEDIA_FACTORY (factory)->priv->backchannel_bandwidth);

  return media;

  /* ERRORS */
no_create:
  {
    g_critical ("no create_pipeline function");
    return NULL;
  }
no_element:
  {
    g_critical ("could not create element");
    return NULL;
  }
no_pipeline:
  {
    g_critical ("can't create pipeline");
    g_object_unref (media);
    return NULL;
  }
}

static GstElement *
gst_rtsp_onvif_media_factory_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstElement *element;
  GError *error = NULL;
  gchar *launch;
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Mostly a copy of the default implementation but with backchannel support below,
   * unfortunately we can't re-use the default one because of how the virtual
   * method is define */

  launch = gst_rtsp_media_factory_get_launch (factory);

  /* we need a parse syntax */
  if (launch == NULL)
    goto no_launch;

  /* parse the user provided launch line */
  element =
      gst_parse_launch_full (launch, NULL, GST_PARSE_FLAG_PLACE_IN_BIN, &error);
  if (element == NULL)
    goto parse_error;

  g_free (launch);

  if (error != NULL) {
    /* a recoverable error was encountered */
    GST_WARNING ("recoverable parsing error: %s", error->message);
    g_error_free (error);
  }

  /* add backchannel pipeline part, if requested */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx)) {
    GstRTSPOnvifMediaFactory *onvif_factory =
        GST_RTSP_ONVIF_MEDIA_FACTORY (factory);
    GstElement *backchannel_bin;
    GstElement *backchannel_depay;
    GstPad *depay_pad, *depay_ghostpad;

    launch =
        gst_rtsp_onvif_media_factory_get_backchannel_launch (onvif_factory);
    if (launch == NULL)
      goto no_launch_backchannel;

    backchannel_bin =
        gst_parse_bin_from_description_full (launch, FALSE, NULL,
        GST_PARSE_FLAG_PLACE_IN_BIN, &error);
    if (backchannel_bin == NULL)
      goto parse_error_backchannel;

    g_free (launch);

    if (error != NULL) {
      /* a recoverable error was encountered */
      GST_WARNING ("recoverable parsing error: %s", error->message);
      g_error_free (error);
    }

    gst_object_set_name (GST_OBJECT (backchannel_bin), "onvif-backchannel");

    backchannel_depay =
        gst_bin_get_by_name (GST_BIN (backchannel_bin), "depay_backchannel");
    if (!backchannel_depay) {
      gst_object_unref (backchannel_bin);
      goto wrongly_formatted_backchannel_bin;
    }

    depay_pad = gst_element_get_static_pad (backchannel_depay, "sink");
    if (!depay_pad) {
      gst_object_unref (backchannel_depay);
      gst_object_unref (backchannel_bin);
      goto wrongly_formatted_backchannel_bin;
    }

    depay_ghostpad = gst_ghost_pad_new ("sink", depay_pad);
    gst_element_add_pad (backchannel_bin, depay_ghostpad);

    gst_bin_add (GST_BIN (element), backchannel_bin);
  }

  return element;

  /* ERRORS */
no_launch:
  {
    g_critical ("no launch line specified");
    g_free (launch);
    return NULL;
  }
parse_error:
  {
    g_critical ("could not parse launch syntax (%s): %s", launch,
        (error ? error->message : "unknown reason"));
    if (error)
      g_error_free (error);
    g_free (launch);
    return NULL;
  }
no_launch_backchannel:
  {
    g_critical ("no backchannel launch line specified");
    gst_object_unref (element);
    return NULL;
  }
parse_error_backchannel:
  {
    g_critical ("could not parse backchannel launch syntax (%s): %s", launch,
        (error ? error->message : "unknown reason"));
    if (error)
      g_error_free (error);
    g_free (launch);
    gst_object_unref (element);
    return NULL;
  }

wrongly_formatted_backchannel_bin:
  {
    g_critical ("invalidly formatted backchannel bin");

    gst_object_unref (element);
    return NULL;
  }
}

static gboolean
    gst_rtsp_onvif_media_factory_has_backchannel_support_default
    (GstRTSPOnvifMediaFactory * factory)
{
  /* No locking here, we just check if it's non-NULL */
  return factory->priv->backchannel_launch != NULL;
}

static void
gst_rtsp_onvif_media_factory_finalize (GObject * object)
{
  GstRTSPOnvifMediaFactory *factory = GST_RTSP_ONVIF_MEDIA_FACTORY (object);

  g_free (factory->priv->backchannel_launch);
  factory->priv->backchannel_launch = NULL;

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

  return
      G_OBJECT_CLASS (gst_rtsp_onvif_media_factory_parent_class)->finalize
      (object);
}

static void
gst_rtsp_onvif_media_factory_class_init (GstRTSPOnvifMediaFactoryClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstRTSPMediaFactoryClass *factory_klass = (GstRTSPMediaFactoryClass *) klass;

  gobject_class->finalize = gst_rtsp_onvif_media_factory_finalize;

  factory_klass->gen_key = gst_rtsp_onvif_media_factory_gen_key;
  factory_klass->construct = gst_rtsp_onvif_media_factory_construct;
  factory_klass->create_element = gst_rtsp_onvif_media_factory_create_element;

  klass->has_backchannel_support =
      gst_rtsp_onvif_media_factory_has_backchannel_support_default;
}

static void
gst_rtsp_onvif_media_factory_init (GstRTSPOnvifMediaFactory * factory)
{
  factory->priv = gst_rtsp_onvif_media_factory_get_instance_private (factory);
  g_mutex_init (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_set_backchannel_launch:
 * @factory: a #GstRTSPMediaFactory
 * @launch: the launch description
 *
 * The gst_parse_launch() line to use for constructing the ONVIF backchannel
 * pipeline in the default prepare vmethod if requested by the client.
 *
 * The pipeline description should return a GstBin as the toplevel element
 * which can be accomplished by enclosing the description with brackets '('
 * ')'.
 *
 * The description should return a pipeline with a single depayloader named
 * depay_backchannel. A caps query on the depayloader's sinkpad should return
 * all possible, complete RTP caps that are going to be supported. At least
 * the payload type, clock-rate and encoding-name need to be specified.
 *
 * Note: The pipeline part passed here must end in sinks that are not waiting
 * until pre-rolling before reaching the PAUSED state, i.e. setting
 * async=false on #GstBaseSink. Otherwise the whole media will not be able to
 * prepare.
 *
 * Since: 1.14
 */
void
gst_rtsp_onvif_media_factory_set_backchannel_launch (GstRTSPOnvifMediaFactory *
    factory, const gchar * launch)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory));

  g_mutex_lock (&factory->priv->lock);
  g_free (factory->priv->backchannel_launch);
  factory->priv->backchannel_launch = g_strdup (launch);
  g_mutex_unlock (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_get_backchannel_launch:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the gst_parse_launch() pipeline description that will be used in the
 * default prepare vmethod for generating the ONVIF backchannel pipeline.
 *
 * Returns: (transfer full): the configured backchannel launch description. g_free() after
 * usage.
 *
 * Since: 1.14
 */
gchar *
gst_rtsp_onvif_media_factory_get_backchannel_launch (GstRTSPOnvifMediaFactory *
    factory)
{
  gchar *launch;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), NULL);

  g_mutex_lock (&factory->priv->lock);
  launch = g_strdup (factory->priv->backchannel_launch);
  g_mutex_unlock (&factory->priv->lock);

  return launch;
}

/**
 * gst_rtsp_onvif_media_factory_has_backchannel_support:
 * @factory: a #GstRTSPMediaFactory
 *
 * Returns %TRUE if an ONVIF backchannel is supported by the media factory.
 *
 * Returns: %TRUE if an ONVIF backchannel is supported by the media factory.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_factory_has_backchannel_support (GstRTSPOnvifMediaFactory *
    factory)
{
  GstRTSPOnvifMediaFactoryClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), FALSE);

  klass = GST_RTSP_ONVIF_MEDIA_FACTORY_GET_CLASS (factory);

  if (klass->has_backchannel_support)
    return klass->has_backchannel_support (factory);

  return FALSE;
}

/**
 * gst_rtsp_onvif_media_factory_set_backchannel_bandwidth:
 * @factory: a #GstRTSPMediaFactory
 * @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_factory_set_backchannel_bandwidth (GstRTSPOnvifMediaFactory
    * factory, guint bandwidth)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory));

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

/**
 * gst_rtsp_onvif_media_factory_get_backchannel_bandwidth:
 * @factory: a #GstRTSPMediaFactory
 *
 * 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_factory_get_backchannel_bandwidth (GstRTSPOnvifMediaFactory
    * factory)
{
  guint bandwidth;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), 0);

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

  return bandwidth;
}

/**
 * gst_rtsp_onvif_media_factory_new:
 *
 * Create a new #GstRTSPOnvifMediaFactory
 *
 * Returns: A new #GstRTSPOnvifMediaFactory
 *
 * Since: 1.14
 */
GstRTSPMediaFactory *
gst_rtsp_onvif_media_factory_new (void)
{
  return g_object_new (GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY, NULL);
}
