/*
 * Copyright © 2016 Red Hat, Inc
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This program 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:
 *       Matthias Clasen <mclasen@redhat.com>
 */

#include "config.h"

#include "account.h"

#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <gio/gio.h>

#include "xdp-context.h"
#include "xdp-dbus.h"
#include "xdp-documents.h"
#include "xdp-impl-dbus.h"
#include "xdp-portal-config.h"
#include "xdp-request.h"
#include "xdp-utils.h"

typedef struct _Account Account;
typedef struct _AccountClass AccountClass;

struct _Account
{
  XdpDbusAccountSkeleton parent_instance;

  XdpDbusImplAccount *impl;
};

struct _AccountClass
{
  XdpDbusAccountSkeletonClass parent_class;
};

GType account_get_type (void) G_GNUC_CONST;
static void account_iface_init (XdpDbusAccountIface *iface);

G_DEFINE_TYPE_WITH_CODE (Account, account, XDP_DBUS_TYPE_ACCOUNT_SKELETON,
                         G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_ACCOUNT,
                                                account_iface_init));

G_DEFINE_AUTOPTR_CLEANUP_FUNC (Account, g_object_unref)

static void
send_response_in_thread_func (GTask        *task,
                              gpointer      source_object,
                              gpointer      task_data,
                              GCancellable *cancellable)
{
  XdpRequest *request = task_data;
  guint response;
  GVariant *results;
  g_auto(GVariantBuilder) new_results =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) idv = NULL;
  g_autoptr(GVariant) namev = NULL;
  const char *image;

  REQUEST_AUTOLOCK (request);

  response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (request), "response"));
  results = (GVariant *)g_object_get_data (G_OBJECT (request), "results");

  if (response != 0)
    goto out;

  idv = g_variant_lookup_value (results, "id", G_VARIANT_TYPE_STRING);
  namev = g_variant_lookup_value (results, "name", G_VARIANT_TYPE_STRING);

  g_variant_builder_add (&new_results, "{sv}", "id", idv);
  g_variant_builder_add (&new_results, "{sv}", "name", namev);

  if (g_variant_lookup (results, "image", "&s", &image))
    {
      g_autofree char *ruri = NULL;
      g_autoptr(GError) error = NULL;

      if (xdp_app_info_is_host (request->app_info))
        ruri = g_strdup (image);
      else
        ruri = xdp_register_document (image, xdp_app_info_get_id (request->app_info), XDP_DOCUMENT_FLAG_NONE, &error);

      if (ruri == NULL)
        {
          g_warning ("Failed to register %s: %s", image, error->message);
        }
      else
        {
          g_debug ("convert uri '%s' -> '%s'\n", image, ruri);
          g_variant_builder_add (&new_results, "{sv}", "image", g_variant_new_string (ruri));
        }
    }

out:
  if (request->exported)
    {
      xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request),
                                      response,
                                      g_variant_builder_end (&new_results));
      xdp_request_unexport (request);
    }
}

static void
get_user_information_done (GObject *source,
                           GAsyncResult *result,
                           gpointer data)
{
  g_autoptr(XdpRequest) request = data;
  guint response = 2;
  g_autoptr(GVariant) results = NULL;
  g_autoptr(GError) error = NULL;
  g_autoptr(GTask) task = NULL;

  if (!xdp_dbus_impl_account_call_get_user_information_finish (XDP_DBUS_IMPL_ACCOUNT (source),
                                                               &response,
                                                               &results,
                                                               result,
                                                               &error))
    {
      g_dbus_error_strip_remote_error (error);
      g_warning ("Backend call failed: %s", error->message);
    }

  g_object_set_data (G_OBJECT (request), "response", GINT_TO_POINTER (response));
  if (results)
    g_object_set_data_full (G_OBJECT (request), "results", g_variant_ref (results), (GDestroyNotify)g_variant_unref);

  task = g_task_new (NULL, NULL, NULL, NULL);
  g_task_set_source_tag (task, get_user_information_done);
  g_task_set_task_data (task, g_object_ref (request), g_object_unref);
  g_task_run_in_thread (task, send_response_in_thread_func);
}

