/* GStreamer
 * Copyright (C) 2017 Make.TV, Inc. <info@make.tv>
 *   Contact: Jan Alexander Steffens (heftig) <jsteffens@make.tv>
 *
 * 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.
 */

#include "gstrtmp2locationhandler.h"
#include "rtmp/rtmputils.h"
#include "rtmp/rtmpclient.h"
#include <string.h>

#define DEFAULT_SCHEME GST_RTMP_SCHEME_RTMP
#define DEFAULT_HOST "localhost"
#define DEFAULT_APPLICATION "live"
#define DEFAULT_STREAM "myStream"
#define DEFAULT_LOCATION "rtmp://" DEFAULT_HOST "/" DEFAULT_APPLICATION "/" DEFAULT_STREAM
#define DEFAULT_SECURE_TOKEN NULL
#define DEFAULT_USERNAME NULL
#define DEFAULT_PASSWORD NULL
#define DEFAULT_AUTHMOD  GST_RTMP_AUTHMOD_AUTO
#define DEFAULT_TIMEOUT 5
#define DEFAULT_FLASH_VERSION NULL

G_DEFINE_INTERFACE (GstRtmpLocationHandler, gst_rtmp_location_handler, 0);

#define GST_CAT_DEFAULT gst_rtmp_location_handler_debug_category
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);

