#include <glib-object.h>

static void
test_fundamentals (void)
{
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_NONE));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INTERFACE));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_CHAR));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UCHAR));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_BOOLEAN));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INT));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UINT));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_LONG));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_ULONG));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_INT64));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_UINT64));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_ENUM));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_FLAGS));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_FLOAT));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_DOUBLE));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_STRING));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_POINTER));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_BOXED));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_PARAM));
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_OBJECT));
  g_assert (G_TYPE_OBJECT == g_object_get_type ());
  g_assert (G_TYPE_IS_FUNDAMENTAL (G_TYPE_VARIANT));
  g_assert (G_TYPE_IS_DERIVED (G_TYPE_INITIALLY_UNOWNED));

  g_assert (g_type_fundamental_next () == G_TYPE_MAKE_FUNDAMENTAL (G_TYPE_RESERVED_USER_FIRST));
}

static void
test_type_qdata (void)
{
  gchar *data;

  g_type_set_qdata (G_TYPE_ENUM, g_quark_from_string ("bla"), "bla");
  data = g_type_get_qdata (G_TYPE_ENUM, g_quark_from_string ("bla"));
  g_assert_cmpstr (data, ==, "bla");
}

static void
test_type_query (void)
{
  GTypeQuery query;

  g_type_query (G_TYPE_ENUM, &query);
  g_assert_cmpint (query.type, ==, G_TYPE_ENUM);
  g_assert_cmpstr (query.type_name, ==, "GEnum");
  g_assert_cmpint (query.class_size, ==, sizeof (GEnumClass));
  g_assert_cmpint (query.instance_size, ==, 0);
}

typedef struct _MyObject MyObject;
typedef struct _MyObjectClass MyObjectClass;
typedef struct _MyObjectClassPrivate MyObjectClassPrivate;

struct _MyObject
{
  GObject parent_instance;

  gint count;
};

struct _MyObjectClass
{
  GObjectClass parent_class;
};

struct _MyObjectClassPrivate
{
  gint secret_class_count;
};

static GType my_object_get_type (void);
G_DEFINE_TYPE_WITH_CODE (MyObject, my_object, G_TYPE_OBJECT,
                         g_type_add_class_private (g_define_type_id, sizeof (MyObjectClassPrivate)) );

static void
my_object_init (MyObject *obj)
{
  obj->count = 42;
}

static void
my_object_class_init (MyObjectClass *klass)
{
}

static void
test_class_private (void)
{
  GObject *obj;
  MyObjectClass *class;
  MyObjectClassPrivate *priv;

  obj = g_object_new (my_object_get_type (), NULL);

  class = g_type_class_ref (my_object_get_type ());
  priv = G_TYPE_CLASS_GET_PRIVATE (class, my_object_get_type (), MyObjectClassPrivate);
  priv->secret_class_count = 13;
  g_type_class_unref (class);

  g_object_unref (obj);

  g_assert_cmpint (g_type_qname (my_object_get_type ()), ==, g_quark_from_string ("MyObject"));
}

static void
test_clear (void)
{
  GObject *o = NULL;
  GObject *tmp;

  g_clear_object (&o);
  g_assert (o == NULL);

  tmp = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  o = g_object_ref (tmp);
  g_assert (o != NULL);

  g_assert_cmpint (tmp->ref_count, ==, 2);
  g_clear_object (&o);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  g_assert (o == NULL);

  g_object_unref (tmp);
}

static void
test_clear_function (void)
{
  GObject *o = NULL;
  GObject *tmp;

  (g_clear_object) (&o);
  g_assert (o == NULL);

  tmp = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  o = g_object_ref (tmp);
  g_assert (o != NULL);

  g_assert_cmpint (tmp->ref_count, ==, 2);
  (g_clear_object) (&o);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  g_assert (o == NULL);

  g_object_unref (tmp);
}

static void
test_set (void)
{
  GObject *o = NULL;
  GObject *tmp;
  gpointer tmp_weak = NULL;

  g_assert (!g_set_object (&o, NULL));
  g_assert (o == NULL);

  tmp = g_object_new (G_TYPE_OBJECT, NULL);
  tmp_weak = tmp;
  g_object_add_weak_pointer (tmp, &tmp_weak);
  g_assert_cmpint (tmp->ref_count, ==, 1);

  g_assert (g_set_object (&o, tmp));
  g_assert (o == tmp);
  g_assert_cmpint (tmp->ref_count, ==, 2);

  g_object_unref (tmp);
  g_assert_cmpint (tmp->ref_count, ==, 1);

  /* Setting it again shouldn’t cause finalisation. */
  g_assert (!g_set_object (&o, tmp));
  g_assert (o == tmp);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  g_assert_nonnull (tmp_weak);

  g_assert (g_set_object (&o, NULL));
  g_assert (o == NULL);
  g_assert_null (tmp_weak);
}

