/*
 * GToolKit - Objective-C interface to the GIMP Toolkit
 * Copyright (c) 1998  Elmar Ludwig - Universitaet Osnabrueck
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include <ctype.h>
#include <stdio.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSMapTable.h>
#include <Foundation/NSLock.h>
#include <Foundation/NSThread.h>

#ifndef DEBUG
#define GTK_NO_CHECK_CASTS
#endif
#include <GToolKit/GToolKit.h>

/*
 * Some general type conversion functions for NSArray.
 */
static void add_to_array (gpointer data, NSMutableArray *array)
{
    [array addObject:Gtk_to_Object(data, NULL)];
}

NSArray *GList_to_NSArray (GList *list)
{
    NSMutableArray *array;

    array = [NSMutableArray arrayWithCapacity:g_list_length(list)];
    g_list_foreach(list, (GFunc) add_to_array, array);
    return array;
}

@interface __GAutorelease : NSObject
{
    gpointer object;
    void (*func)(gpointer);
}
+ (gpointer) release:(gpointer) object with:(void (*)(gpointer)) func;
@end

@implementation __GAutorelease

+ (gpointer) release:(gpointer) _object with:(void (*)(gpointer)) _func
{
    __GAutorelease *obj = [[self new] autorelease];

    obj->object = _object;
    obj->func = _func;
    return _object;
}

- (void) dealloc
{
#ifdef DEBUG
    fprintf(stderr, "(__GAutorelease dealloc) object = %p\n", object);
#endif
    func(object);
    [super dealloc];
}
@end

GList *NSArray_to_GList (NSArray *array)
{
    GList *list = NULL;
    int index, count = [array count];

    for (index = 0; index < count; ++index)
	list = g_list_append(list, [[array objectAtIndex:index] gtk]);

#ifdef GLIST_AUTORELEASE
    return [__GAutorelease release:list with:(void (*)(gpointer)) g_list_free];
#else
    return list;
#endif /* GLIST_AUTORELEASE */
}

extern const char **NSArray_to_StrVec (NSArray *array)
{
    int index, count = [array count];
    const char **vec = g_new(const char *, count + 1);

    vec[count] = NULL;
    for (index = 0; index < count; ++index)
	vec[index] = [[array objectAtIndex:index] cString];

    return [__GAutorelease release:vec with:g_free];
}

static NSLock *global_lock;		// lock for all global variables
static NSMapTable *object_table;	// map gpointer to object

@implementation GTK

/*
 * Find Objective-C object for given gpointer, create one if necessary.
 * CLASS is the object's type name (use NULL to autodetect type name).
 */
id Gtk_to_Object (gpointer gtk, const char *class)
{
    id result = nil;

    if (gtk)
    {
	[global_lock lock];
	result = NSMapGet(object_table, gtk);
	[global_lock unlock];

	if (result == nil)
	{
	    NSString *name = [NSString alloc];

	    if (class) name = [name initWithCString:class];
	    else
	    {
		char *str = g_strdup(gtk_type_name(GTK_OBJECT_TYPE(gtk)));

		if (str[0]) str[1] = toupper(str[1]);	// GtkFoo -> GTKFoo
		if (str[1]) str[2] = toupper(str[2]);
		name = [name initWithCString:str];
		g_free(str);
	    }
	    result = [[NSClassFromString(name) alloc] initWithGtk:gtk];
	    [name release];
	}
    }

    return result;
}

/*
 * Notify the GTK runtime system that resource locking is necessary.
 * This method is intended only for use by the @GTKApplication class
 * and should not be called from user code.
 */
+ (void) taskNowMultiThreaded:(NSNotification *) event;
{
    if (global_lock == nil) global_lock = [NSLock new];
}

/*
 * Return a hash code for this object.
 */
- (unsigned) hash
{
    return (unsigned long) gtk / sizeof (gpointer);
}

/*
 * Compare two objects for equality.
 */
