/*
 * Copyright © 2017 Red Hat, Inc
 * Copyright © 2021 Collabora Ltd.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Florian Müllner <fmuellner@gnome.org>
 *       Matthias Clasen <mclasen@redhat.com>
 */

/*
 * Alternative executable to the canonical 'xdg-open' with a better handling
 * of Steam's URLs.
 * Loosely based on the xdg-open implementation of flatpak-xdg-utils.
 */

#include <libglnx.h>

#include "steam-runtime-tools/glib-backports-internal.h"

#include <glib.h>
#include <glib-object.h>
#include <gio/gunixfdlist.h>

#include <steam-runtime-tools/utils-internal.h>

typedef GUnixFDList AutoUnixFDList;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoUnixFDList, g_object_unref)

static gchar **uris = NULL;
static gboolean opt_print_help = FALSE;
static gboolean opt_print_version = FALSE;

static const GOptionEntry option_entries[] =
{
  /* Imitate the allowed options of the real 'xdg-open' */
  { "manual", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_print_help,
    NULL, NULL },
  { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version,
    "Print version number and exit", NULL },
  { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
    &uris, NULL, NULL },
  { NULL }
};

static gboolean
open_with_portal (const char *uri_or_filename,
                  GError **error)
{
  g_autoptr(GDBusConnection) connection = NULL;

  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);

  if (connection != NULL)
    {
      const gchar *portal_bus_name = "org.freedesktop.portal.Desktop";
      const gchar *portal_object_path = "/org/freedesktop/portal/desktop";
      const gchar *portal_iface_name = "org.freedesktop.portal.OpenURI";
      const gchar *method_name = NULL;
      GVariant *arguments = NULL;   /* floating */
      GVariantBuilder opt_builder;
      g_autoptr(AutoUnixFDList) fd_list = NULL;
      g_autoptr(GFile) file = NULL;
      g_autoptr(GVariant) result = NULL;

      g_debug ("Trying the D-Bus desktop portal");

      g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
      file = g_file_new_for_commandline_arg (uri_or_filename);

      if (g_file_is_native (file))
        {
          /* The canonical 'xdg-open' also handles paths. We try to replicate
           * that too, but it might not always work because the container
           * inside and outside filesystem structure might be different. */
          g_autofree gchar *path = NULL;
          int fd;

          path = g_file_get_path (file);
          fd = open (path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
          if (fd < 0)
            {
              return glnx_throw_errno_prefix (error, "Failed to open '%s'", path);
            }

          fd_list = g_unix_fd_list_new_from_array (&fd, 1); /* adopts the fd */

          arguments = g_variant_new ("(sh@a{sv})",
                                     "", 0,
                                     g_variant_builder_end (&opt_builder));
          method_name = "OpenFile";
        }
      else
        {
          arguments = g_variant_new ("(ss@a{sv})",
                                     "", uri_or_filename,
                                     g_variant_builder_end (&opt_builder));
          method_name = "OpenURI";
        }

      result = g_dbus_connection_call_with_unix_fd_list_sync (connection,
                                                              portal_bus_name,
                                                              portal_object_path,
                                                              portal_iface_name,
                                                              method_name,
                                                              /* sinks floating reference */
                                                              g_steal_pointer (&arguments),
                                                              NULL,
                                                              G_DBUS_CALL_FLAGS_NONE,
                                                              -1,
                                                              fd_list,
                                                              NULL,
                                                              NULL,
                                                              error);

      if (result == NULL)
        return glnx_prefix_error (error,
                                  "Unable to open URL with xdg-desktop-portal");
      else
        return TRUE;
    }
  else
    {
      return glnx_prefix_error (error,
                                "Unable to connect to D-Bus session bus");
    }
}

int
main (int argc,
      char **argv)
{
  const gchar *uri;
  g_autofree gchar *scheme = NULL;
  g_autoptr(GOptionContext) option_context = NULL;
  g_autoptr(GError) portal_error = NULL;
  g_autoptr(GError) error = NULL;

  g_set_prgname ("steam-runtime-urlopen");

  option_context = g_option_context_new ("{ file | URL }");
  g_option_context_add_main_entries (option_context, option_entries, NULL);

  if (!g_option_context_parse (option_context, &argc, &argv, &error))
    {
      g_printerr ("%s: %s\n", g_get_prgname (), error->message);
      return 1;
    }

  if (opt_print_version)
    {
      /* Simply print the version number, similarly to the real xdg-open */
      g_print ("%s\n", VERSION);
      return EXIT_SUCCESS;
    }

  if (uris == NULL || g_strv_length (uris) > 1)
    {
      g_autofree gchar *help = g_option_context_get_help (option_context, TRUE, NULL);
      g_print ("%s\n", help);
      return 1;
    }

  /* In reality this could also be a path, but we call it "uri" for simplicity */
  uri = uris[0];

  scheme = g_uri_parse_scheme (uri);

  if (scheme != NULL && (g_ascii_strcasecmp (scheme, "steamlink") == 0
                         || g_ascii_strcasecmp (scheme, "steam") == 0))
    {
      g_debug ("Passing the URL '%s' to the Steam pipe", uri);
      if (!_srt_steam_command_via_pipe (&uri, 1, &error))
        {
          g_printerr ("%s: %s\n", g_get_prgname (), error->message);
          return 4;
        }

      return EXIT_SUCCESS;
    }

  if (open_with_portal (uri, &portal_error))
    return EXIT_SUCCESS;

  if (scheme != NULL && (g_ascii_strcasecmp (scheme, "http") == 0
                         || g_ascii_strcasecmp (scheme, "https") == 0))
    {
      g_autofree gchar *steam_url = NULL;
      steam_url = g_strjoin ("/", "steam://openurl", uri, NULL);

      g_debug ("Passing the URL '%s' to the Steam pipe", steam_url);
      if (!_srt_steam_command_via_pipe ((const gchar **) &steam_url, 1, &error))
        {
          g_printerr ("%s: %s\n", g_get_prgname (), error->message);
          return 4;
        }

      return EXIT_SUCCESS;
    }

  g_printerr ("%s: %s\n", g_get_prgname (), portal_error->message);
  return 4;
}