static void
test_set_function (void)
{
  GObject *o = NULL;
  GObject *tmp;
  gpointer tmp_weak = NULL;

  g_assert (!(g_set_object) (&o, NULL));
  g_assert (o == NULL);

  tmp = g_object_new (G_TYPE_OBJECT, NULL);
  tmp_weak = tmp;
  g_object_add_weak_pointer (tmp, &tmp_weak);
  g_assert_cmpint (tmp->ref_count, ==, 1);

  g_assert ((g_set_object) (&o, tmp));
  g_assert (o == tmp);
  g_assert_cmpint (tmp->ref_count, ==, 2);

  g_object_unref (tmp);
  g_assert_cmpint (tmp->ref_count, ==, 1);

  /* Setting it again shouldn’t cause finalisation. */
  g_assert (!(g_set_object) (&o, tmp));
  g_assert (o == tmp);
  g_assert_cmpint (tmp->ref_count, ==, 1);
  g_assert_nonnull (tmp_weak);

  g_assert ((g_set_object) (&o, NULL));
  g_assert (o == NULL);
  g_assert_null (tmp_weak);
}

static void
test_set_derived_type (void)
{
  GBinding *obj = NULL;
  GObject *o = NULL;
  GBinding *b = NULL;

  g_test_summary ("Check that g_set_object() doesn’t give strict aliasing "
                  "warnings when used on types derived from GObject");

  g_assert_false (g_set_object (&o, NULL));
  g_assert_null (o);

  g_assert_false (g_set_object (&b, NULL));
  g_assert_null (b);

  obj = g_object_new (my_object_get_type (), NULL);

  g_assert_true (g_set_object (&o, G_OBJECT (obj)));
  g_assert_true (o == G_OBJECT (obj));

  g_assert_true (g_set_object (&b, obj));
  g_assert_true (b == obj);

  g_object_unref (obj);
  g_clear_object (&b);
  g_clear_object (&o);
}

static void
toggle_cb (gpointer data, GObject *obj, gboolean is_last)
{
  gboolean *b = data;

  *b = TRUE;
}

static void
test_object_value (void)
{
  GObject *v;
  GObject *v2;
  GValue value = G_VALUE_INIT;
  gboolean toggled = FALSE;

  g_value_init (&value, G_TYPE_OBJECT);

  v = g_object_new (G_TYPE_OBJECT, NULL);
  g_object_add_toggle_ref (v, toggle_cb, &toggled);

  g_value_take_object (&value, v);

  v2 = g_value_get_object (&value);
  g_assert (v2 == v);

  v2 = g_value_dup_object (&value);
  g_assert (v2 == v);  /* objects use ref/unref for copy/free */
  g_object_unref (v2);

  g_assert (!toggled);
  g_value_unset (&value);
  g_assert (toggled);

  /* test the deprecated variant too */
  g_value_init (&value, G_TYPE_OBJECT);
  /* get a new reference */
  g_object_ref (v);

G_GNUC_BEGIN_IGNORE_DEPRECATIONS
  g_value_set_object_take_ownership (&value, v);
G_GNUC_END_IGNORE_DEPRECATIONS

  toggled = FALSE;
  g_value_unset (&value);
  g_assert (toggled);

  g_object_remove_toggle_ref (v, toggle_cb, &toggled);
}

