/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2007 - 2018 Red Hat, Inc.
 * Copyright (C) 2008 Novell, Inc.
 */

#include "src/core/nm-default-daemon.h"

#include "nm-settings-plugin.h"

#include "nm-utils.h"
#include "libnm-core-intern/nm-core-internal.h"

#include "nm-settings-connection.h"

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

enum {
    UNMANAGED_SPECS_CHANGED,
    UNRECOGNIZED_SPECS_CHANGED,

    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = {0};

G_DEFINE_TYPE(NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT)

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

int
nm_settings_plugin_cmp_by_priority(const NMSettingsPlugin *a,
                                   const NMSettingsPlugin *b,
                                   const GSList           *plugin_list)
{
    nm_assert(NM_IS_SETTINGS_PLUGIN(a));
    nm_assert(NM_IS_SETTINGS_PLUGIN(b));

    if (a != b) {
        int idx_a = g_slist_index((GSList *) plugin_list, a);
        int idx_b = g_slist_index((GSList *) plugin_list, b);

        /* the plugins must be found in the list. */
        nm_assert(idx_a >= 0);
        nm_assert(idx_b >= 0);

        /* plugins that appear first in @plugin_list have higher priority.
         * That means: smaller index -> higher priority. Reverse sort. */
        NM_CMP_DIRECT(idx_b, idx_a);
    }

    return 0;
}

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

GSList *
nm_settings_plugin_get_unmanaged_specs(NMSettingsPlugin *self)
{
    NMSettingsPluginClass *klass;

    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL);

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (!klass->get_unmanaged_specs)
        return NULL;
    return klass->get_unmanaged_specs(self);
}

GSList *
nm_settings_plugin_get_unrecognized_specs(NMSettingsPlugin *self)
{
    NMSettingsPluginClass *klass;

    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), NULL);

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (!klass->get_unrecognized_specs)
        return NULL;
    return klass->get_unrecognized_specs(self);
}

void
nm_settings_plugin_reload_connections(NMSettingsPlugin                      *self,
                                      NMSettingsPluginConnectionLoadCallback callback,
                                      gpointer                               user_data)
{
    NMSettingsPluginClass *klass;

    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));
    g_return_if_fail(callback);

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (klass->reload_connections)
        klass->reload_connections(self, callback, user_data);
}

NMSettingsPluginConnectionLoadEntry *
nm_settings_plugin_create_connection_load_entries(const char *const *filenames, gsize *out_len)
{
    NMSettingsPluginConnectionLoadEntry *entries;
    gsize                                len;
    gsize                                i;

    len = NM_PTRARRAY_LEN(filenames);
    if (len == 0) {
        *out_len = 0;
        return NULL;
    }

    entries = g_new(NMSettingsPluginConnectionLoadEntry, len);
    for (i = 0; i < len; i++) {
        entries[i] = (NMSettingsPluginConnectionLoadEntry){
            .filename = filenames[i],
            .error    = NULL,
            .handled  = FALSE,
        };
    }

    *out_len = len;
    return entries;
}

void
nm_settings_plugin_load_connections(NMSettingsPlugin                      *self,
                                    NMSettingsPluginConnectionLoadEntry   *entries,
                                    gsize                                  n_entries,
                                    NMSettingsPluginConnectionLoadCallback callback,
                                    gpointer                               user_data)
{
    NMSettingsPluginClass *klass;

    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (klass->load_connections)
        klass->load_connections(self, entries, n_entries, callback, user_data);
}

void
nm_settings_plugin_load_connections_done(NMSettingsPlugin *self)
{
    NMSettingsPluginClass *klass;

    g_return_if_fail(NM_IS_SETTINGS_PLUGIN(self));

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (klass->load_connections_done)
        klass->load_connections_done(self);
}

gboolean
nm_settings_plugin_add_connection(NMSettingsPlugin   *self,
                                  NMConnection       *connection,
                                  NMSettingsStorage **out_storage,
                                  NMConnection      **out_connection,
                                  GError            **error)
{
    NMSettingsPluginClass *klass;

    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
    g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);

