/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
/**********************************************************************

  rbgobj_signal.c -

  $Author: ssimons $
  $Date: 2007/07/25 17:04:28 $
  created at: Sat Jul 27 16:56:01 JST 2002

  Copyright (C) 2002-2004  Ruby-GNOME2 Project Team
  Copyright (C) 2002,2003  Masahiro Sakai

**********************************************************************/

#include "rbgprivate.h"

static VALUE cSignal;
VALUE rbgobj_signal_wrap(guint sig_id);

#define default_handler_method_prefix "signal_do_"

/**********************************************************************/

static VALUE signal_func_table;

void
rbgobj_set_signal_func(klass, sig_name, func)
    VALUE klass;
    gchar* sig_name; 
    GValToRValSignalFunc func;
{
    VALUE obj = Data_Wrap_Struct(rb_cData, NULL, NULL, func);
    guint signal_id = g_signal_lookup(sig_name, CLASS2GTYPE(klass));
    rb_hash_aset(signal_func_table, UINT2NUM(signal_id), obj);
}

GValToRValSignalFunc
rbgobj_get_signal_func(guint signal_id)
{
    GValToRValSignalFunc func = NULL;
    VALUE func_obj = rb_hash_aref(signal_func_table, UINT2NUM(signal_id));
    if (!NIL_P(func_obj))
        Data_Get_Struct(func_obj, void, func);
    return func;
}

/**********************************************************************/

static VALUE eNoSignalError;

// FIXME: use rb_protect
static gboolean
accumulator_func(GSignalInvocationHint* ihint,
                 GValue*                return_accu,
                 const GValue*          handler_return,
                 gpointer               data)
{
    VALUE proc = (VALUE)data;
    VALUE val = GVAL2RVAL(return_accu);
    VALUE new = GVAL2RVAL(handler_return);
    VALUE hint = Qnil; // FIXME
    VALUE tmp;
    gboolean continue_emission = TRUE;

    tmp = rb_funcall(proc, rb_intern("call"), 3, hint, val, new);
    /* FIXME */
    if (TYPE(tmp) == T_ARRAY) {
        continue_emission = RVAL2CBOOL(rb_ary_entry(tmp, 0));
        val = rb_ary_entry(tmp, 1);
    } else {
        val = tmp;        
    }
    rbgobj_rvalue_to_gvalue(val, return_accu);

    return continue_emission;
}

static VALUE
gobj_s_signal_new(int argc, VALUE* argv, VALUE self)
{
    const RGObjClassInfo* cinfo = rbgobj_lookup_class(self);
    VALUE signal_name, signal_flags, accumulator, return_type, params;
    GClosure* class_closure;
    GType* param_types;
    guint n_params;
    int i;
    guint sig;

    rb_scan_args(argc, argv, "4*", &signal_name, &signal_flags,
                 &accumulator, &return_type, &params);

    if (cinfo->klass != self)
        rb_raise(rb_eTypeError, "%s isn't registerd class",
                 rb_class2name(self));

    if (SYMBOL_P(signal_name))
        signal_name = rb_str_new2(rb_id2name(SYM2ID(signal_name)));
    else
        StringValue(signal_name);

    {
        VALUE factory;
        VALUE proc;
        ID method_id;

        method_id = rb_to_id(rb_str_concat(rb_str_new2(default_handler_method_prefix), signal_name));

        factory = rb_eval_string(
          "lambda{|klass, id|\n"
          "  lambda{|instance,*args|\n"
          "    klass.instance_method(id).bind(instance).call(*args)\n"
          "  }\n"
          "}\n");
        proc = rb_funcall(factory, rb_intern("call"), 2, self, ID2SYM(method_id));

        class_closure = g_rclosure_new(proc, Qnil, NULL);
        g_rclosure_attach(class_closure, self);
    }

    if (NIL_P(params)) {
        n_params = 0;
        param_types = NULL;
    } else {
        n_params = RARRAY_LEN(params);
        param_types = ALLOCA_N(GType, n_params);
        for (i = 0; i < n_params; i++)
            param_types[i] = rbgobj_gtype_get(RARRAY_PTR(params)[i]);
    }

    sig = g_signal_newv(StringValuePtr(signal_name),
                        cinfo->gtype,
                        NUM2INT(signal_flags),
                        class_closure,
                        NIL_P(accumulator) ? NULL : accumulator_func,
                        NIL_P(accumulator) ? NULL : (gpointer)accumulator,
                        NULL, /* c_marshaller */
                        rbgobj_gtype_get(return_type),
                        n_params,
                        param_types);

    if (!sig)
        rb_raise(rb_eRuntimeError, "g_signal_newv failed");

    if (!NIL_P(accumulator))
        G_RELATIVE(self, accumulator); /* FIXME */

    return rbgobj_signal_wrap(sig);
}