static void
test_initially_unowned (void)
{
  GObject *obj;

  obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);
  g_assert (g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_ref_sink (obj);
  g_assert (!g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_ref_sink (obj);
  g_assert (!g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 2);

  g_object_unref (obj);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_force_floating (obj);
  g_assert (g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_ref_sink (obj);
  g_object_unref (obj);

  obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);
  g_assert_true (g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_take_ref (obj);
  g_assert_false (g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_take_ref (obj);
  g_assert_false (g_object_is_floating (obj));
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_unref (obj);

  if (g_test_undefined ())
    {
      obj = g_object_new (G_TYPE_INITIALLY_UNOWNED, NULL);

#ifdef G_ENABLE_DEBUG
      g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
                             "A floating object GInitiallyUnowned * was finalized*");
#endif
      g_object_unref (obj);
#ifdef G_ENABLE_DEBUG
      g_test_assert_expected_messages ();
#endif
    }
}

static void
test_weak_pointer (void)
{
  GObject *obj;
  gpointer weak;
  gpointer weak2;

  weak = weak2 = obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_add_weak_pointer (obj, &weak);
  g_object_add_weak_pointer (obj, &weak2);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert (weak == obj);
  g_assert (weak2 == obj);

  g_object_remove_weak_pointer (obj, &weak2);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert (weak == obj);
  g_assert (weak2 == obj);

  g_object_unref (obj);
  g_assert (weak == NULL);
  g_assert (weak2 == obj);
}

static void
test_weak_pointer_clear (void)
{
  GObject *obj;
  gpointer weak = NULL;

  g_clear_weak_pointer (&weak);
  g_assert_null (weak);

  weak = obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_add_weak_pointer (obj, &weak);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  g_clear_weak_pointer (&weak);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_null (weak);

  g_object_unref (obj);
}

static void
test_weak_pointer_clear_function (void)
{
  GObject *obj;
  gpointer weak = NULL;

  (g_clear_weak_pointer) (&weak);
  g_assert_null (weak);

  weak = obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_object_add_weak_pointer (obj, &weak);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  (g_clear_weak_pointer) (&weak);
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_null (weak);

  g_object_unref (obj);
}

static void
test_weak_pointer_set (void)
{
  GObject *obj;
  gpointer weak = NULL;

  g_assert_false (g_set_weak_pointer (&weak, NULL));
  g_assert_null (weak);

  obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_assert_true (g_set_weak_pointer (&weak, obj));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  g_assert_true (g_set_weak_pointer (&weak, NULL));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_null (weak);

  g_assert_true (g_set_weak_pointer (&weak, obj));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  g_object_unref (obj);
  g_assert_null (weak);
}

static void
test_weak_pointer_set_function (void)
{
  GObject *obj;
  gpointer weak = NULL;

  g_assert_false ((g_set_weak_pointer) (&weak, NULL));
  g_assert_null (weak);

  obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  g_assert_true ((g_set_weak_pointer) (&weak, obj));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  g_assert_true ((g_set_weak_pointer) (&weak, NULL));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_null (weak);

  g_assert_true ((g_set_weak_pointer) (&weak, obj));
  g_assert_cmpint (obj->ref_count, ==, 1);
  g_assert_true (weak == obj);

  g_object_unref (obj);
  g_assert_null (weak);
}

/* See gobject/tests/threadtests.c for the threaded version */
static void
test_weak_ref (void)
{
  GObject *obj;
  GObject *obj2;
  GObject *tmp;
  GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
  GWeakRef weak2 = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
  GWeakRef weak3 = { { GUINT_TO_POINTER (0xDEADBEEFU) } };
  GWeakRef *dynamic_weak = g_new (GWeakRef, 1);

  /* you can initialize to empty like this... */
  g_weak_ref_init (&weak2, NULL);
  g_assert (g_weak_ref_get (&weak2) == NULL);

  /* ... or via an initializer */
  g_weak_ref_init (&weak3, NULL);
  g_assert (g_weak_ref_get (&weak3) == NULL);

  obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj->ref_count, ==, 1);

  obj2 = g_object_new (G_TYPE_OBJECT, NULL);
  g_assert_cmpint (obj2->ref_count, ==, 1);

  /* you can init with an object (even if uninitialized) */
  g_weak_ref_init (&weak, obj);
  g_weak_ref_init (dynamic_weak, obj);
  /* or set to point at an object, if initialized (maybe to 0) */
  g_weak_ref_set (&weak2, obj);
  g_weak_ref_set (&weak3, obj);
  /* none of this affects its refcount */
  g_assert_cmpint (obj->ref_count, ==, 1);

  /* getting the value takes a ref */
  tmp = g_weak_ref_get (&weak);
  g_assert (tmp == obj);
  g_assert_cmpint (obj->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj->ref_count, ==, 1);

  tmp = g_weak_ref_get (&weak2);
  g_assert (tmp == obj);
  g_assert_cmpint (obj->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj->ref_count, ==, 1);

  tmp = g_weak_ref_get (&weak3);
  g_assert (tmp == obj);
  g_assert_cmpint (obj->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj->ref_count, ==, 1);

  tmp = g_weak_ref_get (dynamic_weak);
  g_assert (tmp == obj);
  g_assert_cmpint (obj->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj->ref_count, ==, 1);

  /* clearing a weak ref stops tracking */
  g_weak_ref_clear (&weak);

  /* setting a weak ref to NULL stops tracking too */
  g_weak_ref_set (&weak2, NULL);
  g_assert (g_weak_ref_get (&weak2) == NULL);
  g_weak_ref_clear (&weak2);

  /* setting a weak ref to a new object stops tracking the old one */
  g_weak_ref_set (dynamic_weak, obj2);
  tmp = g_weak_ref_get (dynamic_weak);
  g_assert (tmp == obj2);
  g_assert_cmpint (obj2->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj2->ref_count, ==, 1);

  g_assert_cmpint (obj->ref_count, ==, 1);

  /* free the object: weak3 is the only one left pointing there */
  g_object_unref (obj);
  g_assert (g_weak_ref_get (&weak3) == NULL);

  /* setting a weak ref to a new object stops tracking the old one */
  g_weak_ref_set (dynamic_weak, obj2);
  tmp = g_weak_ref_get (dynamic_weak);
  g_assert (tmp == obj2);
  g_assert_cmpint (obj2->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj2->ref_count, ==, 1);

  g_weak_ref_clear (&weak3);

  /* unset dynamic_weak... */
  g_weak_ref_set (dynamic_weak, NULL);
  g_assert_null (g_weak_ref_get (dynamic_weak));

  /* initializing a weak reference to an object that had before works */
  g_weak_ref_set (dynamic_weak, obj2);
  tmp = g_weak_ref_get (dynamic_weak);
  g_assert_true (tmp == obj2);
  g_assert_cmpint (obj2->ref_count, ==, 2);
  g_object_unref (tmp);
  g_assert_cmpint (obj2->ref_count, ==, 1);

  /* clear and free dynamic_weak... */
  g_weak_ref_clear (dynamic_weak);

  /* ... to prove that doing so stops this from being a use-after-free */
  g_object_unref (obj2);
  g_free (dynamic_weak);
}

G_DECLARE_FINAL_TYPE (WeakReffedObject, weak_reffed_object,
                      WEAK, REFFED_OBJECT, GObject)

struct _WeakReffedObject
{
  GObject parent;

  GWeakRef *weak_ref;
};

G_DEFINE_TYPE (WeakReffedObject, weak_reffed_object, G_TYPE_OBJECT)

static void
weak_reffed_object_dispose (GObject *object)
{
  WeakReffedObject *weak_reffed = WEAK_REFFED_OBJECT (object);

  g_assert_cmpint (object->ref_count, ==, 1);

  g_weak_ref_set (weak_reffed->weak_ref, object);

  G_OBJECT_CLASS (weak_reffed_object_parent_class)->dispose (object);

  g_assert_null (g_weak_ref_get (weak_reffed->weak_ref));
}

static void
weak_reffed_object_init (WeakReffedObject *connector)
{
}

static void
weak_reffed_object_class_init (WeakReffedObjectClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = weak_reffed_object_dispose;
}

static void
test_weak_ref_on_dispose (void)
{
  WeakReffedObject *obj;
  GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };

  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2390");
  g_test_summary ("Test that a weak ref set during dispose vfunc is cleared");

  g_weak_ref_init (&weak, NULL);

  obj = g_object_new (weak_reffed_object_get_type (), NULL);
  obj->weak_ref = &weak;

  g_assert_cmpint (G_OBJECT (obj)->ref_count, ==, 1);
  g_clear_object (&obj);

  g_assert_null (g_weak_ref_get (&weak));
}