- (BOOL) isEqual:anObject
{
    if (![anObject isKindOfClass:[GTK class]]) return NO;
    return gtk == ((GTK *) anObject)->gtk;
}

 - init
{
    g_error("Objects of type `%s' cannot be created with +new",
	    [[[self class] description] cString]);
    return nil;
}

/*
 * Initialize a new GTK object for the given gtk-object pointer /gtk/,
 * which must not be |NULL|.<br>
 * This is an internal method and should never be called from user code.
 */
- initWithGtk:(gpointer) _gtk
{
    [super init];

    [global_lock lock];
    if (object_table == NULL)
	object_table = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
					NSNonOwnedPointerMapValueCallBacks, 64);
    NSMapInsert(object_table, gtk = _gtk, self);
    [global_lock unlock];
#ifdef DEBUG
    fprintf(stderr, "[%s init] at %p: gtk = %p\n",
	    [[[self class] description] cString], self, gtk);
#endif
    return self;
}

 - (void) dealloc
{
#ifdef DEBUG
    fprintf(stderr, "[%s dealloc] at %p: gtk = %p\n",
	    [[[self class] description] cString], self, gtk);
#endif
    [global_lock lock];
    NSMapRemove(object_table, gtk);
    [global_lock unlock];
    [actions release];
    [super dealloc];
}

#ifdef DEBUG
/*
 * Return a textual description of the receiver.
 */
- (NSString *) description
{
    return [[super description] stringByAppendingFormat:@" gtk=%p", gtk];
}
#endif

/*
 * Return the internal gtk-object pointer. This may be generally useful to
 * access components of the underlying object that do not have access
 * methods. Example:
 * <pre>
 * GTKStyle *style = [button getStyle];
 * GtkStyle *gtk_style = [style gtk];
 *
 * gdk_color_parse("yellow", \&gtk_style-\>bg[GTK_STATE_NORMAL]);
 * gdk_color_alloc(colormap, \&gtk_style-\>bg[GTK_STATE_NORMAL]);</pre>
 */
- (gpointer) gtk
{
    return gtk;
}

/*
 * Set the receiver's tag value to /tag/ (the initial tag value is |0|).
 */
- (void) setTag:(gint32) _tag
{
    tag = _tag;
}

/*
 * Return the receiver's tag value.
 * @see -setTag:
 */
- (gint32) tag
{
    return tag;
}

/*
 * Add a user defined /signal/ (without parameters) to the target class.
 * The signal can be emitted by sending @-emit:signal: to an object of
 * this class.
 */
+ (void) addUserSignal:(NSString *) signal
{
    gtk_object_class_add_user_signal(gtk_type_class([self getType]),
				     NSString_to_String(signal),
				     gtk_signal_default_marshaller,
				     GTK_TYPE_NONE, 0);
}

/*
 * Tell the receiver to emit the given /signal/, i.e. invoke all actions
 * connected to this signal. The /sender/ parameter is ignored.
 * @see -stop:signal:
 */
- (void) emit:sender signal:(NSString *) signal
{
    union { long l; double d; void *p; } result;

    gtk_signal_emit_by_name(gtk, NSString_to_String(signal), &result);
}

/*
 * Stop the emission process for the given /signal/. Attempting to stop the
 * emission of a signal that isn't being emitted does nothing. See the GIMP
 * Toolkit documentation for details. The /sender/ parameter is ignored.
 * @see -emit:signal:
 */
- (void) stop:sender signal:(NSString *) signal
{
    gtk_signal_emit_stop_by_name(gtk, NSString_to_String(signal));
}

/*
 * This is the generic signal forwarder.
 */
static void forward_signal (gpointer gtk, GTKAction *action, guint n_args,
			    GtkArg *args)
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

#ifdef DEBUG
    fprintf(stderr, "(forward_signal init) gtk = %p, action [%p %s %p]\n", gtk,
	    [action target], [NSStringFromSelector([action selector]) cString],
	    [action data]);
#endif
    [action performWithSender:Gtk_to_Object(gtk, NULL) args:args count:n_args];
    [pool release];
