#include "obgtkObject.h"

#include <objc/objc-api.h>
#include <string.h>
#include <stdlib.h>

/* #define DEBUG 1 */

GQuark obgtk_objc_id_key = 0;
#ifdef DEBUG
static int foobar = 0;
#endif

guint OBJC_ID(void)
{
  if(!obgtk_objc_id_key)
    obgtk_objc_id_key = g_quark_from_static_string("objc_id");
  return obgtk_objc_id_key;
}

gpointer
obgtk_signal_relay(GtkObject *object,
		   ObgtkRelayInfo data,
		   gint nargs,
		   GtkArg *args)
{
  id sigobj = nil;
  gpointer retval = 0;
  IMP callfunc;

  g_return_val_if_fail(object != NULL, NULL);
  g_return_val_if_fail(data != NULL, NULL);

#ifdef DEBUG
  printf("Call to method %s [%d] on a %s\n", sel_get_name(data->method),
		nargs, [data->object name], retval);
  fflush(stdout);
#endif

  sigobj = gtk_object_get_data_by_id(object, OBJC_ID());

  if(!sigobj)
    sigobj = data->object;

  callfunc = objc_msg_lookup(data->object, data->method);

  if(callfunc)
    switch(nargs)
      {
      case 0:
	retval = callfunc(data->object, data->method,
				sigobj);
	break;
      case 1:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data);
	break;
      case 2:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data,
				args[1].d.pointer_data);
	break;
      case 3:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data,
				args[1].d.pointer_data,
				args[2].d.pointer_data);
	break;
      case 4:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data,
				args[1].d.pointer_data,
				args[2].d.pointer_data,
				args[3].d.pointer_data);
	break;
      case 5:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data,
				args[1].d.pointer_data,
				args[2].d.pointer_data,
				args[3].d.pointer_data,
				args[4].d.pointer_data);
	break;
      case 6:
	retval = callfunc(data->object, data->method,
				sigobj,
				args[0].d.pointer_data,
				args[1].d.pointer_data,
				args[2].d.pointer_data,
				args[3].d.pointer_data,
				args[4].d.pointer_data,
				args[5].d.pointer_data);
	break;
      default:
	[data->object error:"Can't handle %d arguments to an ObjC routine\n", nargs];
      }
  else
    {
      [data->object error:"Couldn't find method %s on object.",
	sel_get_name(data->method)];
    }

#define DOTYPE(x,X) case GTK_TYPE_##X: *GTK_RETLOC_##X(args[nargs]) = *((x*)&retval); \
break

  switch(args[nargs].type)
    {
      DOTYPE(gchar,CHAR);
      DOTYPE(gboolean,BOOL);
      DOTYPE(gint,INT);
      DOTYPE(guint,UINT);
      DOTYPE(glong,LONG);
      DOTYPE(gfloat,FLOAT);
      DOTYPE(gdouble,DOUBLE);
      DOTYPE(gchar*,STRING);
      DOTYPE(gint,ENUM);
      DOTYPE(gint,FLAGS);
      DOTYPE(gpointer,BOXED);
      DOTYPE(GtkObject*,OBJECT);
      DOTYPE(gpointer,POINTER);
    case GTK_TYPE_NONE:
      break;
    default:
      g_error("Unknown return value type %d\n", args[nargs].type);
    }
#undef DOTYPE
  return retval;
}

gpointer
obgtk_callback_relay(GtkObject *object,
		     gpointer data)
{
  id sigobj = nil;
  IMP callfunc;
  SEL callsel;
  g_return_val_if_fail(object != NULL, NULL);
  g_return_val_if_fail(data != NULL, NULL);

  sigobj = gtk_object_get_data_by_id(object, OBJC_ID());
  callsel = (SEL)data;

  callfunc = objc_msg_lookup(sigobj, callsel);

  return callfunc(sigobj, callsel);
}

@implementation Gtk_Object
- init
{
  self = [super init];
  gtkobject = NULL;
  destroy_handler_id = 0;
  [self error:"Call to [Gtk_Object init] - probably not calling subclass initFooBlah\n"];
  return self;
}

- (GtkObject *)getGtkObject
{
  return gtkobject;
}