static void
_signal_list(VALUE result, GType gtype)
{
    guint n_ids, i;
    guint* ids = g_signal_list_ids(gtype, &n_ids);
    for (i = 0; i < n_ids; i++)
        rb_ary_push(result, rb_str_new2(g_signal_name(ids[i])));
    g_free(ids);
}

static VALUE
gobj_s_signals(int argc, VALUE* argv, VALUE self)
{
    GType gtype;
    VALUE inherited_too, result;

    if (rb_scan_args(argc, argv, "01", &inherited_too) == 0)
        inherited_too = Qtrue;
    gtype = CLASS2GTYPE(self);
    result = rb_ary_new();

    if (RVAL2CBOOL(inherited_too)){
        guint n_interfaces, i;
        GType* interfaces = g_type_interfaces(gtype, &n_interfaces);
        for (i = 0; i < n_interfaces; i++)
            _signal_list(result, interfaces[i]);
        g_free(interfaces);

        for (; gtype; gtype = g_type_parent(gtype))
            _signal_list(result, gtype);
    } else if (GTYPE2CLASS(gtype) == self) {
        _signal_list(result, gtype);
    }

    return result;
}

static VALUE
gobj_s_signal(VALUE self, VALUE name)
{
    const char* sig_name;
    guint sig_id;

    if (SYMBOL_P(name))
        sig_name = rb_id2name(SYM2ID(name));
    else
        sig_name = StringValuePtr(name);
    
    sig_id = g_signal_lookup(sig_name, CLASS2GTYPE(self));
    if (!sig_id)
        rb_raise(eNoSignalError, "no such signal: %s", sig_name);

    return rbgobj_signal_wrap(sig_id);
}

static VALUE
gobj_sig_has_handler_pending(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    VALUE sig, may_be_blocked;
    const char* sig_name;
    guint signal_id;
    GQuark detail;

    rb_scan_args(argc, argv, "11", &sig, &may_be_blocked);

    if (SYMBOL_P(sig))
        sig_name = rb_id2name(SYM2ID(sig));
    else
        sig_name = StringValuePtr(sig);

    if (!g_signal_parse_name(sig_name, CLASS2GTYPE(CLASS_OF(self)), &signal_id, &detail, TRUE))
        rb_raise(eNoSignalError, "no such signal: %s", sig_name);

    return CBOOL2RVAL(g_signal_has_handler_pending(RVAL2GOBJ(self),
                                                   signal_id, detail,
                                                   RVAL2CBOOL(may_be_blocked)));
}

static VALUE
gobj_sig_connect_impl(after, argc, argv, self)
    gboolean after;
    int argc;
    VALUE *argv;
    VALUE self;
{
    VALUE sig, rest;
    int i;
    GClosure* rclosure;
    const char* sig_name;
    guint signal_id;
    GQuark detail;
    VALUE func;
    GObject *g_object;
    gchar *tag;

    rb_scan_args(argc, argv, "1*", &sig, &rest);

    if (NIL_P(rest)) rest = rb_ary_new();

    if (SYMBOL_P(sig))
        sig_name = rb_id2name(SYM2ID(sig));
    else
        sig_name = StringValuePtr(sig);

    if (!g_signal_parse_name(sig_name, CLASS2GTYPE(CLASS_OF(self)), &signal_id, &detail, TRUE))
        rb_raise(eNoSignalError, "no such signal: %s", sig_name);

    func = rb_block_proc();
    rclosure = g_rclosure_new(func, rest, 
                              rbgobj_get_signal_func(signal_id));
    g_rclosure_attach((GClosure *)rclosure, self);
    g_object = RVAL2GOBJ(self);
    tag = g_strdup_printf("%s::%s",
                          G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(g_object)),
                          sig_name);
    g_rclosure_set_tag((GClosure *)rclosure, tag);
    g_free(tag);
    i = g_signal_connect_closure_by_id(g_object, signal_id, detail, rclosure, after);

    return INT2FIX(i);
}

