/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "libnm-client-aux-extern/nm-default-client.h"

#include "nm-libnm-aux.h"

/*****************************************************************************/

NMClient *
nmc_client_new_async_valist(GCancellable       *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer            user_data,
                            const char         *first_property_name,
                            va_list             ap)
{
    NMClient *nmc;

    nmc = NM_CLIENT(g_object_new_valist(NM_TYPE_CLIENT, first_property_name, ap));
    g_async_initable_init_async(G_ASYNC_INITABLE(nmc),
                                G_PRIORITY_DEFAULT,
                                cancellable,
                                callback,
                                user_data);
    return nmc;
}

NMClient *
nmc_client_new_async(GCancellable       *cancellable,
                     GAsyncReadyCallback callback,
                     gpointer            user_data,
                     const char         *first_property_name,
                     ...)
{
    NMClient *nmc;
    va_list   ap;

    va_start(ap, first_property_name);
    nmc = nmc_client_new_async_valist(cancellable, callback, user_data, first_property_name, ap);
    va_end(ap);
    return nmc;
}

/*****************************************************************************/

typedef struct {
    GMainLoop *main_loop;
    NMClient  *nmc;
    GError    *error;
} ClientCreateData;

static void
_nmc_client_new_waitsync_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
{
    ClientCreateData *data = user_data;

    g_async_initable_init_finish(G_ASYNC_INITABLE(source_object), result, &data->error);
    g_main_loop_quit(data->main_loop);
}

/**
 * nmc_client_new:
 * @cancellable: the cancellable to abort the creation.
 * @out_nmc: (out): (transfer full): if give, transfers a reference
 *   to the NMClient instance. Note that this never fails to create
 *   the NMClient GObject, but depending on the return value,
 *   the instance was successfully initialized or not.
 * @error: the error if creation fails.
 * @first_property_name: the name of the first property
 * @...: the value of the first property, followed optionally by more
 *  name/value pairs, followed by %NULL
 *
 * Returns: %TRUE, if the client was successfully initalized.
 *
 * This uses nm_client_new_async() to create a NMClient instance,
 * but it iterates the current GMainContext until the client is
 * ready. As such, it waits for the client creation to complete
 * (like sync nm_client_new()) but it iterates the caller's GMainContext
 * (unlike sync nm_client_new()). This is often preferable, because
 * sync nm_client_new() needs to create an additional internal GMainContext
 * that it can iterate instead. That has a performance overhead that
 * is often unnecessary.
 */
gboolean
nmc_client_new_waitsync(GCancellable *cancellable,
                        NMClient    **out_nmc,
                        GError      **error,
                        const char   *first_property_name,
                        ...)
{
    gs_unref_object NMClient          *nmc = NULL;
    nm_auto_unref_gmainloop GMainLoop *main_loop =
        g_main_loop_new(g_main_context_get_thread_default(), FALSE);
    ClientCreateData data = {
        .main_loop = main_loop,
    };
    va_list ap;

#if NM_MORE_ASSERTS > 10
    /* The sync initialization of NMClient is generally a bad idea, because it
     * brings the overhead of an additional GMainContext. Anyway, since our own
     * code no longer uses that, we hardly test those code paths. But they should
     * work just the same. Randomly use instead the sync initialization in a debug
     * build... */
    if ((g_random_int() % 2) == 0) {
        gboolean success;

        va_start(ap, first_property_name);
        nmc = NM_CLIENT(g_object_new_valist(NM_TYPE_CLIENT, first_property_name, ap));
        va_end(ap);

        /* iterate the context at least once, just so that the behavior from POV of the
         * caller is roughly the same. */
        g_main_context_iteration(nm_client_get_main_context(nmc), FALSE);

        success = g_initable_init(G_INITABLE(nmc), cancellable, error);
        NM_SET_OUT(out_nmc, g_steal_pointer(&nmc));
        return success;
    }
#endif

    va_start(ap, first_property_name);
    nmc = nmc_client_new_async_valist(cancellable,
                                      _nmc_client_new_waitsync_cb,
                                      &data,
                                      first_property_name,
                                      ap);
    va_end(ap);

    g_main_loop_run(main_loop);

    NM_SET_OUT(out_nmc, g_steal_pointer(&nmc));
    if (data.error) {
        g_propagate_error(error, data.error);
        return FALSE;
    }
    return TRUE;
}