/* Half the obgtk bugs lie in these next three methods ;-) */
- castGtkObject:(GtkObject *)castitem
{
  ObgtkRelayInfo mydata;
  self = [super init];
  gtkobject = castitem;
  gtk_object_set_data_by_id(gtkobject, OBJC_ID(), self);
  mydata = g_new(struct _ObgtkRelayInfo, 1);
  mydata->object = self;
  mydata->method = @selector(destroyGtkObject);
  destroy_handler_id = gtk_signal_connect_interp(gtkobject,
				 "destroy",
				 (GtkCallbackMarshal)obgtk_signal_relay,
				 mydata,
				 g_free,
				 GTK_RUN_LAST);
  return self;
}

- destroyGtkObject
{
  if(gtkobject)
    [super free];
  return nil;
}

- free
{
  gtk_signal_disconnect(GTK_OBJECT(gtkobject), destroy_handler_id);
  gtk_object_remove_data_by_id(GTK_OBJECT(gtkobject), OBJC_ID());
  gtk_object_destroy(gtkobject);
  return [super free];
}

- connect:(gchar *) signal_name
{
  return [self connectObj2:signal_name :self :NULL :NULL];
}

- connectObj:(gchar *) signal_name
	     :(id) signalObject
{
  return [self connectObj2:signal_name :signalObject :NULL :NULL];
}

- connectObjMethod:(gchar *) signal_name
		  :(id) signalObject
		  :(SEL) method
{
  return [self connectObj2:signal_name :signalObject :NULL :method];
}

- connectObj2:(gchar *) signal_name
	     :(id) signalObject
	     :(guint *) ret_handler_id
	     :(SEL) method
{
  int i;
  GString *foo;
  GtkSignalQuery *siginfo;
  ObgtkRelayInfo mydata;
  guint rv;

  g_return_val_if_fail(signal_name != NULL, self);

  siginfo = gtk_signal_query(gtk_signal_lookup(signal_name,
					       gtkobject->klass->type));
  if(!siginfo)
    {
      g_warning("Couldn't lookup signal %s\n", signal_name);
      return self;
    }

  if(method)
    {
      foo = g_string_new(signal_name);

      mydata = g_new(struct _ObgtkRelayInfo, 1);
      mydata->object = signalObject;
      g_string_append_c(foo, ':'); /* For the "signalled object"
				      argument */
      for(i = 0; i < siginfo->nparams; i++)
	g_string_append_c(foo, ':'); /* For the various arguments Gtk passes
					--these must be pointers-- */
      mydata->method = method;
    }
  else
    {
      foo = g_string_new(signal_name);

      mydata = g_new(struct _ObgtkRelayInfo, 1);
      mydata->object = signalObject;
      g_string_append_c(foo, ':'); /* For the "signalled object"
				      argument */
      for(i = 0; i < siginfo->nparams; i++)
	g_string_append_c(foo, ':'); /* For the various arguments Gtk passes
					--these must be pointers-- */
      mydata->method = sel_get_uid(foo->str);
    }
  rv = gtk_signal_connect_interp(gtkobject,
				 signal_name,
				 (GtkCallbackMarshal)obgtk_signal_relay,
				 mydata,
				 g_free,
				 GTK_RUN_LAST);
  if(ret_handler_id)
    *ret_handler_id = rv;
  g_free(siginfo);
  if(method)
    g_string_free(foo, TRUE);
     
  return self;
}

- signal_connect:(gchar *) signal_name
      signalFunc:(GtkSignalFunc) signalfunc
	funcData:(gpointer) funcdata
{
  gtk_signal_connect(gtkobject, signal_name, signalfunc, funcdata);
  return self;
}

- set_user_data:(gpointer) thedata
{
  gtk_object_set_user_data(gtkobject, thedata);
  return self;
}

- (gpointer)get_user_data
{
  return gtk_object_get_user_data(gtkobject);
}

- set_data:(const gchar *) key
	  :(gpointer) data
{
  gtk_object_set_data(gtkobject, key, data);
  return self;
}

- (gpointer)
  get_data:(const gchar *) key
{
  return gtk_object_get_data(gtkobject, key);
}

- ref
{
  gtk_object_ref(gtkobject);
  return self;
}

- unref
{
  gtk_object_unref(gtkobject);
  return self;
}

- sink
{
  gtk_object_sink(gtkobject);
  return self;
}

- destroy
{
  gtk_object_destroy(gtkobject);
  return self;
}
@end