static VALUE
gobj_sig_connect(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    return gobj_sig_connect_impl(FALSE, argc, argv, self);
}

static VALUE
gobj_sig_connect_after(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    return gobj_sig_connect_impl(TRUE, argc, argv, self);
}

#if 0
static VALUE
gobj_sig_get_invocation_hint(self)
     VALUE self;
{
    GSignalInvocationHint* hint;
    hint = g_signal_get_invocation_hint(RVAL2GOBJ(self));
    return rb_ary_new3(3,
                       rbgobj_signal_wrap(hint->signal_id),
                       hint->detail ? rb_str_new2(g_quark_to_string(hint->detail)) : Qnil,
                       INT2NUM(hint->run_type));
}
#endif

struct emit_arg{
    VALUE self;
    VALUE args;

    GSignalQuery query;
    GQuark detail;
    GValueArray* instance_and_params;
};

static VALUE
emit_body(struct emit_arg* arg)
{
    GValue param = { 0, };

    g_value_init(&param, G_TYPE_FROM_INSTANCE(RVAL2GOBJ(arg->self)));
    rbgobj_rvalue_to_gvalue(arg->self, &param);
    g_value_array_append(arg->instance_and_params, &param);
    g_value_unset(&param);

    {
        int i;
        for (i = 0; i < arg->query.n_params; i++){
            GType gtype = arg->query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;

            g_value_init(&param,  gtype);

            rbgobj_rvalue_to_gvalue(rb_ary_entry(arg->args, i), &param);
            g_value_array_append(arg->instance_and_params, &param);
            g_value_unset(&param);
        }
    }

    {
        gboolean use_ret = (arg->query.return_type != G_TYPE_NONE);
        GValue return_value = {0,};

        if (use_ret)
            g_value_init(&return_value,
                         arg->query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);

        g_signal_emitv(arg->instance_and_params->values,
                       arg->query.signal_id, arg->detail,
                       (use_ret) ? &return_value : NULL);

        if (use_ret) {
            VALUE ret = GVAL2RVAL(&return_value);
            g_value_unset(&return_value);
            return ret;
        } else {
            return Qnil;
        }
    }
}

static VALUE
emit_ensure(struct emit_arg* arg)
{
    g_value_array_free(arg->instance_and_params);
    return Qnil;
}

static VALUE
gobj_sig_emit(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    VALUE sig;
    const char* sig_name;
    guint signal_id;
    struct emit_arg arg;

    rb_scan_args(argc, argv, "1*", &sig, &arg.args);

    if (SYMBOL_P(sig))
        sig_name = rb_id2name(SYM2ID(sig));
    else
        sig_name = StringValuePtr(sig);

    if (!g_signal_parse_name(sig_name,
                             CLASS2GTYPE(CLASS_OF(self)),
                             &signal_id, &arg.detail, FALSE))
        rb_raise(eNoSignalError, "invalid signal \"%s\"", sig_name);

    g_signal_query(signal_id, &arg.query);

    if (arg.query.n_params != RARRAY_LEN(arg.args))
        rb_raise(rb_eArgError, "wrong number of arguments(%ld for %d)",
                 RARRAY_LEN(arg.args) + 1,
                 arg.query.n_params + 1);

    arg.self = self;
    arg.instance_and_params = g_value_array_new(1 + arg.query.n_params);
    
    return rb_ensure(emit_body, (VALUE)&arg, emit_ensure, (VALUE)&arg);
}

static VALUE
gobj_sig_emit_stop(self, sig)
    VALUE self, sig;
{
    gpointer instance = RVAL2GOBJ(self);
    const char* sig_name;
    guint signal_id;
    GQuark detail;

    if (SYMBOL_P(sig))
        sig_name = rb_id2name(SYM2ID(sig));
    else
        sig_name = StringValuePtr(sig);

    if (!g_signal_parse_name(sig_name,
                             CLASS2GTYPE(CLASS_OF(self)),
                             &signal_id, &detail, FALSE))        
        rb_raise(eNoSignalError, "invalid signal \"%s\"", sig_name);

    g_signal_stop_emission(instance, signal_id, detail);
    return self;
}

static VALUE gobj_sig_handler_unblock(VALUE self, VALUE id);