#if NM_MORE_ASSERTS > 5
    nm_assert(nm_connection_verify(connection, NULL));
#endif

    NM_SET_OUT(out_storage, NULL);
    NM_SET_OUT(out_connection, NULL);

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);
    if (!klass->add_connection) {
        g_set_error_literal(error,
                            NM_SETTINGS_ERROR,
                            NM_SETTINGS_ERROR_NOT_SUPPORTED,
                            "settings plugin does not support adding connections");
        return FALSE;
    }
    return klass->add_connection(self, connection, out_storage, out_connection, error);
}

gboolean
nm_settings_plugin_update_connection(NMSettingsPlugin   *self,
                                     NMSettingsStorage  *storage,
                                     NMConnection       *connection,
                                     NMSettingsStorage **out_storage,
                                     NMConnection      **out_connection,
                                     GError            **error)
{
    NMSettingsPluginClass *klass = NULL;

    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE);
    g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE);
    g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);

#if NM_MORE_ASSERTS > 5
    nm_assert(nm_connection_verify(connection, NULL));
    nm_assert(nm_streq(nm_connection_get_uuid(connection), nm_settings_storage_get_uuid(storage)));
#endif

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);

    NM_SET_OUT(out_storage, NULL);
    NM_SET_OUT(out_connection, NULL);

    if (!klass->update_connection) {
        g_set_error(error,
                    NM_SETTINGS_ERROR,
                    NM_SETTINGS_ERROR_NOT_SUPPORTED,
                    "settings plugin does not support modifying connections");
        return FALSE;
    }
    return klass->update_connection(self, storage, connection, out_storage, out_connection, error);
}

gboolean
nm_settings_plugin_delete_connection(NMSettingsPlugin  *self,
                                     NMSettingsStorage *storage,
                                     GError           **error)
{
    NMSettingsPluginClass *klass = NULL;

    g_return_val_if_fail(NM_IS_SETTINGS_PLUGIN(self), FALSE);
    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), FALSE);
    g_return_val_if_fail(nm_settings_storage_get_plugin(storage) == self, FALSE);

    klass = NM_SETTINGS_PLUGIN_GET_CLASS(self);

    if (!klass->delete_connection) {
        g_set_error(error,
                    NM_SETTINGS_ERROR,
                    NM_SETTINGS_ERROR_NOT_SUPPORTED,
                    "settings plugin does not support deleting connections");
        return FALSE;
    }

    return klass->delete_connection(self, storage, error);
}

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

void
_nm_settings_plugin_emit_signal_unmanaged_specs_changed(NMSettingsPlugin *self)
{
    nm_assert(NM_IS_SETTINGS_PLUGIN(self));

    g_signal_emit(self, signals[UNMANAGED_SPECS_CHANGED], 0);
}

void
_nm_settings_plugin_emit_signal_unrecognized_specs_changed(NMSettingsPlugin *self)
{
    nm_assert(NM_IS_SETTINGS_PLUGIN(self));

    g_signal_emit(self, signals[UNRECOGNIZED_SPECS_CHANGED], 0);
}

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

static void
nm_settings_plugin_init(NMSettingsPlugin *self)
{}

static void
nm_settings_plugin_class_init(NMSettingsPluginClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    signals[UNMANAGED_SPECS_CHANGED] = g_signal_new(NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
                                                    G_OBJECT_CLASS_TYPE(object_class),
                                                    G_SIGNAL_RUN_FIRST,
                                                    0,
                                                    NULL,
                                                    NULL,
                                                    g_cclosure_marshal_VOID__VOID,
                                                    G_TYPE_NONE,
                                                    0);

    signals[UNRECOGNIZED_SPECS_CHANGED] =
        g_signal_new(NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
                     G_OBJECT_CLASS_TYPE(object_class),
                     G_SIGNAL_RUN_FIRST,
                     0,
                     NULL,
                     NULL,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE,
                     0);
}