static void
test_weak_ref_on_run_dispose (void)
{
  GObject *obj;
  GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };

  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/865");
  g_test_summary ("Test that a weak ref is cleared on g_object_run_dispose()");

  obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_weak_ref_init (&weak, obj);

  g_assert_true (obj == g_weak_ref_get (&weak));
  g_object_unref (obj);

  g_object_run_dispose (obj);
  g_assert_null (g_weak_ref_get (&weak));

  g_clear_object (&obj);
  g_assert_null (g_weak_ref_get (&weak));
}

static void
on_weak_ref_toggle_notify (gpointer data,
                           GObject *object,
                           gboolean is_last_ref)
{
  GWeakRef *weak = data;

  if (is_last_ref)
    g_weak_ref_set (weak, object);
}

static void
on_weak_ref_toggle_notify_disposed (gpointer data,
                                    GObject *object)
{
  g_assert_cmpint (object->ref_count, ==, 1);

  g_object_ref (object);
  g_object_unref (object);
}

static void
test_weak_ref_on_toggle_notify (void)
{
  GObject *obj;
  GWeakRef weak = { { GUINT_TO_POINTER (0xDEADBEEFU) } };

  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2390");
  g_test_summary ("Test that a weak ref set on toggle notify is cleared");

  g_weak_ref_init (&weak, NULL);

  obj = g_object_new (G_TYPE_OBJECT, NULL);
  g_object_add_toggle_ref (obj, on_weak_ref_toggle_notify, &weak);
  g_object_weak_ref (obj, on_weak_ref_toggle_notify_disposed, NULL);
  g_object_unref (obj);

  g_assert_cmpint (obj->ref_count, ==, 1);
  g_clear_object (&obj);

  g_assert_null (g_weak_ref_get (&weak));
}

typedef struct
{
  gboolean should_be_last;
  gint count;
} Count;

static void
toggle_notify (gpointer  data,
               GObject  *obj,
               gboolean  is_last)
{
  Count *c = data;

  g_assert (is_last == c->should_be_last);

  if (is_last)
    g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 1);
  else
    g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 2);

  c->count++;
}

