/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2007 - 2008 Novell, Inc.
 * Copyright (C) 2007 - 2012 Red Hat, Inc.
 */

#include "libnm-client-impl/nm-default-libnm.h"

#include "nm-object.h"

#include <stdlib.h>
#include <stdio.h>

#include "nm-utils.h"
#include "nm-dbus-interface.h"
#include "nm-object-private.h"
#include "nm-dbus-helpers.h"
#include "nm-client.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "c-list/src/c-list.h"

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

NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PATH, PROP_CLIENT, );

typedef struct _NMObjectPrivate {
    NMClient      *client;
    NMLDBusObject *dbobj;
} NMObjectPrivate;

G_DEFINE_ABSTRACT_TYPE(NMObject, nm_object, G_TYPE_OBJECT);

#define NM_OBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMObject, NM_IS_OBJECT)

static NMObjectClass *_nm_object_class = NULL;

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

static gpointer
_nm_object_get_private(NMObjectClass *klass, NMObject *self, guint16 extra_offset)
{
    char *ptr;

    nm_assert(klass->priv_ptr_offset > 0);

    ptr = (char *) self;
    ptr += klass->priv_ptr_offset;
    if (klass->priv_ptr_indirect)
        ptr = *((gpointer *) ptr);
    return ptr + extra_offset;
}

NMLDBusObject *
_nm_object_get_dbobj(gpointer self)
{
    return NM_OBJECT_GET_PRIVATE(self)->dbobj;
}

const char *
_nm_object_get_path(gpointer self)
{
    return NM_OBJECT_GET_PRIVATE(self)->dbobj->dbus_path->str;
}

NMClient *
_nm_object_get_client(gpointer self)
{
    return NM_OBJECT_GET_PRIVATE(self)->client;
}

/**
 * nm_object_get_path:
 * @object: a #NMObject
 *
 * Gets the DBus path of the #NMObject.
 *
 * Returns: the object's path. This is the internal string used by the
 * object, and must not be modified.
 *
 * Note that the D-Bus path of an NMObject never changes, even
 * if the instance gets removed from the cache. To find out
 * whether the object is still alive/cached, check nm_object_get_client().
 **/
const char *
nm_object_get_path(NMObject *object)
{
    g_return_val_if_fail(NM_IS_OBJECT(object), NULL);

    return _nm_object_get_path(object);
}

/**
 * nm_object_get_client:
 * @object: a #NMObject
 *
 * Returns the #NMClient instance in which object is cached.
 * Also, if the object got removed from the client cached,
 * this returns %NULL. So it can be used to check whether the
 * object is still alive.
 *
 * Returns: (transfer none): the #NMClient cache in which the
 * object can be found, or %NULL if the object is no longer
 * cached.
 *
 * Since: 1.24
 **/
NMClient *
nm_object_get_client(NMObject *object)
{
    g_return_val_if_fail(NM_IS_OBJECT(object), NULL);

    return _nm_object_get_client(object);
}

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

static void
clear_properties(NMObject *self, NMClient *client)
{
    NMObjectClass                 *klass = NM_OBJECT_GET_CLASS(self);
    const _NMObjectClassFieldInfo *p;

    nm_assert(NM_IS_OBJECT(self));
    nm_assert(!client || NM_IS_CLIENT(client));

    for (p = klass->property_o_info; p; p = p->parent) {
        nml_dbus_property_o_clear_many(_nm_object_get_private(p->klass, self, p->offset),
                                       p->num,
                                       client);
    }

    for (p = klass->property_ao_info; p; p = p->parent) {
        nml_dbus_property_ao_clear_many(_nm_object_get_private(p->klass, self, p->offset),
                                        p->num,
                                        client);
    }
}

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

static gboolean
is_ready(NMObject *self)
{
    NMObjectClass                 *klass  = NM_OBJECT_GET_CLASS(self);
    NMClient                      *client = _nm_object_get_client(self);
    const _NMObjectClassFieldInfo *p;
    guint16                        i;

    nm_assert(NM_IS_CLIENT(client));

    for (p = klass->property_o_info; p; p = p->parent) {
        NMLDBusPropertyO *fields = _nm_object_get_private(p->klass, self, p->offset);

        for (i = 0; i < p->num; i++) {
            if (!nml_dbus_property_o_is_ready(&fields[i]))
                return FALSE;
        }
    }

    for (p = klass->property_ao_info; p; p = p->parent) {
        NMLDBusPropertyAO *fields = _nm_object_get_private(p->klass, self, p->offset);

        for (i = 0; i < p->num; i++) {
            if (!nml_dbus_property_ao_is_ready(&fields[i]))
                return FALSE;
        }
    }

    return TRUE;
}