static VALUE
_sig_handler_block_ensure(arg)
    VALUE arg;
{
    VALUE self = RARRAY_PTR(arg)[0];
    VALUE id   = RARRAY_PTR(arg)[1];
    gobj_sig_handler_unblock(self, id);
    return Qnil;
}

static VALUE
gobj_sig_handler_block(self, id)
    VALUE self, id;
{
    g_signal_handler_block(RVAL2GOBJ(self), NUM2ULONG(id));
    if (rb_block_given_p())
        rb_ensure(rb_yield, self, _sig_handler_block_ensure,
                  rb_ary_new3(2, self, id));
    return self;
}

static VALUE
gobj_sig_handler_unblock(self, id)
    VALUE self, id;
{
    g_signal_handler_unblock(RVAL2GOBJ(self), NUM2ULONG(id));
    return self;
}

static VALUE
gobj_sig_handler_disconnect(self, id)
    VALUE self, id;
{
    g_signal_handler_disconnect(RVAL2GOBJ(self), NUM2ULONG(id));
    return self;
}

static VALUE
gobj_sig_handler_is_connected(self, id)
     VALUE self, id;
{
    return CBOOL2RVAL(g_signal_handler_is_connected(RVAL2GOBJ(self), NUM2ULONG(id)));
}

#if 0
gulong	 g_signal_handler_find		      (gpointer		  instance,
					       GSignalMatchType	  mask,
					       guint		  signal_id,
					       GQuark		  detail,
					       GClosure		 *closure,
					       gpointer		  func,
					       gpointer		  data);
guint	 g_signal_handlers_block_matched      (gpointer		  instance,
					       GSignalMatchType	  mask,
					       guint		  signal_id,
					       GQuark		  detail,
					       GClosure		 *closure,
					       gpointer		  func,
					       gpointer		  data);
guint	 g_signal_handlers_unblock_matched    (gpointer		  instance,
					       GSignalMatchType	  mask,
					       guint		  signal_id,
					       GQuark		  detail,
					       GClosure		 *closure,
					       gpointer		  func,
					       gpointer		  data);
guint	 g_signal_handlers_disconnect_matched (gpointer		  instance,
					       GSignalMatchType	  mask,
					       guint		  signal_id,
					       GQuark		  detail,
					       GClosure		 *closure,
					       gpointer		  func,
					       gpointer		  data);
#endif

static VALUE
chain_from_overridden_body(struct emit_arg* arg)
{
    g_value_init(arg->instance_and_params->values,
                 G_TYPE_FROM_INSTANCE(RVAL2GOBJ(arg->self)));
    rbgobj_rvalue_to_gvalue(arg->self, arg->instance_and_params->values);

    {
        GValue* params = arg->instance_and_params->values + 1;
        int i;
        for (i = 0; i < arg->query.n_params; i++) {
            GType gtype = arg->query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
            g_value_init(params + i, gtype);
            rbgobj_rvalue_to_gvalue(rb_ary_entry(arg->args, i), params + i);
        }
    }

    {
        gboolean use_ret = (arg->query.return_type != G_TYPE_NONE);
        GValue return_value = {0,};

        if (use_ret)
            g_value_init(&return_value,
                         arg->query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);

        g_signal_chain_from_overridden(arg->instance_and_params->values,
                                       (use_ret) ? &return_value : NULL);

        if (use_ret) {
            VALUE ret = GVAL2RVAL(&return_value);
            g_value_unset(&return_value);
            return ret;
        } else {
            return Qnil;
        }
    }
}

static VALUE
gobj_sig_chain_from_overridden(argc, argv, self)
    int argc;
    VALUE* argv;
    VALUE self;
{
    struct emit_arg arg;

    {
        GSignalInvocationHint* hint;
        hint = g_signal_get_invocation_hint(RVAL2GOBJ(self));
        if (!hint)
            rb_raise(rb_eRuntimeError, "can't get signal invocation hint");
        g_signal_query(hint->signal_id, &arg.query);
    }

    if (arg.query.n_params != argc)
        rb_raise(rb_eArgError, "wrong number of arguments(%d for %d)",
                 argc, arg.query.n_params);

    arg.self = self;
    arg.args = rb_ary_new4(argc, argv);
    arg.instance_and_params = g_value_array_new(1 + argc);

    return rb_ensure(chain_from_overridden_body, (VALUE)&arg,
                     emit_ensure, (VALUE)&arg);
}

