#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <glib-object.h>

/*
 * TestPObject, a parent class for TestObject
 */
#define TEST_TYPE_POBJECT        (test_pobject_get_type ())
typedef struct _TestPObject 	 TestPObject;
typedef struct _TestPObjectClass TestPObjectClass;

struct _TestPObject
{
  GObject parent_instance;
};

struct _TestPObjectClass
{
  GObjectClass parent_class;
};

static void
test_pobject_base_init (TestPObjectClass * klass)
{
  GThread *self = g_thread_self ();

  printf ("%p starting test_pobject_base_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

  sleep (1);

  printf ("%p finishing test_pobject_base_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

}

static void
test_pobject_class_init (TestPObjectClass * klass)
{
  GThread *self = g_thread_self ();

  printf ("%p starting class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

  sleep (1);

  printf ("%p finishing class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

}

static gpointer
test_pobject_register_type (GType *type)
{
  static const GTypeInfo info = {
    sizeof (TestPObjectClass),			/* class_size     */
    (GBaseInitFunc) test_pobject_base_init,	/* base_init      */
    (GBaseFinalizeFunc) NULL,			/* base_finalize  */
    (GClassInitFunc) test_pobject_class_init,	/* class_init     */
    (GClassFinalizeFunc) NULL,		 	/* class_finalize */
    NULL,					/* class_data     */
    sizeof (TestPObject),			/* instance_size  */
    0,						/* n_prelocs      */
    (GInstanceInitFunc) NULL			/* instance_init  */
  };

  *type = g_type_register_static (G_TYPE_OBJECT, "TestPObject", &info, 0);

  return NULL;
}

GType
test_pobject_get_type (void)
{
  static GOnce once = G_ONCE_INIT;
  static GType type = 0;

  g_once (&once, (GThreadFunc) test_pobject_register_type, &type);

  return type;
}

/*
 * TestObject
 */
#define TEST_TYPE_OBJECT        (test_object_get_type ())
typedef struct _TestObject 	TestObject;
typedef struct _TestObjectClass TestObjectClass;

struct _TestObject
{
  TestPObject parent_instance;
};

struct _TestObjectClass
{
  TestPObjectClass parent_class;
};

static void
test_object_class_init (TestObjectClass * klass)
{
  GThread *self = g_thread_self ();

  printf ("%p starting class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

  sleep (1);

  printf ("%p finishing class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));
}

static gpointer
test_object_register_type (GType *type)
{
  static const GTypeInfo info = {
    sizeof (TestObjectClass),			/* class_size     */
    (GBaseInitFunc) NULL,			/* base_init      */
    (GBaseFinalizeFunc) NULL,			/* base_finalize  */
    (GClassInitFunc) test_object_class_init,	/* class_init     */
    (GClassFinalizeFunc) NULL,		 	/* class_finalize */
    NULL,					/* class_data     */
    sizeof (TestObject),			/* instance_size  */
    0,						/* n_prelocs      */
    (GInstanceInitFunc) NULL			/* instance_init  */
  };

  *type = g_type_register_static (TEST_TYPE_POBJECT, "TestObject", &info, 0);

  return NULL;
}

GType
test_object_get_type (void)
{
  static GOnce once = G_ONCE_INIT;
  static GType type = 0;

  g_once (&once, (GThreadFunc) test_object_register_type, &type);

  return type;
}

static void *
thread (void *data)
{
  TestObject *object;

  GThread *self = g_thread_self ();

  printf ("%p creating object type %s\n", (gpointer) self, g_type_name (TEST_TYPE_OBJECT));

  object = g_object_new (TEST_TYPE_OBJECT, NULL);

  printf ("%p using object\n", (gpointer) self);

  sleep (1);

  g_object_unref (object);

  printf ("%p done with object\n", (gpointer) self);

  return NULL;
}

/*
 * TestPlugin
 */
#define TEST_TYPE_PLUGIN        (test_plugin_get_type ())
typedef struct _TestPlugin 	 TestPlugin;
typedef struct _TestPluginClass  TestPluginClass;

struct _TestPlugin
{
  GObject parent_instance;
};

struct _TestPluginClass
{
  GObjectClass parent_class;
};

static void
test_dobject_class_init (TestObjectClass * klass)
{
  GThread *self = g_thread_self ();

  printf ("%p starting test_dobject_class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

  sleep (1);

  printf ("%p finishing test_dobject_class_init for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));
}

static void
test_dobject_class_finalize (TestObjectClass * klass)
{
  GThread *self = g_thread_self ();

  printf ("%p starting test_dobject_class_finalize for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));

  sleep (1);

  printf ("%p finishing test_dobject_class_finalize for type %s\n", (gpointer) self, g_type_name (G_OBJECT_CLASS_TYPE (klass)));
}

void test_plugin_complete_type_info (GTypePlugin *plugin,
                                     GType g_type,
                                     GTypeInfo *info,
                                     GTypeValueTable *value_table)
{
  static const GTypeInfo dinfo = {
    sizeof (TestObjectClass),			/* class_size     */
    (GBaseInitFunc) NULL,			/* base_init      */
    (GBaseFinalizeFunc) NULL,			/* base_finalize  */
    (GClassInitFunc) test_dobject_class_init,	/* class_init     */
    (GClassFinalizeFunc) test_dobject_class_finalize, 	/* class_finalize */
    NULL,					/* class_data     */
    sizeof (TestObject),			/* instance_size  */
    0,						/* n_prelocs      */
    (GInstanceInitFunc) NULL			/* instance_init  */
  };

  *info = dinfo;
}

void
test_plugin_use (GTypePlugin *plugin)
{
}

void
test_plugin_unuse (GTypePlugin *plugin)
{
}

static void
test_plugin_complete_interface_info (GTypePlugin *plugin,
                                          GType instance_type,
                                          GType interface_type,
                                          GInterfaceInfo *info)
{
}

static void
test_plugin_iface_init (GTypePluginClass * iface)
{
  iface->use_plugin = test_plugin_use;
  iface->unuse_plugin = test_plugin_unuse;
  iface->complete_type_info = test_plugin_complete_type_info;
  iface->complete_interface_info = test_plugin_complete_interface_info;
}

static gpointer
test_plugin_register_type (GType *type)
{
  static const GTypeInfo info = {
    sizeof (TestPluginClass),	/* class_size     */
    (GBaseInitFunc) 	 NULL,	/* base_init      */
    (GBaseFinalizeFunc)  NULL,	/* base_finalize  */
    (GClassInitFunc) 	 NULL,	/* class_init     */
    (GClassFinalizeFunc) NULL,	/* class_finalize */
    NULL,			/* class_data     */
    sizeof (TestPlugin),	/* instance_size  */
    0,				/* n_prelocs      */
    (GInstanceInitFunc)	 NULL	/* instance_init  */
  };

  static GInterfaceInfo iface_info = {
    (GInterfaceInitFunc) test_plugin_iface_init,	/* interface_init */
    (GInterfaceFinalizeFunc) NULL,			/* interface_finalize */
    (gpointer) NULL					/* interface_data */
  };

  *type = g_type_register_static (G_TYPE_OBJECT, "TestPlugin", &info, 0);
  g_type_add_interface_static (*type, G_TYPE_TYPE_PLUGIN, &iface_info);

  return NULL;
}

GType
test_plugin_get_type (void)
{
  static GOnce once = G_ONCE_INIT;
  static GType type = 0;

  g_once (&once, (GThreadFunc) test_plugin_register_type, &type);

  return type;
}

static GTypePlugin *
make_plugin (void)
{
  return G_TYPE_PLUGIN (g_object_new (test_plugin_get_type (), NULL));
}

GTypePlugin *
get_plugin (void)
{
  static GOnce once = G_ONCE_INIT;

  return g_once (&once, (GThreadFunc) make_plugin, NULL);
}

static gpointer
test_dobject_register_type (GType *type)
{
  GTypePlugin *plugin = get_plugin ();

  *type = g_type_register_dynamic (TEST_TYPE_POBJECT, "TestDObject", plugin, 0);

  return NULL;
}

#define TEST_TYPE_DOBJECT        (test_dobject_get_type ())

GType
test_dobject_get_type (void)
{
  static GOnce once = G_ONCE_INIT;
  static GType type = 0;

  g_once (&once, (GThreadFunc) test_dobject_register_type, &type);

  return type;
}

static void *
thread2 (void *data)
{
  TestObject *object;
  GThread *self = g_thread_self ();

  printf ("%p creating object type %s\n", (gpointer) self, g_type_name (TEST_TYPE_DOBJECT));

  object = g_object_new (TEST_TYPE_DOBJECT, NULL);

  printf ("%p using object\n", (gpointer) self);

  sleep (1);

  g_object_unref (object);

  printf ("%p done with object\n", (gpointer) self);

  return NULL;
}

#define NUM 5

int
main (int argc, char *argv[])
{
  unsigned int i;
  GThread *thread_id[NUM];

  g_thread_init (NULL);
  g_type_init ();

  printf ("%d threads:\n", NUM + 1);

  for (i = 0; i < NUM; i++)
    thread_id[i] = g_thread_create (thread, NULL, TRUE, NULL);
  thread (NULL);

  for (i = 0; i < NUM; i++)
    g_thread_join (thread_id[i]);

  for (i = 0; i < NUM; i++)
    thread_id[i] = g_thread_create (thread2, NULL, TRUE, NULL);
  thread2 (NULL);

  for (i = 0; i < NUM; i++)
    g_thread_join (thread_id[i]);

  return 0;
}
