#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <time.h>

#include <glib-object.h>

static void
increment_name (char *name)
{
  size_t n;
  char c;
  if (!name)
    return;
  n = strlen (name);
  while (n)
    {
      --n;
      c = name[n];
      ++c;
      while (!isalnum (c))
	++c;
      if (c > name[n])
	{
	  name[n] = c;
	  return;
	}
      name[n] = c;
    }
}

static void
filter_logging (const gchar * log_domain, GLogLevelFlags log_level, const gchar * message, gpointer user_data)
{
  if (message)
    {
      /* You can't expect to stuff random data into the type registration functions
       * without getting a few errors.  Here we suppress the boring ones.
       */
      if (strstr (message, "invalid parent type"))
	return;
      if (strstr (message, "cannot derive"))
	return;
      if (strstr (message, "cannot instantiate"))
	return;
      if (strstr (message, "derived from non-classed parent type"))
	return;
      if (strstr (message, "class finalizer specified for static type"))
	return;
      if (strstr (message, "specified interface size"))
	return;
      if (strstr (message, "specified class size"))
	return;
      if (strstr (message, "specified instance size"))
	return;
    }
  g_log_default_handler (log_domain, log_level, message, user_data);
}

static gboolean
random_data (gpointer data, guint length, guint modulus)
{
  guint *array, i;
  if (!data)
    return FALSE;
  array = (guint *) data;
  for (i = 0; i < length; ++i)
    {
      ++array[i];
      if (array[i] >= modulus)
	array[i] = 0;
      else
	return TRUE;
    }
  return FALSE;
}

static gulong
register_static_types (void)
{
  GType parent_type;
  gchar *type_name;
  GTypeInfo info;
  GTypeValueTable value_table1, value_table2, *vtt[3];
  GTypeFlags flags;
  GType type1, type2, max_type, *type_array;
  guint pa, vtn, tier, rpt;
  gulong type_array_len = 0, type_array_len_max = 100000, pos, cnt[3], tot = 0, rtot = 0;
  struct timespec ts;

  ts.tv_sec = 0;

  type_array = (GType *) g_malloc (sizeof (GType) * type_array_len_max);

  value_table1.value_init = 0;
  value_table1.value_free = 0;
  value_table1.value_copy = 0;
  value_table1.value_peek_pointer = 0;
  value_table1.collect_format = 0;
  value_table1.collect_value = 0;
  value_table1.lcopy_format = 0;
  value_table1.lcopy_value = 0;

  value_table2.value_init = (gpointer) 2;
  value_table2.value_free = (gpointer) 2;
  value_table2.value_copy = (gpointer) 2;
  value_table2.value_peek_pointer = (gpointer) 2;
  value_table2.collect_format = "i";
  value_table2.collect_value = (gpointer) 2;
  value_table2.lcopy_format = "";
  value_table2.lcopy_value = (gpointer) 2;

  vtt[0] = 0;
  vtt[1] = &value_table1;
  vtt[2] = &value_table2;

  cnt[0] = 48;
  cnt[1] = 2343;
  cnt[2] = 12241;

  max_type = g_type_fundamental_next ();
  for (parent_type = sizeof (void *); parent_type < max_type; parent_type += sizeof (void *))
    if (type_array_len < type_array_len_max)
      type_array[type_array_len++] = parent_type;

  for (rpt = 0; rpt < 2; ++rpt)
    {
      type_name = g_strdup ("abc000000");
      pos = 0;
      for (tier = 0; tier < 3; ++tier)
	{
	  for (; pos < cnt[tier] && pos < type_array_len; pos += 1 + (tier * 50))
	    {
	      parent_type = type_array[pos];
	      for (flags = 0; flags < 4; ++flags)
		{
		  memset (&info, 0, sizeof (info));
		  do
		    for (pa = 0; pa < 2; ++pa)
		      {
			GTypeInfo uinfo;
			memcpy (&uinfo, &info, sizeof (info));
			if (info.class_size)
			  uinfo.class_size += 1024;
			if (info.instance_size)
			  uinfo.instance_size += 1024;
			uinfo.n_preallocs = 256 * pa;

			for (vtn = 0; vtn < 3; ++vtn)
			  {
			    uinfo.value_table = vtt[vtn];
			    increment_name (type_name);
			    type1 = g_type_register_static (parent_type, type_name, &uinfo, flags * 16);
			    type2 = g_type_register_static (parent_type, type_name, &uinfo, flags * 16);
			    if (type1 != type2)
			      printf ("Failed - expected 2nd static type registration to return the same GType\n");
			    if (!rpt && type1)
			      {
				++tot;
				if (type_array_len < type_array_len_max)
				  type_array[type_array_len++] = type1;
				ts.tv_nsec = rand ();
				if (ts.tv_nsec > 10 && ts.tv_nsec < 10000)
				  nanosleep (&ts, NULL);
			      }
			    if (rpt && type1)
			      ++rtot;
			  }
		      }		// pa
		  while (random_data (&info, 8, 2));
		}		// flags
	    }			// pos
	}			// tier
      g_free (type_name);
    }				// rpt

  if (tot != rtot)
    printf ("Failed - expected the same number of successful static type registrations the 2nd time through\n");

  return tot;
}

