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

#include <ffi.h>

typedef struct {
  GObject parent;
} TestObject;

typedef struct {
  GObjectClass parent_class;
  void (* test1)		(TestObject *object);
  void (* test2)		(TestObject *object, char *str);
  int (* test3)		        (TestObject *object, int i);
} TestObjectClass;

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

GType test_object_get_type (void);

static ffi_type *
g_value_to_ffi_type (const GValue *gvalue, gpointer *value)
{
  ffi_type *rettype = NULL;
  GType type = g_type_fundamental (G_VALUE_TYPE (gvalue));

  switch (type) {
  case G_TYPE_BOOLEAN:
  case G_TYPE_CHAR:
  case G_TYPE_INT:
    rettype = &ffi_type_sint;
    *value = (gpointer)&(gvalue->data[0].v_int);
    break;
  case G_TYPE_UCHAR:
  case G_TYPE_UINT:
    rettype = &ffi_type_uint;
    *value = (gpointer)&(gvalue->data[0].v_uint);
    break;
  case G_TYPE_OBJECT:
  case G_TYPE_STRING:
  case G_TYPE_POINTER:
    rettype = &ffi_type_pointer;
    *value = (gpointer)&(gvalue->data[0].v_pointer);
    break;
  case G_TYPE_FLOAT:
    rettype = &ffi_type_float;
    *value = (gpointer)&(gvalue->data[0].v_float);
    break;
  case G_TYPE_DOUBLE:
    rettype = &ffi_type_double;
    *value = (gpointer)&(gvalue->data[0].v_double);
    break;
  case G_TYPE_LONG:
    rettype = &ffi_type_slong;
    *value = (gpointer)&(gvalue->data[0].v_long);
    break;
  case G_TYPE_ULONG:
    rettype = &ffi_type_ulong;
    *value = (gpointer)&(gvalue->data[0].v_ulong);
    break;
  case G_TYPE_INT64:
    rettype = &ffi_type_sint64;
    *value = (gpointer)&(gvalue->data[0].v_int64);
    break;
  case G_TYPE_UINT64:
    rettype = &ffi_type_uint64;
    *value = (gpointer)&(gvalue->data[0].v_uint64);
    break;
  default:
    rettype = &ffi_type_pointer;
    *value = NULL;
    g_warning ("Unsupported fundamental type: %s", g_type_name (type));
    break;
  }
  return rettype;
}

static void
g_cclosure_marshal_generic (GClosure *closure,
			    GValue *return_gvalue,
			    guint n_param_values,
			    const GValue *param_values,
			    gpointer invocation_hint,
			    gpointer marshal_data)

{
  ffi_type *rtype;
  void *rvalue;
  int n_args;
  ffi_type **atypes;
  void **args;
  int i;
  ffi_cif cif;
  GCClosure *cc = (GCClosure*) closure;

  if (return_gvalue) 
    {
      rtype = g_value_to_ffi_type (return_gvalue, &rvalue);
    }
  else 
    {
      rtype = &ffi_type_void;
      rvalue = NULL;
    }
  
  n_args = n_param_values + 1;
  atypes = g_new (ffi_type*, n_args);
  args =  g_new (gpointer, n_args);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      atypes[n_args-1] = g_value_to_ffi_type (param_values + 0,  
					      &args[n_args-1]);
      atypes[0] = &ffi_type_pointer;
      args[0] = &closure->data;
    }
  else
    {
      atypes[0] = g_value_to_ffi_type (param_values + 0, &args[0]);
      atypes[n_args-1] = &ffi_type_pointer;
      args[n_args-1] = &closure->data;
    }

  for (i = 1; i < n_args - 1; i++)
    atypes[i] = g_value_to_ffi_type (param_values + i, &args[i]);

  if (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, n_args, rtype, atypes) != FFI_OK)
    goto out;

  ffi_call (&cif, marshal_data ? marshal_data : cc->callback, rvalue, args);

 out:
  g_free (atypes);
  g_free (args);
}

enum {
  TEST1,
  TEST2,
  TEST3,
  LAST_SIGNAL
};

static guint object_signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT);

static void test_object_init (TestObject *self) 
{
}

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

  object_signals[TEST1] =
    g_signal_new ("test1",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (TestObjectClass, test1),
		  NULL, NULL,
		  g_cclosure_marshal_generic,
		  G_TYPE_NONE, 0);
  object_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_generic,
		  G_TYPE_NONE, 1,
		  G_TYPE_STRING);
  object_signals[TEST3] =
    g_signal_new ("test3",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (TestObjectClass, test3),
		  NULL, NULL,
		  g_cclosure_marshal_generic,
		  G_TYPE_INT, 1,
		  G_TYPE_DOUBLE);
}

void
test1_callback (TestObject *object, char *data)
{
  g_return_if_fail (TEST_IS_OBJECT (object));
  g_return_if_fail (!strcmp (data, "user-data"));
}

void
test1_callback_swapped (char *data, TestObject *object)
{
  g_return_if_fail (TEST_IS_OBJECT (object));
  g_return_if_fail (!strcmp (data, "user-data"));
}

void
test2_callback (TestObject *object, char *string)
{
  g_return_if_fail (TEST_IS_OBJECT (object));
  g_return_if_fail (!strcmp (string, "string"));
}

int
test3_callback (TestObject *object, double f)
{
  g_return_val_if_fail (TEST_IS_OBJECT (object), -1);
  g_return_val_if_fail (f == 42.0, -1);

  return 20;
}

void
test_cclosure_marshal (void)
{
  TestObject *object;
  gchar *data = "user-data";
  int i;

  g_type_init ();
  
  object = g_object_new (TEST_TYPE_OBJECT, NULL);
  g_signal_connect (G_OBJECT (object),
		    "test1",
		    G_CALLBACK (test1_callback), 
		    data);
  g_signal_connect_swapped (G_OBJECT (object),
		    "test1",
		    G_CALLBACK (test1_callback_swapped), 
		    data);
  g_signal_connect (G_OBJECT (object),
		    "test2",
		    G_CALLBACK (test2_callback), 
		    NULL);
  g_signal_connect (G_OBJECT (object),
		    "test3",
		    G_CALLBACK (test3_callback), 
		    NULL);

  g_signal_emit (G_OBJECT (object), object_signals[TEST1], 0);
  g_signal_emit (G_OBJECT (object), object_signals[TEST2], 0, "string");
  g_signal_emit (G_OBJECT (object), object_signals[TEST3], 0, 42.0, &i);
  g_assert (i == 20);

  g_object_unref (object);
}
  
int main(void)
{
  test_cclosure_marshal ();

  return 0;
}