static void
test_toggle_ref (void)
{
  GObject *obj;
  Count c, c2;

  obj = g_object_new (G_TYPE_OBJECT, NULL);

  g_object_add_toggle_ref (obj, toggle_notify, &c);
  g_object_add_toggle_ref (obj, toggle_notify, &c2);

  c.should_be_last = c2.should_be_last = TRUE;
  c.count = c2.count = 0;

  g_object_unref (obj);

  g_assert_cmpint (c.count, ==, 0);
  g_assert_cmpint (c2.count, ==, 0);

  g_object_ref (obj);

  g_assert_cmpint (c.count, ==, 0);
  g_assert_cmpint (c2.count, ==, 0);

  g_object_remove_toggle_ref (obj, toggle_notify, &c2);

  g_object_unref (obj);

  g_assert_cmpint (c.count, ==, 1);

  c.should_be_last = FALSE;

  g_object_ref (obj);

  g_assert_cmpint (c.count, ==, 2);

  c.should_be_last = TRUE;

  g_object_unref (obj);

  g_assert_cmpint (c.count, ==, 3);

  g_object_remove_toggle_ref (obj, toggle_notify, &c);
}

G_DECLARE_FINAL_TYPE (DisposeReffingObject, dispose_reffing_object,
                      DISPOSE, REFFING_OBJECT, GObject)

typedef enum
{
  PROP_INT_PROP = 1,
  N_PROPS,
} DisposeReffingObjectProperty;

static GParamSpec *dispose_reffing_object_properties[N_PROPS] = {0};

struct _DisposeReffingObject
{
  GObject parent;

  GToggleNotify toggle_notify;
  Count actual;
  Count expected;
  unsigned disposing_refs;
  gboolean disposing_refs_all_normal;

  GCallback notify_handler;
  unsigned notify_called;

  int int_prop;

  GWeakRef *weak_ref;
};

G_DEFINE_TYPE (DisposeReffingObject, dispose_reffing_object, G_TYPE_OBJECT)

static void
on_object_notify (GObject    *object,
                  GParamSpec *pspec,
                  void       *data)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  obj->notify_called++;
}

static void
dispose_reffing_object_dispose (GObject *object)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  g_assert_cmpint (object->ref_count, ==, 1);
  g_assert_cmpint (obj->actual.count, ==, obj->expected.count);

  for (unsigned i = 0; i < obj->disposing_refs; ++i)
    {
      if (i == 0 && !obj->disposing_refs_all_normal)
        {
          g_object_add_toggle_ref (object, obj->toggle_notify, &obj->actual);
        }
      else
        {
          obj->actual.should_be_last = FALSE;
          g_object_ref (obj);
          g_assert_cmpint (obj->actual.count, ==, obj->expected.count);
        }

      obj->actual.should_be_last = TRUE;
    }

  G_OBJECT_CLASS (dispose_reffing_object_parent_class)->dispose (object);

  if (obj->notify_handler)
    {
      unsigned old_notify_called = obj->notify_called;

      g_assert_cmpuint (g_signal_handler_find (object, G_SIGNAL_MATCH_FUNC,
                        0, 0, NULL, obj->notify_handler, NULL), ==, 0);

      g_signal_connect (object, "notify", G_CALLBACK (obj->notify_handler), NULL);

      /* This would trigger a toggle notification, but is not something we may
       * want with https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2377
       * so, we only test this in case we have more than one ref
       */
      if (obj->toggle_notify == toggle_notify)
        g_assert_cmpint (obj->disposing_refs, >, 1);

      g_object_notify (object, "int-prop");
      g_assert_cmpuint (obj->notify_called, ==, old_notify_called);
    }

  g_assert_cmpint (obj->actual.count, ==, obj->expected.count);
}

static void
dispose_reffing_object_init (DisposeReffingObject *connector)
{
}