static VALUE
gobj_s_method_added(klass, id)
    VALUE klass, id;
{
    const RGObjClassInfo* cinfo = rbgobj_lookup_class(klass);
    const char* name = rb_id2name(SYM2ID(id));
    const int prefix_len = strlen(default_handler_method_prefix);
    guint signal_id;

    if (cinfo->klass != klass) return Qnil;
    if (strncmp(default_handler_method_prefix, name, prefix_len)) return Qnil;

    signal_id = g_signal_lookup(name + prefix_len, cinfo->gtype);    
    if (!signal_id) return Qnil;

    {
        GSignalQuery query;
        g_signal_query(signal_id, &query);
        if (query.itype == cinfo->gtype)
            return Qnil;
    }

    {
        VALUE f = rb_eval_string(
          "lambda{|klass, id|\n"
          "  lambda{|instance,*args|\n"
          "    klass.instance_method(id).bind(instance).call(*args)\n"
          "  }\n"
          "}\n");
        VALUE proc = rb_funcall(f, rb_intern("call"), 2, klass, id);
        GClosure* rclosure = g_rclosure_new(proc, Qnil,
                                            rbgobj_get_signal_func(signal_id));
        g_rclosure_attach((GClosure *)rclosure, klass);
        g_signal_override_class_closure(signal_id, cinfo->gtype, rclosure);
    }

    {
        VALUE mod = rb_define_module_under(klass, RubyGObjectHookModule);
        rb_include_module(klass, mod);
        rb_define_method(mod, name, gobj_sig_chain_from_overridden, -1);
    }

    return Qnil;
}

static void
Init_signal_misc()
{
    signal_func_table = rb_hash_new();
    rb_global_variable(&signal_func_table);

    rb_define_method(mMetaInterface, "signal_new", gobj_s_signal_new, -1);
    rb_define_method(mMetaInterface, "signals", gobj_s_signals, -1);
    rb_define_method(mMetaInterface, "signal", gobj_s_signal, 1);

    rb_define_method(cInstantiatable, "signal_has_handler_pending?",
                     gobj_sig_has_handler_pending, -1);
    rb_define_method(cInstantiatable, "signal_connect", gobj_sig_connect, -1);
    rb_define_method(cInstantiatable, "signal_connect_after",
                     gobj_sig_connect_after, -1);

#if 0
    rb_define_method(cInstantiatable, "signal_invocation_hint",
                     gobj_sig_get_invocation_hint, 0);
#endif

    rb_define_method(cInstantiatable, "signal_emit",
                     gobj_sig_emit, -1);
    rb_define_method(cInstantiatable, "signal_emit_stop",
                     gobj_sig_emit_stop, 1);
    rb_define_method(cInstantiatable, "signal_handler_block",
                     gobj_sig_handler_block, 1);
    rb_define_method(cInstantiatable, "signal_handler_unblock",
                     gobj_sig_handler_unblock, 1);
    rb_define_method(cInstantiatable, "signal_handler_disconnect",
                     gobj_sig_handler_disconnect, 1);

    rb_define_method(cInstantiatable, "signal_handler_is_connected?",
                     gobj_sig_handler_is_connected, 1);

    rb_define_singleton_method(cInstantiatable, "method_added",
                               gobj_s_method_added, 1);
}

/**********************************************************************/

VALUE
rbgobj_signal_wrap(sig_id)
    guint sig_id;
{
    VALUE result;
    GSignalQuery* query;

    result = Data_Make_Struct(cSignal, GSignalQuery, NULL, free, query);
    g_signal_query(sig_id, query);
    return result;
}

static VALUE
query_signal_id(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return UINT2NUM(query->signal_id);
}

static VALUE
query_signal_name(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return rb_str_new2(query->signal_name);
}

static VALUE
query_itype(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return rbgobj_gtype_new(query->itype);
}

static VALUE
query_owner(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return GTYPE2CLASS(query->itype);
}

static VALUE
query_return_type(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return rbgobj_gtype_new(query->return_type);
}

static VALUE
query_signal_flags(self)
    VALUE self;
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    return UINT2NUM(query->signal_flags);
}