static void *
thread (void *data)
{
  gulong tot;
  tot = register_static_types ();
  if (data)
    *(gulong *) data = tot;
  return NULL;
}

static void
g_type_plugin_iface_init (GTypePluginClass * iface)
{
  iface->use_plugin = (gpointer) 1;
  iface->unuse_plugin = (gpointer) 1;
  iface->complete_type_info = (gpointer) 1;
  iface->complete_interface_info = (gpointer) 1;
}

GTypePlugin *plugin = NULL;

static void
make_plugin (void)
{
  GTypeInfo info = { 0 };
  GInterfaceInfo iface_info = { 0 };
  GType t, t2;

  info.class_size = 1024;
  info.instance_size = 1024;
  iface_info.interface_init = (gpointer) g_type_plugin_iface_init;
  t = g_type_register_static (G_TYPE_OBJECT, "aplugin", &info, 0);
  g_type_add_interface_static (t, G_TYPE_TYPE_PLUGIN, &iface_info);
  /* test double g_type_add_interface_static */
  g_type_add_interface_static (t, G_TYPE_TYPE_PLUGIN, &iface_info);
  plugin = G_TYPE_PLUGIN (g_object_new (t, NULL));

  t2 = g_type_register_static (G_TYPE_OBJECT, "aplugin2", &info, 0);
  /* test double g_type_add_interface_dynamic */
  g_type_add_interface_dynamic (t2, G_TYPE_TYPE_PLUGIN, plugin);
  g_type_add_interface_dynamic (t2, G_TYPE_TYPE_PLUGIN, plugin);
}

static gulong
register_dynamic_types (void)
{
  GType parent_type;
  gchar *type_name;
  GTypeFlags flags;
  GType type1, type2, max_type, *type_array;
  guint tier, rpt;
  gulong type_array_len = 0, type_array_len_max = 100000, pos, cnt[3], tot = 0, rtot = 0;
  struct timespec ts;

  ts.tv_sec = 0;

  type_array = (GType *) g_malloc (sizeof (GType) * type_array_len_max);

  cnt[0] = 76;
  cnt[1] = 108;
  cnt[2] = 236;

  max_type = g_type_fundamental_next ();
  for (parent_type = sizeof (void *); parent_type < max_type; parent_type += sizeof (void *))
    if (type_array_len < type_array_len_max)
      type_array[type_array_len++] = parent_type;

  for (rpt = 0; rpt < 2; ++rpt)
    {
      type_name = g_strdup ("def000000");
      pos = 0;
      for (tier = 0; tier < 3; ++tier)
	{
	  for (; pos < cnt[tier] && pos < type_array_len; ++pos)
	    {
	      parent_type = type_array[pos];
	      for (flags = 0; flags < 4; ++flags)
		{
		  increment_name (type_name);
		  type1 = g_type_register_dynamic (parent_type, type_name, plugin, flags * 16);
		  type2 = g_type_register_dynamic (parent_type, type_name, plugin, flags * 16);
		  if (type1 != type2)
		    printf ("Failed - expected 2nd dynamic type registration to return the same GType\n");
		  if (!rpt && type1)
		    {
		      ++tot;
		      if (type_array_len < type_array_len_max)
			type_array[type_array_len++] = type1;
		    }
		  if (rpt && type1)
		    ++rtot;
		}		// flags
	    }			// pos
	}			// tier
      g_free (type_name);
    }				// rpt

  if (tot != rtot)
    printf ("Failed - expected the same number of successful dynamic type registrations the 2nd time through\n");

  return tot;
}

static void *
thread2 (void *data)
{
  gulong tot;
  tot = register_dynamic_types ();
  if (data)
    *(gulong *) data = tot;
  return NULL;
}