static void
dispose_reffing_object_set_property (GObject *object,
                                     guint property_id,
                                     const GValue *value,
                                     GParamSpec *pspec)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  switch ((DisposeReffingObjectProperty) property_id)
    {
    case PROP_INT_PROP:
      obj->int_prop = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
dispose_reffing_object_get_property (GObject *object,
                                     guint property_id,
                                     GValue *value,
                                     GParamSpec *pspec)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  switch ((DisposeReffingObjectProperty) property_id)
    {
    case PROP_INT_PROP:
      g_value_set_int (value, obj->int_prop);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
dispose_reffing_object_class_init (DisposeReffingObjectClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  dispose_reffing_object_properties[PROP_INT_PROP] =
      g_param_spec_int ("int-prop", "int-prop", "int-prop",
                        G_MININT, G_MAXINT,
                        0,
                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  object_class->dispose = dispose_reffing_object_dispose;
  object_class->set_property = dispose_reffing_object_set_property;
  object_class->get_property = dispose_reffing_object_get_property;

  g_object_class_install_properties (object_class, N_PROPS,
                                     dispose_reffing_object_properties);
}

static void
test_toggle_ref_on_dispose (void)
{
  DisposeReffingObject *obj;
  gpointer disposed_checker = &obj;

  /* This tests wants to ensure that an object that gets re-referenced
   * (one or multiple times) during its dispose virtual function:
   *  - Notifies all the queued "notify" signal handlers
   *  - Notifies toggle notifications if any
   *  - It does not get finalized
   */

  obj = g_object_new (dispose_reffing_object_get_type (), NULL);
  obj->toggle_notify = toggle_notify;
  obj->notify_handler = G_CALLBACK (on_object_notify);
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Convert to toggle notification */
  g_object_add_toggle_ref (G_OBJECT (obj), obj->toggle_notify, &obj->actual);
  g_assert_cmpint (obj->actual.count, ==, 0);

  obj->actual.should_be_last = TRUE;
  obj->notify_handler = G_CALLBACK (on_object_notify);
  g_object_unref (obj);
  g_assert_cmpint (obj->actual.count, ==, 1);
  g_assert_cmpuint (obj->notify_called, ==, 0);

  /* Remove the toggle reference, making it to dispose and resurrect again */
  obj->disposing_refs = 1;
  obj->expected.count = 1;
  obj->notify_handler = NULL; /* FIXME: enable it when !2377 is in */
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
  g_assert_cmpint (obj->actual.count, ==, 2);
  g_assert_cmpuint (obj->notify_called, ==, 0);

  g_assert_null (disposed_checker);
  g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==,
                   obj->disposing_refs);

  /* Object has been disposed, but is still alive, so add another weak pointer */
  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Remove the toggle reference, making it to dispose and resurrect with
   * more references than before, so that no toggle notify is called
   */
  obj->disposing_refs = 3;
  obj->expected.count = 2;
  obj->notify_handler = G_CALLBACK (on_object_notify);
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
  g_assert_cmpint (obj->actual.count, ==, 2);
  g_assert_cmpint (obj->notify_called, ==, 1);
  obj->expected.count = obj->actual.count;

  g_assert_null (disposed_checker);
  g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==,
                   obj->disposing_refs);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Now remove the first added reference */
  obj->disposing_refs = 0;
  g_object_unref (obj);
  g_assert_nonnull (disposed_checker);
  g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==, 2);
  g_assert_cmpint (obj->actual.count, ==, 2);
  g_assert_cmpint (obj->notify_called, ==, 1);

  /* And the toggle one */
  obj->actual.should_be_last = TRUE;
  obj->notify_handler = NULL;
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
  g_assert_nonnull (disposed_checker);
  g_assert_cmpint (g_atomic_int_get (&G_OBJECT (obj)->ref_count), ==, 1);
  g_assert_cmpint (obj->actual.count, ==, 2);
  obj->expected.count = obj->actual.count;

  g_clear_object (&obj);
  g_assert_null (disposed_checker);
}

static void
toggle_notify_counter (gpointer  data,
                       GObject  *obj,
                       gboolean  is_last)
{
  Count *c = data;
  c->count++;

  if (is_last)
    g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 1);
  else
    g_assert_cmpint (g_atomic_int_get (&obj->ref_count), ==, 2);
}

static void
on_object_notify_switch_to_normal_ref (GObject    *object,
                                       GParamSpec *pspec,
                                       void       *data)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  obj->notify_called++;

  g_object_ref (object);
  g_object_remove_toggle_ref (object, obj->toggle_notify, NULL);
}

static void
on_object_notify_switch_to_toggle_ref (GObject    *object,
                                       GParamSpec *pspec,
                                       void       *data)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);

  obj->notify_called++;

  g_object_add_toggle_ref (object, obj->toggle_notify, &obj->actual);
  g_object_unref (object);
}

static void
on_object_notify_add_ref (GObject    *object,
                          GParamSpec *pspec,
                          void       *data)
{
  DisposeReffingObject *obj = DISPOSE_REFFING_OBJECT (object);
  int old_toggle_cout = obj->actual.count;

  obj->notify_called++;

  g_object_ref (object);
  g_assert_cmpint (obj->actual.count, ==, old_toggle_cout);
}

