/* GObject - GLib Type, Object, Parameter and Signal Library
 * signal.c: Test signal connection and disconnection
 * Copyright (C) 2004, Matthias Clasen, Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#undef	G_LOG_DOMAIN
#define	G_LOG_DOMAIN "TestOverride"

#undef G_DISABLE_ASSERT
#undef G_DISABLE_CHECKS
#undef G_DISABLE_CAST_CHECKS

#undef VERBOSE

#include <string.h>

#include <glib.h>
#include <gprintf.h>
#include <glib-object.h>

typedef struct {
  GObject object;

  gchar *name;
} TestObject;

typedef struct {
  GObjectClass parent_class;

  void (*test) (TestObject *object);
  void (*test2) (TestObject *object);

} TestObjectClass;

enum {
  TEST,
  TEST2,
  LAST_SIGNAL
}

signals[LAST_SIGNAL] = { 0 };

#define TYPE_TEST_OBJECT          (test_object_get_type ())
#define TEST_OBJECT(object)       (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_TEST_OBJECT, TestObject))
#define TEST_OBJECT_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_OBJECT, TestObjectClass))
#define TEST_IS_OBJECT(object)	  (G_TYPE_CHECK_INSTANCE_TYPE ((object), TEST_TYPE_OBJECT))
#define TEST_IS_OBJECT_CLASS(klass)	  (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_OBJECT))
#define TEST_OBJECT_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_OBJECT, TestObjectClass))

G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT);

static void finalize (GObject *object);

static void
test_object_class_init (TestObjectClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = finalize;

  signals[TEST] = g_signal_new ("test",
				G_TYPE_FROM_CLASS (gobject_class),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (TestObjectClass, test),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
  signals[TEST2] = g_signal_new ("test2",
				 G_TYPE_FROM_CLASS (gobject_class),
				 G_SIGNAL_RUN_FIRST,
				 G_STRUCT_OFFSET (TestObjectClass, test2),
				 NULL, NULL,
				 g_cclosure_marshal_VOID__VOID,
				 G_TYPE_NONE, 0);
}

static void
test_object_init (TestObject *self)
{
  self->name = NULL;
}

static gboolean verbose = FALSE;

static gint 
print (const gchar *format, ...)
{
  va_list args;
  gint retval = 0;

  if (verbose)
    {
      va_start (args, format);
      retval = g_vprintf (format, args);
      va_end (args);
    }

  return retval;
}


static void
finalize (GObject *object)
{
  TestObject *test = TEST_OBJECT (object);

  print ("\tfinalizing %s\n", test->name);
  g_free (test->name);
  test->name = NULL;
}

static TestObject *
test_object_new (gchar *name)
{
  TestObject *object = g_object_new (TYPE_TEST_OBJECT, NULL);
  object->name = g_strdup (name);
  return object;
}

gint test_handler_count = 0;
gboolean closure_invalidated = FALSE;
gboolean closure_finalized = FALSE;

static void
test_handler (TestObject *a, TestObject *b)
{
  test_handler_count++;
  print ("\tsignal handled on %s with object data %s\n",
	 a->name, b->name);
}

static void
invalidate_closure (gpointer data, GClosure *closure)
{
  closure_invalidated = TRUE;
  print ("\tclosure %p on object %s invalidated\n",
	 closure, TEST_OBJECT (data)->name);
}

static void
finalize_closure (gpointer data, GClosure *closure)
{
  closure_finalized = TRUE;
  print ("\tclosure %p finalized\n", closure);
}

int
main (int argc, char **argv)
{
  TestObject *a, *b, *c, *d, *e, *i;
  GClosure *closure;
  guint handler, handler2;

  if (argc > 1 && !strcmp (argv[1], "-v"))
    verbose = TRUE;

  g_type_init();

  i = test_object_new ("I");
  a = test_object_new ("A");
  b = test_object_new ("B");
  c = test_object_new ("C");
  d = test_object_new ("D");
  e = test_object_new ("E");
  
  print ("Check that signals don't get emitted after the object is gone.\n");
  test_handler_count = 0;

  g_signal_connect_object (a, "test", G_CALLBACK (test_handler), b, 0);
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_object_unref (b);
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);

  print ("Check that closures get finalized if the instance is gone.\n");
  test_handler_count = 0;
  closure_invalidated = FALSE;
  closure_finalized = FALSE;

  closure = g_cclosure_new_object (G_CALLBACK (test_handler), G_OBJECT (c));
  g_closure_add_invalidate_notifier (closure, c, invalidate_closure);
  g_closure_add_finalize_notifier (closure, c, finalize_closure);
  g_assert (closure->ref_count == 1 && closure->floating);    
  g_signal_connect_closure (i, "test", closure, G_CONNECT_AFTER);
  g_assert (closure->ref_count == 1 && !closure->floating);       
  g_signal_emit (i, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_object_unref (i);
  g_assert (closure_finalized); 

  print ("Check that closures get invalidated if the object is gone.\n");
  test_handler_count = 0;
  closure_invalidated = FALSE;
  closure_finalized = FALSE;

  closure = g_cclosure_new_object (G_CALLBACK (test_handler), G_OBJECT (c));
  g_closure_add_invalidate_notifier (closure, c, invalidate_closure);
  g_closure_add_finalize_notifier (closure, c, finalize_closure);
  g_assert (closure->ref_count == 1 && closure->floating);
  g_signal_connect_closure (a, "test", closure, G_CONNECT_AFTER);
  g_assert (closure->ref_count == 1 &&  /* signal handler takes over */
	    !closure->floating);        /* the initial reference     */
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_object_unref (c);
  g_assert (closure->ref_count == 1); /* still there */
  g_assert (closure_invalidated);     /* but invalidated */
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_signal_handlers_disconnect_matched (a, 
					G_SIGNAL_MATCH_CLOSURE,
					signals[TEST], 0, closure, NULL, NULL);
  g_assert (closure_finalized);       /* now its gone */

  print ("Check that you can keep closures after the signal is disconnected.\n");
  test_handler_count = 0;
  closure_invalidated = FALSE;
  closure_finalized = FALSE;

  closure = g_cclosure_new_object (G_CALLBACK (test_handler), G_OBJECT (d));
  g_closure_add_invalidate_notifier (closure, d, invalidate_closure);
  g_closure_add_finalize_notifier (closure, d, finalize_closure);
  g_assert (closure->ref_count == 1 && closure->floating);
  g_closure_ref (closure);
  g_closure_sink (closure);            /* take over initial reference */
  g_assert (closure->ref_count == 1 && !closure->floating);
  g_signal_connect_closure (a, "test", closure, G_CONNECT_AFTER);
  g_assert (closure->ref_count == 2);  /* signal handler adds a reference */
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_signal_handlers_disconnect_matched (a, 
					G_SIGNAL_MATCH_CLOSURE,
					signals[TEST], 0, closure, NULL, NULL);
  g_assert (closure->ref_count == 1);  /* we still have a reference */
  g_assert (!closure_finalized); 
  g_closure_unref (closure);
  g_assert (closure_finalized); 
 
  print ("Check multiple connections.\n");
  test_handler_count = 0;
  closure_invalidated = FALSE;
  closure_finalized = FALSE;

  closure = g_cclosure_new_object (G_CALLBACK (test_handler), G_OBJECT (e));
  g_closure_add_invalidate_notifier (closure, e, invalidate_closure);
  g_closure_add_finalize_notifier (closure, e, finalize_closure);
  g_assert (closure->ref_count == 1 && closure->floating);
  handler = g_signal_connect_closure (a, "test", closure, G_CONNECT_AFTER);
  g_assert (closure->ref_count == 1 && !closure->floating);
  handler2 = g_signal_connect_closure (a, "test2", closure, G_CONNECT_AFTER);
  g_assert (closure->ref_count == 2);
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 1);
  g_signal_emit (a, signals[TEST2], 0);
  g_assert (test_handler_count == 2);
  g_signal_handler_disconnect (a, handler);
  g_assert (closure->ref_count == 1);
  g_signal_emit (a, signals[TEST], 0);
  g_assert (test_handler_count == 2);
  g_signal_emit (a, signals[TEST2], 0);
  g_assert (test_handler_count == 3);
  g_signal_handler_disconnect (a, handler2);
  g_assert (closure_invalidated); 
  g_assert (closure_finalized); 
  g_object_unref (e);

  return 0;
}