static gboolean
validate_reason (const char  *key,
                 GVariant    *value,
                 GVariant    *options,
                 gpointer     user_data,
                 GError     **error)
{
  const char *string = g_variant_get_string (value, NULL);

  if (g_utf8_strlen (string, -1) > 256)
    {
      g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
                   "Not accepting overly long reasons");
      return FALSE;
    }

  return TRUE;
}

static XdpOptionKey user_information_options[] = {
  { "reason", G_VARIANT_TYPE_STRING, validate_reason },
};

static gboolean
handle_get_user_information (XdpDbusAccount *object,
                             GDBusMethodInvocation *invocation,
                             const gchar *arg_parent_window,
                             GVariant *arg_options)
{
  Account *account = (Account *) object;
  XdpRequest *request = xdp_request_from_invocation (invocation);
  const char *app_id = xdp_app_info_get_id (request->app_info);
  g_autoptr(GError) error = NULL;
  g_autoptr(XdpDbusImplRequest) impl_request = NULL;
  g_auto(GVariantBuilder) options =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

  g_debug ("Handling GetUserInformation");

  REQUEST_AUTOLOCK (request);

  impl_request = xdp_dbus_impl_request_proxy_new_sync (
    g_dbus_proxy_get_connection (G_DBUS_PROXY (account->impl)),
    G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
    g_dbus_proxy_get_name (G_DBUS_PROXY (account->impl)),
    request->id,
    NULL, &error);

  if (!impl_request)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_request_set_impl_request (request, impl_request);
  xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation));

  xdp_filter_options (arg_options, &options,
                      user_information_options, G_N_ELEMENTS (user_information_options),
                      NULL, NULL);

  g_debug ("options filtered");

  xdp_dbus_impl_account_call_get_user_information (account->impl,
                                                   request->id,
                                                   app_id,
                                                   arg_parent_window,
                                                   g_variant_builder_end (&options),
                                                   NULL,
                                                   get_user_information_done,
                                                   g_object_ref (request));

  xdp_dbus_account_complete_get_user_information (object, invocation,
                                                 request->id);

  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static void
account_iface_init (XdpDbusAccountIface *iface)
{
  iface->handle_get_user_information = handle_get_user_information;
}

static void
account_dispose (GObject *object)
{
  Account *account = (Account *) object;

  g_clear_object (&account->impl);

  G_OBJECT_CLASS (account_parent_class)->dispose (object);
}

static void
account_init (Account *account)
{
}

static void
account_class_init (AccountClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = account_dispose;
}

static Account *
account_new (XdpDbusImplAccount *impl)
{
  Account *account;

  account = g_object_new (account_get_type (), NULL);
  account->impl = g_object_ref (impl);

  xdp_dbus_account_set_version (XDP_DBUS_ACCOUNT (account), 1);

  g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (account->impl), G_MAXINT);

  return account;
}

void
init_account (XdpContext *context)
{
  g_autoptr(Account) account = NULL;
  GDBusConnection *connection = xdp_context_get_connection (context);
  XdpPortalConfig *config = xdp_context_get_config (context);
  XdpImplConfig *impl_config;
  g_autoptr(XdpDbusImplAccount) impl = NULL;
  g_autoptr(GError) error = NULL;

  impl_config = xdp_portal_config_find (config, ACCOUNT_DBUS_IMPL_IFACE);
  if (impl_config == NULL)
    return;

  impl = xdp_dbus_impl_account_proxy_new_sync (connection,
                                               G_DBUS_PROXY_FLAGS_NONE,
                                               impl_config->dbus_name,
                                               DESKTOP_DBUS_PATH,
                                               NULL,
                                               &error);

  if (impl == NULL)
    {
      g_warning ("Failed to create account proxy: %s", error->message);
      return;
    }

  account = account_new (impl);

  xdp_context_take_and_export_portal (context,
                                      G_DBUS_INTERFACE_SKELETON (g_steal_pointer (&account)),
                                      XDP_CONTEXT_EXPORT_FLAGS_NONE);
}