static void
test_toggle_ref_and_notify_on_dispose (void)
{
  DisposeReffingObject *obj;
  gpointer disposed_checker = &obj;

  /* This tests wants to ensure that toggle signal emission during dispose
   * is properly working if the object is revitalized by adding new references.
   * It also wants to check that toggle notifications are not happening if a
   * notify handler is removing them at this phase.
   */

  obj = g_object_new (dispose_reffing_object_get_type (), NULL);
  obj->toggle_notify = toggle_notify_counter;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Convert to toggle notification */
  g_object_add_toggle_ref (G_OBJECT (obj), obj->toggle_notify, &obj->actual);
  g_assert_cmpint (obj->actual.count, ==, 0);

  obj->notify_handler = G_CALLBACK (on_object_notify);
  g_object_unref (obj);
  g_assert_cmpint (obj->actual.count, ==, 1);
  g_assert_cmpuint (obj->notify_called, ==, 0);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Check that notification is triggered after being queued */
  obj->disposing_refs = 1;
  obj->expected.count = 1;
  obj->notify_handler = G_CALLBACK (on_object_notify);
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
   /* FIXME: adjust the count to 1 when !2377 is in */
  g_assert_cmpint (obj->actual.count, ==, 4);
  g_assert_cmpuint (obj->notify_called, ==, 1);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Check that notification is triggered after being queued, but no toggle
   * notification is happening if notify handler switches to normal reference
   */
  obj->disposing_refs = 1;
  obj->expected.count = 4;
  obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_normal_ref);
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
  g_assert_cmpint (obj->actual.count, ==, 5);
  g_assert_cmpuint (obj->notify_called, ==, 2);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Check that notification is triggered after being queued, but that toggle
   * is happening if notify handler switched to toggle reference
   */
  obj->disposing_refs = 1;
  obj->disposing_refs_all_normal = TRUE;
  obj->expected.count = 5;
  obj->notify_handler = G_CALLBACK (on_object_notify_switch_to_toggle_ref);
  g_object_unref (obj);
  g_assert_cmpint (obj->actual.count, ==, 7);
  g_assert_cmpuint (obj->notify_called, ==, 3);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  /* Check that notification is triggered after being queued, but that toggle
   * is not happening if current refcount changed.
   */
  obj->disposing_refs = 1;
  obj->disposing_refs_all_normal = FALSE;
  obj->expected.count = 7;
  obj->notify_handler = G_CALLBACK (on_object_notify_add_ref);
  g_object_remove_toggle_ref (G_OBJECT (obj), obj->toggle_notify, NULL);
  g_assert_cmpint (obj->actual.count, ==, 8);
  g_assert_cmpuint (obj->notify_called, ==, 4);
  g_object_unref (obj);

  disposed_checker = &obj;
  g_object_add_weak_pointer (G_OBJECT (obj), &disposed_checker);

  obj->disposing_refs = 0;
  obj->expected.count = 9;
  g_clear_object (&obj);
  g_assert_null (disposed_checker);
}

static gboolean global_destroyed;
static gint global_value;

static void
data_destroy (gpointer data)
{
  g_assert_cmpint (GPOINTER_TO_INT (data), ==, global_value);

  global_destroyed = TRUE;
}

static void
test_object_qdata (void)
{
  GObject *obj;
  gpointer v;
  GQuark quark;

  obj = g_object_new (G_TYPE_OBJECT, NULL);

  global_value = 1;
  global_destroyed = FALSE;
  g_object_set_data_full (obj, "test", GINT_TO_POINTER (1), data_destroy);
  v = g_object_get_data (obj, "test");
  g_assert_cmpint (GPOINTER_TO_INT (v), ==, 1);
  g_object_set_data_full (obj, "test", GINT_TO_POINTER (2), data_destroy);
  g_assert (global_destroyed);
  global_value = 2;
  global_destroyed = FALSE;
  v = g_object_steal_data (obj, "test");
  g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
  g_assert (!global_destroyed);

  global_value = 1;
  global_destroyed = FALSE;
  quark = g_quark_from_string ("test");
  g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (1), data_destroy);
  v = g_object_get_qdata (obj, quark);
  g_assert_cmpint (GPOINTER_TO_INT (v), ==, 1);
  g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (2), data_destroy);
  g_assert (global_destroyed);
  global_value = 2;
  global_destroyed = FALSE;
  v = g_object_steal_qdata (obj, quark);
  g_assert_cmpint (GPOINTER_TO_INT (v), ==, 2);
  g_assert (!global_destroyed);

  g_object_set_qdata_full (obj, quark, GINT_TO_POINTER (3), data_destroy);
  global_value = 3;
  global_destroyed = FALSE;
  g_object_unref (obj);

  g_assert (global_destroyed);
}

typedef struct {
  const gchar *value;
  gint refcount;
} Value;

static gpointer
ref_value (gpointer value, gpointer user_data)
{
  Value *v = value;
  Value **old_value_p = user_data;

  if (old_value_p)
    *old_value_p = v;

  if (v)
    v->refcount += 1;

  return value;
}

static void
unref_value (gpointer value)
{
  Value *v = value;

  v->refcount -= 1;
  if (v->refcount == 0)
    g_free (value);
}

static
Value *
new_value (const gchar *s)
{
  Value *v;

  v = g_new (Value, 1);
  v->value = s;
  v->refcount = 1;

  return v;
}