static VALUE
query_param_types(self)
    VALUE self;
{
    GSignalQuery* query;
    VALUE result;
    int i;
    Data_Get_Struct(self, GSignalQuery, query);

    result = rb_ary_new2(query->n_params);
    for (i = 0; i < query->n_params; i++)
        rb_ary_store(result, i, rbgobj_gtype_new(query->param_types[i]));

    return result;
}

static VALUE
query_inspect(self)
    VALUE self;
{
    GSignalQuery* query;
    gchar* s;
    VALUE result, v;

    Data_Get_Struct(self, GSignalQuery, query);

    v = rb_inspect(GTYPE2CLASS(query->itype));

    s = g_strdup_printf("#<%s: %s#%s>",
                        rb_class2name(CLASS_OF(self)),
                        StringValuePtr(v),
                        query->signal_name);
    result = rb_str_new2(s);
    g_free(s);

    return result;
}

#define query_is_flag(flag) \
    static VALUE \
    query_is_##flag(self) \
        VALUE self; \
    { \
        GSignalQuery* query; \
        Data_Get_Struct(self, GSignalQuery, query); \
        return CBOOL2RVAL(query->signal_flags & flag); \
    }

query_is_flag(G_SIGNAL_RUN_FIRST)
query_is_flag(G_SIGNAL_RUN_LAST)
query_is_flag(G_SIGNAL_RUN_CLEANUP)
query_is_flag(G_SIGNAL_NO_RECURSE)
query_is_flag(G_SIGNAL_DETAILED)
query_is_flag(G_SIGNAL_ACTION)
query_is_flag(G_SIGNAL_NO_HOOKS)


static gboolean
hook_func(GSignalInvocationHint* ihint,
          guint                  n_param_values,
          const GValue*          param_values,
          gpointer               data)
{
    GClosure* closure = data;
    GValue ret_val ={0,};
    gboolean ret;

    g_value_init(&ret_val, G_TYPE_BOOLEAN);
    g_closure_invoke(closure, &ret_val, n_param_values, param_values, ihint);
    ret = g_value_get_boolean(&ret_val);
    g_value_unset(&ret_val);

    return ret;
}

static gulong
g_signal_add_emission_hook_closure (guint     signal_id,
                                    GQuark    detail,
                                    GClosure* closure)
{
    guint hook_id;
    g_closure_ref(closure);
    g_closure_sink(closure);
    hook_id = g_signal_add_emission_hook(signal_id, detail,
                                         &hook_func, closure,
                                         (GDestroyNotify)&g_closure_unref);
    return hook_id;
}

static VALUE
signal_add_emission_hook(int argc, VALUE* argv, VALUE self)
{
    GSignalQuery* query;
    VALUE proc;
    guint hook_id;
    GQuark detail = 0;
    GClosure* closure;

    Data_Get_Struct(self, GSignalQuery, query);

    if (query->signal_flags & G_SIGNAL_DETAILED) {
        VALUE detail_obj;
        if (rb_scan_args(argc, argv, "01&", &detail_obj, &proc) == 1) {
            if (SYMBOL_P(detail_obj))
                detail = g_quark_from_string(rb_id2name(SYM2ID(detail_obj)));
            else
                detail = g_quark_from_string(StringValuePtr(detail_obj));
        }
    } else {
        rb_scan_args(argc, argv, "00&", &proc);
    }

    closure = g_rclosure_new(proc, Qnil,
                             rbgobj_get_signal_func(query->signal_id));
    g_rclosure_attach(closure, self);
    hook_id = g_signal_add_emission_hook_closure(query->signal_id, detail, closure);
    return ULONG2NUM(hook_id);
}

static VALUE
signal_remove_emission_hook(VALUE self, VALUE hook_id)
{
    GSignalQuery* query;
    Data_Get_Struct(self, GSignalQuery, query);
    g_signal_remove_emission_hook(query->signal_id, NUM2ULONG(hook_id));
    return Qnil;
}