static gulong
register_fundamental_types (void)
{
  gchar *type_name;
  GTypeInfo info;
  GTypeValueTable value_table1, value_table2, *vtt[3];
  GTypeFlags flags;
  GType type1, type2;
  guint pa, vtn, rpt, tf;
  gulong tot = 0, rtot = 0;
  GTypeFundamentalInfo finfo;
  GType type_id;

  value_table1.value_init = 0;
  value_table1.value_free = 0;
  value_table1.value_copy = 0;
  value_table1.value_peek_pointer = 0;
  value_table1.collect_format = 0;
  value_table1.collect_value = 0;
  value_table1.lcopy_format = 0;
  value_table1.lcopy_value = 0;

  value_table2.value_init = (gpointer) 2;
  value_table2.value_free = (gpointer) 2;
  value_table2.value_copy = (gpointer) 2;
  value_table2.value_peek_pointer = (gpointer) 2;
  value_table2.collect_format = "i";
  value_table2.collect_value = (gpointer) 2;
  value_table2.lcopy_format = "";
  value_table2.lcopy_value = (gpointer) 2;

  vtt[0] = 0;
  vtt[1] = &value_table1;
  vtt[2] = &value_table2;

  for (rpt = 0; rpt < 2; ++rpt)
    {
      type_name = g_strdup ("ghi000000");
      type_id = 88;
      for (tf = 0; tf < 16; ++tf)
	{
	  finfo.type_flags = tf;
	  for (flags = 0; flags < 4; ++flags)
	    {
	      for (pa = 0; pa < 4; ++pa)
		{
		  memset (&info, 0, sizeof (info));
		  if (finfo.type_flags & G_TYPE_FLAG_INSTANTIATABLE)
		    {
		      finfo.type_flags |= G_TYPE_FLAG_CLASSED;
		      info.instance_size = 1024;
		    }
		  if (finfo.type_flags & G_TYPE_FLAG_CLASSED)
		    info.class_size = 1024;
		  if (pa & 1)
		    {
		      if (!(finfo.type_flags & G_TYPE_FLAG_CLASSED))
			continue;
		      info.base_init = (gpointer) 3;
		    }
		  if (pa & 2)
		    {
		      if (!(finfo.type_flags & G_TYPE_FLAG_CLASSED))
			continue;
		      info.class_init = (gpointer) 7;
		    }

		  for (vtn = 0; vtn < 3; ++vtn)
		    {
		      if (type_id > G_TYPE_FUNDAMENTAL_MAX)
			continue;
		      info.value_table = vtt[vtn];
		      increment_name (type_name);
		      type1 = g_type_register_fundamental (type_id, type_name, &info, &finfo, flags * 16);
		      type2 = g_type_register_fundamental (type_id, type_name, &info, &finfo, flags * 16);
		      if (type1 != type2)
			printf ("Failed - expected 2nd fundamental type registration to return the same GType\n");
		      if (type1)
			type_id += 4;
		      if (!rpt && type1)
			++tot;
		      if (rpt && type1)
			++rtot;
		    }
		}		// pa
	    }			// flags
	}			// finfo.type_flags
      g_free (type_name);
    }				// rpt

  if (tot != rtot)
    printf ("Failed - expected the same number of successful fundamental type registrations the 2nd time through\n");

  return tot;
}

static void *
thread3 (void *data)
{
  gulong tot;
  tot = register_fundamental_types ();
  if (data)
    *(gulong *) data = tot;
  return NULL;
}

#define NUM 3

int
main (int argc, char *argv[])
{
  guint i;
  pthread_t thread_id[NUM];
  gulong tot[NUM];
  gchar *log_domain = "GLib-GObject";
  guint log_handler_id;

  g_thread_init (NULL);
  g_type_init ();

  log_handler_id = g_log_set_handler (log_domain, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, filter_logging, NULL);

  for (i = 0; i < NUM; ++i)
    pthread_create (&thread_id[i], NULL, thread, tot + i);

  for (i = 0; i < NUM; ++i)
    pthread_join (thread_id[i], NULL);

  for (i = 0; i < NUM; ++i)
    if (tot[i] != tot[0])
      printf ("Failed - expected the number of static type registrations to be the same in all threads");

  if (tot[0] < 100000)
    printf ("Failed - expected > 100000 static type registrations \n");

  make_plugin ();

  for (i = 0; i < NUM; ++i)
    pthread_create (&thread_id[i], NULL, thread2, tot + i);

  for (i = 0; i < NUM; ++i)
    pthread_join (thread_id[i], NULL);

  for (i = 0; i < NUM; ++i)
    if (tot[i] != tot[0])
      printf ("Failed - expected the number of dynamic type registrations to be the same in all threads");

  if (tot[0] < 500)
    printf ("Failed - expected > 500 dynamic type registrations \n");

  for (i = 0; i < NUM; ++i)
    pthread_create (&thread_id[i], NULL, thread3, tot + i);

  for (i = 0; i < NUM; ++i)
    pthread_join (thread_id[i], NULL);

  for (i = 0; i < NUM; ++i)
    if (tot[i] != tot[0])
      printf ("Failed - expected the number of fundamental type registrations to be the same in all threads");

  if (tot[0] < 200)
    printf ("Failed - expected > 200 fundamental type registrations \n");

  g_log_remove_handler (log_domain, log_handler_id);

  return 0;
}