#ifdef DEBUG
    fprintf(stderr, "(forward_signal exit) gtk = %p\n", gtk);
#endif
}

/*
 * Connect the /action/ (a target/selector pair) to the given /signal/ in
 * the receiver. The object will call the
 * @GTKAction#-performWithSender:args:count: method of the @GTKAction and
 * pass itself as the sending object when the given /signal/ is emitted.
 * Example:
 * <pre>
 * - (void) foo:sender
 * ...
 * - (void) bar:sender data:(myType *) data
 * ...
 *
 * [window connectSignal:@"destroy" withAction:
 *    [GTKAction actionWithTarget:anObject selector:@selector(foo:)]];
 *
 * [button connectSignal:@"clicked" withAction:
 *    [GTKAction actionWithTarget:anObject selector:@selector(bar:data:)
 *     data:\&myData]];</pre>
 */
- (void) connectSignal:(NSString *) signal withAction:(GTKAction *) action
{
    gtk_signal_connect_interp(gtk, NSString_to_String(signal),
			(GtkCallbackMarshal) forward_signal, action, NULL, NO);
    if (actions == nil) actions = [NSMutableArray new];
    [actions addObject:action];
}

/*
 * Connect the /target///selector/ pair to the given /signal/ in the
 * receiver. This method is equivalent to the call:
 * <pre>
 * [... connectSignal:/signal/ withAction:
 *    [GTKAction actionWithTarget:/target/ selector:/sel/]]</pre>
 * Use the @-connectSignal:withAction: method if you need a reference
 * to the GTKAction object (to block/unblock\/disconnect it later).
 */
- (void) connectSignal:(NSString *) signal withTarget:target sel:(SEL) sel
{
    [self connectSignal:signal withAction:
	  [GTKAction actionWithTarget:target selector:sel]];
}

/*
 * Connect the /target///selector/ pair to the given /signal/ in the
 * receiver. This method is equivalent to the call:
 * <pre>
 * [... connectSignal:/signal/ withAction:
 *    [GTKAction actionWithTarget:/target/ selector:/sel/ data:/data/]]</pre>
 * Use the @-connectSignal:withAction: method if you need a reference
 * to the GTKAction object (to block/unblock\/disconnect it later).
 */
- (void) connectSignal:(NSString *) signal withTarget:target sel:(SEL) sel
	 data:(const void *) data
{
    [self connectSignal:signal withAction:
	  [GTKAction actionWithTarget:target selector:sel data:data]];
}

/*
 * Similar to @-connectSignal:withAction: except the /action/ is connected
 * in the "after" slot. This allows a signal handler to be guaranteed to
 * run after other signal handlers connected to the same signal on the
 * same object and after the class function associated with the signal.
 * @see -connectSignal:withAction:
 */
- (void) connectSignal:(NSString *) signal withActionAfter:(GTKAction *) action
{
    gtk_signal_connect_interp(gtk, NSString_to_String(signal),
			(GtkCallbackMarshal) forward_signal, action, NULL, YES);
    if (actions == nil) actions = [NSMutableArray new];
    [actions addObject:action];
}

/*
 * Disconnect the /action/ from all signals in the receiver that it is
 * connected to. Multiple signal handlers may be disconnected with this call.
 */
- (void) disconnectAction:(GTKAction *) action
{
    gtk_signal_disconnect_by_data(gtk, action);
    [actions removeObject:action];
}

/*
 * Blocks the /action/ for all signals in the receiver that are connected
 * to it. Multiple signal handlers may be blocked with this call.
 */
- (void) blockAction:(GTKAction *) action
{
    gtk_signal_handler_block_by_data(gtk, action);
}

/*
 * Unblocks the /action/ for all signals in the receiver that are connected
 * to it. Multiple signal handlers may be unblocked with this call.
 */
- (void) unblockAction:(GTKAction *) action
{
    gtk_signal_handler_unblock_by_data(gtk, action);
}
@end
