/*
 * Copyright (C) 2025 The Phosh Developers
 *
 * SPDX-License-Identifier: LGPL-2.1+
 *
 * Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
 */

#include "lcb-cbd.h"
#include "gio/gio.h"
#include "lcb-dbus.h"
#include "lcb-main.h"
#include "lcb-util.h"

#include <glib/gstdio.h>

typedef struct {
  GMainLoop    *loop;
  GAsyncResult *res;
} LcbSyncData;


static void
on_open_messages_sync (GObject      *object,
                       GAsyncResult *res,
                       gpointer      user_data)
{
  LcbSyncData *data = user_data;
  data->res = g_object_ref (res);

  g_main_loop_quit (data->loop);
}

static GBytes *
on_open_messages_get_bytes (GDBusProxy   *proxy,
                            GAsyncResult *result,
                            GError      **error)
{
  g_autoptr (GUnixFDList) fd_list = NULL;
  g_autoptr (GVariant) var_tuple = NULL;
  g_autoptr (GVariant) var_handle = NULL;
  g_autoptr (GMappedFile) file = NULL;
  gint32 fd_handle_index;
  g_autofd int fd = -1;

  var_tuple = g_dbus_proxy_call_with_unix_fd_list_finish (G_DBUS_PROXY (proxy),
                                                          &fd_list,
                                                          result,
                                                          error);
  if (!var_tuple || !fd_list)
    return NULL;

  var_handle = g_variant_get_child_value (var_tuple, 0);
  fd_handle_index = g_variant_get_handle (var_handle);

  /* there should only be a single fd */
  g_assert (fd_handle_index == 0);
  g_assert (g_unix_fd_list_get_length (fd_list) == 1);

  fd = g_unix_fd_list_get (fd_list, fd_handle_index, error);
  if (fd == -1)
    return NULL;

  file = g_mapped_file_new_from_fd (fd, FALSE, error);
  if (!file)
    return NULL;

  return g_mapped_file_get_bytes (file);
}

static void
on_open_messages (GObject      *proxy,
                  GAsyncResult *result,
                  gpointer      user_data)
{
  g_autoptr (GBytes) bytes = NULL;
  g_autoptr (GTask) task = NULL;
  g_autoptr (GError) error = NULL;
  GListModel *messages;

  g_assert (G_IS_TASK (user_data));

  task = user_data;

  bytes = on_open_messages_get_bytes (G_DBUS_PROXY (proxy), result, &error);
  if (error) {
    g_task_return_error (task, g_steal_pointer (&error));
    return;
  }
  messages = lcb_util_load_messages_from_bytes (bytes, &error);
  if (!messages) {
    g_task_return_error (task, g_steal_pointer (&error));
    return;
  }
  g_task_return_pointer (task, messages, g_object_unref);
}

void
lcb_cbd_get_messages (GCancellable       *cancellable,
                      GAsyncReadyCallback callback,
                      gpointer            user_data)
{
  g_autoptr (GTask) task = NULL;
  LcbDBusCbd *proxy = lcb_get_proxy ();

  g_return_if_fail (LCB_DBUS_IS_CBD (proxy));

  task = g_task_new (NULL, cancellable, callback, user_data);
  g_task_set_source_tag (task, lcb_cbd_get_messages);

  /* TODO raise issue about generated code not using GUnixFDList
   * can the generated code be used at all for passing FDs?
   */
  g_dbus_proxy_call_with_unix_fd_list (G_DBUS_PROXY (proxy),
                                       "OpenMessages",
                                       NULL,
                                       G_DBUS_CALL_FLAGS_NONE,
                                       -1,
                                       NULL,
                                       cancellable,
                                       on_open_messages,
                                       g_steal_pointer (&task));
}

/**
 * lcb_cbd_get_messages_finish:
 * @result: The `GAsyncResult`
 * @error: An error location
 *
 * Finishes the asyn operation started with [func@cbd_get_messages_async].
 *
 * Returns: (transfer full): The messages
 */
GListModel *
lcb_cbd_get_messages_finish (GAsyncResult *result, GError **error)
{
  GTask *task;

  g_return_val_if_fail (G_IS_TASK (result), NULL);

  task = G_TASK (result);
  g_assert (g_task_get_source_tag (task) == lcb_cbd_get_messages);
  return g_task_propagate_pointer (task, error);
}

/**
 * lcb_cbd_get_messages_sync:
 * @error: An error location
 *
 * Get all messages.
 *
 * Returns: (transfer full): The messages
 */
GListModel *
lcb_cbd_get_messages_sync (GError **error)
{
  g_autoptr (GMainContext) context = g_main_context_new ();
  g_autoptr (GMainLoop) loop = NULL;
  GListModel *messages;
  LcbSyncData data;

  g_main_context_push_thread_default (context);
  loop = g_main_loop_new (context, FALSE);

  data = (LcbSyncData) {
    .loop = loop,
    .res = NULL,
  };

  lcb_cbd_get_messages (NULL, on_open_messages_sync, &data);
  g_main_loop_run (data.loop);

  messages = lcb_cbd_get_messages_finish (data.res, error);

  if (data.res)
    g_object_unref (data.res);
  g_main_context_pop_thread_default (context);

  return messages;
}