static void
Init_signal_class()
{
    VALUE cSignalFlags, cSignalMatchType;

    cSignal = rb_define_class_under(mGLib, "Signal", rb_cData);

    rb_define_method(cSignal, "id", query_signal_id, 0);
    rb_define_method(cSignal, "name", query_signal_name, 0);
    rb_define_method(cSignal, "flags", query_signal_flags, 0);
    rb_define_method(cSignal, "itype", query_itype, 0);
    rb_define_method(cSignal, "owner", query_owner, 0);
    rb_define_method(cSignal, "return_type", query_return_type, 0);
    rb_define_method(cSignal, "param_types", query_param_types, 0);
    rb_define_method(cSignal, "inspect", query_inspect, 0);

    rb_define_method(cSignal, "add_emission_hook", signal_add_emission_hook, -1);
    rb_define_method(cSignal, "remove_emission_hook", signal_remove_emission_hook, 1);

    /* GSignalFlags */
    cSignalFlags = G_DEF_CLASS(G_TYPE_SIGNAL_FLAGS, "SignalFlags", mGLib);
    G_DEF_CONSTANTS(cSignal, G_TYPE_SIGNAL_FLAGS, "G_SIGNAL_");
    rb_define_const(cSignalFlags, "MASK", INT2NUM(G_SIGNAL_FLAGS_MASK));
    rb_define_const(cSignal, "FLAGS_MASK", INT2NUM(G_SIGNAL_FLAGS_MASK));

    rb_define_method(cSignal, "run_first?", query_is_G_SIGNAL_RUN_FIRST, 0);
    rb_define_method(cSignal, "run_last?", query_is_G_SIGNAL_RUN_LAST, 0);
    rb_define_method(cSignal, "run_cleanup?", query_is_G_SIGNAL_RUN_CLEANUP, 0);
    rb_define_method(cSignal, "no_recurse?", query_is_G_SIGNAL_NO_RECURSE, 0);
    rb_define_method(cSignal, "detailed?", query_is_G_SIGNAL_DETAILED, 0);
    rb_define_method(cSignal, "action?", query_is_G_SIGNAL_ACTION, 0);
    rb_define_method(cSignal, "no_hooks?", query_is_G_SIGNAL_NO_HOOKS, 0);


    /* GConnectFlags */
    G_DEF_CLASS(G_TYPE_CONNECT_FLAGS, "ConnectFlags", mGLib);
    G_DEF_CONSTANTS(cSignal, G_TYPE_CONNECT_FLAGS, "G_");

    /* GSignalMatchType */
    cSignalMatchType = G_DEF_CLASS(G_TYPE_SIGNAL_MATCH_TYPE,
				   "SignalMatchType", mGLib);
    G_DEF_CONSTANTS(cSignal, G_TYPE_SIGNAL_MATCH_TYPE, "G_SIGNAL_");
    rb_define_const(cSignalMatchType, "MASK", INT2NUM(G_SIGNAL_MATCH_MASK));
    rb_define_const(cSignal, "MATCH_MASK", INT2NUM(G_SIGNAL_MATCH_MASK));

    rb_define_const(cSignal, "TYPE_STATIC_SCOPE", INT2FIX(G_SIGNAL_TYPE_STATIC_SCOPE));

    eNoSignalError = rb_define_class_under(mGLib, "NoSignalError", rb_eNameError);
}

/**********************************************************************/

void
rbgobj_define_action_methods(VALUE klass)
{
    GType gtype = CLASS2GTYPE(klass);
    GString* source;
    guint n_ids;
    guint* ids;
    int i;

    if (gtype == G_TYPE_INTERFACE)
        return;

    ids = g_signal_list_ids(gtype, &n_ids);
    if (n_ids == 0)
        return;

    source = g_string_new(NULL);
    for (i = 0; i < n_ids; i++){
        GSignalQuery query;
        g_signal_query(ids[i], &query);

        if (query.signal_flags & G_SIGNAL_ACTION) {
            gchar* method_name = g_strdup(query.signal_name);
            gchar* p;
            GString* args;
            int j;

            for (p = method_name; *p; p++)
                if (*p == '-')
                    *p = '_';

            args = g_string_new(NULL);
            for (j = 0; j < query.n_params; j++)
                g_string_append_printf(args, ",x%d", j);

            g_string_append_printf(
                source,
                "def %s(%s)\n  signal_emit('%s'%s)\nend\n",
                method_name,
                (query.n_params > 0) ? args->str + 1 : "", // hack
                query.signal_name,
                args->str);

            g_free(method_name);
            g_string_free(args, TRUE);
        }
    }

    if (source->len > 0)
        rb_funcall(klass, rb_intern("module_eval"), 1, rb_str_new2(source->str));
    g_string_free(source, TRUE);
}

/**********************************************************************/

void
Init_gobject_gsignal()
{
    Init_signal_class();
    Init_signal_misc();
}