static void
gst_rtmp_location_handler_default_init (GstRtmpLocationHandlerInterface * iface)
{
  GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "rtmp2locationhandler", 0,
      "RTMP2 Location Handling");

  g_object_interface_install_property (iface, g_param_spec_string ("location",
          "Location", "Location of RTMP stream to access", DEFAULT_LOCATION,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_enum ("scheme",
          "Scheme", "RTMP connection scheme",
          GST_TYPE_RTMP_SCHEME, DEFAULT_SCHEME,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_string ("host",
          "Host", "RTMP server host name", DEFAULT_HOST,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_int ("port", "Port",
          "RTMP server port", 1, 65535,
          gst_rtmp_scheme_get_default_port (DEFAULT_SCHEME),
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface,
      g_param_spec_string ("application", "Application",
          "RTMP application path", DEFAULT_APPLICATION,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_string ("stream",
          "Stream", "RTMP stream path", DEFAULT_STREAM,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_string ("username",
          "User name", "RTMP authorization user name", DEFAULT_USERNAME,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_string ("password",
          "Password", "RTMP authorization password", DEFAULT_PASSWORD,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface,
      g_param_spec_string ("secure-token", "Secure token",
          "RTMP authorization token", DEFAULT_SECURE_TOKEN,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_enum ("authmod",
          "Authorization mode", "RTMP authorization mode",
          GST_TYPE_RTMP_AUTHMOD, DEFAULT_AUTHMOD,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface, g_param_spec_uint ("timeout",
          "Timeout", "RTMP timeout in seconds", 0, G_MAXUINT, DEFAULT_TIMEOUT,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRtmpLocationHandler::tls-validation-flags:
   *
   * TLS certificate validation flags used to validate server
   * certificate.
   *
   * GLib guarantees that if certificate verification fails, at least one
   * error will be set, but it does not guarantee that all possible errors
   * will be set. Accordingly, you may not safely decide to ignore any
   * particular type of error.
   *
   * For example, it would be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if
   * you want to allow expired certificates, because this could potentially be
   * the only error flag set even if other problems exist with the
   * certificate.
   */
  g_object_interface_install_property (iface,
      g_param_spec_flags ("tls-validation-flags", "TLS validation flags",
          "TLS validation flags to use", G_TYPE_TLS_CERTIFICATE_FLAGS,
          G_TLS_CERTIFICATE_VALIDATE_ALL,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_interface_install_property (iface,
      g_param_spec_string ("flash-version", "Flash version",
          "Flash version reported to the server", DEFAULT_FLASH_VERSION,
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static GstURIType
uri_handler_get_type_sink (GType type)
{
  return GST_URI_SINK;
}

static GstURIType
uri_handler_get_type_src (GType type)
{
  return GST_URI_SRC;
}

static const gchar *const *
uri_handler_get_protocols (GType type)
{
  return gst_rtmp_scheme_get_strings ();
}

static gchar *
uri_handler_get_uri (GstURIHandler * handler)
{
  GstRtmpLocation location = { 0, };
  gchar *string;

  g_object_get (handler, "scheme", &location.scheme, "host", &location.host,
      "port", &location.port, "application", &location.application,
      "stream", &location.stream, NULL);

  string = gst_rtmp_location_get_string (&location, TRUE);
  gst_rtmp_location_clear (&location);
  return string;
}

static gboolean
uri_handler_set_uri (GstURIHandler * handler, const gchar * string,
    GError ** error)
{
  GstRtmpLocationHandler *self = GST_RTMP_LOCATION_HANDLER (handler);
  GstUri *uri;
  const gchar *scheme_sep, *path_sep, *stream_sep, *host, *userinfo;
  GstRtmpScheme scheme;
  guint port;
  gboolean ret = FALSE;

  GST_DEBUG_OBJECT (self, "setting URI from %s", GST_STR_NULL (string));
  g_return_val_if_fail (string, FALSE);

  scheme_sep = strstr (string, "://");
  if (!scheme_sep) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
        "URI lacks scheme: %s", string);
    return FALSE;
  }

  path_sep = strchr (scheme_sep + 3, '/');
  if (!path_sep) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
        "URI lacks path: %s", string);
    return FALSE;
  }

  stream_sep = strrchr (path_sep + 1, '/');
  if (!stream_sep) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
        "URI lacks stream: %s", string);
    return FALSE;
  }

  {
    gchar *string_without_path = g_strndup (string, path_sep - string);
    uri = gst_uri_from_string_escaped (string_without_path);
    g_free (string_without_path);
  }

  if (!uri) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        "URI failed to parse: %s", string);
    return FALSE;
  }

  gst_uri_normalize (uri);

  scheme = gst_rtmp_scheme_from_uri (uri);
  if (scheme < 0) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
        "URI has bad scheme: %s", string);
    goto out;
  }

  host = gst_uri_get_host (uri);
  if (!host) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
        "URI lacks hostname: %s", string);
    goto out;
  }

  port = gst_uri_get_port (uri);
  if (port == GST_URI_NO_PORT) {
    port = gst_rtmp_scheme_get_default_port (scheme);
  }

  {
    const gchar *path = path_sep + 1, *stream = stream_sep + 1;
    gchar *application = g_strndup (path, stream_sep - path);

    GST_DEBUG_OBJECT (self, "setting location to %s://%s:%u/%s stream %s",
        gst_rtmp_scheme_to_string (scheme), host, port, application, stream);

    g_object_set (self, "scheme", scheme, "host", host, "port", port,
        "application", application, "stream", stream, "username", NULL,
        "password", NULL, NULL);

    g_free (application);
  }

  userinfo = gst_uri_get_userinfo (uri);
  if (userinfo) {
    gchar *user, *pass;
    gchar **split = g_strsplit (userinfo, ":", 2);

    if (!split || !split[0] || !split[1]) {
      g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
          "Failed to parse username:password data");
      g_strfreev (split);
      goto out;
    }

    if (g_strrstr (split[1], ":") != NULL)
      GST_WARNING_OBJECT (self, "userinfo %s contains more than one ':', will "
          "assume that the first ':' delineates user:pass. You should escape "
          "the user and pass before adding to the URI.", userinfo);

    user = g_uri_unescape_string (split[0], NULL);
    pass = g_uri_unescape_string (split[1], NULL);
    g_strfreev (split);

    g_object_set (self, "username", user, "password", pass, NULL);
    g_free (user);
    g_free (pass);
  }

  ret = TRUE;

out:
  gst_uri_unref (uri);
  return ret;
}

void
gst_rtmp_location_handler_implement_uri_handler (GstURIHandlerInterface * iface,
    GstURIType type)
{
  switch (type) {
    case GST_URI_SINK:
      iface->get_type = uri_handler_get_type_sink;
      break;
    case GST_URI_SRC:
      iface->get_type = uri_handler_get_type_src;
      break;
    default:
      g_return_if_reached ();
  }
  iface->get_protocols = uri_handler_get_protocols;
  iface->get_uri = uri_handler_get_uri;
  iface->set_uri = uri_handler_set_uri;
}

gboolean
gst_rtmp_location_handler_set_uri (GstRtmpLocationHandler * handler,
    const gchar * uri)
{
  GError *error = NULL;
  gboolean ret;

  g_return_val_if_fail (GST_IS_RTMP_LOCATION_HANDLER (handler), FALSE);

  ret = gst_uri_handler_set_uri (GST_URI_HANDLER (handler), uri, &error);
  if (!ret) {
    GST_ERROR_OBJECT (handler, "Failed to set URI: %s", error->message);
    g_object_set (handler, "scheme", DEFAULT_SCHEME, "host", NULL,
        "port", gst_rtmp_scheme_get_default_port (DEFAULT_SCHEME),
        "application", NULL, "stream", NULL, NULL);
    g_error_free (error);
  }
  return ret;
}