static void
test_object_qdata2 (void)
{
  GObject *obj;
  Value *v, *v1, *v2, *v3, *old_val;
  GDestroyNotify old_destroy;
  gboolean res;

  obj = g_object_new (G_TYPE_OBJECT, NULL);

  v1 = new_value ("bla");

  g_object_set_data_full (obj, "test", v1, unref_value);

  v = g_object_get_data (obj, "test");
  g_assert_cmpstr (v->value, ==, "bla");
  g_assert_cmpint (v->refcount, ==, 1);

  v = g_object_dup_data (obj, "test", ref_value, &old_val);
  g_assert (old_val == v1);
  g_assert_cmpstr (v->value, ==, "bla");
  g_assert_cmpint (v->refcount, ==, 2);
  unref_value (v);

  v = g_object_dup_data (obj, "nono", ref_value, &old_val);
  g_assert (old_val == NULL);
  g_assert (v == NULL);

  v2 = new_value ("not");

  res = g_object_replace_data (obj, "test", v1, v2, unref_value, &old_destroy);
  g_assert (res == TRUE);
  g_assert (old_destroy == unref_value);
  g_assert_cmpstr (v1->value, ==, "bla");
  g_assert_cmpint (v1->refcount, ==, 1);

  v = g_object_get_data (obj, "test");
  g_assert_cmpstr (v->value, ==, "not");
  g_assert_cmpint (v->refcount, ==, 1);

  v3 = new_value ("xyz");
  res = g_object_replace_data (obj, "test", v1, v3, unref_value, &old_destroy);
  g_assert (res == FALSE);
  g_assert_cmpstr (v2->value, ==, "not");
  g_assert_cmpint (v2->refcount, ==, 1);

  unref_value (v1);

  res = g_object_replace_data (obj, "test", NULL, v3, unref_value, &old_destroy);
  g_assert (res == FALSE);
  g_assert_cmpstr (v2->value, ==, "not");
  g_assert_cmpint (v2->refcount, ==, 1);

  res = g_object_replace_data (obj, "test", v2, NULL, unref_value, &old_destroy);
  g_assert (res == TRUE);
  g_assert (old_destroy == unref_value);
  g_assert_cmpstr (v2->value, ==, "not");
  g_assert_cmpint (v2->refcount, ==, 1);

  unref_value (v2);

  v = g_object_get_data (obj, "test");
  g_assert (v == NULL);

  res = g_object_replace_data (obj, "test", NULL, v3, unref_value, &old_destroy);
  g_assert (res == TRUE);

  v = g_object_get_data (obj, "test");
  g_assert (v == v3);

  ref_value (v3, NULL);
  g_assert_cmpint (v3->refcount, ==, 2);
  g_object_unref (obj);
  g_assert_cmpint (v3->refcount, ==, 1);
  unref_value (v3);
}

int
main (int argc, char **argv)
{
  g_test_init (&argc, &argv, NULL);

  g_setenv ("G_ENABLE_DIAGNOSTIC", "1", TRUE);

  g_test_add_func ("/type/fundamentals", test_fundamentals);
  g_test_add_func ("/type/qdata", test_type_qdata);
  g_test_add_func ("/type/query", test_type_query);
  g_test_add_func ("/type/class-private", test_class_private);
  g_test_add_func ("/object/clear", test_clear);
  g_test_add_func ("/object/clear-function", test_clear_function);
  g_test_add_func ("/object/set", test_set);
  g_test_add_func ("/object/set-function", test_set_function);
  g_test_add_func ("/object/set/derived-type", test_set_derived_type);
  g_test_add_func ("/object/value", test_object_value);
  g_test_add_func ("/object/initially-unowned", test_initially_unowned);
  g_test_add_func ("/object/weak-pointer", test_weak_pointer);
  g_test_add_func ("/object/weak-pointer/clear", test_weak_pointer_clear);
  g_test_add_func ("/object/weak-pointer/clear-function", test_weak_pointer_clear_function);
  g_test_add_func ("/object/weak-pointer/set", test_weak_pointer_set);
  g_test_add_func ("/object/weak-pointer/set-function", test_weak_pointer_set_function);
  g_test_add_func ("/object/weak-ref", test_weak_ref);
  g_test_add_func ("/object/weak-ref/on-dispose", test_weak_ref_on_dispose);
  g_test_add_func ("/object/weak-ref/on-run-dispose", test_weak_ref_on_run_dispose);
  g_test_add_func ("/object/weak-ref/on-toggle-notify", test_weak_ref_on_toggle_notify);
  g_test_add_func ("/object/toggle-ref", test_toggle_ref);
  g_test_add_func ("/object/toggle-ref/ref-on-dispose", test_toggle_ref_on_dispose);
  g_test_add_func ("/object/toggle-ref/ref-and-notify-on-dispose", test_toggle_ref_and_notify_on_dispose);
  g_test_add_func ("/object/qdata", test_object_qdata);
  g_test_add_func ("/object/qdata2", test_object_qdata2);

  return g_test_run ();
}