static void
obj_changed_notify(NMObject *self)
{
    NMObjectClass                 *klass  = NM_OBJECT_GET_CLASS(self);
    NMClient                      *client = _nm_object_get_client(self);
    const _NMObjectClassFieldInfo *p;

    nm_assert(NM_IS_CLIENT(client));

    for (p = klass->property_o_info; p; p = p->parent) {
        nml_dbus_property_o_notify_changed_many(_nm_object_get_private(p->klass, self, p->offset),
                                                p->num,
                                                client);
    }

    for (p = klass->property_ao_info; p; p = p->parent) {
        nml_dbus_property_ao_notify_changed_many(_nm_object_get_private(p->klass, self, p->offset),
                                                 p->num,
                                                 client);
    }
}

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

static void
register_client(NMObject *self, NMClient *client, NMLDBusObject *dbobj)
{
    NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE(self);

    nm_assert(!priv->client);
    nm_assert(NML_IS_DBUS_OBJECT(dbobj));
    nm_assert(dbobj->nmobj == G_OBJECT(self));

    priv->client = client;
    priv->dbobj  = nml_dbus_object_ref(dbobj);
}

static void
unregister_client(NMObject *self, NMClient *client, NMLDBusObject *dbobj)
{
    NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE(self);

    nm_assert(NM_IS_CLIENT(client));
    nm_assert(priv->client == client);
    priv->client = NULL;

    _nm_client_queue_notify_object(client, self, obj_properties[PROP_CLIENT]);

    clear_properties(self, client);
}

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

static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    NMObject *self = NM_OBJECT(object);

    switch (prop_id) {
    case PROP_PATH:
        g_value_set_string(value, nm_object_get_path(self));
        break;
    case PROP_CLIENT:
        g_value_set_object(value, nm_object_get_client(self));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

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

static void
nm_object_init(NMObject *object)
{
    NMObject        *self = NM_OBJECT(object);
    NMObjectPrivate *priv;

    priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_OBJECT, NMObjectPrivate);

    self->_priv = priv;

    c_list_init(&self->obj_base.queue_notify_lst);

    NML_DBUS_LOG(_NML_NMCLIENT_LOG_LEVEL_COERCE(NML_DBUS_LOG_LEVEL_TRACE),
                 "nmobj[" NM_HASH_OBFUSCATE_PTR_FMT "]: creating",
                 NM_HASH_OBFUSCATE_PTR(self));
}

static void
dispose(GObject *object)
{
    NMObject        *self = NM_OBJECT(object);
    NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE(self);

    if (!self->obj_base.is_disposing) {
        NML_DBUS_LOG(_NML_NMCLIENT_LOG_LEVEL_COERCE(NML_DBUS_LOG_LEVEL_TRACE),
                     "nmobj[" NM_HASH_OBFUSCATE_PTR_FMT "]: disposing",
                     NM_HASH_OBFUSCATE_PTR(self));
    }

    self->obj_base.is_disposing = TRUE;

    nm_assert(c_list_is_empty(&self->obj_base.queue_notify_lst));
    nm_assert(!priv->client);
    nm_assert(!priv->dbobj || !priv->dbobj->nmobj);

    clear_properties(self, NULL);

    G_OBJECT_CLASS(nm_object_parent_class)->dispose(object);

    nm_clear_pointer(&priv->dbobj, nml_dbus_object_unref);
}

static void
nm_object_class_init(NMObjectClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    _nm_object_class = klass;

    g_type_class_add_private(klass, sizeof(NMObjectPrivate));

    object_class->get_property = get_property;
    object_class->dispose      = dispose;

    klass->register_client    = register_client;
    klass->unregister_client  = unregister_client;
    klass->is_ready           = is_ready;
    klass->obj_changed_notify = obj_changed_notify;

    /**
     * NMObject:path:
     *
     * The D-Bus object path.
     *
     * The D-Bus path of an object instance never changes, even if the object
     * gets removed from the cache. To see whether the object is still in the
     * cache, check NMObject:client.
     **/
    obj_properties[PROP_PATH] = g_param_spec_string(NM_OBJECT_PATH,
                                                    "",
                                                    "",
                                                    NULL,
                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * NMObject:client:
     *
     * The NMClient instance as returned by nm_object_get_client().
     *
     * When an NMObject gets removed from the NMClient cache,
     * the NMObject:path property stays unchanged, but this client
     * instance gets reset to %NULL. You can use this property to
     * track removal of the object from the cache.
     *
     * Since: 1.34
     **/
    obj_properties[PROP_CLIENT] = g_param_spec_object(NM_OBJECT_CLIENT,
                                                      "",
                                                      "",
                                                      NM_TYPE_CLIENT,
                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}
